您的位置:首页 > 其它

37 WebGL多个模型组成一个复杂的模型

2017-06-10 23:14 288 查看


案例查看地址:点击这里<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Title</title>
<style>
body {
margin: 0;
text-align: center;
}

#canvas {
margin: 0;
}
</style>
</head>
<body onload="main()">
<canvas id="canvas" height="800" width="1200"></canvas>
</body>
<script src="lib/webgl-utils.js"></script>
<script src="lib/webgl-debug.js"></script>
<script src="lib/cuon-utils.js"></script>
<script src="lib/cuon-matrix.js"></script>
<script>
// 顶点着色器
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" +
"}";

// 片元着色器
var FSHADER_SOURCE = "" +
"#ifdef GL_ES\n" +
"precision mediump float;\n" +
"#endif\n" +
"varying vec4 v_Color;\n" +
"void main () {" +
" gl_FragColor = v_Color;\n" +
"}\n";

//主函数,页面加载完成触发
function main() {
//获取canvas对象
var canvas = document.getElementById("canvas");

//获取WebGL上下文
var gl = getWebGLContext(canvas);
if (!gl) {
console("您的浏览器不支持WebGL");
return;
}

//初始化着色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log("无法初始化着色器");
return;
}

//通过创建缓冲区并赋值数据给attribute变量 并返回绘制次数
var n = initVertexBuffers(gl);
if (n < 0) {
console.log("无法设置缓冲区的相关信息");
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_NormalMatrix || !u_MvpMatrix) {
console.log("无法获取到相关的存储位置");
return;
}

//创建一个视点(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);

//添加键盘按键交互事件
document.onkeydown = function (e) {
keydown(e, 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 = -45.0;//joint1的旋转角度(度
var g_joint2Angle = 0.0; //joint2的当前角度
var g_joint3Angle = 0.0; //joint3的当前角度

function keydown(event, gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
switch (event.keyCode) {
case 38: // 上键 -> 以joint1为中心沿着z轴旋转(增量)
if (g_joint1Angle < 135.0) g_joint1Angle += angle_step;
break;
case 40: // 下键 -> 以joint1为中心沿着z轴旋转(减量)
if (g_joint1Angle > -135.0) g_joint1Angle -= angle_step;
break;
case 39: // 右键 -> 以y轴进行水平旋转(增量)
g_arm1Angle = (g_arm1Angle + angle_step) % 360;
break;
case 37: // 左键 -> 以y轴进行水平旋转(减量)
g_arm1Angle = (g_arm1Angle - angle_step) % 360;
break;
case 90: // Z键 -> 使joint2正向转动
g_joint2Angle = (g_joint2Angle + angle_step) % 360;
break;
case 88: // X键 -> 使joint2负向转动
g_joint2Angle = (g_joint2Angle - angle_step) % 360;
break;
case 86: // V键 -> 使joint3正向转动
if (g_joint3Angle < 60.0) {
g_joint3Angle = (g_joint3Angle + angle_step) % 360;
}
break;
case 67: // C键 -> 使joint3负向转动
if (g_joint3Angle > -60.0) {
g_joint3Angle = (g_joint3Angle - angle_step) % 360;
}
break;
default:
return; // 其他按键没作用
}

draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
}

function initVertexBuffers(gl) {
// Vertex coordinates(长方体3宽度,高度10,长度3,其原点在其底部)
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
]);

// 每一个面的法向量
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
]);

//创建缓冲区并赋值attribute
if (!initArrayBuffer(gl, "a_Position", vertices, gl.FLOAT, 3)) return -1;
if (!initArrayBuffer(gl, "a_Normal", normals, gl.FLOAT, 3)) return -1;

//取消缓冲区buffer绑定
gl.bindBuffer(gl.ARRAY_BUFFER, null);

//创建一个缓冲区对象,并将索引绑定到缓冲区
var indexBuffer = gl.createBuffer();
if (!indexBuffer) {
console.log("无法创建索引缓冲区");
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("无法创建缓冲区");
return false;
}

//将数据写入缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

//获取到attribute变量的存储位置,并将变量绑定缓冲区
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log("无法获取到变量的" + 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);

//绘制基座
var baseHeight = 2.0;//基座的高度
g_modelMatrix.setTranslate(0.0, -12.0, 0.0);//设置为平移矩阵,并沿y轴向下移动12
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);//沿y轴旋转
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);//移至joint1
g_modelMatrix.rotate(g_joint1Angle, 0.0, 0.0, 1.0);//沿z轴旋转
drawBox(gl, n, 4.0, arm2Length, 4.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);

//机器人手掌的制作
var palmLength = 2.0;
g_modelMatrix.translate(0.0, arm2Length, 0.0);//移至joint2
g_modelMatrix.rotate(g_joint2Angle, 0.0, 1.0, 0.0);//沿y轴旋转
drawBox(gl, n, 2.0, palmLength, 6.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);

//移至手掌一端的中点
g_modelMatrix.translate(0.0, palmLength, 0.0);

//绘制finger1 第一个手指头
pushMatrix(g_modelMatrix);//将当前的g_modelMatrix的值保存
g_modelMatrix.translate(0.0, 0.0, 2.0);//沿z轴移动
g_modelMatrix.rotate(g_joint3Angle, 1.0, 0.0, 0.0);//沿x轴旋转
drawBox(gl, n, 1.0, 2.0, 1.0, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
g_modelMatrix = popMatrix();//获取到压入时的g_modelMatrix矩阵的值

//绘制finger2 第二个手指头
g_modelMatrix.translate(0.0, 0.0, -2.0);//沿z轴负方向移动
g_modelMatrix.rotate(-g_joint3Angle, 1.0, 0.0, 0.0);//沿x轴旋转
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(); //法线坐标变换矩阵

//绘制立方体
function drawBox(gl, n, width, height, depth, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {
pushMatrix(g_modelMatrix);

g_modelMatrix.scale(width, height, depth);
//计算出计算模型视图矩阵,并赋值给u_MvpMatrix
g_mvpMatrix.set(viewProjMatrix);
g_mvpMatrix.multiply(g_modelMatrix);
gl.uniformMatrix4fv(u_MvpMatrix, false, g_mvpMatrix.elements);

//获取模型矩阵的逆转置矩阵,并赋值u_NormalMatrix
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();
}

</script>
</html>相对于上一节的代码,着色器部分一点都没有修改。
在keydown()事件中,增加了新增部件的旋转角度变量,并相应的增加了鼠标控制的事件。





在draw()函数内,将额外增加的结构也相应的绘制出来。模型的各个部件base、arm1、arm2、palm、finger1和finger2等虽然都是立方体,但是长宽各不相同,所以本例相应的扩展了drawBox()函数,添加了三个参数width(宽度)、height(高度)和depth(深度)。
为什么会需要添加pushMatrix()和popMatrix()两个存储矩阵的压栈和出栈?
因为如果按以前的那样顺序绘制的话,finger2关联的地方将不是手掌palm,而是finger1,所以,在绘制finger1之前,将矩阵存储起来,然后在绘制完finger1之后,再绘制finger2,这样两个手指都关联到了手掌palm上了。只要栈足够深,用这种方法就可以绘制任意复杂的层次结构模型。我们只需要按照层次结构,从高到底绘制部件,并在绘制“具有兄弟部件”的部件前将模型矩阵压入栈,绘制完再弹出即可。
为什么drawBox()函数内部也需要压栈入栈?
drawBox()函数首先将模型矩阵乘以由width、height和depth参数组成的缩放矩阵,是绘制出的立方体尺寸与设想的一样。如果不是用压栈出栈,绘制第一个的缩放矩阵,会对第二个绘制的缩放矩阵产生一定的影响,所以,要将缩放之前的默认大小保存下来,就用到了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