专栏首页进击的多媒体开发​OpenGL 学习系列---投影矩阵

​OpenGL 学习系列---投影矩阵

在 OpenGL 坐标系统 文章中,根据点的坐标变换得出了如下的公式:

这个公式每左乘一个矩阵,都代表了一种坐标系的变换。

转化为着色器脚本语言如下:

1attribute vec4 a_Position;
2uniform mat4 u_ModelMatrix;
3uniform mat4 u_ProjectionMatrix;
4uniform mat4 u_ViewMatrix;
5void main()
6{
7    gl_Position  = u_ProjectionMatrix * u_ViewMatrix * u_ModelMatrix * a_Position;
8}

本篇文章就主要是对投影矩阵来分析的。

OpenGL 在观察空间转换到裁剪空间时,需要用到投影矩阵。而在着色器脚本中,也需要提供一个投影矩阵给对应的 u_ProjectionMatrix变量。

首先要在程序里绑定到对应的变量,然后再给变量赋值。

1// 绑定到着色器脚本中的对应变量
2private static final String U_ProMatrix = "u_ProjectionMatrix";
3private int uProMatrixLocation;
4uProMatrixLocation = glGetUniformLocation(mProgram,U_ProMatrix);
5// 给变量赋值,projectionMatrix 为投影矩阵
6glUniformMatrix4fv(uProMatrixLocation,1,false,projectionMatrix,0)

正如前文讲到的,投影矩阵会创建一个视景体对物体坐标进行裁剪,得到的裁剪坐标再经过透视除法之后,就会得到归一化设备坐标。归一化设备坐标再经过视口转换,最终将坐标映射到了屏幕上。

OpenGL 提供了两种投影方式:正交投影和透视投影。

正交投影矩阵

不管是正交投影还是透视投影,最终都是将视景体内的物体投影在近平面上,这也是 3D 坐标转换到 2D 坐标的关键一步。

而近平面上的坐标接着也会转换成归一化设备坐标,再映射到屏幕视口上。

为了解决之前的图像拉伸问题,就是要保证近平面的宽高比和视口的宽高比一致,而且是以较短的那一边作为 1 的标准,让图像保持居中。

OpenGL 提供了 Matrix.orthoM 函数来生成正交投影矩阵。

 1    /**
 2     * Computes an orthographic projection matrix.
 3     *
 4     * @param m returns the result 正交投影矩阵
 5     * @param mOffset 偏移量,默认为 0 ,不偏移
 6     * @param left 左平面距离
 7     * @param right 右平面距离
 8     * @param bottom 下平面距离
 9     * @param top 上平面距离
10     * @param near 近平面距离
11     * @param far 远平面距离
12     */
13    public static void orthoM(float[] m, int mOffset,
14        float left, float right, float bottom, float top,
15        float near, float far)
16

需要注意的是,我们的左、上、右、下距离都是相对于近平面中心的。

近平面的坐标原点位于中心,向右为

轴正方向,向上为

轴正方向,所以我们的 left、bottom 要为负数,而 right、top 要为正数。同时,近平面和远平面的距离都是指相对于视点的距离,所以 near、far 要为正数,而且

可以在 GLSurfaceView 的 surfaceChanged 里面来设定正交投影矩阵。

1  @Override
2    public void onSurfaceChanged(GL10 gl, int width, int height) {
3        float aspectRatio = width > height ? (float) width / (float) height : (float) height / (float) width;
4        if (width > height){
5            Matrix.orthoM(projectionMatrix,0,-aspectRatio,aspectRatio,-1f,1f,0f,10f);
6        }else {
7            Matrix.orthoM(projectionMatrix,0,-1f,1f,-aspectRatio,aspectRatio,0f,10f);
8        }
9    }

这样的话,就把近平面的宽高比设定与视口的宽高比一致了。

透视投影矩阵

OpenGL 提供了两个函数来创建透视投影矩阵:frustumMperspectiveM

frustumM

frustumM 函数创建的视景体是一个锥形。

它的视景体有点类似于正交投影,在参数理解上基本都相同的。

 1/**
 2     * Defines a projection matrix in terms of six clip planes.
 3     *
 4     * @param m the float array that holds the output perspective matrix
 5     * @param offset the offset into float array m where the perspective
 6     *        matrix data is written
 7     * @param left 
 8     * @param right
 9     * @param bottom
10     * @param top
11     * @param near
12     * @param far
13     */
14    public static void frustumM(float[] m, int offset,
15            float left, float right, float bottom, float top,
16            float near, float far)
17

需要注意的是 near 和 far 变量的值必须要大于 0 。因为它们都是相对于视点的距离,也就是照相机的距离。

