绘制一个立方体(WebGL旋转变换)
源码下载
知识点
- 旋转变换矩阵
- 复合变换
- drawArrays()方法的绘制模式LINE_LOOP与LINES比较,多次调用
- 着色器语言内置函数
通过第三、四案例知道,显示器上显示的实际上时平面的像素,可以简单理解为三维几何体放在你眼睛和显示器之间,几何体在显示器上的投影,视线一定的情况下,
你看到的投影效果取决几何体的位置状态,如果你学过画法几何应该知道轴测图的概念,没学过画法几何,你初高中也应该见过老师在黑板上画立方体的三维效果图,
如果你的视线沿着立方体的边线,投影就是一个平面的正方形,四条边线没有立体效果。如果你想呈现立体效果,你就要调整你看立方体的角度,那么问题来了,立方体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
体验测试
- 你可以更改第17行代码的旋转角度数值,刷新浏览器,观察不同角度立方体的效果
- 尝试更改第85~89行代码绘制函数的第一个参数绘制模式,可以分别测试LINES、LINE_LOOP、LINE_STRIP,看看有什么不同,进一步加深对绘制模式的理解,对图元装配的理解
着色器内置函数
- radians()函数:角度值转化为弧度制,参数是浮点数float,比如45时,要写成45.0
- cos是余弦函数,参数要求是弧度值且是浮点数
- sin是正弦函数,参数要求是弧度值且是浮点数
- 你可以更改第17行代码的旋转角度数值,刷新浏览器,观察不同角度立方体的效果
- 尝试更改第85~89行代码绘制函数的第一个参数绘制模式,可以分别测试LINES、LINE_LOOP、LINE_STRIP,看看有什么不同,进一步加深对绘制模式的理解,对图元装配的理解
代码执行流程简述
执行流程和前的案例大同小异,主要是多次调用了绘制命令。第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 |
是否等于?
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 |
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 |
是否等于?
zsinβ+xcosβ |
y |
zcosβ-xsinβ |
1 |
总结
- 如果几何体经过多次旋转可以把每一次的旋转矩阵,连续进行乘法运算,最后再左乘顶点的齐次坐标
- 旋转变换和平移变换同时存在,旋转矩阵和平移矩阵一样都是四阶矩阵,因此同样可以先进行乘法运算得到的仍是四阶矩阵,最后再左乘顶点的齐次坐标,这种情况也就是复合变换
视觉测试
你可以长时间凝视本案例浏览器页面上的立方体线框,你的大脑中会有两种立体效果来回切换,大脑混淆,立方体的前后位置来回切换,
主要原因是线框模式不符合实际生活物体光学效应。如果想真实模仿生活就要建立光照模型,模拟物体表面与光线的作用,要知光照模型,且听下回分解。
本节课绘制立方体没用采用面,而是投影线,就是因为没有光照模拟的几何体没有任何立体效果。