在说完矩阵相关的知识后,就可以进入三维世界了。三维世界有两个很基本的东西:
相机:
- 正交相机就是 正交投影矩阵 * 视图矩阵 * 物体顶点
- 透视相机就是 透视投影矩阵 * 视图矩阵 * 物体顶点
三维物体:本文就说正方形的绘制,三维物体都是由三角形组成的,两个三角形组成一个面,六个面组成一个正方体,相信大家都知道了。
本文相关的代码在链接的ch7文件夹。
正方体绘制
采用另外一种绘制方式gl.drawElements(),它与之前的gl.drawArrays(),不同,能够避免重复定义顶点,保持顶点数最小。不过,这个新引进一个概念就是,索引,它和数组中的索引概念类似,顶点存在数组中,通过顶点索引写入缓存区,绑定到gl.ELEMENT_ARRAY_BUFFER上。使用方法类似于gl.drawArrays()。
void gl.drawElements(mode, count, type, offset);
参数
-
mode
这个和gl.drawArrays()的模式一样,分别有七种类型:gl.POINTS、gl.LINE_STRIP、gl.LINE_LOOP、gl.LINES、gl.TRIANGLE_STRIP、gl.TRIANGLE_FAN、gl.TRIANGLES。 -
count
整数型 指定要渲染的元素数量.
- type
枚举类型 指定元素数组缓冲区中的值的类型。可能的值是:
gl.UNSIGNED_BYTE
gl.UNSIGNED_SHORT
当使用 OES_element_index_uint 扩展时:
gl.UNSIGNED_INT
- offset
字节单位 指定元素数组缓冲区中的偏移量。必须是给定类型大小的有效倍数.
function initVertexBuffers(gl) {
// Create a cube
// 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, // front
0, 3, 4, 0, 4, 5, // right
0, 5, 6, 0, 6, 1, // up
1, 6, 7, 1, 7, 2, // left
7, 4, 3, 7, 3, 2, // down
4, 7, 6, 4, 6, 5 // back
]);
// 创建缓冲区对象
var vertexColorBuffer = gl.createBuffer();
var indexBuffer = gl.createBuffer();
if (!vertexColorBuffer || !indexBuffer) {
return -1;
}
//将顶点坐标和颜色写入缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
var FSIZE = verticesColors.BYTES_PER_ELEMENT;
// 将缓冲区顶点数据分配给a_Position 并开启
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if(a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return -1;
}
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 6, 0);
gl.enableVertexAttribArray(a_Position);
// 将缓冲区颜色数据分配给a_Color并开启
var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
if(a_Color < 0) {
console.log('Failed to get the storage location of a_Color');
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;
}
......
//绘制立方体
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
三维物体的前后关系处理
在真实世界中,如果把盒子一前一后放置在桌子上的话,前面的盒子会挡住部分后面的盒子。但在WebGL默认情况下,不是如此,为了加快绘图,是按照顶点在缓冲区中的顺序来处理它们的,只有先定义远的物体,再定义近的物体,才能产生正确的效果。
隐藏面消除
为了解决上个问题,WebGL提供了隐藏面消除功能。它会帮助我们消除那些被遮挡的表面(隐藏面),这样不用考虑顶点在缓冲区中的顺序,远处的物体自动会被近处的物体遮挡。
- 开启隐藏面消除功能
gl.enable(gl.DEPTH_TEST);
- 在绘制之前,清除深度缓冲区
gl.clear(gl.DEPTH_BUFFER_BIT);
深度冲突
当两个几何图形或者是两个表面特别接近(比如两个三角形X、Y接近,Z值完全相同)的时候,会出现新的问题,使得表面看起来斑斑驳驳的,这种现象称为深度冲突。WebGL提供一种被称为多边形偏移的机制来解决这个问题。该机制将自动在Z值加上一个偏移量,偏移量的值有物体表面相对观察值视线的角度来确定。
- 启动多边形偏移
gl.enable(gl.POLYGON_OFFSET_FILL),;
- 在绘制之前指定用来计算偏移量的参数
gl.polygonOffset(1.0,1.0);
以上只是很简单的三维世界,光照,阴影还有其他各种效果,我们都没有考虑,道路艰且长啊。