WebGL平移变换

  本节课的平移变换在“四、WebGL坐标系源码”的基础上更改,以下几种方法都是沿着X轴平移-0.4,也就是把所有顶点X坐标分别加-0.4

方法一

  重新定义第30行定义的顶点数组数据,用下面的代码直接替换第30~34行的代码,数学计算任务由人完成。

30       var data=new Float32Array([
31           -0.4, 0, 1,
32           -0.4, 1, 0,
33            0.6, 0, 0
34       ])

方法二

  把下面的代码插入到34行后面,借助for循环,数学计算任务交给CPU处理器

    for(var i = 0;i<9;i += 3 )
{
    data[i] += -0.4;
} 

方法三

  使用下面代码替换第22行代码

20   'gl_Position =vec4(apos.x-0.4,apos.y,apos.z,1);' +

方法四:平移矩阵法

  使用矩阵的乘法运算进行三维模型的平移、缩放、旋转、剪切等几何变换,是学习计算机图形必须掌握的内容,用到的数学知识主要是线性代数。

  用下面的代码完全替换第12~18行的顶点着色器程序,数学计算任务交给GPU处理器,具体说是顶点处理器(Vertex Processor),顶点处理能够完成矩阵的乘法运算

    var vertexShaderSource = '' +
        //attribute声明vec4类型变量apos
        'attribute vec4 apos;'+
        'void main(){' +
        //创建平移矩阵(沿x轴平移-0.4)
            //1   0   0  -0.4
            //0   1   0    0
            //0   0   1    0
            //0   0   0    1
        'mat4 m4 = mat4(1,0,0,0,  0,1,0,0,  0,0,1,0,  -0.4,0,0,1);'+
        //平移矩阵m4左乘顶点坐标(vec4类型数据可以理解为线性代数中的nx1矩阵,即列向量)
        '   gl_Position = m4*apos;' +
        '}';

平移矩阵解析

  如果你学过的线性代数早已忘记,或者不知道为什么一个列向量右乘一个矩阵得到一个新的列向量的计 算过程就是平移的过程为了降低理解的难度,不采用正向推理讲解,仅仅利用矩阵的乘法法则反向验证。

一个点的坐标是(x,y,z),假设沿着X、Y、Z轴分别平移Tx、Ty、Tz,毫无疑问平移后的坐标是(x+Tx,y+Ty,z+Tz)。

请用矩阵的乘法运算法则验证下面矩阵的等式是否成立?

1 0 0 Tx
0 1 0 Ty
0 0 1 Tz
0 0 0 1
x
y
z
1
是否等于?
x+Tz
y+Ty
z+Tz
1

    总结

  1. 上面计算式的巧妙之处就是把三维坐标,增加一个元素1,用4x1矩阵表示,nx1矩阵也称为列向量,对应的数据类型就是vec4
  2. n维向量增加一个维度用n+1维向量表示就是齐次坐标
  3. 上面的4x4矩阵矩阵,就是平移矩阵,平移矩阵左乘顶点的>齐次坐标,结果仍然是一个齐次坐标,也就是平移后的坐标。
  4. 矩阵的乘法满足结合律,如果多次平移,可以把所有的平移矩阵先进行乘法运算,然后左乘要平移顶点的齐次坐标
  5. 通过这个验证计算也可以说明线性代数中的大量定律并不是拍脑门想出来的,是对所有实际问题的高度总结, 这些总结出来的运算法则对于管理学、物理学、工程、机械、计算机等领域都是适用的。就个人经历而言,不管多自由度的机械手设计,还是无人机的飞行控制,还是 计算机图形学涉及到的矩阵变换是没有任何区别的。

第三章的源代码

1    <!DOCTYPE html>
2    <html lang="en">
3    <head>
4        <meta charset="UTF-8">
5    </head>
6    <body>
7    <canvas id="webgl" width="500" height="500" style="background-color: #0d72da"></canvas>
8    <script>
9        var canvasElement=document.getElementById('webgl');
10       var gl=canvasElement.getContext('webgl');
11       //顶点着色器源码
12       var vertexShaderSource = '' +
13           //attribute声明vec4类型变量apos
14           'attribute vec4 apos;'+
15           'void main(){' +
16           //顶点坐标apos赋值给内置变量gl_Position
17           '   gl_Position =apos;' +
18           '}';
19       //片元着色器源码
20       var fragShaderSource = '' +
21           'void main(){' +
22           '   gl_FragColor = vec4(1.0,0.0,0.0,1.0);' +
23           '}';
24       //初始化着色器
25       var program = initShader(gl,vertexShaderSource,fragShaderSource);
26       //获取顶点着色器的位置变量apos
27       var aposLocation = gl.getAttribLocation(program,'apos');
28   
29       //9个元素构建三个顶点的xyz坐标值
30       var data=new Float32Array([
31           0, 0, 1,
32           0, 1, 0,
33           1, 0, 0
34       ]);
35   
36       //创建缓冲区对象
37       var buffer=gl.createBuffer();
38       //绑定缓冲区对象
39       gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
40       //顶点数组data数据传入缓冲区
41       gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
42       //第二个代码案例vertexAttribPointer方法的第二个参数是2,这里是3
43       gl.vertexAttribPointer(aposLocation,3,gl.FLOAT,false,0,0);
44       //允许数据传递
45       gl.enableVertexAttribArray(aposLocation);
46       //开始绘制图形,使用TRIANGLES模式,三点构成一个平面
47       gl.drawArrays(gl.TRIANGLES,0,3);
48   
49       //声明初始化着色器函数
50       function initShader(gl,vertexShaderSource,fragmentShaderSource){
51           var vertexShader = gl.createShader(gl.VERTEX_SHADER);
52           var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
53           gl.shaderSource(vertexShader,vertexShaderSource);
54           gl.shaderSource(fragmentShader,fragmentShaderSource);
55           gl.compileShader(vertexShader);
56           gl.compileShader(fragmentShader);
57           var program = gl.createProgram();
58           gl.attachShader(program,vertexShader);
59           gl.attachShader(program,fragmentShader);
60           gl.linkProgram(program);
61           gl.useProgram(program);
62           return program;
63       }
64   </script>
65   </body>
66   </html>