04.drawElements-绘制一个正方体

309 阅读6分钟

全部章节

01.shader-最简单的webgl程序

02.buffer-在一个着色器程序中绘制多个点

03.drawArrays-绘制其他平面图形

04.drawElements-绘制一个正方体

05.texture-使用材质贴图

06.frameBuffer-将webgl绘制的正方体作为材质

绘制一个正方形

上章中讲解了,在webgl中要绘制一个面,都是通过三角形拼接的,要确定一个三角形,就需要定义三个顶点。
如何绘制一个正方体。
正方体的特点是,有6个面,8个角。而我们绘制图形先需要确定的就是点,那就是先定义正方体的8个顶点。

0.5, 0.5, 0.5, 
-0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
-0.5, -0.5, -0.5,

我们定义正方体的八个顶点坐标如上。

接下来我们只需要每个面绘制两个三角形就可以形成一个正方体。
但我们回忆一下上一章中讲到的绘制三角形的三个模式,都无法只通过这八个顶点信息就完成一个正方体。

如果我们在TRIANGLES模式(一般使用TRIANGLES模式去绘制面,因为不存在点共用的情况)下绘制一个正方体,就需要36个顶点(需要六个面,一个面需要两个三角形,一个三角形需要三个顶点)。

我们直接把这36个顶点以此输入到buffer,那么一个点其实重复定义了4次,内存压力就是原来的四倍。
这肯定是不合适的,好在webgl提供了一种用法可以重复利用缓冲区的顶点数据,drawElements。 接下来我们先看一下完成的程序和运行效果

效果

04_result.png

代码

<canvas id="cvs" style="width: 400px; height: 400px" height="400px" width="400px"></canvas>
<script src="./04customApi.js"></script>

<script>
  const VS = `
    attribute vec4 a_Position;
    attribute vec4 a_Color;
    uniform mat4 v_ModelMatrix;
    varying vec4 v_Color;
    void main() {
      gl_Position = v_ModelMatrix * a_Position;
      v_Color = a_Color;
    }
  `;

  const FS = `
    #ifdef GL_ES
    precision mediump float;
    #endif
    varying vec4 v_Color;
    void main() {
      gl_FragColor = v_Color;
    }
  `;
</script>

<script>
  const cvs = document.getElementById('cvs')
  const gl = cvs.getContext('webgl')
  initShader(gl, VS, FS)
  const n = bindBuffer(gl, [
    {
      name: 'a_Position',
      size: 3,
      type: gl.FLOAT,
      stride: 6,
      offset: 0
    },
    {
      name: 'a_Color',
      size: 3,
      type: gl.FLOAT,
      stride: 6,
      offset: 3
    },
  ], new Float32Array([
    0.5, 0.5, 0.5,      1.0, 0.0, 0.0, // 红色
    -0.5, 0.5, 0.5,     0.0, 1.0, 0.0, // 绿色
    -0.5, -0.5, 0.5,    0.0, 0.0, 1.0, // 蓝色
    0.5, -0.5, 0.5,     1.0, 1.0, 0.0, // 黄色
    0.5, -0.5, -0.5,    0.0, 1.0, 1.0, // 淡蓝
    0.5, 0.5, -0.5,     1.0, 0.0, 1.0, // 紫色
    -0.5, 0.5, -0.5,    1.0, 0.0, 1.0, // 紫色
    -0.5, -0.5, -0.5,   1.0, 0.0, 1.0, // 紫色
  ]))

  const buffer = gl.createBuffer()

  const vertices = 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
  ])
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  gl.clearColor(1.0, 1.0, 1.0, 1.0)
  gl.clear(gl.COLOR_BUFFER_BIT)

  gl.uniformMatrix4fv(gl.getUniformLocation(gl.program, 'v_ModelMatrix'), false, new Float32Array([
    0.7071067690849304,  0.5,  -0.5,  0,
    -0.5,  0.8535534143447876,  0.1464466154575348,  0,
    0.5,  0.1464466154575348,  0.8535534143447876,  0,
    0,  0,  0,  1
  ]));

  gl.drawElements(gl.TRIANGLES, vertices.length, gl.UNSIGNED_BYTE, 0)
</script>

解释

我们先解释一下v_ModelMatrix,其实绘制正方体是不需要这个矩阵的,他的作用是可以让物体形变
简单的来说我们要实现一个物体旋转或者缩放,是需要重新计算物体的每个顶点的位置的,但是直接计算是困难的,但是矩阵的几何意义就拥有让每个点对应变化的效果。

这也是在WebglAPI基础之后的系列会设计到的矩阵的内容。

本章中引入只是为了让大家更好的观察正方面,如果没有这个矩阵,我们只能看到正方体的正面。我们直接忽略这个矩阵,那么着色器程序其实和上章的是一样的。

本章中矩阵的作用

我们先简单讲一下本章中这个矩阵的含义,然后大家就可以把这两段代码忽略

gl_Position = v_ModelMatrix * a_Position;

首先是顶点着色器中,用矩阵乘输入的点坐标,就相当于对每个输入的点都做了相应的几何变换,最后就映射到了对整个物体做了相应的几何变换

