全部章节
06.frameBuffer-将webgl绘制的正方体作为材质
绘制多点
上章我们为了更好的去理解为什么要用一个program去储存一对着色器程序,使用了两对着色器程序去实现了绘制两个点的功能。 但实际上这个功能在一个着色器程序上就能够完成。
效果
代码
<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
- attribute 顶点着色器中的全局变量,储存逐顶点信息
- 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中的数据分割成如下图所示
通过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上查看学习。