您的位置:首页 > 其它

28 WebGL绘制立方体

2017-06-02 17:54 471 查看
案例查看地址:点击这里
到现在为止,一直学的都是绘制一些简单的三角形。下面,我们将学习如何绘制如图所示的一个立方体,目标:



如果按以前所学的知识,制作这个正方体就需要使用三角形两个拼一个矩形,最后拼出来六个正方形来组成这个立方体,由于绘制三角形需要重复调用顶点,虽然只需要8个顶点的位置,但是,绘制三角形的时候,却需要多次调用,所以给我们提供了一个gl.drawElements()函数的解决方案。使用该函数替代gl.drawArrays()函数进行绘制,能够避免重复定义顶点,报错顶点数量最小。为此,你需要知道模型的每一个顶点的坐标,这些顶点坐标描述了整个模型。
我们将立方体拆成顶点和三角形,如图7.32所示。立方体呗拆成6个面:前、后、左、右、上、下,每个面都由两个三角形组成,与三角形列表中的两个三角形相关联。每个三角形都有3个顶点,与顶点列表中的3个顶点相关联。三角形列表中的数字表示该三角形的3个顶点在顶点列表中的索引值。顶点列表中共有8个顶点,索引值为从0到7。



而现在新的gl.drawElements()方法就是通过顶点索引绘制物体,首先我们来看一下如何使用gl.drawElements(),我们需要在gl.ELEMENT_ARRAY_BUFFER(而不是之前一直使用的gl.ARRAY_BUFFER)中指定顶点的索引值。所以两种方法最重要的区别就在于gl.ELEMENT_ARRAY_BUFFER,它管理这具有索引结构的三位模型数据。



如果传入的type参数和gl.ELEMENT_ARRAY_BUFFER中的数据类型(Uint8Array或Uint16Array)不一致,也并不会出现错误。但是如果两者不一致,比如缓冲区是Uint16Array类型的,而传入的参数为gl.UNSIGNED_SHORT,那么程序会错误地理解缓冲区中的数据,并会绘制出一些不可预测的东西。
下面书写一个案例:



<!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_Color;\n" +
"uniform mat4 u_ModelViewMatrix;\n" +
"varying vec4 v_Color;\n" +
"void main(){" +
" gl_Position = u_ModelViewMatrix * a_Position;\n" +
" v_Color = a_Color;\n" +
"}\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";

//声明js需要的相关变量
var canvas = document.getElementById("canvas");
var gl = getWebGLContext(canvas);

function main() {
if (!gl) {
console.log("你的浏览器不支持WebGL");
return;
}

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

var n = initVertexBuffers(gl);
if (n < 0) {
console.log("无法创建缓冲区");
return;
}

//设置视角矩阵的相关信息
var u_ModelViewMatrix = gl.getUniformLocation(gl.program, "u_ModelViewMatrix");
if (u_ModelViewMatrix < 0) {
console.log("无法获取矩阵变量的存储位置");
return;
}

//设置底色
gl.clearColor(0.0, 0.0, 0.0, 1.0);

//进入场景初始化
draw(gl, n, u_ModelViewMatrix);
}

function draw(gl, n, u_ModelViewMatrix) {
//设置视角矩阵的相关信息(视点,视线,上方向)
var viewMatrix = new Matrix4();
viewMatrix.setLookAt(3,3,7,0,0,0,0,1,0);

//设置模型矩阵的相关信息
var modelMatrix = new Matrix4();
modelMatrix.setRotate(0, 0, 0, 1);

//设置透视投影矩阵
var projMatrix = new Matrix4();
projMatrix.setPerspective(30,canvas.width/canvas.height,1,100);

//计算出模型视图矩阵 viewMatrix.multiply(modelMatrix)相当于在着色器里面u_ViewMatrix * u_ModelMatrix
var modeViewMatrix = projMatrix.multiply(viewMatrix.multiply(modelMatrix));

//将试图矩阵传给u_ViewMatrix变量
gl.uniformMatrix4fv(u_ModelViewMatrix, false, modeViewMatrix.elements);

//开启隐藏面清除
gl.enable(gl.DEPTH_TEST);

//清空颜色和深度缓冲区
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

//绘制图形
gl.drawElements(gl.TRIANGLES,n,gl.UNSIGNED_BYTE,0);

}

