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

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

本文转载自: https://blog.csdn.net/u012943767/article/details/51122785




背景

由于项目中需要用到屏幕坐标和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"
        "}";

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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

加载着色器程序

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

绑定FBO

    glDeleteTextures(1, &colorTexture);
    glDeleteFramebuffers(1, &mFBO);
    glDeleteRenderbuffers(1, &depthRenderbuffer);
    // Create a texture object to apply to model
    glGenTextures(1, &colorTexture);
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    // Set up filter and wrap modes for this texture object
    glTexParameteri(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);
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

我这里做的操作是绑定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;

}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

上面代码中我对读出来的像素 除以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);
    }
 
 
  • 1
  • 2
  • 3
  • 4

其中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;
    }

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

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

阅读更多 登录后自动展开
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值