这章主要探讨矩阵,这些矩阵表明了应用在咱们场景上的变换,容许咱们移动物体。然而在webGL api中并无一个专门的camera对象,只有矩阵。好消息是使用矩阵来取代相机对象能让webgl在不少复杂动画中拥有更高的灵活性。html
第四章中主要内容:html5
一、了解场景从3d世界到二维屏幕所经历的变换web
二、学习仿射变换算法
三、将矩阵匹配到ESSL uniforms变量中编程
四、了解Model-View矩阵和透视投影矩阵canvas
五、构造法线变换矩阵api
六、建立一个相机对象使用它来旋转3d场景数组
WebGL中并无一个能够操控的相机对象。然而咱们能够假设咱们渲染在canvas上的场景就是咱们的相机捕获的。这章中咱们将学习用4x4矩阵来表达一个相机对象。app
每次咱们移动咱们的相机时,都须要根据相机的新位置跟新相机中的对象。为了达到目的,咱们须要系统的处理每一个顶点来将变换应用上去,以便产生新的视点坐标位置。相似的,咱们须要保证对象的发现和光照方向在相机移动后仍能保持一致。因此咱们须要分析两种变换:顶点和法线编程语言
webgl场景中的物体在咱们的屏幕看到他们以前会经历多种不一样的变换。每个变换都用4x4的矩阵来表达。如何将只有xyz属性的顶点与4x4矩阵相乘?简要回答是将咱们的元组增长一个维度,每个顶点如今拥有4个维度了,这种坐标表达方式成为齐次坐标。
Homogeneous coordinates
齐次坐标系在任何计算机图形程序中都是关键的一部分。它使得咱们可以用4x4矩阵来表达仿射变换(旋转、缩放、切变、平移)和投影变换。在齐次坐标系中,顶点拥有四个部分:x、y、z、w。前三个部分是顶点在欧几里得空间中的坐标。第四个部分是透视部分。因此四元组(x, y, z, w)将咱们带到了一个新的空间:投影空间。
齐次坐标系使得咱们能够在一中特殊的方程组中求出解,这个方程组中每个方程都表示一个与系统中其余直线平行的直线。咱们知道在欧几里得空间中,对这种方程组是无解的,由于他们没有交点。然而在投影空间中,这种方程序有解,这些直线将会在无穷远处相交。这实际上表明了他们的透视部分的值是0.关于这种观点的一个很好的物理类比就是想象一下全部的平行铁路都会来视线的终点处相交。
从齐次坐标系变换到欧几里得坐标系是很容易的。
反过来,从欧几里得空间到投影空间,咱们只须要加上第四维并将它设置为1.
从齐次坐标系到欧几里得坐标系,咱们除以w便可;从欧几里得坐标系到齐次坐标系咱们将第四维设置为1.w=0的其次坐标表明一个在无穷远处的点。
另外一个关于齐次坐标系须要知道的事情是,顶点有一个w=1的齐次坐标,向量的齐次坐标w=0.因此在Phong顶点着色器中,法线向量的处理以下:
vNormal = vec3(uNMatrix * vec4(aVertexNormal, 0.0));
顶点的处理以下:
//Transformed vertex position
vec4 vertex = uMVMatrix * vec4(aVertexPosition, 1.0);
下面来看一下咱们的顶点在呈如今屏幕前须要经历的一系列变换:
Model transform
咱们从对象坐标系来开始咱们的分析。顶点坐标在这个空间中被指定(相似矢量切片)。而后若是咱们想移动或者旋转对象,咱们会使用一个矩阵来编码这些变换。这个矩阵被称为 模型矩阵 。一旦咱们用模型矩阵来乘以咱们对象中的顶点,咱们将会得到新的顶点坐标。这些新的顶点决定物体在咱们的3d世界中的位置。
在对象坐标系中,每一个物体均可以自由定义它的原点而后指定它的顶点相对于原点的位置;在世界坐标系中,原点被全部的物体所共享。世界坐标系容许咱们知道物体相对于其余物体的位置。经过模型变换咱们能够决定对象在3d世界中的位置。
View transform
接下来的变换是视点变换,将坐标系的原点转换到视点。视点是咱们的眼睛或者相机相对于世界原点的位置。换句话说,视图变换是经过视点坐标来切换世界坐标。这个变换能够用视点矩阵来编码。咱们经过模型变换后的顶点坐标来乘以这个矩阵。这个操做的结果是获得原点是视点的一堆顶点坐标。咱们将在这个坐标系中来操做咱们的相机。
Projection transform
下面的操做被称为投影变换。这个操做决定了多大的视点空间将被渲染和它将如何被匹配到计算机屏幕上。这个区域被称为视锥体,它由六个平面来定义(近平面、远平面、上底面、下底面、左平面、右平面),以下所示:
这六个平面被编码在透视矩阵中。任何坐落于视锥体外面的顶点在应用投影变换后将被裁剪,并在后续的处理中被抛弃。通过投影变换后的空间成为裁切空间。(透视矩阵由视锥体产生,通过透视矩阵变换后的空间称为裁切空间,顶点的w坐标可能不在是1)
视锥体的形状和范围决定了从3d视点空间到2d屏幕的投影类型。若是远平面和近平面拥有相同的维度,视锥体将产生一个正射投影。不然将产生透视投影,以下图所示:
目前为止咱们仍然是在齐次坐标系中工做,因此裁切坐标有四个部分:x、y、z、w。裁剪是经过比较x、y、z与齐次坐标w。若是其中任何一个大于+w或者小于-w,那么这个顶点位于视锥体外并被抛弃。
Perspective division
一旦决定了多大的视域空间将被渲染,视锥体将被匹配到近平面来为了产生一张2d图片。近景面的内容是将被渲染到咱们计算机屏幕的内容。
不一样的操做系统和显示设备会有不一样的机制来渲染2d信息到屏幕上。为了保证全部状况下的健壮性,webgl提供一个独立于任何硬件的中介坐标系统。这个空间被称为归一化设备坐标(NDC)。
归一化设备坐标经过将得到的裁剪坐标除以w份量得到。这也就是为何这一步叫作透视除法。另外要记住当你除以齐次坐标后,咱们从投影空间转到了欧几里得空间,因此NDC坐标只有三个份量。在NDC坐标中,x y坐标表明了你的顶点在归一化2d屏幕上的位置,而z坐标编码了深度信息,即物体相对于近景面和远景面的位置。尽管在这里咱们工做在2d屏幕上,咱们仍然保持着深度信息。这容许webgl后面基于物体到近景面的距离来决定如何显示物体的叠盖关系。 当使用归一化设备坐标后,深度信息被编码在了z份量上。
透视除法将视锥体变换精一个立方体中,原点在立方体的中心,最小坐标是(-1, -1, -1)最大坐标是(1, 1, 1).并且z轴的方向是相反的。以下所示:
Viewport transform
最后NDC被匹配到视口坐标。这一步将这些坐标映射到屏幕上的可视区域。在webgl中,这个区域由html5的canvas提供,以下图所示:
与以前的步骤不一样,视口变换不是由矩阵变换产生的。在这里咱们使用webgl的viewport函数。
Normal transformations法线变换
当顶点被变换后,法线向量也须要被变换,以便他们可以指向正确的方向。咱们能够考虑用模型视图矩阵来作这件事,可是存在一个问题:模型视图矩阵并不必定可以保持发现的垂直性。
这个问题发生在单轴缩放或者切变的矩阵中。在咱们的例子中,咱们有一个通过延y轴缩放的三角形。咱们可以看到在应用完模型视图矩阵后,法线N'不在是该表面的法线。因此咱们要从新计算法线矩阵。
Calculating the Normal matrix
若是两个向量垂直,他们的点乘积是0.
N*S = 0
S是经过表面两个不一样的顶点构造的一个向量。
M做为模型视图矩阵。咱们可使用M来变换S:
S' = MS
咱们想要找到一个矩阵K来容许咱们作相似的变换。对于法线N:
N' = KN
变换后仍然会保持N'与S'的垂直性:
N'*S' = 0
而后将N'和S'替换:
(KN)*(MS) = 0
因为向量能够表示成1x3的矩阵,因此点乘能够表示为对一个向量矩阵的转置与第二个向量相乘:
(KN)T(MS) = 0
矩阵相乘的转置等于矩阵的转置逆序相乘:
NTKTMS = 0
矩阵相乘知足结合律,因此:
NT(KTM)S = 0
由于N*S=0,因此NTS = 0.因此这表明(KTM)=I,由于只有标准单位对角矩阵才使得NI=N;
综上结论:
WebGL implementation WebGL的实现方式
如今咱们来看一下咱们是如何在webgl中实现顶点和法线的变换。下面的图展现了咱们在理论上所学到的和理论与webgl实践的关系:
在webgl中,咱们应用于物体上坐标来获得视口坐标的五个变换被组织在三个矩阵和一个webgl方法中:
一、模型视图矩阵将模型变换和视点变换组织在一个矩阵中。当咱们经过这个矩阵来乘以咱们的顶点,咱们就获得的视点坐标
二、法线矩阵经过将模型视图矩阵求逆并转置获得。这个矩阵被用来乘以法线向量以便用来计算光照
三、透视矩阵用来组织投影变换和透视除法,而后咱们就获得了NDC设备归一化坐标。
最后咱们使用gl.viewport来说NDC坐标匹配到视口坐标。
gl.viewport(minX, minY, width, height);
视口坐标的原点在canvas的左上角。
JavaScript matrices
glmatrixwebgl自己没有提供方法来实现矩阵操做。webgl作的全部工做就是提供一种将矩阵传递给着色器的方式。因此咱们须要使用一个JavaScript类库来让咱们拥有操做矩阵的能力。这里使用这个类库。
Mapping JavaScript matrices to ESSL uniforms
因为模型视图矩阵和透视矩阵在一次渲染过程当中不会改变,因此咱们将他们作uniforms类型传递给着色程序。好比,若是咱们要讲场景中的一个物体作平移操做,咱们须要用平移操做后的新坐标来绘制全部物体。将全部的物体画在新位置是在一个渲染步骤中完成的。
然而,在渲染被(drawArrays或drawElements)调用以前,咱们须要确保着色器拥有一个更新后的矩阵版本。具体步骤以下:
var reference= getUniformLocation(Object program, String uniformName)
gl.uniformMatrix4fv(WebGLUniformLocation reference, bool transpose,float[] matrix);其中matrix是一个JavaScript变量
uniformMatrix[2 3 4]fv(reference, transpose, matrix)可以经过reference来加载 2x2, 3x3, 4x4的浮点数据矩阵到uniform型着色器变量中。reference是WebGLUniformLocation型的变量。为了实际操做的简便,它一般是int数字。按照规范,transpose的值必须是false。matrix老是浮点类型。matrix一般是4,9,16个元素的按照列向量形式的数组。其中matrix参数也能够是Float32Array类型。这是一种JavaScript类型数组,使用二进制来存储数据,可以提高性能。
Working with matrices in ESSL
在以下着色器代码中:
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
将一个计算值传递给了以前定义的gl_Position变量。gl_Position将包含着色器最近处理的顶点的裁剪坐标。这里应该记住着色过程是并行的,每个顶点将会被一个顶点着色器的实例所处理。
为了得到给定顶点的裁切坐标,咱们须要先乘以模型视图矩阵而后是投影矩阵。为了达到这个目的咱们由左到右来计算(矩阵乘法不知足交换律)。
另外,注意咱们须要包含齐次坐标来加强aVertexPosition属性。由于咱们一般在欧几里得空间中来定义咱们的图形。幸运的是ESSL容许咱们经过增长确实部分来建立一个四维向量vec4。由于模型视图矩阵和透视矩阵都是在齐次坐标系中被描述的。
The Model-View matrix
模型视图矩阵容许在咱们的场景中实现仿射变换。仿射是一个数学名词来描述一种对物体应用变换后不会改变物体结构的变换。
其中:
m13 m14 m15表明平移
m4 m8 m12老是0,m16老是1;
The Camera matrix
webgl中没有专门的相机对象,咱们假想的相机对象由一个4x4矩阵所表达。
假定咱们的相机坐落于世界的原点,朝向Z轴的负方向。关于相机的运动问题咱们分红两个步骤来看: 相机的平移和相机的旋转 。
(本书中使用的是列向量来作讲解,而glmatrix中使用的是行向量)
The Camera matrix is the inverse of the Model-View matrix
相机矩阵跟模型视图矩阵为互逆关系。
根据程序想要达到的效果不一样,相机类型也分为两种类型:轨道相机和跟踪相机。
轨道相机达到的目的是以一个世界中心店来作旋转、平移;好比当一个地球所有展示在你面前时,你对地球的旋转、拉近地球距离,这时候就是轨道相机。
跟踪相机的原点在相机位置;以相机为原点进行旋转前进后退等;cs游戏就是典型的跟踪相机模式。
这两种类型的相机在实现起来的差异就是对相机的模型矩阵的平移和旋转的前后顺序不一样;轨道相机是先旋转相机的模型矩阵再平移;跟踪相机是先平移相机的模型矩阵,在对平移后的矩阵作旋转。
最终获得一个相机的模型矩阵,对这个相机的模型矩阵求逆就是以相机为原点的模型视图矩阵。
(跟踪相机和轨道相机是能够相互转化的,咱们只须要把平移跟旋转的顺序变更一下便可)
Translating the camera in the line of sight
在视域中移动相机;对于轨道相机因为老是指向世界的中心点,因此咱们一般使用世界坐标的z轴来改变物体与世界的远近。
而对于跟踪相机,因为他可能指向世界的任何地方,因此咱们须要知道在世界坐标系中相机的朝向。
对于行向量来讲先旋转后平移,数学上的矩阵操做顺序应该为:vRT;而对于列向量应该是TRv;这是由矩阵的运算法则所决定的。
而在实际编程语言中,一般使用一维数组来存储4x4矩阵的16个元素;因此有行存储和列存储的说法。所谓的行存储和列存储的区分就在于数组的前四个元素存储的是矩阵的第一列仍是第一行;表示列的称为列存储,表示行的成为行存储。以下图数组的前四个元素对应矩阵中的第一列,因此是列存储。
当须要对物体或相机作一系列变换时,咱们首先要肯定矩阵的变换顺序,好比对webgl这种列主序的,先旋转后平移的矩阵操做是:TRv。 肯定矩阵顺序后,接下来肯定矩阵类库的api调用顺序 。向glmatrix这种类库提供的api,对先旋转后平移这种矩阵操做的实现方式是:
若是初学者要肯定类库api的调用顺序,最好看一下类库矩阵运算部分的源码,不然容易绕晕。其实无论类库的api如何设计若是调用,只要保证计算的结果符合数学公式便可。
最后获得一个要传给着色器的最终矩阵,要保证这个一维数组是以列主序的方式存储。
综上来看,一点要保证获得的一维数组的元素在数学上是按照正确的矩阵运算顺序获得的结果。第二要保证传给着色器的一维数组是按照列主序的方式来存储的。
Camera model
相机矩阵中编码了关于相机轴的指向信息。如图所示,左上角的3x3矩阵表明了相机的三个轴:
因为实际中相机矩阵是模型视图矩阵的逆矩阵,包含在相机矩阵中左上角的3x3的旋转矩阵,能告诉咱们在世界空间中相机的三个轴的方向。
The Perspective matrix
透视矩阵包含投影变换和透视除法。这两步合起来将一个3d场景转换成一个立方体(后面经过视口变换匹配到2d画布上)。
实际上,透视矩阵决定了相机捕获到的图像中几何物体。在真是世界中,相机镜头会决定最终的图像是如何变形扭曲的。在webgl世界中,咱们使用透视矩阵来模拟这个过程。另外在webgl世界中咱们能够有另外一种非透视的表达:平行投影。
透视矩阵决定了相机的视野,也就是多少3d空间会被相机捕获。视野使用角度做为单位,这个术语一般与视角这个术语交叉使用。
Perspective or orthogonal projection
透视投影可以实现近大远小的效果,它更接近咱们的眼睛观察到的真实世界,由于它给咱们的大脑一些深度提示信息,因此咱们能感到距离感。
相反,平行投影使用平行线,没有近大远小的感受。因此在平行投影中深度信息被丢失了。
使用glMatrix,咱们经过调用mat4.persective或者mat4.ortho来设置透视投影或平行投影矩阵。
Structure of the WebGL examples
webgl app的生命周期:
矩阵操做函数: