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

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


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


矩阵变换库: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 变量,并如常将平移和旋转后的红色三角形。

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值