WebGL实战篇(六)——三维正交投影

2,183 阅读4分钟

本文正在参加「金石计划」

前言

从本节开始,我们就要进入三维的世界中了,准备好了吗?Let's go! 之前我们已经在 WebGL从入门到实战 - 鹤云云的专栏 - 掘金 (juejin.cn)中推导了坐标映射的矩阵。

我们再次回顾一下我们是如何将一个坐标范围为 0xwidth0 \le x \le width 转换到 1x1-1 \le x \le 1之间的。

0xwidth0widthxwidthwidthwidth0xwidth102xwidth212xwidth110 \le x \le width \\ \quad \\ \rArr \qquad \frac{0}{width} \le \frac{x}{width} \le \frac{width}{width} \\ \quad \\ \rArr \qquad0 \le \frac{x}{width} \le 1 \\ \quad \\ \rArr \qquad 0 \le \frac{2x}{width} \le 2 \\ \quad \\ \rArr \qquad -1 \le \frac{2x}{width} - 1 \le 1

所以,可以得到转换后的 x'的坐标为:

x=2xwidth1x' = \frac{2x}{width} - 1

对于 y 坐标与 z 坐标,同理的有: y=2yheight1y' = \frac{2y}{height} - 1z=2zdepth1z' = \frac{2z}{depth} - 1

我们将其转换成矩阵,可以写为:

proj=[2width00102height01002depth1001]\bold {proj} = \begin{bmatrix} \frac{2}{width} & 0 & 0 & -1 \\ 0 & \frac{2}{height} & 0 & -1 \\ 0 & 0 & \frac{2}{depth} & -1 \\ 0 & 0 & & 1 \end{bmatrix}

上面这个矩阵,我们将其称为投影矩阵

正交投影

WebGL从入门到实战 - 鹤云云的专栏 - 掘金 (juejin.cn)中我们已经完成了在 2D 平面中的变换,在 3 维世界中同样也是类似的,只不过我们多了一个 z 坐标。想象我们的三维空间是一个长为width,高为 height,深为depth 的立方体。

cube.png

还记得上面我们的 proj\bold {proj}矩阵吗?我们不需要对它进行任何修改。我现在只需要修改我们顶点数据。现在我们准备绘制一个立方体,我们开始修改我们的顶点数据。

//prettier-ignore
const pointPos = [
    // front-face
    0, 0, 0, width, 0, 0, width, height, 0, width, height, 0, 0, height, 0, 0, 0, 0,
    // back-face
    0, 0, depth, width, 0, depth, width, height, depth, width, height, depth, 0, height, depth, 0, 0, depth,
    // left-face
    0, 0, 0, 0, height, 0, 0, height, depth, 0, height, depth, 0, 0, depth, 0, 0, 0,
    // right-face
    width, 0, 0, width, height, 0, width, height, depth, width, height, depth, width, 0, depth, width, 0, 0,
    // top-face
    0, height, 0, width, height, 0, width, height, depth, width, height, depth, 0, height, depth, 0, height, 0,
    // bottom-face
    0, 0, 0, width, 0, 0, width, 0, depth, width, 0, depth, 0, 0, depth, 0, 0, 0,
];

//prettier-ignore
const colors = [
    1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0,
    1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0,
    1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1,
    0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
    0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1,
    0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
    0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
]

我们除了修改顶点的位置数据之外,我们为了更好的展示立方体,还额外的为每个顶点提供了颜色信息。往顶点中传入颜色数据的方法与传入位置信息类似。

gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
const a_color = gl.getAttribLocation(program, 'a_color');
// 我们不再采用这种方式进行传值
gl.vertexAttribPointer(
    a_color,
    3,
    gl.FLOAT,
    false,
    Float32Array.BYTES_PER_ELEMENT * 3,
    0
);
gl.enableVertexAttribArray(a_color);

另外,之前我们使用 vertexAttribPointer这个 API 时,其中的第二个参数,原来是 2。现在由于我们是 3D 图形,我们每个坐标是 3 维向量,所以我们需要修改为 3。

更多细节

绘制 3D 图形与 2D 图形还有很多不同,我们在这里列举 2 个容易被忽略的地方。就是深度缓冲剔除面

我们简单的介绍一下深度缓冲区和剔除面

深度缓冲区

深度缓冲区是 WebGL 中用来存储每个像素的深度值(与摄像机的距离)的一种缓冲区。它可以用来判断哪些物体在前面,哪些物体在后面,从而实现隐藏面消除的效果。

要使用深度缓冲区,需要先通过这个 API 启用它gl.enable(gl.DEPTH_TEST)

值得注意的是,一般每次绘制的时候我们都需要将之前的深度缓冲区清除。我们使用 gl.clear(gl.DEPTH_BUFFER_BIT)。但是我们与此同时还需要清空颜色缓冲区,我们使用位运算符号 '|' 来同时清除它们。gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)

剔除面

在 WebGL 中的三角形有正反面的概念,正面三角形的顶点顺序是逆时针方向, 反面三角形是顺时针方向。

WebGL 可以只绘制正面或反面三角形。如果你要使用这个特性,需要通过 API gl.enable(gl.CULL_FACE)来开启。 WebGL 默认剔除背面的三角形,也就是说三角形的顶点顺序是顺时针的的话,则该三角形不会被绘制。

运用剔除面技术,可以提高 WebGL 的性能。

其余的程序与之前的代码几乎无异。你可以通过 Demo 和文末的代码进行对比。

总结

今天我们快速的学习了如何在WebGL中绘制一个立方体,我们仅仅只是修改了传入的顶点数据。另外需要注意的是在绘制3D场景时,我们通常都需要开启深度测试(DEPTH_TEST)。

正交投影的内容还是相对比较简单的。接下来我们会进一步的讲解透视投影的知识。敬请关注!