function initVertexBuffers(gl) {
// 创建一个立方体
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
var verticesColors = new Float32Array([
// 设置顶点和颜色(偷的顶点代码位置)
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // v0 White
-1.0, 1.0, 1.0, 1.0, 0.0, 1.0, // v1 Magenta
-1.0, -1.0, 1.0, 1.0, 0.0, 0.0, // v2 Red
1.0, -1.0, 1.0, 1.0, 1.0, 0.0, // v3 Yellow
1.0, -1.0, -1.0, 0.0, 1.0, 0.0, // v4 Green
1.0, 1.0, -1.0, 0.0, 1.0, 1.0, // v5 Cyan
-1.0, 1.0, -1.0, 0.0, 0.0, 1.0, // v6 Blue
-1.0, -1.0, -1.0, 0.0, 0.0, 0.0 // v7 Black
]);

//顶点索引
var indices = new Uint8Array([
0, 1, 2, 0, 2, 3, // 前
0, 3, 4, 0, 4, 5, // 右
0, 5, 6, 0, 6, 1, // 上
1, 6, 7, 1, 7, 2, // 左
7, 4, 3, 7, 3, 2, // 下
4, 7, 6, 4, 6, 5 // 后
]);

//创建缓冲区对象
var vertexColorBuffer = gl.createBuffer();
var indexBuffer = gl.createBuffer();
if (!vertexColorBuffer || !indexBuffer) {
console.log("无法创建缓冲区对象");
return -1;
}

//绑定缓冲区对象并写入数据
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

//获取数组中一个元素所占的字节数
var fsize = verticesColors.BYTES_PER_ELEMENT;

//获取顶点位置变量位置
var a_Position = gl.getAttribLocation(gl.program, "a_Position");
if (a_Position < 0) {
console.log("无法获取顶点位置的存储变量");
return -1;
}

//对位置的顶点数据进行分配,并开启
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, fsize * 6, 0);
gl.enableVertexAttribArray(a_Position);

//获取顶点颜色的变量
var a_Color = gl.getAttribLocation(gl.program, "a_Color");
if (a_Color < 0) {
console.log("无法获取顶点位置的存储变量");
return -1;
}

//对位置的顶点数据进行分配,并开启
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, fsize * 6, fsize * 3);
gl.enableVertexAttribArray(a_Color);

//将顶点索引数据写入缓冲区对象
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW);

return indices.length;
}
</script>
</html>在124行:
本案例不再使用verticesColor中的顶点顺序来进行绘制,所以必须额外注意每个顶点的索引值,我们要通过索引值来指定绘制的顺序。比如说,第1个顶点的索引为0,第2个顶点的索引为1,等等。
183行:
缓冲区对象indexBuffer中的数据来自于数组indices,该数组以索引值的形式存储了绘制顶点的顺序。索引值是整型数,所以数组的类型是Uint8Array(无符号8位整型数)。如果有超过256个顶点,那么就应该使用Uint16Array。indices中的元素如图7.33中的三角形列表所示,每3个索引值为1组,指向3个顶点,由这3个顶点组成1个三角形。通常我们不需要手动创建这些顶点和索引数据,因为三维建模工具会帮助我们创建它们。



在调用gl.drawElements()时,WebGL首先充绑定到gl.ELEMENT_ARRAY_BUFFER的缓冲区(也就是indexBuffer)中获取索引值,然后根据该索引值,从绑定到gl.ARRAY_BUFFER的缓冲区(即vertexColorBuffer)中获取顶点的坐标、颜色等信息,然后传递给attribute变量并执行顶点着色器。对每个索引值都这样做,最后就绘制出了整个立方体,而且值调用了一次gl.drawElements()。这种方式通过索引来访问顶点数据,从而循环利用顶点信息,控制内存的开销,但代价是你需要通过索引来简介地访问顶点,在某种程度上使程序赋值话了,所以,两个绘制方法各有优劣,具体用哪一个取决于具体的需求。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: