OpenGL ES中的透视投影矩阵问题。

前言

以前是在Android中利用OpenGL ES显示三维物体,显示Obj的模型,自己看了官方给的OpenGL的文档,显示一个三角形从头学的,加上学习参考别人的代码过来的。在这个过程之中,我一直都是使用的 调用Matrix.setLookAtM设置一个照相机的矩阵,然后调用 Matrix.frustumM创建一个透视投影矩阵,然后将两个矩阵相乘,传给着色器做映射,并没有发现什么问题。
最近,在系统的学习OpenGL ES的开发(之前都是为了完成功能而半路出家),买了一本《OpenGL ES应用开发实践指南》来看,并且跟着书上给的逻辑和自己的理解来敲书上的程序。书中是实现一个空气曲棍球的桌面,画出桌面很简单,但是需要透视投影来让它看起来是三维的一样,我用自己的理解调用了创建透视投影并设置照相机,并没有如愿以偿,而是程序闪退打不开。查看书上内容,书上说:“Matrix.frustumM有个缺陷,会影响某些类型的投影,而另外一个透视投影Matrix.perspectiveM只是从冰淇淋三明治安卓版本才有的,所以要自己实现Matrix.perspectiveM的代码”。书上并没有说什么缺陷,对于小白的我也一脸懵逼,所以写了这篇博客总结一下,自己以后复习用。

1.查看两个函数的源码

/**
     * Defines a projection matrix in terms of six clip planes.
     *
     * @param m the float array that holds the output perspective matrix
     * @param offset the offset into float array m where the perspective
     *        matrix data is written
     * @param left
     * @param right
     * @param bottom
     * @param top
     * @param near
     * @param far
     */
    public static void frustumM(float[] m, int offset,
            float left, float right, float bottom, float top,
            float near, float far) {
        if (left == right) {
            throw new IllegalArgumentException("left == right");
        }
        if (top == bottom) {
            throw new IllegalArgumentException("top == bottom");
        }
        if (near == far) {
            throw new IllegalArgumentException("near == far");
        }
        if (near <= 0.0f) {
            throw new IllegalArgumentException("near <= 0.0f");
        }
        if (far <= 0.0f) {
            throw new IllegalArgumentException("far <= 0.0f");
        }
        final float r_width  = 1.0f / (right - left);
        final float r_height = 1.0f / (top - bottom);
        final float r_depth  = 1.0f / (near - far);
        final float x = 2.0f * (near * r_width);
        final float y = 2.0f * (near * r_height);
        final float A = (right + left) * r_width;
        final float B = (top + bottom) * r_height;
        final float C = (far + near) * r_depth;
        final float D = 2.0f * (far * near * r_depth);
        m[offset + 0] = x;
        m[offset + 5] = y;
        m[offset + 8] = A;
        m[offset +  9] = B;
        m[offset + 10] = C;
        m[offset + 14] = D;
        m[offset + 11] = -1.0f;
        m[offset +  1] = 0.0f;
        m[offset +  2] = 0.0f;
        m[offset +  3] = 0.0f;
        m[offset +  4] = 0.0f;
        m[offset +  6] = 0.0f;
        m[offset +  7] = 0.0f;
        m[offset + 12] = 0.0f;
        m[offset + 13] = 0.0f;
        m[offset + 15] = 0.0f;
    }
/**
     * Defines a projection matrix in terms of a field of view angle, an
     * aspect ratio, and z clip planes.
     *
     * @param m the float array that holds the perspective matrix
     * @param offset the offset into float array m where the perspective
     *        matrix data is written
     * @param fovy field of view in y direction, in degrees
     * @param aspect width to height aspect ratio of the viewport
     * @param zNear
     * @param zFar
     */
    public static void perspectiveM(float[] m, int offset,
          float fovy, float aspect, float zNear, float zFar) {
        float f = 1.0f / (float) Math.tan(fovy * (Math.PI / 360.0));
        float rangeReciprocal = 1.0f / (zNear - zFar);

        m[offset + 0] = f / aspect;
        m[offset + 1] = 0.0f;
        m[offset + 2] = 0.0f;
        m[offset + 3] = 0.0f;

        m[offset + 4] = 0.0f;
        m[offset + 5] = f;
        m[offset + 6] = 0.0f;
        m[offset + 7] = 0.0f;

        m[offset + 8] = 0.0f;
        m[offset + 9] = 0.0f;
        m[offset + 10] = (zFar + zNear) * rangeReciprocal;
        m[offset + 11] = -1.0f;

        m[offset + 12] = 0.0f;
        m[offset + 13] = 0.0f;
        m[offset + 14] = 2.0f * zFar * zNear * rangeReciprocal;
        m[offset + 15] = 0.0f;
    }

从源码中我们可以看出:
(1)frustumM是通过6个剪切平面来定义投影矩阵的。
对应参数如下图所示:
在这里插入图片描述

这个矩阵和以下矩阵相似:
在这里插入图片描述

这个投影矩阵是DX情况下CVV空间所使用的,且是特殊情况:right left 大小相同 一正一负,投影点正好在屏幕中心点。
具体原理和推导参考:https://blog.csdn.net/linuxheik/article/details/78969526
https://www.cnblogs.com/wbaoqing/p/5428052.html
(2)perspectiveM是通过视野的角度、纵横比和z剪切平面来定义投影矩阵。

对应参数如下图所示:
在这里插入图片描述
这个透视投影来源于视锥体
在这里插入图片描述
具体原理参考:https://www.cnblogs.com/bluebean/p/5276111.html
最后获得的投影矩阵为:
在这里插入图片描述
和源码相比,我们会发现第三列和源码是成相反数的关系,那是因为归一化设备的坐标列采用的左手坐标,而OpenGL中采用的是右手坐标,两者的z轴方向正相反,所以我们需要额外乘一个负数来吧z轴的方向反过来。
那么,还有一个问题,用了这个矩阵还需要设置照相机的位置吗?
经过我的简单实验,是可以设置照相机位置的,并且图像会随着设置的不同而变化,但是具体如何使用我还是不清楚,因为调用这一个构造一个投影矩阵就完成了我的预期目标。在这里插入图片描述
并且,值得注意一点的是:
传入的参数near=1,far=10时,是把这个视锥体从z值为-1的位置开始,在z值为-10的位置结束的。所以需要我们把物体中的坐标z值放在这个范围之内才能看到我们的物体。

frustumM缺陷的推测:
可能是因为我们的屏幕中心在投影点的这种特殊情况的时候才能够正常使用,猜测,有待考证。
一些别人提出的问题:
原问题:https://issuetracker.google.com/issues/36952241
翻译:截锥体似乎错误地将X分量乘以它应该的两倍,从而使不对称的截锥体发生了扭曲。
当使用非对称的截锥体时,截锥体会错误地使X值倾斜两倍。这是由以下代码块中的第一行代码引起的:

final float A = 2.0f * ((right + left) * r_width);
final float B = (top + bottom) * r_height;
final float C = (far + near) * r_depth;

相反,第一行应定义如下:

final float A = (right + left) * r_width;

这似乎在所有版本的Android中都存在。
当前解决方式:
能用frustumM就用,不能用转成perspectiveM把,大不了自己手写一个工具类,存放自己写的投影矩阵,具体情况具体适用。

如果有人知道更加具体的原因,区别,原理啥的请在评论里面引一下路,我学习一下,
Thanks♪(・ω・)ノ

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenGL ES 投影矩阵可以使用透视投影和正交投影两种方式进行设置。 透视投影是一种模拟人眼观察物体的方式,近处的物体看起来比远处的物体大,而且在远处的物体看起来比在近处的物体更小。在 OpenGL ES 透视投影矩阵通常由 glFrustum() 或者 gluPerspective() 函数生成。这些函数所需要的参数包括视角、画面宽高比、近裁剪面和远裁剪面的距离。 正交投影则是一种无论远近都能保持物体大小一致的投影方式。在 OpenGL ES ,正交投影矩阵通常由 glOrtho() 函数生成。这个函数需要指定可视空间的左、右、上、下、近、远六个面。 下面是一个简单的 OpenGL ES 代码示例,使用透视投影和正交投影来绘制一个立方体: ```c++ // 使用透视投影绘制立方体 glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustum(-1, 1, -1, 1, 1, 10); // 绘制立方体 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0, 0, -5); glRotatef(angle, 1, 1, 1); glColor3f(1, 0, 0); glutWireCube(2); // 使用正交投影绘制立方体 glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-2, 2, -2, 2, 1, 10); // 绘制立方体 glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0, 0, -5); glRotatef(angle, 1, 1, 1); glColor3f(0, 0, 1); glutWireCube(2); ``` 其,glFrustum() 函数生成的透视投影矩阵,可以让离观察者比较近的物体显得比较大,而离观察者比较远的物体显得比较小。glOrtho() 函数生成的正交投影矩阵,则可以让物体无论离观察者多远,都保持一致的大小。 需要注意的是,在使用不同的投影方式时,需要先将当前矩阵模式设置为投影矩阵模式,然后再进行矩阵的设置和变换。最后,需要将矩阵模式设置回模型视图矩阵模式,以便绘制物体。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值