Android OpenGL ES 2.0 屏幕坐标和3D世界坐标转换

2024-08-22 15:08

本文主要是介绍Android OpenGL ES 2.0 屏幕坐标和3D世界坐标转换,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景

由于项目中需要用到屏幕坐标和3d坐标相互转换的功能。经过我查询大量资料,发现很多文章都只是说明了OpenGL的坐标系统和坐标变换的过程。并没有实现转换坐标的代码示例。介绍坐标系统的文章很多,请自行百度。下面我说一下实现坐标变换的思路和代码。

坐标变换思路

在网上查询了很久,有写文章说到OpenGL 有一种拾取方式为射线拾取,是可以获取的屏幕到3D模型坐标之间的转换的。但是我并没有找到代码实现示例。后来看到有一种方法拾取很巧妙,用的是OpenGL 的FBO。

FBO实现思路

关于FBO的解释,请看网友写的博客【OpenGL】OpenGL帧缓存对象(FBO:Frame Buffer Object)

我的思路是这样的,在传输顶点数据到渲染管线的时候,开启一个FBO,然后FBO的颜色选用的是传进来的坐标。

假设我一个点的坐标为(x,y,z),那么我在改点设置的颜色值为 RGBA (x ,y ,z ,1.0) , 这样处理的时候我就在FBO中绘制了一个相同的模型,但是我的颜色值包含了顶点的数据。这时候只需要调用 glReadPixels ,就能读取一个屏幕的颜色值,进而转化为坐标系。

本人代码用JNI实现,具体实现如下

屏幕坐标转换3D坐标

使用FBO实现

Shader程序

static const char mPickVertexShader[] ="attribute vec3 a_position;\n""uniform highp mat4 u_mvpMatrix;\n""uniform lowp float u_modeIndex;\n""varying lowp vec4 v_color;\n""void main(){\n""v_color = vec4(a_position.x,a_position.y,a_position.z,1.0);\n""gl_Position = u_mvpMatrix * vec4(a_position,1.0);\n""}\n";static const char mPickFragmentShader[] ="uniform highp mat4 u_mvpMatrix;\n""uniform highp vec2 u_dimensionFactors;\n""varying lowp vec4 v_color;\n""void main(){\n""gl_FragColor = v_color;\n""}";

在Shader中我把顶点数据封装在 v_color 中,然后在片元着色代码中把v_color 传送带渲染管线

加载着色器程序

    mPickProgram = createProgram(mPickVertexShader,mPickFragmentShader);mPickMVPMatrix = glGetUniformLocation(mPickProgram,"u_mvpMatrix");mPickPosition = glGetAttribLocation(mPickProgram,"a_position");

绑定FBO

    glDeleteTextures(1, &colorTexture);glDeleteFramebuffers(1, &mFBO);glDeleteRenderbuffers(1, &depthRenderbuffer);// Create a texture object to apply to modelglGenTextures(1, &colorTexture);glBindTexture(GL_TEXTURE_2D, colorTexture);// Set up filter and wrap modes for this texture objectglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);// Allocate a texture image we can render into// Pass NULL for the data parameter since we don't need to// load image data. We will be generating the image by// rendering to this texture.glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,NULL);glGenRenderbuffers(1, &depthRenderbuffer);glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16,width, height);glGenFramebuffers(1, &mFBO);glBindFramebuffer(GL_FRAMEBUFFER, mFBO);glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D, colorTexture, 0);glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {LOGI("failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));mFBO = 0;}LOGI("bindFBO success : %d" ,mFBO);glClearColor(0.11f, 0.25f, 0.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glUseProgram(mPickProgram);const GLfloat *mMVPMatrix = (*env)->GetFloatArrayElements(env, array, GL_FALSE);glUniformMatrix4fv(mPickMVPMatrix,1,GL_FALSE,mMVPMatrix);glVertexAttribPointer(mPickPosition, 3, GL_FLOAT, GL_FALSE, 3 * 4, mVertices);(*env)->ReleaseFloatArrayElements(env, array, mMVPMatrix, 0);glEnableVertexAttribArray(mPickPosition);glDrawArrays(GL_TRIANGLE_STRIP, 0, vCount);

我这里做的操作是绑定FBO之后进行一次绘制,绘制的时候把顶点数据(mVertices)和总变换矩阵(mMVPMatrix)传入了OpenGL中,然后就可以通过glReadPixels 来获取屏幕中的一个像素了。