gl.uniformMatrix4fv(gl.getUniformLocation(gl.program, 'v_ModelMatrix'), false, new Float32Array([
  0.7071067690849304,  0.5,  -0.5,  0,
  -0.5,  0.8535534143447876,  0.1464466154575348,  0,
  0.5,  0.1464466154575348,  0.8535534143447876,  0,
  0,  0,  0,  1
]));

然后是js代码中的这段代码,意义是给着色器中的矩阵传值,传入的值是一个四阶矩阵(4X4),几何意义是绕y轴逆时针旋转45°,同时绕z轴逆时针旋转45°

ELEMENT ARRAY BUFFER

忽略上述两段代码,实际我们本章就增加了elementBuffer这个概念。

const buffer = gl.createBuffer()
const vertices = 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
])
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

这段代码很熟悉,因为第二章的时候我们第一次接触到buffer的时候写过类似的
只不过这边的区别是bindBuffer和bufferData中的第一个参数由gl.ARRAY_BUFFER变成了gl.ELEMENT_ARRAY_BUFFER。

gl.ARRAY_BUFFER我们之前讲过是把每个顶点的信息放在了缓冲区,然后可以通过vertexAttribPointer去给每个顶点附上信息。

而gl.ELEMENT_ARRAY_BUFFER记录的是每个顶点的索引信息,索引的数据就是gl.ARRAY_BUFFER中的数据。

用图直观感受一下

04_process.png

这样我们就实现了用8个缓冲数据,绘制了36个顶点,实现了一个正方体。

颜色问题

但是我们目前的正方体的显示效果是不好的,因为有很多顶点共用,顶点的颜色也不相同,导致每个面都是渐变色的。

那么如何实现六个面,每个面的颜色都是纯色,且和其他面颜色不相同。

04_result_color.png

要是一个三角形纯色,那么三个顶点的颜色必须是相同的

我们观察vertices数组可以发现,每个面其实只跟四个顶点有关,如front面,它的组成由,012,023两个三角组成,而这两个三角其实只包含四个顶点,于是我们只要保证这四个点的颜色是相同的。

但是right面也用到了0和3两个顶点,如果0123的颜色都是红色,front面就会是红色,但是right面想要是绿色的话,就实现不了了,03已经是红色的了。

所以right面的顶点不能共用front面的顶点,最终我们会发现,要使得每个面的颜色是纯色且各不相同就得保证各个面之间都不会存在共用顶点。那么每个面需要四个独立的顶点,总共就需要24个顶点

顶点数据修改成下方

0.5, 0.5, 0.5,       1, 0, 0,
-0.5, 0.5, 0.5,      1, 0, 0, 
-0.5, -0.5, 0.5,     1, 0, 0,
0.5, -0.5, 0.5,      1, 0, 0, // v0-v1-v2-v3 front
0.5, 0.5, 0.5,       0, 1, 0,
0.5, -0.5, 0.5,      0, 1, 0,
0.5, -0.5, -0.5,     0, 1, 0,
0.5, 0.5, -0.5,      0, 1, 0, // v0-v3-v4-v5 right
0.5, 0.5, 0.5,       1, 0, 1,
0.5, 0.5, -0.5,      1, 0, 1,
-0.5, 0.5, -0.5,     1, 0, 1,
-0.5, 0.5, 0.5,      1, 0, 1, // v0-v5-v6-v1 up
-0.5, 0.5, 0.5,      1, 1, 0,
-0.5, 0.5, -0.5,     1, 1, 0,
-0.5, -0.5, -0.5,    1, 1, 0,
-0.5, -0.5, 0.5,     1, 1, 0, // v1-v6-v7-v2 left
-0.5, -0.5, -0.5,    0, 0, 1,
0.5, -0.5, -0.5,     0, 0, 1,
0.5, -0.5, 0.5,      0, 0, 1,
-0.5, -0.5, 0.5,     0, 0, 1, // v7-v4-v3-v2 down
0.5, -0.5, -0.5,     0, 1, 1,
-0.5, -0.5, -0.5,    0, 1, 1,
-0.5, 0.5, -0.5,     0, 1, 1,
0.5, 0.5, -0.5,      0, 1, 1, // v4-v7-v6-v5 back

索引数据需改成如下

0, 1, 2, 0, 2, 3,    // front
4, 5, 6, 4, 6, 7,    // right
8, 9, 10, 8, 10, 11,    // up
12, 13, 14, 12, 14, 15,    // left
16, 17, 18, 16, 18, 19,    // down
20, 21, 22, 20, 22, 23     // back

程序运行结果如下

04_result_six_error.png

发现面的绘制顺序不对,导致显示有问题,而在刚才渐变色很乱的时候能难看出来。

那这样难道我们一定需要按照真实的观察物体的前后关系来绘制面吗。显然这样很费力,webgl提供了我们一个深度绘制的开关,我们只需要添加和修改一段代码即可解决绘制顺序不对的问题 开启gl.DEPTH_TEST功能,并在清理画布的时候加上gl.DEPTH_BUFFER_BIT

gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT )

04_result_six.png

总结

本章通过绘制一个正方体,讲述了elementsBuffer的作用,它能让我们复用相同的点数据,来减少缓冲区的存储压力。

相关代码gitee