WebGL笔记(三):绘制立方体
终于要在三维的场景里绘制一个三维的物体了——1个Cube。
我们先来厘清几个事情:
- 一个立方体有6个面,每个面4个顶点,所以要绘制24个顶点。
- 每个顶点位置由3个数字表示,所以顶点信息是72个数字的数组。
- 每个顶点颜色由4个数字表示,所以颜色信息是96个数字的数组。
先看一看长长的顶点数组:
var vertices = [
// Front face
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,
// Back face
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,
// Top face
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
// Bottom face
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,
// Right face
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
1.0, -1.0, 1.0,
// Left face
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0
];
这没什么可说的。比较有趣的是,还有一个“顶点索引”数组:
var cubeVertexIndices = [
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // back
8, 9, 10, 8, 10, 11, // top
12, 13, 14, 12, 14, 15, // bottom
16, 17, 18, 16, 18, 19, // right
20, 21, 22, 20, 22, 23 // left
]
开头说过了,一共24个顶点,这些数字应该是顶点的索引值。那他们的用处是什么呢?引用MDN原文:
https://developer.mozilla.org/en/WebGL/Creating_3D_objects_using_WebGL
The
cubeVertexIndices
array defines each face as a pair of triangles, specifying each triangle's vertices as an index into the cube's vertex array. Thus the cube is described as a collection of 12 triangles.
E文不好的童鞋不用担心我来翻译一下,咳:大概其啊,呐,cubeVertexIndices这个数组呢,将每一个面定义为一对三角形,并且标出了每个三角形顶点在vertices数组中的索引键。这样以来呢,这个Cube——就是立方体啦——就被描述为一个12个三角形的集合。接触过3D建模的童鞋,可能对此不会太陌生。
然后是颜色数组。MDN原文为了减少篇幅,只定义了6个颜色给6个面,然后用程序扩展为96个。
var colorGroups = [
[1.0, 0.0, 1.0, 1.0], // white
[1.0, 0.0, 0.0, 1.0], // red
[0.0, 1.0, 0.0, 1.0], // green
[0.0, 0.0, 1.0, 1.0], // blue
[0.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 0.0, 1.0]
];
var generatedColors = [];
for(var i = 0; i < 6; i++){
for(var j = 0; j < 4; j++){
generatedColors = generatedColors.concat(colorGroups[i]);
}
}
颜色每个值的取值范围是0.0~1.0之间的浮点数,每一组4个值分别是红、绿、蓝和透明度。那段扩展的代码比较简单:用一个嵌套循环,将每个数组复制4次,组成一个新的数组。
虽然颜色和顶点的数据有所变化,但是它们的定义和操作没有变化。
绘制开始前,有两个需要注意的地方:
一、在绘制(draw…)之前,应当对场景做初始设置。
// 黑底, 不透明
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 清除所有
gl.clearDepth(1.0);
// Enable depth testing / 开启...深度测试?
gl.enable(gl.DEPTH_TEST);
// Near things obscure far things / 近处物体遮挡远处物体?
gl.depthFunc(gl.LEQUAL);
因为这次涉及较为复杂的图形,如果不做设置,尤其是不开启DEPTH_TEST的话,往往会出现比较搞笑的情况。
二、有些语句是有顺序依赖的。
在第一篇中曾经提到过类似问题,本例中也有一处:
1: //定义顶点
2: var cubeVerticesBuffer = gl.createBuffer();
3: gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesBuffer);
4: gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
5: //传送数据到Shader指定变量
6: gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
7:
8: //定义颜色
9: var cubeVerticesColorBuffer = gl.createBuffer();
10: gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesColorBuffer);
11: gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(generatedColors), gl.STATIC_DRAW);
12: //传送颜色数据到Shader
13: gl.vertexAttribPointer(vertexColorAttribute, 4, gl.FLOAT, false, 0, 0);
在某个数据被bindBuffer之后,当前缓存数据即为该数据,在下次更改之前,必须及时传递到Shader中(vertexAttribPointer)。上面第6、13行如果顺序发生变化的话可能会引发顶点绘制错误。
下面附上所有代码,简单写了注释,不再赘述。MatrixHelper增加了rotate方法,末尾写了一个小动画,可以看到立方体旋转起来。
从下一篇开始,要尝试封装自己的WebGL库,像颜色、顶点之类的复杂数据也计划设计为便于操作的js类。
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebGL Step 03</title>
<style type="text/css">
canvas{ background-color:#666; }
</style>
<script type="text/ecmascript" src="https://files.cnblogs.com/muse/sylvester.js"></script>
<script type="text/ecmascript" src="https://files.cnblogs.com/muse/glUtils.js"></script>
<script type="text/ecmascript">
function MatrixHelper(){ this.matrix = Matrix.I(4); }
MatrixHelper.prototype = {
/* makePerspective */
make : function(fovy, aspect, znear, zfar){
this.ppm = makePerspective(fovy, aspect, znear, zfar);
},
/* multMatrix */
mult : function(m){
this.matrix = this.matrix.x(m);
},
/* mvTranslate */
trans : function(v){
this.mult(Matrix.Translation($V([v[0], v[1], v[2]])).ensure4x4());
},
/* setMatrixUniforms */
set : function(gl, sProg){
if(!!this.ppm){
gl.uniformMatrix4fv(gl.getUniformLocation(sProg, "uPMatrix")
, false, new Float32Array(this.ppm.flatten()));
}
if(!!this.matrix){
gl.uniformMatrix4fv(gl.getUniformLocation(sProg, "uMVMatrix")
, false, new Float32Array(this.matrix.flatten()));
}
},
/* mvRotate */
rotate : function(angle, v){
var m = Matrix.Rotation(angle * Math.PI / 180.0, $V([v[0], v[1], v[2]])).ensure4x4();
this.mult(m);
}
}
</script>
</head>
<body>
<canvas id="glcanvas" width="640" height="480">看来您的浏览器不支持<code><canvas></code>标记</canvas>
<script type="text/ecmascript">
var testVertexCode = '\
attribute vec3 aVertexPosition;\
attribute vec4 aVertexColor;\
uniform mat4 uMVMatrix;\
uniform mat4 uPMatrix;\
varying lowp vec4 vColor;\
void main(void){\
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\
vColor = aVertexColor;\
}',
testFragmentCode = '\
varying lowp vec4 vColor;\
void main(void){\
gl_FragColor = vColor;\
}'
;
var vertices = [
// Front face
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,
// Back face
-1.0, -1.0, -1.0,
-1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
1.0, -1.0, -1.0,
// Top face
-1.0, 1.0, -1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
// Bottom face
-1.0, -1.0, -1.0,
1.0, -1.0, -1.0,
1.0, -1.0, 1.0,
-1.0, -1.0, 1.0,
// Right face
1.0, -1.0, -1.0,
1.0, 1.0, -1.0,
1.0, 1.0, 1.0,
1.0, -1.0, 1.0,
// Left face
-1.0, -1.0, -1.0,
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, 1.0, -1.0
];
var cubeVertexIndices = [
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // back
8, 9, 10, 8, 10, 11, // top
12, 13, 14, 12, 14, 15, // bottom
16, 17, 18, 16, 18, 19, // right
20, 21, 22, 20, 22, 23 // left
]
var colorGroups = [
[1.0, 0.0, 1.0, 1.0], // white
[1.0, 0.0, 0.0, 1.0], // red
[0.0, 1.0, 0.0, 1.0], // green
[0.0, 0.0, 1.0, 1.0], // blue
[0.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 0.0, 1.0]
];
var generatedColors = [];
for(var i = 0; i < 6; i++){
for(var j = 0; j < 4; j++){
generatedColors = generatedColors.concat(colorGroups[i]);
}
}
var canvas = document.getElementById('glcanvas');
var gl = canvas.getContext('experimental-webgl');
// 黑底, 不透明
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// 清除所有
gl.clearDepth(1.0);
// Enable depth testing / 开启...深度测试?
gl.enable(gl.DEPTH_TEST);
// Near things obscure far things / 近处物体遮挡远处物体?
gl.depthFunc(gl.LEQUAL);
//定义Vertext Shader
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, testVertexCode);
gl.compileShader(vertShader);
//定义Fragment Shader
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, testFragmentCode);
gl.compileShader(fragShader);
//定义Program
var program = gl.createProgram();
//附加两个Shader到program
gl.attachShader(program, vertShader);
gl.attachShader(program, fragShader);
//引用
gl.linkProgram(program);
gl.useProgram(program);
//定位Shader所需变量并启用
var vertexPositionAttribute = gl.getAttribLocation(program, 'aVertexPosition');
gl.enableVertexAttribArray(vertexPositionAttribute);
var vertexColorAttribute = gl.getAttribLocation(program, 'aVertexColor');
gl.enableVertexAttribArray(vertexColorAttribute);
//定义顶点索引
var cubeVerticesIndexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVerticesIndexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
//定义顶点
var cubeVerticesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
//传送数据到Shader指定变量
gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0);
//定义颜色
var cubeVerticesColorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(generatedColors), gl.STATIC_DRAW);
//传送颜色数据到Shader
gl.vertexAttribPointer(vertexColorAttribute, 4, gl.FLOAT, false, 0, 0);
//调整位置
var matrix = new MatrixHelper();
matrix.trans([0.0, 0.0, -6.0]);
matrix.make(40, 640 / 480, 0.1, 100.0);
//动画函数
var animate = function(){
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
matrix.rotate(1, [1, 0, 1]);
matrix.set(gl, program);
gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_SHORT, 0);
}
//转吧
setInterval(animate, 40);
</script>
</body>
</html>