获取像素

    JNIEXPORT jobject JNICALL Java_com_package_className_screenToProjectionPosition(JNIEnv *env, jobject obj, jint x, jint y) {GLubyte pixelColor[4];glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixelColor);glBindFramebuffer(GL_FRAMEBUFFER,0);jclass glkvector2class = (*env)->FindClass(env,"com/package/GLKVector3");jmethodID constructId = (*env)->GetMethodID(env,glkvector2class,"<init>","(FFF)V");jobject outObject = (*env)->NewObject(env,glkvector2class,constructId,(GLfloat)pixelColor[0] / 256,// red component(GLfloat)pixelColor[1] / 256,// green component(GLfloat)pixelColor[2] / 256);LOGI("model color r:%f g:%f b:%f",(GLfloat)pixelColor[0],(GLfloat)pixelColor[1],(GLfloat)pixelColor[2]);return outObject;}

上面代码中我对读出来的像素 除以256是因为我模型最大值为1,所以除以256刚好得到实际坐标。

在获取坐标的时候,首先需要绑定FBO,然后调用glDrawArrays一次,然后就可以用glReadPixels获取到坐标了。。

另外需要注意的是这些操作需要在OpenGL的上下文中调用才会生效。不然的话,glReadPixels获取到的数据都是0。我的实现方式是通过Handler 发送一个任务然后在onDrawFrame中执行这些操作。等这些任务执行完毕之后就能获取到3D的坐标了。

3D坐标转换屏幕坐标

这个简单了,只需要将3D坐标经过传入OpenGL的最终变换矩阵执行一次变换即可。

public GLKVector2 projectionToScreenPosition(GLKVector3 position) {GLKVector3 v = GeoUtils.GLKMatrix4MultiplyAndProjectVector3(MatrixState.mMVPMatrix, position);return new GLKVector2(v.x, v.y);}

其中GLKMatrix4MultiplyAndProjectVector3具体实现如下:

/*** 将物体坐标转换成世界坐标* @param matrixLeft* @param vectorRight* @return*/public static GLKVector3 GLKMatrix4MultiplyAndProjectVector3(float[] matrixLeft, GLKVector3 vectorRight) {GLKVector4 v4 = GLKMatrix4MultiplyVector4(matrixLeft, GLKVector4Make(vectorRight.x, vectorRight.y, vectorRight.z, 1.0f));return GLKVector3MultiplyScalar(GLKVector3Make(v4.x, v4.y, v4.z), 1.0f / v4.w);}public static GLKVector3 GLKVector3Make(float x, float y, float z) {GLKVector3 v = new GLKVector3();v.x = x;v.y = y;v.z = z;return v;}public static GLKVector4 GLKMatrix4MultiplyVector4(float[] matrixLeft, GLKVector4 vector) {GLKVector4 v4 = new GLKVector4();v4.x = matrixLeft[0] * vector.x + matrixLeft[4] * vector.y + matrixLeft[8] * vector.z + matrixLeft[12] * vector.w;v4.y = matrixLeft[1] * vector.x + matrixLeft[5] * vector.y + matrixLeft[9] * vector.z + matrixLeft[13] * vector.w;v4.z = matrixLeft[2] * vector.x + matrixLeft[6] * vector.y + matrixLeft[10] * vector.z + matrixLeft[14] * vector.w;v4.w = matrixLeft[3] * vector.x + matrixLeft[7] * vector.y + matrixLeft[11] * vector.z + matrixLeft[15] * vector.w;return v4;}public static GLKVector4 GLKVector4Make(float x, float y, float z, float w) {GLKVector4 v4 = new GLKVector4();v4.x = x;v4.y = y;v4.z = z;v4.w = w;return v4;}

这是屏幕坐标和3d坐标相互转换的一种解决方案。笔者已经实现了转换,误差并没有多少,基本上都是正确的。如果有什么问题,欢迎提出。

这篇关于Android OpenGL ES 2.0 屏幕坐标和3D世界坐标转换的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1096617

相关文章

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

Python 标准库time时间的访问和转换问题小结

《Python标准库time时间的访问和转换问题小结》time模块为Python提供了处理时间和日期的多种功能,适用于多种与时间相关的场景,包括获取当前时间、格式化时间、暂停程序执行、计算程序运行时... 目录模块介绍使用场景主要类主要函数 - time()- sleep()- localtime()- g

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Java将时间戳转换为Date对象的方法小结

《Java将时间戳转换为Date对象的方法小结》在Java编程中,处理日期和时间是一个常见需求,特别是在处理网络通信或者数据库操作时,本文主要为大家整理了Java中将时间戳转换为Date对象的方法... 目录1. 理解时间戳2. Date 类的构造函数3. 转换示例4. 处理可能的异常5. 考虑时区问题6.

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

基于C#实现将图片转换为PDF文档

《基于C#实现将图片转换为PDF文档》将图片(JPG、PNG)转换为PDF文件可以帮助我们更好地保存和分享图片,所以本文将介绍如何使用C#将JPG/PNG图片转换为PDF文档,需要的可以参考下... 目录介绍C# 将单张图片转换为PDF文档C# 将多张图片转换到一个PDF文档介绍将图片(JPG、PNG)转

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo