【笔记】《WebGL编程指南》学习-第4章高级变换与动画基础(1-平移然后旋转))

来源:互联网 发布:枪匠遥感勘测数据 编辑:程序博客网 时间:2024/05/18 01:37

目标:实现三角形的 先旋转再平移
结果:
这里写图片描述


本节要使用一个专为本书编写的矩阵函数库。有了矩阵函数库,进行如“平移,然后旋转”这种复合的变换就很简单了。


矩阵变换库:cuon-matrix.js

在 OpenGL 中,我们无需手动指定变换矩阵的每个元素,因为 OpenGL 提供了一系列有用的函数来帮助我们穿件变换矩阵。比如,通过调用 glTranslate ()函数并传入在X、Y、Z轴上的平移的距离就可以创建一个平移矩阵。

这里写图片描述

遗憾的是,WebGL 没有提供类似的矩阵函数,如果想要使用他们,你就得自己编写,或者使用其他人已经编写好的。因为矩阵函数非常有用,所以书的作者专门编写了一个JS函数库 cuon-matrix.js。该函数允许你通过Open GL 中类似的方法创建变换矩阵。虽然这个库是专门为本书编写的,你也可以在自己的程序中使用它。

Matrix4 是矩阵库提供的新类型,顾名思义,Matrix4 对象表示一个 4x4 的矩阵。该对象内部使用类型化数组 Floated2Array 来存储矩阵的元素。

我们来利用 Matrix4 和其相关的方法来把 RotatedTriangles_Matrix 程序重写一遍,找找使用这个矩阵函数库的感觉。


示例程序

这个示例程序相比于第3章中的 RotatedTriangle_Matrix.js,唯一的改动发生在新步骤上:创建变换矩阵,并将变换矩阵传给顶点着色器。

在 RotatedTriangle_Matrix.js 中,我们是这样创键变换矩阵的:

var radian = Math.PI * ANGLE / 180.0; // Convert to radians  var cosB = Math.cos(radian), sinB = Math.sin(radian);  var xformMatrix = new Float32Array([     cosB, sinB, 0.0, 0.0,    -sinB, cosB, 0.0, 0.0,      0.0,  0.0, 1.0, 0.0,      0.0,  0.0, 0.0, 1.0  ]);

在本例中,你需要利用一个 Matrix 对象比调用其setRotate()方法计算出旋转矩阵来重写这部分:

  var ANGLE = 90.0;    //为旋转矩阵创建 Matrix4 对象    var xformMatrix = new Matrix4();    //将 xformMatrix 设置为旋转矩阵    xformMatrix.setRotate(ANGLE, 0, 0, 1);    //将旋转矩阵传输给顶点着色器    var u_xformMatrix = gl.getUniformLocation(gl.program, 'u_xformMatrix');    if(u_xformMatrix < 0){        console.log("Failed to get the storage location of u_xformMatrix");        return;    }    gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix.elements);

可见,创建变换矩阵然后传给 uniform 变量的基本步骤,在这两个示例程序中是相同的:使用 new 操作符新建一个 Matrix4 对象,就像我们使用 new 操作符创建一个 Array 对象或 Date 对象一样。我们新建了一个 Matrxi4对象 xformMatrix,并调用其 setRotate()方法把自身设为计算出的旋转矩阵。

setRotate()函数接受的参数是旋转角(角度制而非弧度制)和旋转轴(x, y, z)。旋转轴(x, y, z)表示旋转是绕着从原点(0, 0, 0)指向(x, y, z)的轴进行的。第3章说过,如果旋转角度值是正值,那么旋转就是逆时针方向的。本例中的旋转是绕Z轴进行的,所以旋转轴设为(0, 0, 1):

    xformMatrix.setRotate(ANGLE, 0, 0, 1);

类似的,如果是绕 X 轴旋转的,那么旋转轴的三个分量x = 1, y = 0, z = 0;如果是绕 Y 轴旋转的,则 x = 0, y = 1, z = 0。一旦你在 xformMatrix 变量中设置好了旋转矩阵,剩下的任务只是用相同的 gl.uniformMatrix4fv()方法将旋转矩阵传入顶点着色器。注意,你不能将 Matrix4 对象直接作为最后一个参数传入,因为该方法的最后一个参数必须是类型化数组。你应当使用 Matrix4 对象的 elements 属性访问存储矩阵元素的类型化数组:

gl.uniformMatrix4fv(u_xformMatrix, false, xformMatrix.elements);

Matrix4 对象所支持的方法和属性如下表所示:

这里写图片描述
这里写图片描述

从上表中科建,Matrix4 对象有两种方法:一种方法的名称中含有前缀 set,另一种则不含。包含 set 前缀的方法会根据参数计算出变换矩阵,然后将变换矩阵写入到自身中;而不含 set 前缀的方法,会先根据参数计算出变换矩阵,然后将自身与刚刚计算得到的变换矩阵相乘,然后把最终得到的记过再写入到 Matrix4 对象中。

如上表所示, Matrix4 对象的方法十分强大且灵活。更重要的是,有了这些函数,进行变换就会变得轻而易举。比如,如果本例中你不希望对三金星进行旋转,而希望对它进行平移,你只需要重写一下内容:

xformMatrix.setTranslate(0.5, 0.5, 0.0);

复合变换

