WebGL层次模型——多节点模型

目录

多节点模型

MultiJointModel中的层次结构

控制各部件旋转角度的变量

示例程序——共用顶点数据,通过模型矩阵缩放实现(MultiJointModel.js) 

MultiJointModel.js(按键响应部分) 

MultiJointModel.js(绘制模型部分) 

MultiJointModel.js(绘制部件drawBox())

示例程序——各自对应各自顶点数据实现(MultiJointModel_segment.js)

代码详解 

示例效果


多节点模型

本文将把WebGL层次模型——单节点模型_山楂树の的博客-CSDN博客JointMode扩展为MultiJointModel,后者绘制一个具有多个关节的完整的机器人手臂,包括基座(base)、上臂(arm1)、前臂(arm2)、手掌(palm)、两根手指(finger1 & finger2),全部可以通过键盘来控制。arm1和arm2的连接关节joint1位于arm1顶部,arm2和palm的连接关节joint2位于arm2顶部,finger1和finger2位于palm一端,如下图所示。

MultiJointModel中的层次结构

用户可以通过键盘操纵机器人手臂,arm1和arm2的操作和JointModel一样,此外,还可以使用x和z键旋转joint2(腕关节),使用C和V键旋转finger1和finger2。控制这些小部件旋转角度的全局变量,如下图所示。 

控制各部件旋转角度的变量

我们将用如下两种方式实现 

示例程序——共用顶点数据,通过模型矩阵缩放实现(MultiJointModel.js) 

示例程序MultiJointModelJointModel相比,主要有两处不同:keydown()函数响应更多的按键情况,draw()函数绘制各部件的逻辑更复杂了。首先来看keydown()函数,如下所示:

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 (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) returnvar n = initVertexBuffers(gl);gl.clearColor(0.0, 0.0, 0.0, 1.0);gl.enable(gl.DEPTH_TEST);var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix'); // 模型视图投影矩阵var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix'); // 用于计算法向量的矩阵// 计算视图投影矩阵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;   // 手臂1的旋转角度(度)
var g_joint1Angle = 45.0; // 关节1的旋转角度(度)
var g_joint2Angle = 0.0;  // 关节2的旋转角度(度)
var g_joint3Angle = 0.0;  // 关节3的旋转角度(度)
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-axisif (g_joint1Angle < 135.0) g_joint1Angle += ANGLE_STEP;break;case 38: // Down arrow key -> the negative rotation of joint1 around the z-axisif (g_joint1Angle > -135.0) g_joint1Angle -= ANGLE_STEP;break;case 39: // Right arrow key -> the positive rotation of arm1 around the y-axisg_arm1Angle = (g_arm1Angle + ANGLE_STEP) % 360;break;case 37: // Left arrow key -> the negative rotation of arm1 around the y-axisg_arm1Angle = (g_arm1Angle - ANGLE_STEP) % 360;break;case 90: // 'z'key -> the positive rotation of joint2g_joint2Angle = (g_joint2Angle + ANGLE_STEP) % 360;break;case 88: // 'x'key -> the negative rotation of joint2g_joint2Angle = (g_joint2Angle - ANGLE_STEP) % 360;break;case 86: // 'v'key -> the positive rotation of joint3if (g_joint3Angle < 60.0) g_joint3Angle = (g_joint3Angle + ANGLE_STEP) % 360;break;case 67: // 'c'key -> the nagative rotation of joint3if (g_joint3Angle > -60.0) g_joint3Angle = (g_joint3Angle - ANGLE_STEP) % 360;break;default: return; // Skip drawing at no effective action}// Draw the robot armdraw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
}function initVertexBuffers(gl) {// 坐标(一侧长度为1,原点位于底部中心的立方体)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 front0.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 right0.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 down0.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]);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 front1.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 right0.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 left0.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 down0.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,    // front4, 5, 6, 4, 6, 7,    // right8, 9, 10, 8, 10, 11,    // up12, 13, 14, 12, 14, 15,    // left16, 17, 18, 16, 18, 19,    // down20, 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();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();gl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);var a_attribute = gl.getAttribLocation(gl.program, attribute);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);// 基座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);// Arm1var arm1Length = 10.0;g_modelMatrix.translate(0.0, baseHeight, 0.0);     // Move onto the baseg_modelMatrix.rotate(g_arm1Angle, 0.0, 1.0, 0.0);  // Rotate around the y-axisdrawBox(gl, n, 3.0, arm1Length, 3.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // Draw// Arm2var arm2Length = 10.0;g_modelMatrix.translate(0.0, arm1Length, 0.0);       // Move to joint1g_modelMatrix.rotate(g_joint1Angle, 0.0, 0.0, 1.0);  // Rotate around the z-axisdrawBox(gl, n, 4.0, arm2Length, 4.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // Draw// A palmvar palmLength = 2.0;g_modelMatrix.translate(0.0, arm2Length, 0.0);       // Move to palmg_modelMatrix.rotate(g_joint2Angle, 0.0, 1.0, 0.0);  // Rotate around the y-axisdrawBox(gl, n, 2.0, palmLength, 6.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);  // Draw// 移动至手掌中心g_modelMatrix.translate(0.0, palmLength, 0.0);// 第一个手指pushMatrix(g_modelMatrix); // 此操作防止第二个手指依赖第一个手指的模型矩阵,如旋转平移大变换或小变换的操作g_modelMatrix.translate(0.0, 0.0, 2.0);g_modelMatrix.rotate(g_joint3Angle, 1.0, 0.0, 0.0);  // Rotate around the x-axisdrawBox(gl, n, 1.0, 2.0, 1.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);g_modelMatrix = popMatrix();// 第二个手指g_modelMatrix.translate(0.0, 0.0, -2.0);g_modelMatrix.rotate(-g_joint3Angle, 1.0, 0.0, 0.0);  // Rotate around the x-axisdrawBox(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();  // 法向量变换的矩阵
//绘制长方体
function drawBox(gl, n, width, height, depth, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {pushMatrix(g_modelMatrix);   // 每次绘制部件前,保存绘制前的模型矩阵,因为会有缩放操作,以防影响下个部件的大小// 长宽高缩放对应系数g_modelMatrix.scale(width, height, depth);// 计算模型视图投影矩阵并将其传递给u_MvpMatrixg_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);g_modelMatrix = popMatrix();   // 释放绘制前保存的模型矩阵供下个部件使用
}

MultiJointModel.js(按键响应部分) 

本例的keydown()函数,除了需要在方向键被按下时作出响应,更新g_arm1Angle和g_jointAngle变量(就像JointModel中一样),还需要在Z键、X键、V键和C键被按下时做出响应(第61、64、67和70行),更新g_joint2Angle和g_joint3Angle变量。在此之后,就调用draw()函数,把整个模型画出来。

模型的各个部件base、arm1、arm2、palm、finger1和finger2等虽然都是立方体,但是长宽高各不相同。所以本例扩展了drawBox()函数,添加了3个参数:

新增加的3个参数表示部件的宽度、高度和长度(深度),drawBox()会根据这3个参数,将部件分毫不差地绘制出来。

MultiJointModel.js(绘制模型部分) 

draw()函数的任务和JointModel中的相同,就是对每个部件进行:(1)平移,(2)旋转,(3)绘制。首先,base不会旋转,所以只需要将其移动到合适的位置(第132行),再调用drawBox()进行绘制。通过向drawBox()传入参数,我们指定base的宽度是10,高度是2,长度是10,即一个扁平的基座。 

然后,按照arm1、arm2和palm这些部件在模型中的层次顺序,对每一个部件都进行上述三个步骤,这与JointModel中的是一样的。

比较麻烦的是finger1和finger2,因为它们并不是上下层的关系,而是都连接在palm上,此时要格外注意计算模型矩阵的过程。首先来看finger1,它相对于palm原点沿Z轴平移了2.0单位,并且可以绕X轴旋转,我们执行上述三个步骤。相关代码如下所示:

接着看finger2,如果遵循上述同样的步骤,沿z轴平移-2.0个单位并绕X轴旋转(这是finger2相对palm的位置)就会出现问题。在将模型矩阵“沿z轴平移-2.0个单位”之前,模型矩阵实际上处于绘制finger1的状态,这会导致finger2连接在finger1而不是palm上,使得finger1转动带动finger2。 

所以,我们需要在绘制finger1之前,先将模型矩阵保存起来;绘制完finger1后,再将保存的模型矩阵取出来作为当前的模型矩阵,并继续绘制finger2。可以使用一个栈结构来完成这项操作:调用pushMatrix()并将模型矩阵g_modelMatrix作为参数传入,将当时模型矩阵的状态保存起来(第157行),然后在绘制完finger1后,调用popMatrix()获取之前保存的矩阵,并赋给g_modelMatrix(第161行),使模型矩阵又回到绘制finger1之前的状态,在此基础上绘制finger2。

pushMatrix()函数和popMatrix()函数如下所示,它们使用全局变量g_matrixStack来存储矩阵(第169行),前者向栈中压入一个矩阵,而后者从栈中弹出一个。

只要栈足够深,用这种方法就可以绘制任意复杂的层次结构模型。我们只需要按照层次顺序,从高到低绘制部件,并在绘制“具有兄弟部件”的部件前将模型矩阵压入栈,绘制完再弹出即可。

MultiJointModel.js(绘制部件drawBox())

最后看一下drawBox()函数,该函数的任务是绘制机器人手臂的一个部件,它接收若干个参数:

参数width、height和depth分别表示待绘制部件的宽度、高度和深度。其他的参数与JointMode.js中无异:参数viewMatrix表示视图矩阵,u_MvpMatrix表示模型试图投影矩阵,u_NormalMatrix表示用来计算变换后的法向量矩阵,后两者将被传给顶点着色器中相应的同名uniform变量。 

此外,与JointMode不同的是,本例中部件的三维模型是标准化的立方体,其边长为1,原点位于底面。drawBox()函数的定义如下所示:

如你所见,drawBox()函数首先将模型矩阵乘以由width、height和depth参数生成的缩放矩阵,使绘制出的立方体尺寸与设想的一样。然后使用pushMatrix()函数将模型矩阵压入栈中(第182行),使用popMatrix()再重新获得之(第195行)。如果不这样做,当绘制arm2的时候,对arm1的拉伸效果还会仍然留在模型矩阵中,并影响arm2的绘制。所以,在第195行执行之后,模型矩阵又回到了第182行的状态。 

虽然pushMatrix()函数和popMatrix()函数使代码变得更复杂了,但这是值得的,因为你只用了一组顶点数据就绘制了好几个大小位置各不相同的立方体部件。或者,我们也可以对每一个部件都单独使用一组顶点数据,接下来就看看如何实现。

示例程序——各自对应各自顶点数据实现(MultiJointModel_segment.js)

这一节将换一种方式来绘制机器人手臂,那就是,对每一个部件,都定义一组顶点数据,并存储在一个单独的缓冲区对象中。通常,一个部件的顶点数据包括坐标、法向量、索引值等,但是这里的每个部件都是立方体,所以你可以让各部件共享法向量和索引值,而仅仅为各个部件单独定义顶点坐标。每个部件的顶点坐标数据分别存储在对应的缓冲区中,在绘制整条机器人手臂时轮流使用。如下显示了程序的代码。

// MultiJointModel_segment.js (c) 2012 matsuda
// Vertex shader program
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' +// The followings are some shading calculation to make the arm look three-dimensional'  vec3 lightDirection = normalize(vec3(0.0, 0.5, 0.7));\n' + // Light direction'  vec4 color = vec4(1.0, 0.4, 0.0, 1.0);\n' +  // Robot color'  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() {// Retrieve <canvas> elementvar canvas = document.getElementById('webgl');// Get the rendering context for WebGLvar gl = getWebGLContext(canvas);if (!gl) {console.log('Failed to get the rendering context for WebGL');return;}// Initialize shadersif (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {console.log('Failed to intialize shaders.');return;}// Set the vertex informationvar n = initVertexBuffers(gl);if (n < 0) {console.log('Failed to set the vertex information');return;}// Set the clear color and enable the depth testgl.clearColor(0.0, 0.0, 0.0, 1.0);gl.enable(gl.DEPTH_TEST);// Get the storage locations of attribute and uniform variablesvar a_Position = gl.getAttribLocation(gl.program, 'a_Position');var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');if (a_Position < 0 || !u_MvpMatrix || !u_NormalMatrix) {console.log('Failed to get the storage location of attribute or uniform variable');return;}// Calculate the view projection matrixvar 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 pressdocument.onkeydown = function(ev){ keydown(ev, gl, n, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix); };draw(gl, n, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix);
}var ANGLE_STEP = 3.0;     // The increments of rotation angle (degrees)
var g_arm1Angle = 90.0;   // The rotation angle of arm1 (degrees)
var g_joint1Angle = 45.0; // The rotation angle of joint1 (degrees)
var g_joint2Angle = 0.0;  // The rotation angle of joint2 (degrees)
var g_joint3Angle = 0.0;  // The rotation angle of joint3 (degrees)function keydown(ev, gl, o, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix) {switch (ev.keyCode) {case 40: // Up arrow key -> the positive rotation of joint1 around the z-axisif (g_joint1Angle < 135.0) g_joint1Angle += ANGLE_STEP;break;case 38: // Down arrow key -> the negative rotation of joint1 around the z-axisif (g_joint1Angle > -135.0) g_joint1Angle -= ANGLE_STEP;break;case 39: // Right arrow key -> the positive rotation of arm1 around the y-axisg_arm1Angle = (g_arm1Angle + ANGLE_STEP) % 360;break;case 37: // Left arrow key -> the negative rotation of arm1 around the y-axisg_arm1Angle = (g_arm1Angle - ANGLE_STEP) % 360;break;case 90: // 'z'key -> the positive rotation of joint2g_joint2Angle = (g_joint2Angle + ANGLE_STEP) % 360;break; case 88: // 'x'key -> the negative rotation of joint2g_joint2Angle = (g_joint2Angle - ANGLE_STEP) % 360;break;case 86: // 'v'key -> the positive rotation of joint3if (g_joint3Angle < 60.0)  g_joint3Angle = (g_joint3Angle + ANGLE_STEP) % 360;break;case 67: // 'c'key -> the nagative rotation of joint3if (g_joint3Angle > -60.0) g_joint3Angle = (g_joint3Angle - ANGLE_STEP) % 360;break;default: return; // Skip drawing at no effective action}// Drawdraw(gl, o, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix);
}var g_baseBuffer = null;     // Buffer object for a base
var g_arm1Buffer = null;     // Buffer object for arm1
var g_arm2Buffer = null;     // Buffer object for arm2
var g_palmBuffer = null;     // Buffer object for a palm
var g_fingerBuffer = null;   // Buffer object for fingersfunction initVertexBuffers(gl){// Vertex coordinate (prepare coordinates of cuboids for all segments)var vertices_base = new Float32Array([ // Base(10x2x10)5.0, 2.0, 5.0, -5.0, 2.0, 5.0, -5.0, 0.0, 5.0,  5.0, 0.0, 5.0, // v0-v1-v2-v3 front5.0, 2.0, 5.0,  5.0, 0.0, 5.0,  5.0, 0.0,-5.0,  5.0, 2.0,-5.0, // v0-v3-v4-v5 right5.0, 2.0, 5.0,  5.0, 2.0,-5.0, -5.0, 2.0,-5.0, -5.0, 2.0, 5.0, // v0-v5-v6-v1 up-5.0, 2.0, 5.0, -5.0, 2.0,-5.0, -5.0, 0.0,-5.0, -5.0, 0.0, 5.0, // v1-v6-v7-v2 left-5.0, 0.0,-5.0,  5.0, 0.0,-5.0,  5.0, 0.0, 5.0, -5.0, 0.0, 5.0, // v7-v4-v3-v2 down5.0, 0.0,-5.0, -5.0, 0.0,-5.0, -5.0, 2.0,-5.0,  5.0, 2.0,-5.0  // v4-v7-v6-v5 back]);var vertices_arm1 = new Float32Array([  // Arm1(3x10x3)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 front1.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 right1.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 down1.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 vertices_arm2 = new Float32Array([  // Arm2(4x10x4)2.0, 10.0, 2.0, -2.0, 10.0, 2.0, -2.0,  0.0, 2.0,  2.0,  0.0, 2.0, // v0-v1-v2-v3 front2.0, 10.0, 2.0,  2.0,  0.0, 2.0,  2.0,  0.0,-2.0,  2.0, 10.0,-2.0, // v0-v3-v4-v5 right2.0, 10.0, 2.0,  2.0, 10.0,-2.0, -2.0, 10.0,-2.0, -2.0, 10.0, 2.0, // v0-v5-v6-v1 up-2.0, 10.0, 2.0, -2.0, 10.0,-2.0, -2.0,  0.0,-2.0, -2.0,  0.0, 2.0, // v1-v6-v7-v2 left-2.0,  0.0,-2.0,  2.0,  0.0,-2.0,  2.0,  0.0, 2.0, -2.0,  0.0, 2.0, // v7-v4-v3-v2 down2.0,  0.0,-2.0, -2.0,  0.0,-2.0, -2.0, 10.0,-2.0,  2.0, 10.0,-2.0  // v4-v7-v6-v5 back]);var vertices_palm = new Float32Array([  // Palm(2x2x6)1.0, 2.0, 3.0, -1.0, 2.0, 3.0, -1.0, 0.0, 3.0,  1.0, 0.0, 3.0, // v0-v1-v2-v3 front1.0, 2.0, 3.0,  1.0, 0.0, 3.0,  1.0, 0.0,-3.0,  1.0, 2.0,-3.0, // v0-v3-v4-v5 right1.0, 2.0, 3.0,  1.0, 2.0,-3.0, -1.0, 2.0,-3.0, -1.0, 2.0, 3.0, // v0-v5-v6-v1 up-1.0, 2.0, 3.0, -1.0, 2.0,-3.0, -1.0, 0.0,-3.0, -1.0, 0.0, 3.0, // v1-v6-v7-v2 left-1.0, 0.0,-3.0,  1.0, 0.0,-3.0,  1.0, 0.0, 3.0, -1.0, 0.0, 3.0, // v7-v4-v3-v2 down1.0, 0.0,-3.0, -1.0, 0.0,-3.0, -1.0, 2.0,-3.0,  1.0, 2.0,-3.0  // v4-v7-v6-v5 back]);var vertices_finger = new Float32Array([  // Fingers(1x2x1)0.5, 2.0, 0.5, -0.5, 2.0, 0.5, -0.5, 0.0, 0.5,  0.5, 0.0, 0.5, // v0-v1-v2-v3 front0.5, 2.0, 0.5,  0.5, 0.0, 0.5,  0.5, 0.0,-0.5,  0.5, 2.0,-0.5, // v0-v3-v4-v5 right0.5, 2.0, 0.5,  0.5, 2.0,-0.5, -0.5, 2.0,-0.5, -0.5, 2.0, 0.5, // v0-v5-v6-v1 up-0.5, 2.0, 0.5, -0.5, 2.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 down0.5, 0.0,-0.5, -0.5, 0.0,-0.5, -0.5, 2.0,-0.5,  0.5, 2.0,-0.5  // v4-v7-v6-v5 back]);// Normalvar 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 front1.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 right0.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 left0.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 down0.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 verticesvar indices = new Uint8Array([0, 1, 2,   0, 2, 3,    // front4, 5, 6,   4, 6, 7,    // right8, 9,10,   8,10,11,    // up12,13,14,  12,14,15,    // left16,17,18,  16,18,19,    // down20,21,22,  20,22,23     // back]);// Write coords to buffers, but don't assign to attribute variablesg_baseBuffer = initArrayBufferForLaterUse(gl, vertices_base, 3, gl.FLOAT);g_arm1Buffer = initArrayBufferForLaterUse(gl, vertices_arm1, 3, gl.FLOAT);g_arm2Buffer = initArrayBufferForLaterUse(gl, vertices_arm2, 3, gl.FLOAT);g_palmBuffer = initArrayBufferForLaterUse(gl, vertices_palm, 3, gl.FLOAT);g_fingerBuffer = initArrayBufferForLaterUse(gl, vertices_finger, 3, gl.FLOAT);if (!g_baseBuffer || !g_arm1Buffer || !g_arm2Buffer || !g_palmBuffer || !g_fingerBuffer) return -1;// Write normals to a buffer, assign it to a_Normal and enable itif (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;// Write the indices to the buffer objectvar 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 initArrayBufferForLaterUse(gl, data, num, type){var buffer = gl.createBuffer();   // Create a buffer objectif (!buffer) {console.log('Failed to create the buffer object');return null;}// Write date into the buffer objectgl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);// Store the necessary information to assign the object to the attribute variable laterbuffer.num = num;buffer.type = type;return buffer;
}function initArrayBuffer(gl, attribute, data, num, type){var buffer = gl.createBuffer();   // Create a buffer objectif (!buffer) {console.log('Failed to create the buffer object');return false;}// Write date into the buffer objectgl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);// Assign the buffer object to the attribute variablevar 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 variablegl.enableVertexAttribArray(a_attribute);return true;
}// Coordinate transformation matrix
var g_modelMatrix = new Matrix4(), g_mvpMatrix = new Matrix4();function draw(gl, n, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix) {// Clear color and depth buffergl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);// Draw a basevar baseHeight = 2.0;g_modelMatrix.setTranslate(0.0, -12.0, 0.0);drawSegment(gl, n, g_baseBuffer, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix);// Arm1var arm1Length = 10.0;g_modelMatrix.translate(0.0, baseHeight, 0.0);     // Move onto the baseg_modelMatrix.rotate(g_arm1Angle, 0.0, 1.0, 0.0);  // Rotate around the y-axisdrawSegment(gl, n, g_arm1Buffer, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix); // Draw// Arm2var arm2Length = 10.0;g_modelMatrix.translate(0.0, arm1Length, 0.0);       // Move to joint1g_modelMatrix.rotate(g_joint1Angle, 0.0, 0.0, 1.0);  // Rotate around the z-axisdrawSegment(gl, n, g_arm2Buffer, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix); // Draw// A palmvar palmLength = 2.0;g_modelMatrix.translate(0.0, arm2Length, 0.0);       // Move to palmg_modelMatrix.rotate(g_joint2Angle, 0.0, 1.0, 0.0);  // Rotate around the y-axisdrawSegment(gl, n, g_palmBuffer, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix);  // Draw// Move to the center of the tip of the palmg_modelMatrix.translate(0.0, palmLength, 0.0);// Draw finger1pushMatrix(g_modelMatrix);g_modelMatrix.translate(0.0, 0.0, 2.0);g_modelMatrix.rotate(g_joint3Angle, 1.0, 0.0, 0.0);  // Rotate around the x-axisdrawSegment(gl, n, g_fingerBuffer, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix);g_modelMatrix = popMatrix();// Finger2g_modelMatrix.translate(0.0, 0.0, -2.0);g_modelMatrix.rotate(-g_joint3Angle, 1.0, 0.0, 0.0);  // Rotate around the x-axisdrawSegment(gl, n, g_fingerBuffer, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix);
}var g_matrixStack = []; // Array for storing a matrix
function pushMatrix(m) { // Store the specified matrix to the arrayvar m2 = new Matrix4(m);g_matrixStack.push(m2);
}function popMatrix() { // Retrieve the matrix from the arrayreturn g_matrixStack.pop();
}var g_normalMatrix = new Matrix4();  // Coordinate transformation matrix for normals// Draw segments
function drawSegment(gl, n, buffer, viewProjMatrix, a_Position, u_MvpMatrix, u_NormalMatrix) {gl.bindBuffer(gl.ARRAY_BUFFER, buffer);// Assign the buffer object to the attribute variablegl.vertexAttribPointer(a_Position, buffer.num, buffer.type, false, 0, 0);// Enable the assignment of the buffer object to the attribute variablegl.enableVertexAttribArray(a_Position);// Calculate the model view project matrix and pass it to u_MvpMatrixg_mvpMatrix.set(viewProjMatrix);g_mvpMatrix.multiply(g_modelMatrix);gl.uniformMatrix4fv(u_MvpMatrix, false, g_mvpMatrix.elements);// Calculate matrix for normal and pass it to u_NormalMatrixg_normalMatrix.setInverseOf(g_modelMatrix);g_normalMatrix.transpose();gl.uniformMatrix4fv(u_NormalMatrix, false, g_normalMatrix.elements);// Drawgl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

代码详解 

 示例程序的关键点是:(1)为每个部件单独创建一个缓冲区,在其中存储顶点的坐标数据;(2)绘制部件之前,将相应缓冲区对象分配给a_Position变量;(3)开启a_Position变量并绘制该部件。

main()函数的流程很简单,包括初始化缓冲区(第47行),获取a_Position的存储地址(第58行),然后调用draw()函数进行绘制(第73行)等。

接着来看initVertxBuffers()函数(第121行),该函数之前定义了若干全局变量,表示存储各个部件顶点坐标数据的缓冲区对象(第115~119行)。本例与MultiJoint-Model.js的主要区别在顶点坐标上(第123行),我们不再使用一个立方体经过不同变换来绘制不同的部件,而是将每个部件的顶点坐标分开定义在不同的数组中(比如base立方体的顶点坐标定义在vertice_base中,arm1立方体的顶点坐标定义在vertices_arm1中,等等)。真正创建这些缓冲区对象是由initArrayBufferForLaterUse()函数完成的(第189~193行)。该函数定义如下: 

initArrayBufferForLaterUse()函数首先创建了缓冲区对象(第212行),然后向其中写入数据(第218~219行)。注意,函数并没有将缓冲区对象分配给attribute变量(gl.vertexAttribPointer())或开启attribute变量(gl.enableVertexAttribArray()),这两个步骤将留到真正进行绘制之前再完成。另外,为了便于将缓冲区分配给attribute变量,我们手动为其添加了两个属性num和type(第222~223行)。

这里利用了JavaScript的一个有趣的特性,就是可以自由地为对象添加新的属性。你可以直接通过属性名为对象添加新属性,并向其赋值。如你所见,我们为缓冲区对象添加了新的num属性并保存其中顶点的个数(第222行),添加了type属性以保存数据类型(第223行)。当然,也可以通过相同的方式访问这些属性。注意,在使用JavaScript的这项特性时应格外小心,如果不小心拼错了属性名,浏览器也不会报错。同样你也应该记得,这样做会增加性能开销。

最后,调用draw()函数绘制整个模型(第311行),与MultiJointModel中一样。但是调用drawSegments()函数的方式与前例调用drawBox()函数的方式有所不同,第3个参数是存储了顶点坐标数据的缓冲区对象,如下所示。 

drawSegments()函数的定义在第311行,它将缓冲区对象分配给a_Position变量(第314行)并开启之(第316行),然后调用gl.drawElements()进行绘制操作(第327行)。这里使用了之前为缓冲区对象添加的num和type属性。

 这一次,你不必再像前例中那样,在绘制每个部件时对模型矩阵进行缩放操作了,因为每个部件的顶点坐标都已经事先定义好了。同样也没必要再使用栈来管理模型矩阵,所以pushMatrix()函数和popMatrix()也不需要了。

示例效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/83467.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

刷题日记——将x减到0的最小操作数

将x减到0的最小操作数 题目链接&#xff1a;https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/ 题目解读 题目要求移除元素总和等于参数x&#xff0c;这道题给我的第一感觉就是从数组的两边入手&#xff0c;对数据进行加和删除&#xff0c;但是这里有一…

滚雪球学Java(24):Java反射

&#x1f3c6;本文收录于「滚雪球学Java」专栏&#xff0c;专业攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;带你早日登顶&#x1f680;&#xff0c;欢迎大家关注&&收藏&#xff01;持续更新中&#xff0c;up&#xff01;up&#xff01;up&#xff01;&#xf…

EasySwipeMenuLayout - 独立的侧滑删除

官网 GitHub - anzaizai/EasySwipeMenuLayout: A sliding menu library not just for recyclerview, but all views. 项目介绍 A sliding menu library not just for recyclerview, but all views. Recommended in conjunction with BaseRecyclerViewAdapterHelper Feature…

TS泛型的使用

函数中使用泛型&#xff1a; function identity<T>(arg: T): T {return arg; }let result identity<number>(10); // 传入number类型&#xff0c;返回number类型 console.log(result); // 输出: 10let value identity<string>(Hello); // 传入string类型&a…

ad18学习笔记十二:如何把同属性的元器件全部高亮?

1、先选择需要修改的器件的其中一个。 2、右键find similar objects&#xff0c;然后在弹出的对话框中&#xff0c;将要修改的属性后的any改为same 3、像这样勾选的话&#xff0c;能把同属性的元器件选中&#xff0c;其他器件颜色不变 注意了&#xff0c;如果这个时候&#xff…

初学phar反序列化

以下内容参考大佬博客&#xff1a;PHP Phar反序列化浅学习 - 跳跳糖 首先了解phar是什么东东 Phar是PHP的压缩文档&#xff0c;是PHP中类似于JAR的一种打包文件。它可以把多个文件存放至同一个文件中&#xff0c;无需解压&#xff0c;PHP就可以进行访问并执行内部语句。 默认开…

VuePress网站如何使用axios请求第三方接口

前言 VuePress是一个纯静态网站生成器,也就是它是无后端,纯前端的,那想要在VuePress中,发送ajax请求,请求一些第三方接口,有时想要达到自己一些目的 在VuePress中&#xff0c;使用axios请求第三方接口&#xff0c;需要先安装axios&#xff0c;然后引入&#xff0c;最后使用 本文…

爬虫框架Scrapy学习笔记-2

前言 Scrapy是一个功能强大的Python爬虫框架&#xff0c;它被广泛用于抓取和处理互联网上的数据。本文将介绍Scrapy框架的架构概览、工作流程、安装步骤以及一个示例爬虫的详细说明&#xff0c;旨在帮助初学者了解如何使用Scrapy来构建和运行自己的网络爬虫。 爬虫框架Scrapy学…

【Linux学习笔记】权限

1. 普通用户和root用户权限之间的切换2. 权限的三个w2.1. 什么是权限&#xff08;what&#xff09;2.1.1. 用户角色2.1.2. 文件属性 2.2. 怎么操作权限呢&#xff1f;&#xff08;how&#xff09;2.2.1. ugo-rwx方案2.2.2. 八进制方案2.2.3. 文件权限的初始模样2.2.4. 进入一个…

并发编程——synchronized

文章目录 原子性、有序性、可见性原子性有序性可见性 synchronized使用synchronized锁升级synchronized-ObjectMonitor 原子性、有序性、可见性 原子性 数据库事务的原子性&#xff1a;是一个最小的执行的单位&#xff0c;一次事务的多次操作要么都成功&#xff0c;要么都失败…

蓝桥杯 题库 简单 每日十题 day6

01 删除字符 题目描述 给定一个单词&#xff0c;请问在单词中删除t个字母后&#xff0c;能得到的字典序最小的单词是什么&#xff1f; 输入描述 输入的第一行包含一个单词&#xff0c;由大写英文字母组成。 第二行包含一个正整数t。 其中&#xff0c;单词长度不超过100&#x…

记录selenium和chrome使用socks代理打开网页以及查看selenium的版本

使用前&#xff0c;首先打开socks5全局代理。 之前我还写过一篇关于编程中使用到代理的情况&#xff1a; 记录一下python编程中需要使用代理的解决方法_python 使用全局代理_小小爬虾的博客-CSDN博客 在本文中&#xff0c;首先安装selenium和安装chrome浏览器。 参考我的文章…

用VS Code运行C语言(安装VS Code,mingw的下载和安装)

下载并安装VS code。 安装扩展包&#xff1a; 此时&#xff0c;写完代码右键之后并没有运行代码的选项&#xff0c;如图&#xff1a; 接下来安装编译器mingw。 下载链接&#xff1a; https://sourceforge.net/projects/mingw-w64/ 得到压缩包&#xff1a; 解压&#xff1a; …

滚雪球学Java(26):Java进制转换

&#x1f3c6;本文收录于「滚雪球学Java」专栏&#xff0c;专业攻坚指数级提升&#xff0c;助你一臂之力&#xff0c;带你早日登顶&#x1f680;&#xff0c;欢迎大家关注&&收藏&#xff01;持续更新中&#xff0c;up&#xff01;up&#xff01;up&#xff01;&#xf…

由于数字化转型对集成和扩展性的要求,定制化需求难以满足,百数低代码服务商该如何破局?

当政策、技术环境的日益成熟&#xff0c;数字化转型逐步成为企业发展的必选项&#xff0c;企业数字化转型不再是一道选择题&#xff0c;而是决定其生存发展的必由之路。通过数字化转型升级生产方式、管理模式和组织形式&#xff0c;激发内生动力&#xff0c;成为企业顺应时代变…

最新适合小白前端 Javascript 高级常见知识点详细教程(每周更新中)

1. window.onload 窗口或者页面的加载事件&#xff0c;当文档内容完全加载完成会触发的事件&#xff08;包括图形&#xff0c;JS脚本&#xff0c;CSS文件&#xff09;&#xff0c;就会调用处理的函数。 <button>点击</button> <script> btn document.q…

python项目2to3方案预研

目录 官方工具2to3工具安装参数解释基本使用工具缺陷 future工具安装参数解释基本使用工具缺陷 python-modernize工具安装参数解释基本使用工具缺陷 pyupgrade工具安装参数解释基本使用工具缺陷 对比 官方工具2to3 2to3 是Python官方提供的用于将Python 2代码转换为Python 3代…

单例模式(饿汉模式 懒汉模式)与一些特殊类设计

文章目录 一、不能被拷贝的类 二、只能在堆上创建类对象 三、只能在栈上创建类对象 四、不能被继承的类 五、单例模式 5、1 什么是单例模式 5、2 什么是设计模式 5、3 单例模式的实现 5、3、1 饿汉模式 5、3、1 懒汉模式 &#x1f64b;‍♂️ 作者&#xff1a;Ggggggtm &#x…

VM虚拟机CentOS7.9x64 LVM硬盘扩容

软件版本&#xff1a;VMWare Workstation14 虚拟机CentOS 7.9X64位 GParted 0.33.0 一、虚拟机安装gparted软件 sudo yum install epel-release sudo yum install gparted sudo yum install yum-utils git gnome-common gcc-c sudo yum-builddep gparted 二、关闭虚拟机&a…

Hive行转列[一行拆分成多行/一列拆分成多列]

场景&#xff1a; hive有张表armmttxn_tmp&#xff0c;其中有一个字段lot_number&#xff0c;该字段以逗号分隔开多个值&#xff0c;每个值又以冒号来分割料号和数量&#xff0c;如&#xff1a;A3220089:-40,A3220090:-40,A3220091:-40,A3220083:-40,A3220087:-40,A3220086:-4…