当用视图矩阵确定了照相机的位置时,要确保物体距离视点的位置在 near 和 far 的区间范围内,否则就会看不到物体。

由于透视投影会产生近大远小的效果,当照相机位置不变,改变 near 的值时也会改变物体大小,near 越小,则离视点越近,相当于物体越远,那么显示的物体也就越小了。

当然也可以 near 和 far 的距离不动,改变摄像机的位置来改变观察到的物体大小。

perspectiveM

OpenGL 还提供了 perspectiveM 函数来创建投影矩阵,它的视景体和 frustumM 函数相同,但是构造的参数有所不同。

 1 /**
 2     * Defines a projection matrix in terms of a field of view angle, an
 3     * aspect ratio, and z clip planes.
 4     *
 5     * @param m the float array that holds the perspective matrix
 6     * @param offset the offset into float array m where the perspective
 7     *        matrix data is written
 8     * @param fovy field of view in y direction, in degrees
 9     * @param aspect width to height aspect ratio of the viewport
10     * @param zNear
11     * @param zFar
12     */
13    public static void perspectiveM(float[] m, int offset,
14          float fovy, float aspect, float zNear, float zFar)
15

视景体不再需要确定近平面左、上、右、下距离了。

通过视角来决定我们能看到的视野大小。视角就是图中所示的那个夹角。另外的参数是视口的宽高比,还有近平面和远平面的距离,参数个数减少了。

上述图片左边是 90 视角,右边是 45 度视角。显然,视野角度越大,则看到的内容更多,但是物体显得更小,而视野角度越小,则看的内容更少,但物体显得更大。

frustumM不同的是,一旦确定了视角和宽高比,那么整个摄像机视野也就确定了,此时完整的锥形视野已经形成了,也就是说物体的近大远小效果已经完成了。这时,近平面距离和远平面距离只是想要截取锥形视野中的那一部分了。不像在frustumM函数中,近、远平面的距离还能够调整近大远小的效果。

参考

  1. 《OpenGL ES 应用开发实践指南》
  2. 《OpenGL ES 3.x 游戏开发》

具体代码详情,可以参考我的 Github 项目:

https://github.com/glumes/AndroidOpenGLTutorial

本文分享自微信公众号 - 纸上浅谈(glumes_blog),作者:glumes

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-06-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • OpenGL 学习系列---观察矩阵

    在 OpenGL 投影矩阵 这篇文章中,讲述了 OpenGL 坐标系统中的投影矩阵,有两种类型的投影矩阵,分别是正交投影和透视投影。

    glumes
  • 渐变过渡的相册(shader)

    相册是一个大家比较熟悉的场景,一般我们是实现的都是那种跑马灯式的轮播相册,这里异名给大家提供一个利用shader实现图片渐变过渡的相册思路

    glumes
  • 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件

    我们已经能够加载 YUV 帧并显示了,那是把一张图片转换成 YUV 帧得到的素材。

    glumes
  • 框架源码调试实战之easypoi异常解决方案精讲

    最近有个同事遇到了个棘手的问题,easypoi导出文件出了bug,却不知道是怎么回事,无从下手,无可奈何,由于事态紧急,只能火急火急的求助于我。我问他...

    Java深度编程
  • Android自定义view实现水波进度条控件

    通过自定义view实现了一个水滴滴落到水波面,溅起水花并且水波流动上涨的进度条控件。之前看到过好多水波流动的进度条,感觉欠缺些东西,就想到了水滴到水平面,溅起水...

    砸漏
  • 数据结构 第8讲 KMP算法

    j=5:T′="abaa",前缀为"a",后缀为"a",相等且l=1;前缀为"ab",后缀为"aa",不等;前缀为"aba",后缀为"baa",不等,因此nex...

    rainchxy
  • link 链表反转

    输入: a -> b -> c -> d -> e -> f  输出: b -> a -> d -> c -> f -> e

    阳光岛主
  • 11张图让你彻底明白jdk1.7 hashmap的死循环是如何产生的

    jdk1.7 hashmap的循环依赖问题是面试经常被问到的问题,如何回答不好,可能会被扣分。今天我就带大家一下梳理一下,这个问题是如何产生的,以及如何解决这个...

    苏三说技术
  • ASP.NET MVC5+EF6+EasyUI 后台管理系统(76)-微信公众平台开发-网页授权

     前言 网页授权是:应用或者网站请求你用你的微信帐号登录,同意之后第三方应用可以获取你的个人信息 网上说了一大堆参数,实际很难理解和猜透,我们以实际的代码来...

    用户1149182
  • SAP 关于ABST2的若干问题

    年关岁尾,总是会遇到关于ABST2的问题,前年,去年,今年,同样的问题老生常谈,年年有余

    matinal

扫码关注云+社区

领取腾讯云代金券