现在,你对 Matrix4 对象应该有了基本的了解,下面就来看看如何利用 Matrix4 将两次变换组合起来,即进行一次平移,再进行一次旋转。

显然,示例中包含了以下两种变换:

  1. 将三角形沿着 X 轴平移一段距离。
  2. 在此基础上,旋转三角形。

这里写图片描述

讲解了这么多,我们可以先写下第1条中的坐标方程式。

这里写图片描述

然后对<平移后的坐标>进行旋转

这里写图片描述

当然你也可以分布计算这两个等式,但更好的方法是,将等式1代入到等式2中,把两个等式组合起来:

这里写图片描述

这里

这里写图片描述

等于

这里写图片描述

最后,我们可以在JS中计算<旋转矩阵>x<平移矩阵>,然后将得到的矩阵传入顶点着色器。像这样,我们就可以把多个变换复合起来了。一个模型可能经过了多次变换,将这些变换全部复合成一个等效的变换,就得到了 模型变换,或称 建模变换,相应地,模型变换的矩阵称为 模型矩阵

再来复习一下矩阵的乘法:
这里写图片描述

如上所示,将两个 3x3 矩阵 A 与 B 相乘的结果如下:

这里写图片描述

上式是两个 3x3 矩阵相乘的结果,实际用到的模型矩阵是 4x4 的矩阵。然后要注意,矩阵相乘的次序很重要,A*B 的结果并不一定等于 B*A。

如你所料,cuon-matrix.js 中的 Matrix4 对象支持矩阵乘法。下面就来看一下如何使用 Matrix4 对象进行矩阵乘法,从而将多个变换复合起来,实现先平移,然后旋转。

RotatedTranslatedTriangle.js

//顶点着色器程序var VSHADER_SOURCE =    'attribute vec4 a_Position;'+    'uniform mat4 u_ModelMatrix;'+    'void main(){'+    'gl_Position = u_ModelMatrix * a_Position;'+    '}';//片元着色器程序var FSHADER_SOURCE=    'void main(){'+    'gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);'+    '}';function main() {    //获取canvas元素    var canvas = document.getElementById("webgl");    if(!canvas){        console.log("Failed to retrieve the <canvas> element");        return;    }    //获取WebGL绘图上下文    var gl = getWebGLContext(canvas);    if(!gl){        console.log("Failed to get the rendering context for WebGL");        return;    }    //初始化着色器    if(!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)){        console.log("Failed to initialize shaders.");        return;    }    //设置顶点位置    var n = initVertexBuffers(gl);    if (n < 0) {        console.log('Failed to set the positions of the vertices');        return;    }    //计算模型矩阵    var ANGLE = 60.0; //旋转角    var Tx = 0.5; //平移距离    //为旋转矩阵创建 Matrix4 对象    var modelMatrix = new Matrix4();    modelMatrix.setRotate(ANGLE, 0, 0, 1);    modelMatrix.translate(Tx, 0, 0);    //将旋转矩阵传输给顶点着色器    var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');    if(u_ModelMatrix < 0){        console.log("Failed to get the storage location of u_ModelMatrix");        return;    }    gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);    //指定清空<canvas>颜色    gl.clearColor(0.0, 0.0, 0.0, 1.0);    //清空<canvas>    gl.clear(gl.COLOR_BUFFER_BIT);    //绘制三个点    gl.drawArrays(gl.TRIANGLES, 0, n);}function initVertexBuffers(gl) {    var vertices = new Float32Array([        0.0, 0.3, -0.3, -0.3, 0.3, -0.3    ]);    var n=3; //点的个数    //创建缓冲区对象    var vertexBuffer = gl.createBuffer();    if(!vertexBuffer){        console.log("Failed to create thie buffer object");        return -1;    }    //将缓冲区对象保存到目标上    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);    //向缓存对象写入数据    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);    var a_Position = gl.getAttribLocation(gl.program, 'a_Position');    if(a_Position < 0){        console.log("Failed to get the storage location of a_Position");        return -1;    }    //将缓冲区对象分配给a_Postion变量    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);    //连接a_Postion变量与分配给它的缓冲区对象    gl.enableVertexAttribArray(a_Position);    return n;}

最关键的两行,我们计算了<旋转矩阵>x<平移矩阵>:

modelMatrix.setRotate(ANGLE, 0, 0, 1);    modelMatrix.translate(Tx, 0, 0);

我们首先调用了包含 set 前缀的方法 setRotate(),传入的参数用以计算旋转矩阵,并写入 mpdelMatrix。接下来,我们调用了不带 set 前缀的方法 translate(),意思就是,先计算出一个平移矩阵,然后用原先存储在 modelMatrix 变量中的矩阵乘以这个新计算平移矩阵,将得到的结果写回 modelMatrix中。由于在第一步之后,modelMatrix 已经包含了一个旋转矩阵,那么经过了这一步,modelMatrix中的矩阵就是<旋转矩阵>x<平移矩阵>了。

你可能回注意到,“先平移后旋转”的顺序与构造模型矩阵<旋转矩阵>x<平移矩阵>的顺序是相反的,这是因为变换矩阵最终要与三角形的三个顶点的原始坐标矢量相乘。

最后,我们把矩形矩阵传递给顶点着色器中的 u_ModelMatrix 变量,并如常将平移和旋转后的红色三角形。

这里写图片描述

阅读全文
0 0
原创粉丝点击