OpenGL ES---矩阵变换

 上一节中,借助 OpenGL ES 对二维图形的绘制,我们了解了相关概念。本节,我们来谈一个有趣的问题,坐标变换。
 坐标变换对于渲染管线来说,是一个非常重要的概念。我们通过它,将一个三维对象从原始的模型坐标系中,一步一步投射到屏幕坐标系中。而可编程的 vertex shader(顶点渲染器) 给我们自定义变换过程提供了基础。

概述

 接下来,我们来看看坐标变换的流程。

坐标变换流程

 如上图所示,modeling transformation、view transformation、projection transformation 发生在顶点着色器中(vertex shader)。而 perspective division 和 view transformation 随后发生,且不可自定义过程。不同的转换,实际上是将要绘制的对象带入到了不同的坐标系中。我们注意到有这样几个坐标系:

  • 模型坐标系:主要用以定义描述绘制对象;
  • 世界坐标系:将要绘制的对象放置到世界坐标系中。所有的绘制对象需要一个共同的坐标系来决定对象之间的相对位置。正如一个杯子的坐标,只是用来描述杯子本身的形体,我们还需要把它们放到世界空间中,在它的旁边,可能还有茶壶、桌子等等。
  • 视坐标:也叫照相机坐标系,你可以想象我们在那个方向、那个位置放置了一个照相机,也可以说,那是我们眼睛的位置。
  • 裁剪坐标系:将区域进行裁剪,有些东西可能在视线之外,就需要裁掉;
  • 归一化坐标系:OpenGL 认为它所绘制的区域是一个正方形,每个方向上范围在[-1,1]之间。
  • 屏幕坐标系:或者说是窗口坐标系,就是将归一化坐标系投射到实际屏幕上。

更加形象的过程,如下图所示:

坐标变换流程

接下来,我们再来详细看看每种变换过程。

模型变换

 模型变换的主要作用,是将对象从模型坐标系中转移到公共的世界坐标系中,模型坐标通常用来描述对象本身。这种变换包括了:平移、旋转、放缩,而变换本身实际上是通过与一个 4*4 的变换矩阵相乘实现的。

平移矩阵:

平移矩阵

缩放矩阵:

缩放矩阵

沿 X 轴旋转矩阵:

沿 X 轴旋转矩阵

沿 Y 轴旋转矩阵:

沿 Y 轴旋转矩阵

沿 Z 轴旋转矩阵:

沿 Z 轴旋转矩阵

 你可能会疑惑,三维世界里的对象当然会使用三维坐标,为什么会使用 4*4 的变换矩阵。

模型变换矩阵

 从数学上讲,对于一个三维空间中的对象,旋转、缩放这些变换是矩阵乘法问题,而平移是矩阵加法问题。使用4个分量来描述三维坐标就是为了能够使得平移过程能够以乘法表示,从而使得 p’ = m1*p + m2(m1 旋转缩放矩阵, m2 为平移矩阵, p 为原向量 ,p’ 为变换后的向量)。转换到 p’ = M*p的形式。此处还可以参考链接
 同时,我们使用四个分量来描述三维世界的坐标系统实际上是个齐次坐标系统,它可以描述无穷远点。
 下文引用维基百科:

一条通过原点 (0, 0) 的线之方程可写作 nx + my = 0,其中 n 及 m 不能同时为 0。以参数表示,则能写成 x = mt, y = − nt。令 Z=1/t,则线上的点之笛卡儿坐标可写作 (m/Z, − n/Z)。在齐次坐标下,则写成 (m, − n, Z)。当 t 趋向无限大,亦即点远离原点时,Z 会趋近于 0,而该点的齐次坐标则会变成 (m, −n, 0)。因此,可定义 (m, −n, 0) 为对应 nx + my = 0 这条线之方向的无穷远点之齐次坐标。因为欧氏平面上的每条线都会与透过原点的某一条线平行,且因为平行线会有相同的无穷远点,欧氏平面每条线上的无穷远点都有其齐次坐标。

 在实际操作过程中,我们并不会真的先去计算好这些矩阵,然后再进行变换,android.opengl.Matrix 类能够帮助我们计算好它们,具体可以去查询相关 API 。

