这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战
本文标题:WebGL第三十课:多个绘制对象的参数调节-颜色
友情提示
这篇文章是WebGL课程专栏的第30篇,强烈建议从前面开始看起。因为花了大量的工夫来讲解向量的概念和矩阵运算。这些基础知识会影响你的思维。
本课代码直接跳转获取:三十课代码
引子
在上篇文章中,我们可以看到,如果想更改多个绘制对象中的某一个对象的参数时,我们直接重新申请的gl中的buffer,然后重新把所有的顶点数据传入到buffer中,进而绘制。
这种办法主要是针对顶点数据不得不改的时候,比如说,你本来画的是一个格子形状的东西,但是后面需要变成球形的东西。这种时候直接替换新的顶点数据是可以的。
但是,有的时候,我们并不是想要把格子-->球形,仅仅是想换一下格子的颜色。就为了这个,整个替换顶点数据当然不好。
那怎么办呢?使用 shader 中的 uniform 变量,即可。
program uniform buffer 厘清关系
在这里,回顾一下,program 和 uniform 和 buffer 的关系。
当我们绘制三角形的时候,简单这么说:当你调用绘制api(例如gl.drawArrays)之前,必须要选定一个 program,然后调用绘制api,这个时候,program 就开始跑。
怎么跑呢?
答:迭代当前buffer中的数据,例如坐标啦,颜色啦,啥的,然后在屏幕上绘制一个点,每三个点,就用插值法,绘制中间的区域,也就是绘制一个三角形,这样就将一个buffer中的数据都绘制完成。在这个过程之前,你可以设置一下uniform变量,那么在这个绘制过程中,这个变量就会保持不变。当某一个buffer的数据绘制结束之后,你可以随时更改uniform变量,然后接着绘制。
你发现了吗?也就是说不同buffer的数据,可以共用一个 program,但是在切换buffer的时候,我们可以对uniform变量进行修改,从而得到我们的目的,那就是,不同绘制对象的某些参数,可以自由独立的控制,例如可以控制颜色。
你又发现了吗?这样写,不用重新生成buffer数据。真是个好办法!
利用 uniform 来设置颜色
首先第一步要在 fragment_shader里加一个uniform变量,按照习惯,我们命名为:u_color。
由于颜色是RGB三个数组成的,这个 u_color应该是一个vec3类型:
uniform vec3 u_color;
然后需要在 js 代码里,获取这个uniform变量引用:
u_color = gl.getUniformLocation(program, "u_color");
这块可以留意一下,获取一个变量的引用,是需要传program的,这也恰恰说明了,uniform是属于program的。
如何给这个变量传值呢?如下:
gl.uniform3f(u_color, this.color.R, this.color.G, this.color.B);
这里注意下,由于 u_color 变量是 vec3 类型,所以传值的时候,需要使用 uniform3f 这个api,后面需要传三个参数,分别代表 vec3 的第一个元素,第二个元素,第三个元素。
这里三个值分别代表 颜色 RGB。
好了,我们要想这个 u_color 生效,还需要真正的设置一下颜色:
gl_FragColor = vec4(u_color, 1.0);
看一下上面的代码,我们完全用这个uniform变量来设置颜色,而忽略buffer中的 color 部分了。所以,我们在构造buffer的时候,不用把颜色传递进去了。
这里说一点,buffer中到底应不应该带颜色,取决于你的需求,大部分时候,buffer中是应该带上颜色的。
我们可以使用uniform这种动态的变量,和buffer中的静态颜色,进行叠加计算,从而得出好玩的效果。
这点后面再说。
绘制多个格子
在上篇文章,我们定义了一个 GridObject 的class,来描述一个格子的行为。
其中 render 方法,根据本文提出的新做法,需要修改如下:
render(gl, program) {
gl.bindBuffer(gl.ARRAY_BUFFER, this.glbuffer);
if (this.modelUpdated) {
this.modelUpdated = false;
if (this.a_PointVertex == null) {
this.a_PointVertex = gl.getAttribLocation(program, 'a_PointVertex');
}
}
//////////////////////////
gl.vertexAttribPointer(this.a_PointVertex, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(this.a_PointVertex);
gl.uniform3f(u_color, this.color.R, this.color.G, this.color.B); // 设置颜色uniform
gl.drawArrays(gl.TRIANGLES, 0, this.pointCount); // 绘制当前格子的buffer
}
我们可以看到,主要就是在绘制之前,要修改一下 u_color。
如果有两个 GridObject 对象,前后分别调用 render 的话,那么设置uniform和绘制的顺序如下:
- 第一个格子 设置 program 中的 u_color
- 第一个格子 进行绘制 第一个格子 自己的buffer
- 第二个格子 设置 program 中的 u_color
- 第二个格子 进行绘制 第二个格子 自己的buffer
如果有更多的格子,只要按照这个顺序,去写逻辑,那么每个格子之间是不会乱的,而自始至终,u_color 就是一个变量,被所有的格子所共用。
还是用滑竿来做个小实验
<p>
<b>第一个格子的颜色R:</b>
<input id="gridcolorR" type="range" min="0" max="1" value="0" step="0.01" oninput="gl_draw()" />
<b id="gridcolorRvalue">0</b>
</p>
<p>
<b>第一个格子的颜色G:</b>
<input id="gridcolorG" type="range" min="0" max="1" value="0" step="0.01" oninput="gl_draw()" />
<b id="gridcolorGvalue">0</b>
</p>
<p>
<b>第一个格子的颜色B:</b>
<input id="gridcolorB" type="range" min="0" max="1" value="0" step="0.01" oninput="gl_draw()" />
<b id="gridcolorBvalue">0</b>
</p>
在 gl_draw 这个回调函数里,设置一下第一个格子的颜色, 然后绘制两个格子:
gridOne.color.R = gridColorRDom.value;
gridOne.color.G = gridColorGDom.value;
gridOne.color.B = gridColorBDom.value;
gridOne.render(gl, program);
gridTwo.render(gl, program);
效果如下:
正文结束,下面是答疑
小丫丫说:除了颜色之外,还有什么可以单独调节?
- 位置,旋转,拉伸,等等。