WebGL通过VAO和VBO渲染基本流程

基本流程

  • 创建缓冲区对象 gl.createBuffer()
  • 绑定缓冲区对象 gl.bindBuffer()
  • 将数据写入缓冲区对象 gl.bufferData()
  • 将缓冲区对象分配给一个attribute变量 gl.vertexAttribPointer()
  • 开启attribute变量 gl.enableVertexAttribArray()
  • 绘制 gl.drawArray() 或 gl.drawElements()

    下面分开详解这些API。

  • createBuffer():WebGLBuffer

    本函数对应OpenGL中的glGenBuffers(), 用于创建一个指代缓冲区对象的名字,用WebGLBuffer对象作为代理。这个函数并不会真正在GPU显存上开辟空间,只是返回了一个类似于指针的对象。

  • bindBuffer(target: uint, buffer: WebGLBuffer):void

    • target: 绑定的目标(OpenGL内部会采取一种最优的内存管理策略,根据缓存对象完成绑定的情况来分配它对应的内存,这些可用的缓存结合点称为目标),指定buffer是存储顶点属性数据还是数据的索引。gl.ARRAY_BUFFER, gl.ELEMENT_ARRAY_BUFFER
    • buffer: createBuffer创建的对象, 如果buffer为null,则禁用对target的绑定

    本函数将createBuffer创建的buffer指代的缓冲区绑定为当前缓冲区(current buffer)。WebGL的绘制是单通道的,一次只能绘制一组数据,每次绘制的就是当前缓冲区buffer中的数据。后续所有将数据放入缓冲区的函数操作的都是这个当前缓冲区。
    bindBuffer主要完成以下几点任务:1.如果buffer是第一次被绑定且它非空,则WebGL将创建一个与该buffer对应的缓冲区对象VBO,且置其为当前缓冲区对象;2.如果绑定一个已经创建的缓冲区对象,则将本对象置为当前缓冲区对象;3.如果buffer为null,则不再对当前target应用任何缓冲对象。

  • bufferData(target: uint, data: TypedArray, usage: uint)

    • target 同上,需要和bindBuffer使用的target相同
    • data 类型化数组,存储顶点数据
    • usage 设置分配数据之后的读取和写入方式,用于指导WebGL如何使用这些存储的数据。本参数帮助WebGL做优化,所以,即便你传入了错误的值,也不会终止程序,但可能导致性能下降。这个参数对能否达到最优性能至关重要。枚举值有:

      gl.STATIC_DRAW 数据存储内容只写入一次,然后多次绘制
      STATIC_开头,就是说数据的变动非常有限,或者根本就没有——因为它本质上是静态数据。这类标识符显然需要用于那些只修改过一次就不再变动的数据类型。OpenGl会在内部对数据重新处理,以保证它在内存中的布置更为合理,或者使用最优的数据格式。虽然这一步操作代价可能很大,但是只进行一次,整体还是理想的。

      gl.DYNAMIC_DRAW 数据存储内容会被反复写入和反复使用
      DYNAMIC_开头,说明数据变动非常频繁,而变动过程中对数据的使用也非常频繁。例如一个建模程序,它所使用的数据可能被用户所编辑,此时有必要使用这个标识符。这时,一个可能的情况是数据在多帧内被持续使用,然后被修改,然后再次被多帧使用,如此反复。

      gl.STREAM_DRAW 数据存储内容只写入一次,不会被频繁绘制
      与gl.DYNAMIC_DRAW相反,它的缓存修改是有规律的,并且每次修改数据后只会少量地加以使用。这种时候,OpenGL甚至可能不会讲数据拷贝到快速的图像内存中,而是直接在原地进行访问。这种情形通常发生在CPU端执行应用程序诸如物理仿真的操作时,此时每一帧都会给出一些新的数据集,供程序调取。

    本函数有两个任务:1.为顶点数据分配GPU存储空间(显存);2.将数据从前端的RAM拷贝至分配的存储空间中,这需要执行一次IO行为。

  • vertexAttribPointer(location: int, size: unit, type: unit, normalized: boolean, stride: int, offset: int)

    • location attribute变量在shader中的索引,通过gl.getAttribLocation()获得
    • size attribute变量占有缓冲区数组中每个顶点的元素个数(1-4),如果size比attribute变量需要的数据小,则自动补全为0
    • type 数据格式
    • normalized 是否将非浮点数的数据归一化为[0, 1]或[-1, 1]区间
    • stride 指定相邻两个顶点之间的字节数,以字节为单位
    • offset 指定缓冲区对象中的偏移量,以字节为单位

    本函数用于告诉WebGL如何使用顶点数组中的数据:每个attribute变量如何从类型化数组中分配数据。它可以将缓冲区中的数据一次性的分配给某个attribute变量。

    
    var verticesSizes = new Float32Array([
         // 顶点坐标和点的尺寸
         0.0, 0.5, 10.0, // 第一个点
         -0.5, -0.5, 20.0, // 第二个点
         0.5, -0.5, 30.0 // 第三个点
    ]);
    
    var FSIZE = verticesSizes.BYTES_PER_ELEMENT;
    ...
    gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0);
    gl.enableVertexAttribArray(a_Position);
    ...
    gl.vertexAttribPointer(a_Size, 1, gl.FLOAT, false, FSIZE * 3, 2);
    gl.enableVertexAttribArray(a_Size);
    
    
  • enableVertexAttribArray(location: int)

    • location 同上

    本函数告诉WebGL顶点缓存中记录了哪些顶点属性数据。在GPU从顶点缓存中读取数据之前,必须使用gl.enableVertexAttribArray()来开启attribute变量对应的顶点属性数组。如果不启用的话,WebGL会使用静态顶点属性。每个顶点的静态顶点属性都是一个默认值。每个顶点的静态顶点属性可以通过gl.vertexAttrib*()系列函数来设置。

  • gl.drawArray or gl.drawElements

    gl.drawArray(mode, first, count)

    • mode 表示构建图元的类型,必须是gl.TRIANGLES,gl.LINE_LOOP,gl.LINES, gl.POINTS等其中之一
    • first 数组中的起始位置,表示从本顶点开始绘制
    • count 绘制的顶点数

    使用数组元素建立连续的的几何图元序列,每个启用的数组中起始位置为first,结束位置为first + count - 1.

    gl.drawElements(mode, count, type, offset)

    • mode 同drawArray
    • count
    • type 索引数组中元素的类型,必须是gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT, gl.UNSIGNED_INT其中之一
    • offset

    使用count个元素来定义一系列几何图元,而元素的索引值保存在一个绑定到gl.ELEMENT_ARRAY_BUFFER的缓存中(元素数组缓存)。offset定义了索引数组中的绘制起点,单位是字节。