绘制一个立方体(WebGL旋转变换)

源码下载 绘制立方体

  通过第三、四案例知道,显示器上显示的实际上时平面的像素,可以简单理解为三维几何体放在你眼睛和显示器之间,几何体在显示器上的投影,视线一定的情况下, 你看到的投影效果取决几何体的位置状态,如果你学过画法几何应该知道轴测图的概念,没学过画法几何,你初高中也应该见过老师在黑板上画立方体的三维效果图, 如果你的视线沿着立方体的边线,投影就是一个平面的正方形,四条边线没有立体效果。如果你想呈现立体效果,你就要调整你看立方体的角度,那么问题来了,立方体8个顶点,如果让 立方体的三条棱线和xyz轴重合,你可以很容易用Javascript的数组写出他的坐标,假设立方体顶点是WebGL坐标系中的相对值±0.5,8各顶点就是(±0.5,±0.5,±0.5),排列组合就可以, 立方体相对WebGL的坐标系如果是倾斜放的(棱线与坐标轴不重合),你就需要笔算出当前8个顶点的坐标值,立方体还算规则,如果一张人脸上千甚至更多的顶点坐标怎么办?这个时候算法上就是需要 矩阵乘法运算来辅助,对于旋转而言就是旋转矩阵,对于硬件而言就是需要GPU,大规模并行处理顶点数据。上一节课说过平移变换,本节课涉及到的就是旋转变换。

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           //设置几何体轴旋转角度为30度,并把角度值转化为浮点值
17           'float radian = radians(30.0);'+
18           //求解旋转角度余弦值
19           'float cos = cos(radian);'+
20           //求解旋转角度正弦值
21           'float sin = sin(radian);'+
22           //引用上面的计算数据,创建绕x轴旋转矩阵
23           // 1      0       0    0
24           // 0   cosα   sinα   0
25           // 0  -sinα   cosα   0
26           // 0      0        0   1
27           'mat4 mx = mat4(1,0,0,0,  0,cos,-sin,0,  0,sin,cos,0,  0,0,0,1);'+
28           //引用上面的计算数据,创建绕y轴旋转矩阵
29           // cosβ   0   sinβ    0
30           //     0   1   0        0
31           //-sinβ   0   cosβ    0
32           //     0   0   0        1
33           'mat4 my = mat4(cos,0,-sin,0,  0,1,0,0,  sin,0,cos,0,  0,0,0,1);'+
34           //两个旋转矩阵、顶点齐次坐标连乘
35           '   gl_Position = mx*my*apos;' +
36           '}';
37       //片元着色器源码
38       var fragShaderSource = '' +
39           'void main(){' +
40           '   gl_FragColor = vec4(1.0,0.0,0.0,1.0);' +
41           '}';
42       //初始化着色器
43       var program = initShader(gl,vertexShaderSource,fragShaderSource);
44       //获取顶点着色器的位置变量apos
45       var aposLocation = gl.getAttribLocation(program,'apos');
46   
47       //9个元素构建三个顶点的xyz坐标值
48       var data=new Float32Array([
49           //z为0.5时,xOy平面上的四个点坐标
50            0.5,  0.5,  0.5,
51           -0.5,  0.5,  0.5,
52           -0.5, -0.5,  0.5,
53            0.5, -0.5,  0.5,
54           //z为-0.5时,xOy平面上的四个点坐标
55            0.5,  0.5, -0.5,
56           -0.5,  0.5, -0.5,
57           -0.5, -0.5, -0.5,
58            0.5, -0.5, -0.5,
59           //上面两组坐标分别对应起来组成一一对
60           0.5,  0.5,  0.5,
61           0.5,  0.5,  -0.5,
62   
63           -0.5,  0.5,  0.5,
64           -0.5,  0.5,  -0.5,
65   
66           -0.5, -0.5,  0.5,
67           -0.5, -0.5,  -0.5,
68   
69           0.5, -0.5,  0.5,
70           0.5, -0.5,  -0.5,
71       ]);
72   
73       //创建缓冲区对象
74       var buffer=gl.createBuffer();
75       //绑定缓冲区对象
76       gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
77       //顶点数组data数据传入缓冲区
78       gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
79       //缓冲区中的数据按照一定的规律传递给位置变量apos
80       gl.vertexAttribPointer(aposLocation,3,gl.FLOAT,false,0,0);
81       //允许数据传递
82       gl.enableVertexAttribArray(aposLocation);
83   
84       //LINE_LOOP模式绘制前四个点
85       gl.drawArrays(gl.LINE_LOOP,0,4);
86       //LINE_LOOP模式从第五个点开始绘制四个点
87       gl.drawArrays(gl.LINE_LOOP,4,4);
88       //LINES模式绘制后8个点
89       gl.drawArrays(gl.LINES,8,8);
90   
91       //声明初始化着色器函数
92       function initShader(gl,vertexShaderSource,fragmentShaderSource){
93           var vertexShader = gl.createShader(gl.VERTEX_SHADER);
94           var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
95           gl.shaderSource(vertexShader,vertexShaderSource);
96           gl.shaderSource(fragmentShader,fragmentShaderSource);
97           gl.compileShader(vertexShader);
98           gl.compileShader(fragmentShader);
99           var program = gl.createProgram();
100          gl.attachShader(program,vertexShader);
101          gl.attachShader(program,fragmentShader);
102          gl.linkProgram(program);
103          gl.useProgram(program);
104          return program;
105      }
106  </script>
107  </body>
108  </html>
109  

体验测试

着色器内置函数

代码执行流程简述

  执行流程和前的案例大同小异,主要是多次调用了绘制命令。第48行定义的data顶点数据初始化时,会存入内存中,执行第76行的命令内存中的数据一次性传入显存缓冲区中,传入缓冲区中的顶点数据 可以通过drawArrays方法多次调用,每次drawArrays方法调用,顶点经过渲染管线得到的像素相关数据都会存入帧缓存中,后一次调用,前一次调用生成的像素数据不会清空,最终形成一幅完整的立方体线框图。

旋转变换矩阵解析

假设一个点的坐标是(x,y,z),经过旋转变换后的坐标为(x,,y,,z,)

绕Z轴旋转γ角度

z的坐标不变不变,x、y的坐标发生变化,在笛卡尔坐标系下通过简单的数学计算就可以知道结果,x,=xcosγ-ysinγ,y,=xsinγ+ycosγ

  这个过程如何用矩阵的乘法描述,如何利用线性代数进行建模,如果你有足够的数学训练,其实很简单,和上一节课一样为了简化问题, 不做深入线性代数的矩阵变换,仅仅采用矩阵的乘法法则进行验证

cosγ sinγ 0 0
-sinγ cosγ 0 0
0 0 1 0
0 0 0 1
x
y
z
1
是否等于?
xcosγ-ysinγ
xsinγ+ycosγ
z
1

绕X轴旋转α角度

x的坐标不变不变,y、z的坐标发生变化,y,=ycosα-zsinα,z,=ysinα+zcosα

1 0 0 0
0 cosα sinα 0
0 -sinα cosα 0
0 0 0 1
x
y
z
1
是否等于?
x
ycosα-zsinα
ysinα+zcosα
1

绕Y轴旋转β角度

y的坐标不变不变,z、x的坐标发生变化,z,=zcosβ-xsinβ,x,=zsinβ+xcosβ

cosβ 0 sinβ 0
0 1 0 0
-sinβ 0 cosβ 0
0 0 0 1
x
y
z
1
是否等于?
zsinβ+xcosβ
y
zcosβ-xsinβ
1

    总结

  1. 如果几何体经过多次旋转可以把每一次的旋转矩阵,连续进行乘法运算,最后再左乘顶点的齐次坐标
  2. 旋转变换和平移变换同时存在,旋转矩阵和平移矩阵一样都是四阶矩阵,因此同样可以先进行乘法运算得到的仍是四阶矩阵,最后再左乘顶点的齐次坐标,这种情况也就是复合变换

视觉测试

  你可以长时间凝视本案例浏览器页面上的立方体线框,你的大脑中会有两种立体效果来回切换,大脑混淆,立方体的前后位置来回切换, 主要原因是线框模式不符合实际生活物体光学效应。如果想真实模仿生活就要建立光照模型,模拟物体表面与光线的作用,要知光照模型,且听下回分解。 本节课绘制立方体没用采用面,而是投影线,就是因为没有光照模拟的几何体没有任何立体效果。