//矩阵旋转
Matrix.rotateM(rotationM,0,90,0,1,0);
//矩阵相乘
Matrix.multiplyMM(transformationM,0,eyesMatrix,0,rotationM,0);

视图变换

 你可以假设将一个摄像机朝着某个方向在某个位置观察着三维对象,虽然在 OpenGL ES 中并不存在这样的设备。我们都知道运动是相对的,移动摄像机对物体进行拍摄,相对而言,也可以是移动物体,以达到通过移动摄像机而从不同角度观察物体的目的。

视图坐标

 这种变换,从本质上而言和矩阵的模型变换并没有差别,实际上所执行的都是矩阵乘法。即,以视图变换矩阵和目标对象相乘。具体的推到过程这里将省略,我们可以使用 Matrix 类提供的方法获取:

获取视图变换矩阵

 API 原型为:

   /**
     * Defines a viewing transformation in terms of an eye point, a center of
     * view, and an up vector.
     *
     * @param rm returns the result
     * @param rmOffset index into rm where the result matrix starts
     * @param eyeX eye point X
     * @param eyeY eye point Y
     * @param eyeZ eye point Z
     * @param centerX center of view X
     * @param centerY center of view Y
     * @param centerZ center of view Z
     * @param upX up vector X
     * @param upY up vector Y
     * @param upZ up vector Z
     */
    public static void setLookAtM(float[] rm, int rmOffset,
            float eyeX, float eyeY, float eyeZ,
            float centerX, float centerY, float centerZ, float upX, float upY,
            float upZ) {
            ......
}

投影变换

 投影变换,从技术上讲是将对象从视坐标系下转义到裁剪坐标系下。它是在顶点着色器返回 gl_Position 之前进行的最后一次变换,接着,是通过透视除法(w分量),将裁剪坐标系转到归一化坐标系下。
 我们最常用的投影是正交投影和透视投影。

正交投影

 对于正交投影,使用平行光线对三维对象进行投影,所以所投影出来的影像没有现实世界中远近的概念。在 Android 应用开发中,可以使用以下函数来获得变换矩阵:

public static void orthoM(float[] m, int mOffset,
        float left, float right, float bottom, float top,
        float near, float far)

 由上可知,至少我们可以通过正交投影来设置要显示的区域。比如,为了让显示图形在显示的时候不发生压缩等形变:

@Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
        // 根据屏幕方向设置投影矩阵
        float ratio= width > height ? (float)width / height : (float)height / width;
        if (width > height) {
            // 横屏
            Matrix.orthoM(projectionMatrix, 0, -ratio, ratio, -1, 1, 0, 5);
        } else {
            Matrix.orthoM(projectionMatrix, 0, -1, 1, -ratio, ratio, 0, 5);
        }
    }

 下面我们来看下透视投影。

透视投影

 对于透视投影,观察空间是一个视椎体,远端大近端小,投射光线显然不是平行线,这样物体投射出来就有了远近的概念。离眼睛越远的地方,物体越小,越近,同一个物体显示越大。

public static void frustumM(float[] m, int offset,
            float left, float right, float bottom, float top,
            float near, float far) 

视口变换

 该过程是自动执行且固定不变的,用以进行从归一化坐标系到实际屏幕坐标系的转换过程。

小结

 整个过程,我觉得有点像拍电影。将若干人物(模型坐标)集中在一个拍摄场景(世界坐标)中,在拍摄过程中,会进行镜头移动到一个固定角度(视图坐标),调好焦距(投影坐标),进行拍摄。

参考链接:
GLSL Programming/Vertex Transformations
OpenGL ES 投影变换 Projection
Article - World, View and Projection Transformation Matrices
这次,彻底搞懂 OpenGL 矩阵转换

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容