出于性能原因,我正在尝试将游戏中的图形迁移到 OpenGL。

  • 我需要使用精确的屏幕坐标绘制一个对象。假设在 240x320 屏幕中心有一个 100x100 像素的盒子。
  • 我需要围绕 Z 轴旋转它,保持它的大小。
  • 我需要围绕 X 轴旋转它,具有透视效果,保留(或接近)其大小。
  • 我需要围绕 Y 轴旋转它,具有透视效果,保持(或接近)其大小。

  • Here's a picture.

    到目前为止,我设法完成了前两个任务:
    public void onDrawFrame(GL10 gl) {
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        gl.glLoadIdentity();
        gl.glTranslatef(120, 160, 0); // move rotation point
        gl.glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate
        gl.glTranslatef(-120, -160, 0); // restore rotation point
        mesh.draw(gl); // draws 100x100 px rectangle with the following coordinates: (70, 110, 170, 210)
    }
    
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glOrthof(0f, (float)width, (float)height, 0f, -1f, 1f);
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
    }
    

    但是当我试图围绕 x 或 y 旋转我的盒子时,我的盒子发生了令人讨厌的事情并且没有透视效果。我尝试使用其他函数而不是 glRotate(glFrustum、glPerspective、gluLookAt、应用“倾斜”矩阵),但我无法使它们正常工作。

    最佳答案



    对于透视,您还需要一些镜头长度,这决定了 FOV。 FOV 是视平面距离与可见范围之比。在近平面的情况下,它因此变为 {left,right,top,bottom}/near 。为简单起见,我们假设水平 FOV 和对称投影,即

    FOV = 2*|left|/near = 2*|right|/near = extent/distance
    

    或者如果你更喜欢角度
    FOV = 2*tan(angular FOV / 2)
    

    对于 90° FOV,镜头的长度是焦平面宽度的一半。您的焦平面是 240x320 像素,所以左右各 120 度,上下各 160 度。 OpenGL 并没有真正的焦点,但我们可以说近和远之间的中间平面是“焦点”。

    因此,假设对象的平均范围约为可见平面限制的数量级,即对于 240x360 的可见平面,对象的平均大小约为 200 像素。因此,从近到远剪辑的距离为 200,因此在焦平面上为 +- 100。所以对于 90° 的 FOV 焦平面有距离
    2*tan(90°/2) = extent/distance
    
    2*tan(45°) = 2 = 240/distance
                 2*distance = 240
                 distance = 120
    

    120,因此远近剪裁距离分别为 20 和 220。

    最后但并非最不重要的近剪裁平面限制必须按near_distance/focal_distance = 20/120 缩放

    所以
    left   = -120 * 20/120 = -20
    right  =  120 * 20/120 =  20
    bottom = -180 * 20/120 = -30
    top    =  180 * 20/120 =  30
    

    所以这给了我们 glFrustum 参数:
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum(-20, 20, -30, 30, 20, 220);
    

    最后但并非最不重要的是,我们必须将世界原点移动到“焦点”平面
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0, 0, -120);
    



    完毕。



    透视不保留大小。这就是使它成为一个视角的原因。您可以使用非常长的镜头,即小 FOV。

    代码更新

    作为一般专业提示:在绘图处理程序中执行所有 OpenGL 操作。不要在重塑处理程序中设置投影。它很丑,一旦你想要一些 HUD 或其他类型的覆盖,你无论如何都必须丢弃它。所以这里是如何改变它:
    public void onDrawFrame(GL10 gl) {
        // fov, extents are parameters set somewhere else
        // 2*tan(fov/2.) = width/distance =>
        float distance = width/(2.*tan(fov));
        float near = distance - extent/2;
        float far  = distance + extent/2;
    
        if(near < 1.) {
            near = 1.;
        }
        float  left  =  (-width/2) * near/distance;
        float  right =  ( width/2) * near/distance;
        float bottom = (-height/2) * near/distance;
        float    top = ( height/2) * near/distance;
    
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    
        gl.glViewport(0, 0, width, height);
    
        gl.glMatrixMode(GL10.GL_PROJECTION);
        gl.glLoadIdentity();
        gl.glFrustum(left, right, bottom, top, near, far);
    
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        gl.glLoadIdentity();
    
        gl.glTranslatef(0, 0, -focal);
    
        gl.glTranslatef(120, 160, 0); // move rotation point
        gl.glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate
        gl.glTranslatef(-120, -160, 0); // restore rotation point
        mesh.draw(gl); // draws 100x100 px rectangle with the following coordinates: (70, 110, 170, 210)
    }
    
    public void onSurfaceChanged(GL10 gl, int new_width, int new_height) {
        width = new_width;
        height = new_height;
    }
    

    关于Android OpenGL - 简单 2D 转换的困难,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/8700768/

    10-13 08:24