🧢 [WebGL编程指南] 7. 进入三维世界
视点和视线
观察者所处的位置称为视点,从视点出发沿者观察方向的射线称作视线。
为了确定观察者的状态,需要获取视点和观察目标点(被观察的目标点),可以用来确定视线。要把观察到的景象会知道屏幕上还需要知道上方向。

webGL中默认
视图矩阵
根据1. 自定义的观察者状态,绘制观察者看到的景象,与 2. 使用默认状态名单时对三位对象进行平移、旋转等变化,再绘制看到的景象,这两种行为是等价的。
假设 (0, 0 , 1)。则等价于将顶点坐标往z轴负方向移动1.0个单位。类似的上方向就是对顶点左边乘以旋转矩阵。

再此基础上还能添加平移、缩放、旋转矩阵,这时矩阵被称为模型矩阵

// 顶点着色器
const VSHADER_SOURCE =
"attribute vec4 a_Position;\n" +
"attribute vec4 a_Color;\n" +
"uniform vec4 u_ViewMatrix;\n" + // 视图矩阵
"void main() {\n" +
" gl_Position = a_Position * u_ViewMatrix;\n" + // 设置坐标
" gl_PointSize = 10.0;\n" + // 设置大小
" v_Color = a_Color;\n" + // 将数据传递给片元着色器
"}\n";
// ...
const u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix')
// 三方库创建视图矩阵
const viewMatrix = new Matrix4()
viewMatrix.lookAt(
0.20, 0.25, 0.25,
0 ,0, 0,
0, 1, 0
)
gl.uniformMatrix4fv(u_ViewMatrix , false, u_ViewMatrix.elements)利用键盘改变视点
let g_eyeX = 0.20
let g_eyeY = 0.25
let g_eyeZ = 0.25
// ...
document.onkeydown = function (e) {
if (e.keyCode == 39) { // 右键
g_eyeX -= 0.01
} else if (e.keyCode == 37) { // 左键
g_eyeX += 0.01
} else {
return
}
viewMatrix.setLookAt(
g_eyeX, g_eyeY, g_eyeZ,
0, 0, 0,
0, 1, 0
)
gl.uniformMatrix4fv(u_ViewMatrix , false, u_ViewMatrix.elements)
// 清除缓冲区
gl.cear(gl.COLOR_BUFFER_BIT)
gl.drawArrays(gl.TRIANGLES, 0, n)
}可视范围
当视点在极左或极右时,图形会缺一角,这是因为没有指定可视范围。图形只有在可视范围内,WebGL才会绘制它。

可视空间
分别由(right, top, -near), (-left, top, -near), (-left, -bottom -near), (right,-bottom, -near) 和 (right, top, far), (-left, top ,far), (-left, -bottom far), (right,-bottom, far) 组成。
Canvas上显示的是可视空间中物体在近裁剪面上的投影。如果裁剪面的宽高比和canvas不同,画面会按照canvas宽高比压缩

// 顶点着色器
const VSHADER_SOURCE =
"attribute vec4 a_Position;\n" +
"attribute vec4 a_Color;\n" +
"uniform mat4 u_ProjMatrix;\n" +
"void main() {\n" +
" gl_Position = u_ProjMatrix * a_Position;\n" + // 设置坐标
" v_Color = a_Color;\n" + // 将数据传递给片元着色器
"}\n";
通过可视空间的投影矩阵,与顶点坐标相乘,获取图像的投影坐标。通过扩大可视区域可以补上缺失的角。


投影矩阵可以实现通过与顶点坐标相乘,得到投影坐标。
投影矩阵可以使较远的三角形看上去变小,会使三角形不同程度地平移以贴近视线中心

正确处理对象的前后关系
默认情况下,WebGL为了加速绘图操作,是按照顶点在缓冲区的顺序来处理他们的。为了解决这个问题,webGL提供了隐藏面消除的功能。
// 开启隐藏面消除功能
gl.enable(gl.DEPTH_TEST)
// 在绘制之前清除深度缓冲区
gl.clear(gl.DEPTH_BUFFER_BIT)深度冲突
隐藏面消除大多情况下都能很好的完成问题,但当集合体的表面极为接近时,会出现新的问题,使得表面斑斑驳驳,这种现象被称为深度冲突。

WebGL提供多边形偏移(polygon offset)的机制来解决这个问题。它会自动在Z值上加一个偏移量,偏移量物体表面和视线角度决定。
// 启用多边形偏移
gl.enable(gl.POLYFON_OFFSET_FILL)
// 绘制之前指定计算偏移量的参数
gl.polygonOffset(1.0, 1.0)
立方体
绘制立方体可以1. 通过三角形组成,这样一个面需要6个顶点。也可以通过gl.TRIANGLE_FAN 模式绘制,这样一个面只需要4个顶点。不过WebGL也提供了一个完美方案gl.drawElements 可以避免重复定义顶点,使顶点数量保持最小。
需要在gl.ELEMENT_ARRAY_BUFFER 指定顶点(gl.drawArray 时用的gl.ARRAY_BUFFER)


需要将立方体拆成顶点和三角形。立方体有前后左右6个面,每个面由两个三角形组成,每个三角形有三个顶点,因此定义一个面需要指定6个顶点的索引
const VSHADER_SOURCE =
"..."
"void main() {\n" +
" gl_Position = u_MvpMatrix * a_Position;\n" + // 设置坐标
" v_Color = a_Color;\n" +
"}\n";
// 片元着色器
const FSHADER_SOURCE =
"void main() {\n" +
" gl_FragColor = v_Color;\n" + // 设置颜色
"}\n";
// ...
// 定义顶点和颜色
const verticesColors = new Float32Array([
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // v0 白色
...
-1.0, -1.0, -1.0, 0.0, 0.0 ,0.0 // v7 黑色
])
// 顶点索引
const indeces = new Unit8Array([
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
])
// 创建顶点坐标buffer
// ...
// 创建索引buffer
const indeBuffer = gl.createBuffer()
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW)
// ...
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.drawElements(gl.TRIANLGES, n, gl.UNSIGNED_BYTE, 0)// ...
// 定义顶点
const vertices = new Float32Array([
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // 第一个面
...
])
// 颜色
const colors = new Float32Array([
0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, 0.4, 0.4, 1.0, // 第一个面的颜色
...
])
const indeces = new Unit8Array([
0, 1, 2, 0, 2, 3, // 前
4, 5, 6, 4, 6, 7, //右
...
])
// 创建顶点缓冲区
// 写数据
// 创建颜色缓冲区
// 写数据