本文用例来自 mdn webGL 教程的 sample5 和 sample6
数据怎么从 JavaScript 送到 GLSL,是一个十分简单,但是初见又有点绕的问题。解析这个问题需要把传入的数据分为 attribute 和 uniform 两个类型。
先非常简单地提一下两种类型的区别:
- attribute 是只能 vertex shader 接收的数据,运行每个点都会传入不同的数据
- uniform 是从外部传入的固定数据,每个点都是一样的
另外还有 varying,可以把数据从 vertex shader 输出到 fragment shader,具体区别和举例再开别的坑,总之本文先聚焦到如何从 JavaScript 传值到 GLSL,下面先讲 attribute 的传入方式。
传入 attribute
bindBuffer
会将一个 buffer 绑定到一个 target,也就是 gl.ARRAY_BUFFER
,它专门用于储存 vertex shader 的 attribute。
为了更容易理解,可以把它当成一个激活的标志,同一个 target 仅能有一个在激活状态。bindBuffer
就意味着当前的 ARRAY_BUFFER
就是刚 bind 的那个 buffer,然后就可以使用 bufferData
往当前激活的 buffer 写入数据(复制到显存)。
至此,成功将 JavaScript 的数据写入到显存,但仍有问题,shader 里的变量如何关联到这段数据?
通过 getAttribLocation
可以获得一个 shader 程序的某个 attribute 的地址,当然 uniform 同理。
接着就跟上面思路差不多,通过 bindBuffer
把 buffers.position
设为当前激活 buffer。然后使用 vertexAttribPointer
把当前激活的 gl.ARRAY_BUFFER
塞到上一步获取的 attribute 地址。最后的 enableVertexAttribArray
用于启用某个地址的 attribute,因为默认所有 attribute 都是未启用的,都需要通过这个接口启用,另外启用不必一定放在最后,拿到地址就能直接启用。
最后简单总结一下 attribute 如何从 JavaScript 传到 vertex shader:
createBuffer
创建 bufferbindBuffer
把 buffer 地址设置为当前激活的ARRAY_BUFFER
bufferData
向当前激活 buffer 写入数据(通过 JavaScript ArrayBuffer)getAttribLocation
获取某个需要注入数据的 attribute 的地址- 因为运行过程中容易激活其他 buffer,所以在赋值之前重新激活特定 buffer
vertexAttribPointer
把当前激活的ARRAY_BUFFER
注入某个 attribute 地址enableVertexAttribArray
启用 attribute
其实最绕的就是 bindBuffer
,只要明白这是一个“激活”的动作,那么一切就自然简单明了。
bindBuffer
的 target 还可以传入 ELEMENT_ARRAY_BUFFER
,设置当前激活的 indexBuffer,这种数据用于规定点如何组成面。
传入 uniform
至于传入 uniform,就简单得多了,在这个例子中,关键就一个 uniformMatrix[234]fv
,向 vertex shader 传入 mat4 可以这么写:
也就是传入 Float32Array
到 getUniformLocation
得到的地址即可。
类似的方法还有 uniform[1234][fi][v]
这么一堆,都用于向 shader 传入其他类型的 uniform 数据。
特殊的 sampler
最后是初见最懵逼的 sampler2D 传值,仅看代码是一头雾水:
为什么又 active 又 bind 的呢?我们首先要理解这个过程中涉及到两个名词:
- texture unit,用于处理 texture 映射,GPU 至少要有 8 个 texture unit,当然正常会有更多,可以通过
gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)
获取具体数量 - texture object,储存 texture 数据的对象,跟 buffer object 类似,但更为复杂
下面再分解动作解析上面的代码:
这三步和 attribute 类似,创建 texture object,激活,然后塞数据。
activeTexture
指的是激活 texture unit,设为 gl.TEXTURE0
当前激活的 texture unit 就是 0。然后再 bindTexture
可以让当前 texture object 绑定到当前 texture unit(这里我无法理解,为什么激活 object 会附带绑定功能,求懂的大佬们补充)。
虽然一直在说 sampler2D,但其实变量类型归根到底还是 uniform,所以最后还是通过 uniform1i
传入到 shader。特殊的是传给他的值是一个 texture unit 地址,官方规范说明,加载 sampler 仅能用 glUniform1i 和 glUniform1iv。