然后我们直接取齐次坐标中的x’和y’值,并将其线形映射到屏幕上,比如点(0,0)出现在屏幕中央,点(-1,1)出现在屏幕左上角。
WebGL
WebGL中对于模型视图矩阵和投影矩阵的操作依赖于第三方库,比如Oak3D或glMatrix,WebGL本身不支持(或者说不限制)任何对模型视图矩阵和投影矩阵的操作。
WebGL是在浏览器端运行的,所以使用JavaScript编程。下面的代码来自www.hiwebgl.com翻译的LearningWebGL.com的WebGL教程。 以glMatrix库为例: // 新建空模型视图矩阵
var mvMatrix = mat4.create(); // 将矩阵设置为单位阵 mat4.identity(mvMatrix); // 平移和旋转
mat4.translate(mvMatrix, [-1.5, 0.0, -8.0]); mat4.rotate(mvMatrix, degToRad(45), [0, 1, 0]);
将矩阵设置为单位阵相当于说:“这个矩阵表示什么都还没做(平移、旋转、缩放)呢”,事实上,任意点坐标乘以单位矩阵都只能得到自己,正说明“什么都没做”。
平移矩阵的函数mat4.translate()做的仅仅是将mvMatrix左乘一个平移矩阵而已。 旋转矩阵的函数mat4.rotate()也许比较复杂,它做的是上面我们讨论过的“围绕任意轴旋转”的问题,这个函数默认使用“本地轴”,即过所有平移效果累加后的那一点的轴,参数向量[0,1,0]是轴的指向,因此上面的函数调用处理了一个围绕本地y轴的旋转。 // 新建空投影矩阵
var pMatrix = mat4.create(); // 初始化投影矩阵
mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
投影矩阵不会因为场景里模型的位置变化或观察者的移动而变化(当然如果你想模拟观察者戴眼镜的过程你可能要考虑),故而投影矩阵只需要一次初始化就够了。初始化需要给出相机空间的前、后、左、右、上、下边界,很容易从函数调用里传入的参数推知:包括前、后边界,相机空间的宽高比和水平视场角。
如果你使用脚本调试工具监测矩阵对象mvMatrix和pMatrix,就会发现他们仅仅是有16个元素的Float32Array对象而已,你完全可以亲自处理它。
值得一提的是glMatrix库的函数大多不返回处理后的矩阵,在将矩阵作为参数传入时已经给了函数修改矩阵的权利,很少的情况下需要会写这样的代码(但其他的库不一定这样):
xMatrix = matX.operate();
使用库函数或自力更生处理完矩阵后,通过着色器程序传递到着色器中(着色器程序是JavaScript脚本里的概念,而着色器是用其他脚本语言编写的在显卡中运行的逻辑,这些不在本文的讨论范围内): // 设置着色器程序 ……
shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, \
shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, \……
// 将模型视图矩阵和投影矩阵传入着色器
gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix); gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
然后看看着色器里的代码,这是用x-shader类型的脚本语言写的: void main(void){ ……
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); …… }
可以看到屏幕上点坐标由初始点坐标左乘模型视图矩阵,再左乘投影矩阵得到的。对于较复杂的场景,我猜测可能需要重新编写着色器,将模型矩阵和视图矩阵拆开处理。
综上所述,模型视图矩阵和投影矩阵是三维计算机图形学的基石。关于这两个矩阵的知识虽然不是进行3D图形编程的必须,但是至少能够帮助我们更好地了解那些库函数在做些什么,或者自己直接操作矩阵对象。
OpenGL矩阵推导——模型视图变化
在三维编程中,模型视图变换是从三维世界到二维屏幕中一个很重要的变换,但是这个变换往往很多人都不太理解,要么是事而非。而这方面的文章不是太少就是讲的太浅没有真正的理解模型视图变换,本人在这个过程中曾经走过很多歪路,不过好在最终在自己的不懈努力下终于降伏了这只猛虎。本人就以自己的理解,通过矩阵推导过程一步一步来了解模型视图变化,最后通过两个OpenGl的程序来进一步理解模型视图矩阵。先从一个基本的模型视图—透视投影变换讲起。
透射投影是将相机空间中的点从视锥体(frustum)变换到规则观察体(Canonical View Volume 以下简称CVV)中,待裁剪完毕后进行透视除法的行为。
透视投影变换是令很多刚刚进入3D图形领域的开发人员感到迷惑乃至神秘的一个图形技术。其中的理解困难在于步骤繁琐,对一些基础知识过分依赖,一旦对它们中的任何地方感到陌生,立刻导致理解停止不前。
主流的3D APIs 都把透射投影的具体细节进行了封装,从而只需一个函数便可生成一个透射投影矩阵比如gluPerspective(),使得我们不需要了解其算法便可实现三维到二维的转化,然而实事是,一些三维图形或游戏开发人员遇到一些视图矩阵的问题往往会不知所措,比如视景体裁剪。
以下部分内容是从别处那转过来的,主要感谢Twinsen和一个叫丁欧南的高中生。 透视投影变换是在齐次坐标下进行的,而齐次坐标本身就是一个令人迷惑的概念,这里我们先把它理解清楚。齐次坐标
对于一个向量v以及基oabc, 可以找到一组坐标(v1,v2,v3),使得 v = v1 a + v2 b + v3 c (1)
而对于一个点p,则可以找到一组坐标(p1,p2,p3),使得 p – o = p1 a + p2 b + p3 c (2)
从上面对向量和点的表达,我们可以看出为了在坐标系中表示一个点(如p),我们把点的位置看作是对这个基的原点o所进行的一个位移,即一个向量——p – o(有的书中把这样的向量叫做位置向量——起始于坐标原点的特殊向量),我们在表达这个向量的同时用等价的方式表达出了点p:
p = o + p1 a + p2 b + p3 c (3)
(1)(3)是坐标系下表达一个向量和点的不同表达方式。这里可以看出,虽然都是用代数分量的形式表达向量和点,但表达一个点比一个向量需要额外的信息。如果我写出一个代数分量表达(1, 4, 7),谁知道它是个向量还是个点! 我们现在把(1)(3)写成矩阵的形式:
这里(a,b,c,o)是坐标基矩阵,右边的列向量分别是向量v和点p在基下的坐标。这样,向量和点在同一个基下就有了不同的表达:3D向量的第4个代数分量是0,而3D点的第4个代数分量是1。像这种这种用4个代数分量表示3D几何概念的方式是一种齐次坐标表示。 ―齐次坐标表示是计算机图形学的重要手段之一,它既能够用来明确区分向量和点,同时也更易用于进行仿射(线性)几何变换。‖——F.S. Hill, JR
这样,上面的(1, 4, 7)如果写成(1,4,7,0),它就是个向量;如果是(1,4,7,1),它就是个点。 下面是如何在普通坐标(OrdinaryCoordinate)和齐次坐标(Homogeneous Coordinate)之间进行转换:
从普通坐标转换成齐次坐标时, 如果(x,y,z)是个点,则变为(x,y,z,1); 如果(x,y,z)是个向量,则变为(x,y,z,0) 从齐次坐标转换成普通坐标时,
如果是(x,y,z,1),则知道它是个点,变成(x,y,z); 如果是(x,y,z,0),则知道它是个向量,仍然变成(x,y,z)
以上是通过齐次坐标来区分向量和点的方式。从中可以思考得知,对于平移T、旋转R、缩放S这3个最常见的仿射变换,平移变换只对于点才有意义,因为普通向量没有位置概念,只有大小和方向,这可以通过下面的式子清楚地看出: