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

265 阅读7分钟

全部章节

01.shader-最简单的webgl程序

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

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

04.drawElements-绘制一个正方体

05.texture-使用材质贴图

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

绘制多点

上章我们为了更好的去理解为什么要用一个program去储存一对着色器程序,使用了两对着色器程序去实现了绘制两个点的功能。 但实际上这个功能在一个着色器程序上就能够完成。

效果

02_result.png

代码

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

<script>
  const VS = `
    attribute vec4 a_Position;
    attribute vec4 a_Color;
    varying vec4 v_Color;
    void main() {
      gl_Position = a_Position;
      gl_PointSize = 10.0;
      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.0, 0.0,   1.0, 0.0, 0.0,
    0.0, 0.0, 0.0,    0.0, 1.0, 0.0,
    0.5, 0.0, 0.0,    0.0, 0.0, 1.0
  ]))
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  gl.clear(gl.COLOR_BUFFER_BIT)
  gl.drawArrays(gl.POINT, 0, n)

  function bindBuffer(gl, attributes, vertices) {
    const buffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer)

    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

    const fSize = vertices.BYTES_PER_ELEMENT

    attributes.forEach(attribute => {
      gl.vertexAttribPointer(gl.getAttribLocation(gl.program, attribute.name), attribute.size, attribute.type, false, attribute.stride * fSize, attribute.offset * fSize)
      gl.enableVertexAttribArray(gl.getAttribLocation(gl.program, attribute.name))
    })
    return vertices.length / attributes[0].stride
  }

</script>

解释

上章已经讲过initShader这个函数了,所以直接放到了customApi里,介绍可见行数

着色器代码

本例着色器代码相对于上一章中的,缺少了两个实际矢量的赋值,即没有给gl_Position和gl_FragColor直接赋值vec4。
而是定义了几个变量,然后将变量赋值给了gl_Position和gl_FragColor。

顶点着色器

attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;

顶点着色器中定义了三个变量,我们先解释一下attribute和varying这两个限定词,这样也便于理解为什么需要定义一个a_Color和一个v_Color

  1. attribute 顶点着色器中的全局变量,储存逐顶点信息
  2. varying 顶点着色器向片元着色器传递数据的全局变量

attribute是用于存在每个顶点的数据信息,比如说a_Position用于存放每个顶点的位置,a_Color存放每个顶点的颜色。
(PS: 后续章节中会涉及到uniform,在实际案例中,两者对比更方便理解两个限定词的区别,此处简单说明一下)

varying是用于把变量透传给片元着色器的。

在本例中,我们需要实现多个不同颜色的点,那么我们就需要告诉着色器,点1的位置和颜色,点2的位置和颜色,着色器对应使用a_Position和a_Color暂存变量,顶点着色器中需要使用到顶点的位置信息,所以把a_Position赋值给gl_Position。
而顶点着色器中是不处理颜色信息的,需要在片元着色器中处理,所以需要利用v_Color去透传a_Color的色值给片元着色器,并赋值给gl_FragColor

片元着色器

#ifdef GL_ES
precision mediump float;
#endif
varying vec4 v_Color;

上面说得到了varying限定词的意义,写法就是在片元着色器和顶点着色器定义相同名的变量,就能实现数据的传递。

if相关的三行的意义为,如果宏存在GL_ES,那么就全局定义为中精度数值。这里涉及到了很多定义,比如预处理指令,宏,精度等。本文不做解释,有兴趣的可以去搜一下。需要这三行的原因主要是,片元着色器中没有默认的精度,不设置着色器会报错。

js代码

相对于上章,本例中多了一个bindBuffer的方法,从入参上可以理解,这是去绑定着色器中a_Position和a_Color值的,接下来我们具体解释一下

bindBuffer

buffer

const buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

前三行,都是围绕了buffer这个东西在进行,创建,绑定,传入数据。

首先,创建buffer,buffer译为缓冲区,简单理解为存放数据的地方。
然后,绑定buffer,可以和上章中讲到的program一样理解,一段webgl代码中我可以创建很多个buffer的,什么时候用哪个buffer就需要用bindBuffer来告知webgl上下文。
最后,切换到了哪个缓冲区后,就需要往这个缓冲区里塞值,vertices就是传入的值
可以从上边参数中看到,vertices就是一个三十二位的浮点数组,gl.STATIC_DRAW,即第三个参数代表了数据的属性,大致是是否经常被使用或修改,具体的可选值可以去MDN上看。

vertexAttribPointer 和 enableVertexAttribArray

vertexAttribPointer代表了,从当前绑定的缓冲区中读数据。依次解释一下该函数的几个参数,从而理解读取数据的规则是什么。
第一个参数代表了变量的索引,getAttribLocation的作用是,查找输入着色器程序中和输入参数同名的attribute变量,并返回索引值,
第二个参数是,单次获取的值的个数,例如我们一个点需要xyz三个值,那么size就为3
第三个参数是,数值的类型,一般为FLOAT,其他的可选值可以去MDN上看
第四个参数是,是否需要归一化,及归一化的区间定义,一般为false,需要的情况会在相应案例中解释
第五个参数是,一段值的长度,比如说本例中,该值为6,就是说以六个数作为一段,那么第二个点的x值就是从第七个数字取,y值从第八个数字取,以此类推。
第六个参数是,一段中的偏移位,比如本例中的color的值,offset为3,那么color的r值是每段的第四个值

本例中,会把vertices中的数据分割成如下图所示 02_pointer.png

通过stride和offset两个参数的说明可以发现,如果我们有数据重复的情况,其实我们是可以共用数据的。

打个比方,我们要实现两个点,第一个点,位置在1,0,0颜色是红色,第二个点,位置在0,1,0颜色是绿色 那么其实我们给到position和color的值都是1,0,0和0,1,0。此时我们可以仅传入[1,0,0, 0,1,0]这个数字,然后对position和color的size,stride以及offset都设置成3, 3, 0。就实现了数据共用。

enableVertexAttribArray激活变量获取到数据,对应可以用disableVertexAttribArray去关闭,大致意思就是只有激活了,用vertexAttribPointer传值才是有效的,不过无论是在vertexAttribPointer之前还是之后执行enable都是可以的。

drawArrays

最后我们发现,drawArrays的参数也变了,主要是第三个参数,由1变成了n,而n的计算是在addData中返回的,计算的公式是浮点数组的长度/每段数据的长度,那么获取到的就是总共的点数。

本章解释一下drawArrays的参数,
第一个参数,绘制的形状,本例中是点,还能绘制线和三角形,具体会在后续章节涉及。
第二个参数,第一个绘制的点从哪开始。
第三个参数,总共绘制多少个点。

我们可以把尝试本例中的第二个参数改成1,那么程序就会报错。
因为你告诉了webgl要从第二个点开始绘制,总共绘制3个点,但是你却只给webgl了三个点的数据,所以就报错了
但是如果我们把n改成2,那么就会发现,红点不见了,因为红点在数据中是第一个点,我们要求了webgl从第二个点开始绘制,所以跳过了红点。

总结

本章中实现了绘制多点的着色器程序,主要想让大家了解buffer的作用,以及给webgl传值的一种方式,顺带了解了一下drawArrays方法的第二个和第三个参数的含义。
通过program和buffer的例子,我们也发现,webglApi需要不停的去切换绑定program或者buffer然后去对这个program或者buffer执行完所有操作,再去切换下一个。可以去该网站看一下webgl的执行流程!
webgl绘制状态流程

顺带一提,本章中只介绍了一种给着色器程序传值的方式,还有vertexAttrib*,以及传入uniform变量的api,会在后续章节中提及,也可以自行去MDN上查看学习。

相关代码gitee