9-层次模型
一、学习要点
本章的重点是层次模型,有了层次模型,你就可以在场景中处理复杂的三维模型,如游戏角色(而不仅仅是三角形或者立方体)
- 由多个简单的部件组成的复杂模型
- 为复杂物体(机器人手臂)简历具有层次化结构的三维模型
- 使用模型矩阵,模拟机器人手臂上的关节运动
- 研究 initShader()函数的实现,了解初始化着色器的内部细节。
二、多个简单模型组成的复杂模型
绘制多个小部件组成的复杂模型,最关键的问题是如何处理模型的整体移动,以及各个小部件间的相对移动。下图以人类手臂为例。
- 手臂的每个部分可以环绕关节运动
- 上臂可以绕肩关节运动,并带动前臂、手掌、手指一起运动。
- 前臂可以绕肘关节运动,并带动手掌手指但不影响上臂。 以此类推 位于运动部位一下的其他部位会随着一起运动 而 位于关节部位以上的部位不受影响。此外所有的运动都是围绕关节的转动。
1. 层级结构模型
对于机器人手臂这样的层级结构模型,通常的做法是按照模型中的各个部件的层次顺序,从高到低逐一绘制,并在关节上应用模型矩阵。
较为简单的情况 部件A转动带动部件B如果简单处理 B也施加A的旋转矩阵即可。比如上臂绕肩转动30度然后绘制肘关节以下也施加同样的一个模型矩阵。如下图
如果肘关节的角度是10度和肩关节不一样,就要先施加上臂绕肩关节转动的30度矩阵,然后再施加前臂绕肘关节转动的十度的矩阵,将这两个矩阵相乘才是“ 肘关节模型矩阵”。
2. 单关节模型
由两个立方体组成的机器人手臂。为了方便我们后面增加手掌和手指 我们将手臂倒过来,arm1为上臂,arm2为前臂
// 顶点着色器
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' + // 顶点位置数据
'attribute vec4 a_Normal;\n' + // 法向量数据
'uniform mat4 u_MvpMatrix;\n' + // 变换矩阵
'uniform mat4 u_NormalMatrix;\n' + //逆转置矩阵
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
// 光照相关 使得场景更逼真
' vec3 lightDirection = normalize(vec3(0.0, 0.5, 0.7));\n' + // 光的方向
' vec4 color = vec4(1.0, 0.4, 0.0, 1.0);\n' +
' vec3 normal = normalize((u_NormalMatrix * a_Normal).xyz);\n' +
' float nDotL = max(dot(normal, lightDirection), 0.0);\n' +
' v_Color = vec4(color.rgb * nDotL + vec3(0.1), color.a);\n' +
'}\n';
// Fragment shader program
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
function main() {
/
var canvas = document.getElementById('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 intialize shaders.');
return;
}
// 获取顶点坐标
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the vertex information');
return;
}
// 清理背景 增加深度校验
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
// 获取uniform变量的存储位置
var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
if (!u_MvpMatrix || !u_NormalMatrix) {
console.log('Failed to get the storage location');
return;
}
// 计算视图投影矩阵
var viewProjMatrix = new Matrix4();
viewProjMatrix.setPerspective(50.0, canvas.width / canvas.height, 1.0, 100.0);
viewProjMatrix.lookAt(20.0, 10.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
// 注册键盘事件
document.onkeydown = function(ev){ keydown(ev, gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); };
draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
}
var ANGLE_STEP = 3.0; // 旋转角度的增量
var g_arm1Angle = -90.0; // arm1的旋转角度
var g_joint1Angle = 0.0; // 关节1的旋转角度(也是arm2的角度)
function keydown(ev, gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
switch (ev.keyCode) {
case 38:
if (g_joint1Angle < 135.0) g_joint1Angle += ANGLE_STEP;
break;
case 40:
if (g_joint1Angle > -135.0) g_joint1Angle -= ANGLE_STEP;
break;
case 39:
g_arm1Angle = (g_arm1Angle + ANGLE_STEP) % 360;
break;
case 37:
g_arm1Angle = (g_arm1Angle - ANGLE_STEP) % 360;
break;
default: return;
}
draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
}
function initVertexBuffers(gl) {
var vertices = new Float32Array([
1.5, 10.0, 1.5, -1.5, 10.0, 1.5, -1.5, 0.0, 1.5, 1.5, 0.0, 1.5, // v0-v1-v2-v3 front
1.5, 10.0, 1.5, 1.5, 0.0, 1.5, 1.5, 0.0,-1.5, 1.5, 10.0,-1.5, // v0-v3-v4-v5 right
1.5, 10.0, 1.5, 1.5, 10.0,-1.5, -1.5, 10.0,-1.5, -1.5, 10.0, 1.5, // v0-v5-v6-v1 up
-1.5, 10.0, 1.5, -1.5, 10.0,-1.5, -1.5, 0.0,-1.5, -1.5, 0.0, 1.5, // v1-v6-v7-v2 left
-1.5, 0.0,-1.5, 1.5, 0.0,-1.5, 1.5, 0.0, 1.5, -1.5, 0.0, 1.5, // v7-v4-v3-v2 down
1.5, 0.0,-1.5, -1.5, 0.0,-1.5, -1.5, 10.0,-1.5, 1.5, 10.0,-1.5 // v4-v7-v6-v5 back
]);
// 法向量
var normals = new Float32Array([
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back
]);
// 顶点索引
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
]);
if (!initArrayBuffer(gl, 'a_Position', vertices, gl.FLOAT, 3)) return -1;
if (!initArrayBuffer(gl, 'a_Normal', normals, gl.FLOAT, 3)) return -1;
gl.bindBuffer(gl.ARRAY_BUFFER, null);
var indexBuffer = gl.createBuffer();
if (!indexBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
return indices.length;
}
function initArrayBuffer(gl, attribute, data, type, num) {
var buffer = gl.createBuffer();
if (!buffer) {
console.log('Failed to create the buffer object');
return false;
}
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log('Failed to get the storage location of ' + attribute);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
gl.enableVertexAttribArray(a_attribute);
return true;
}
// 坐标变换矩阵
var g_modelMatrix = new Matrix4(), g_mvpMatrix = new Matrix4();
function draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Arm1
var arm1Length = 10.0;
g_modelMatrix.setTranslate(0.0, -12.0, 0.0);
g_modelMatrix.rotate(g_arm1Angle, 0.0, 1.0, 0.0);
drawBox(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // Draw
// Arm2
g_modelMatrix.translate(0.0, arm1Length, 0.0); //将手臂2向上移动10
g_modelMatrix.rotate(g_joint1Angle, 0.0, 0.0, 1.0); // 绕Z轴旋转
g_modelMatrix.scale(1.3, 1.0, 1.3); // 让立方体粗一点
drawBox(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // Draw
}
var g_normalMatrix = new Matrix4(); //法线的旋转矩阵
// 绘制立方体
function drawBox(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
// 计算视图矩阵
g_mvpMatrix.set(viewProjMatrix);
g_mvpMatrix.multiply(g_modelMatrix);
gl.uniformMatrix4fv(u_MvpMatrix, false, g_mvpMatrix.elements);
// 计算发现变换矩阵
g_normalMatrix.setInverseOf(g_modelMatrix);
g_normalMatrix.transpose();
gl.uniformMatrix4fv(u_NormalMatrix, false, g_normalMatrix.elements);
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}
这次绘制的立方体和之前的位于原点的有点区别:
3.多节点模型
这一节绘制一个具有多个关节的完整机器人手臂,包括基座(base),上臂(arm1), 前臂(arm2),手掌(palm),两根手指(finger1 & finger2) 结构如下图:
这里面要注意绘制顺序 是从下往上的。另外指头的绘制上有一个区别 后面注释有添加解释。
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Normal;\n' +
'uniform mat4 u_MvpMatrix;\n' +
'uniform mat4 u_NormalMatrix;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' vec3 lightDirection = normalize(vec3(0.0, 0.5, 0.7));\n'
' vec4 color = vec4(1.0, 0.4, 0.0, 1.0);\n' +
' vec3 normal = normalize((u_NormalMatrix * a_Normal).xyz);\n' +
' float nDotL = max(dot(normal, lightDirection), 0.0);\n' +
' v_Color = vec4(color.rgb * nDotL + vec3(0.1), color.a);\n' +
'}\n';
var FSHADER_SOURCE =
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_FragColor = v_Color;\n' +
'}\n';
function main() {
var canvas = document.getElementById('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 intialize shaders.');
return;
}
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the vertex information');
return;
}
// Set the clear color and enable the depth test
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
// Get the storage locations of uniform variables
var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
if (!u_MvpMatrix || !u_NormalMatrix) {
console.log('Failed to get the storage location');
return;
}
// Calculate the view projection matrix
var viewProjMatrix = new Matrix4();
viewProjMatrix.setPerspective(50.0, canvas.width / canvas.height, 1.0, 100.0);
viewProjMatrix.lookAt(20.0, 10.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
// Register the event handler to be called on key press
document.onkeydown = function(ev){ keydown(ev, gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); };
draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // Draw the robot arm
}
var ANGLE_STEP = 3.0; //变换一次变化的幅度
var g_arm1Angle = 90.0; // arm1的当前角度
var g_joint1Angle = 45.0; // joint1的当前角度
var g_joint2Angle = 0.0; // joint2的当前角度
var g_joint3Angle = 0.0; // joint3的当前角度
function keydown(ev, gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
switch (ev.keyCode) {
case 40: // Up arrow key -> the positive rotation of joint1 around the z-axis
if (g_joint1Angle < 135.0) g_joint1Angle += ANGLE_STEP;
break;
case 38: // Down arrow key -> the negative rotation of joint1 around the z-axis
if (g_joint1Angle > -135.0) g_joint1Angle -= ANGLE_STEP;
break;
case 39: // Right arrow key -> the positive rotation of arm1 around the y-axis
g_arm1Angle = (g_arm1Angle + ANGLE_STEP) % 360;
break;
case 37: // Left arrow key -> the negative rotation of arm1 around the y-axis
g_arm1Angle = (g_arm1Angle - ANGLE_STEP) % 360;
break;
case 90: // 'z'key -> the positive rotation of joint2
g_joint2Angle = (g_joint2Angle + ANGLE_STEP) % 360;
break;
case 88: // 'x'key -> the negative rotation of joint2
g_joint2Angle = (g_joint2Angle - ANGLE_STEP) % 360;
break;
case 86: // 'v'key -> the positive rotation of joint3
if (g_joint3Angle < 60.0) g_joint3Angle = (g_joint3Angle + ANGLE_STEP) % 360;
break;
case 67: // 'c'key -> the nagative rotation of joint3
if (g_joint3Angle > -60.0) g_joint3Angle = (g_joint3Angle - ANGLE_STEP) % 360;
break;
default: return; // Skip drawing at no effective action
}
draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
}
function initVertexBuffers(gl) {
// Coordinates(Cube which length of one side is 1 with the origin on the center of the bottom)
var vertices = new Float32Array([
0.5, 1.0, 0.5, -0.5, 1.0, 0.5, -0.5, 0.0, 0.5, 0.5, 0.0, 0.5, // v0-v1-v2-v3 front
0.5, 1.0, 0.5, 0.5, 0.0, 0.5, 0.5, 0.0,-0.5, 0.5, 1.0,-0.5, // v0-v3-v4-v5 right
0.5, 1.0, 0.5, 0.5, 1.0,-0.5, -0.5, 1.0,-0.5, -0.5, 1.0, 0.5, // v0-v5-v6-v1 up
-0.5, 1.0, 0.5, -0.5, 1.0,-0.5, -0.5, 0.0,-0.5, -0.5, 0.0, 0.5, // v1-v6-v7-v2 left
-0.5, 0.0,-0.5, 0.5, 0.0,-0.5, 0.5, 0.0, 0.5, -0.5, 0.0, 0.5, // v7-v4-v3-v2 down
0.5, 0.0,-0.5, -0.5, 0.0,-0.5, -0.5, 1.0,-0.5, 0.5, 1.0,-0.5 // v4-v7-v6-v5 back
]);
// Normal
var normals = new Float32Array([
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back
]);
// Indices of the vertices
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
]);
// Write the vertex property to buffers (coordinates and normals)
if (!initArrayBuffer(gl, 'a_Position', vertices, gl.FLOAT, 3)) return -1;
if (!initArrayBuffer(gl, 'a_Normal', normals, gl.FLOAT, 3)) return -1;
// Unbind the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// Write the indices to the buffer object
var indexBuffer = gl.createBuffer();
if (!indexBuffer) {
console.log('Failed to create the buffer object');
return -1;
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
return indices.length;
}
function initArrayBuffer(gl, attribute, data, type, num) {
// Create a buffer object
var buffer = gl.createBuffer();
if (!buffer) {
console.log('Failed to create the buffer object');
return false;
}
// Write date into the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// Assign the buffer object to the attribute variable
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log('Failed to get the storage location of ' + attribute);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
// Enable the assignment of the buffer object to the attribute variable
gl.enableVertexAttribArray(a_attribute);
return true;
}
// Coordinate transformation matrix
var g_modelMatrix = new Matrix4(), g_mvpMatrix = new Matrix4();
function draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
// 清空颜色缓冲区和深度缓冲区背景色
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 绘制基座
var baseHeight = 2.0;
g_modelMatrix.setTranslate(0.0, -12.0, 0.0);
drawBox(gl, n, 10.0, baseHeight, 10.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
// Arm1
var arm1Length = 10.0;
g_modelMatrix.translate(0.0, baseHeight, 0.0); // 移至基座
g_modelMatrix.rotate(g_arm1Angle, 0.0, 1.0, 0.0); // 旋转
drawBox(gl, n, 3.0, arm1Length, 3.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // 绘制
// Arm2
var arm2Length = 10.0;
g_modelMatrix.translate(0.0, arm1Length, 0.0); // Move to joint1
g_modelMatrix.rotate(g_joint1Angle, 0.0, 0.0, 1.0); // Rotate around the z-axis
drawBox(gl, n, 4.0, arm2Length, 4.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // Draw
// A palm
var palmLength = 2.0;
g_modelMatrix.translate(0.0, arm2Length, 0.0); // Move to palm
g_modelMatrix.rotate(g_joint2Angle, 0.0, 1.0, 0.0); // Rotate around the y-axis
drawBox(gl, n, 2.0, palmLength, 6.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // Draw
// Move to the center of the tip of the palm
g_modelMatrix.translate(0.0, palmLength, 0.0);
// Draw finger1
pushMatrix(g_modelMatrix); // 这里使用栈结构 主要是因为finger1和finger2相对的都是palm 所以finger2不能受到finger1的影响,需要保存finger1变换之前的矩阵
g_modelMatrix.translate(0.0, 0.0, 2.0);
g_modelMatrix.rotate(g_joint3Angle, 1.0, 0.0, 0.0); // Rotate around the x-axis
drawBox(gl, n, 1.0, 2.0, 1.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
g_modelMatrix = popMatrix();
// Draw finger2
g_modelMatrix.translate(0.0, 0.0, -2.0);
g_modelMatrix.rotate(-g_joint3Angle, 1.0, 0.0, 0.0); // Rotate around the x-axis
drawBox(gl, n, 1.0, 2.0, 1.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
}
var g_matrixStack = []; // 存储矩阵的栈
function pushMatrix(m) { // 将矩阵压入栈
var m2 = new Matrix4(m);
g_matrixStack.push(m2);
}
function popMatrix() { // 将矩阵弹出栈
return g_matrixStack.pop();
}
var g_normalMatrix = new Matrix4(); // Coordinate transformation matrix for normals
// Draw rectangular solid
function drawBox(gl, n, width, height, depth, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
pushMatrix(g_modelMatrix); // Save the model matrix
// Scale a cube and draw
g_modelMatrix.scale(width, height, depth);
// Calculate the model view project matrix and pass it to u_MvpMatrix
g_mvpMatrix.set(viewProjMatrix);
g_mvpMatrix.multiply(g_modelMatrix);
gl.uniformMatrix4fv(u_MvpMatrix, false, g_mvpMatrix.elements);
// Calculate the normal transformation matrix and pass it to u_NormalMatrix
g_normalMatrix.setInverseOf(g_modelMatrix);
g_normalMatrix.transpose();
gl.uniformMatrix4fv(u_NormalMatrix, false, g_normalMatrix.elements);
// Draw
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
g_modelMatrix = popMatrix(); // Retrieve the model matrix
}
用这种方法就可以绘制任意复杂的层次结构模型。我们只需要按照层次顺序,从高到低绘制部件,并在绘制具有兄弟部件的部件钱将模型矩阵亚茹栈 绘制完后再弹出即可。
4. 着色器 和着色器程序对象,深入讲解(initShaders() 函数作用)
之前我们一直使用辅助函数initShaders()。它隐藏了建立和初始化着色器的细节。 我们本着好奇的目的探索一些这个是如何将字符串形式的GLSL ES代码编译为显卡中运行的着色器程序呢。
具体分为以下7步。
- 创建着色器对象(gl.createShader())
- 向着色器对象中填充着色器程序的源代码(gl.shaderSource())
- 编译着色器(gl.compileShader())
- 创建程序对象(gl.createProgram())
- 为程序对象分配着色器(gl.attachShader())
- 连接程序对象(gl.linkProgram)
- 使用程序对象(gl.useProgram())
名词解释: 着色器对象:着色器对象管理着一个顶点着色器或者一个片元着色器。每个着色器都有一个着色器对象。 程序对象:程序对象是管理着着色器对象的容器。WebGL中,一个程序对象必须包含一个顶点着色器和一个片元着色器。
创建着色器对象(gl.createShader())
所有的着色器对象都必须通过调用 gl.createShader() 来创建
指定着色器对象的代码(gl.shaderSource())
编译着色器 (gl.compileShader())
向着色器中传入源代码后,还需要编译才能使用。如果用新的代码地换掉了旧的代码,需要手动重新编译。 当调用编译函数时,如果着色器源代码存在错误,会出现编译错误,可以使用gl.getShaderParameter() 函数来检测着色器的状态该。
调用gl.getShaderParameter(shader,pname) 将 pname 指定为 gl.COMPILE_STATUS就可以检测着色器是否编译成功。如果失败会返回false,可以通过gl.getShaderInfoLog() 来获取错误日志。
创建程序对象(gl.createProgram)
为程序对象分配着色器对象(gl.attachShader())
连接程序对象(gl.linkProgram())
在程序对象分配了两个着色器对象后,还需要将顶点着色器和片元着色器连接起来。
连接的目的是保证:
- 顶点着色器和片元着色器的varying 变量同名同类型,且一一对应;
- 顶点着色对每个varying变量赋了值;
- 顶点着色器和片元你着色器中的同名uniform变量也是同类型的(不需要一一对应)
- 着色器中的attribute uniform varying 变量没有超过着色器上限。
告知 Webgl系统所使用的程序对象 (gl.useProgram(program))
5.总结
本章我们学习了WebGL对于层次模型的绘制,并讲解了initShaders()函数的创建细节。 至此我们学习了所有webGL的基础知识,我们运用这些基础知识应该可以创建出复杂的图形了,但是好像还是少点什么。感觉我们的交互不够或者是复杂图形的顶点那么多怎么去找。下面我们会学习一些图形序方面的高级技术,并了解Webgl和他们是如何配合使用的