这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战
本文标题:WebGL第二十九课:针对多个绘制对象的代码重构
友情提示
这篇文章是WebGL课程专栏的第29篇,强烈建议从前面开始看起。因为花了大量的工夫来讲解向量的概念和矩阵运算。这些基础知识会影响你的思维。
本课代码直接跳转获取:二十九课代码
引子
前面的文章中,我们大多数都是在绘制一个对象,也就是说一个画面里,只调用了一次
drawArrays这个api来绘制。
那么如果,我们有多个独立的东西需要绘制的话,我们肯定就需要多次单独调用drawArrays这个api。
本次文章的目的就是用一个尽量舒服的代码结构,来完成多个绘制目标的初始化,进而渲染。
单个绘制目标
我们这次课用来做例子的单个目标,选定为一个小格子(矩形)。也就是说,我们要画出多个小格子,并且可以自由控制其中的任何一个格子,例如,大小,位置等等,而不影响其他格子的状态。
那么单个格子的模型,根据我们的管理,使用6个点,两个三角形来表示。
同时这个格子,具有一些可以调节的参数:
- 宽 width
- 高 height
- x坐标
- y坐标
- 颜色
上面五个参数都是一个float类型,你可能会问颜色不是三个float吗?
好吧,为了简化操作,我们RGB三个数值选取一样的,所以就用一个float完事。。。。。。
下面的代码,使用了 js 里的 class 语法,这样写起来清晰一点:
class GridObject {
// 宽 高 x坐标 y坐标
// 左下角为基准点
constructor(width, height, posx, posy, color) {
this.width = width;
this.height = height;
this.posx = posx;
this.posy = posy;
this.color = color;
}
}
那么这些数据最终是要传送给gl的,我们肯定会在gl里申请一个buffer,来存储这部分数据,问题就来了:
单个格子是否使用单独的buffer?
这个答案不唯一。我们这次就用单个的buffer来存储单个的格子,而不是所有格子都用一个buffer。
所以在constructor里,添加相应的变量:
constructor(......) {
......
this.glbuffer = null; // 存储数据的buffer
this.a_PointVertex = null; // vertex_shader中 坐标 数据的引用
this.a_Color = null; // vertex_shader中 颜色 数据的引用
}
那么数据既然已经准备好了,我们就想办法,让他变成gl所需要的格式,这里不再反复提及,平坦化数据如下:
this.data = [
// 第一个三角形
this.posx, this.posy, this.color, // 左下角点
this.posx + this.width, this.posy, this.color, // 右下角点
this.posx + this.width, this.posy + this.height, this.color, // 右上角点
// 第二个三角形
this.posx + this.width, this.posy + this.height, 1 - this.color, // 右上角点
this.posx, this.posy + this.height, 1 - this.color, // 左上角点
this.posx, this.posy, 1 - this.color, // 左下角点
];
this.dataArr = new Float32Array(this.data);
this.pointCount = 6; // 一个格子固定六个点 两个三角形
上面颜色部分值得一提的是,两个三角形的颜色互补,这是为了更好的观察他们。
生成数据既然好了,那么我们就将数据传入到gl中:
this.glbuffer = gl.createBuffer(); // 创建新的buffer
gl.bindBuffer(gl.ARRAY_BUFFER, this.glbuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.dataArr, gl.STATIC_DRAW);
以上是数据生成和传入部分,那么渲染部分怎么来搞呢。
对于单个格子来说,只需要管理自己的绘制逻辑即可
- 第一点:将当前gl中的上下文切换到当前对象相应的变量。
- 第二点:vertex_shader 中attribute的设置。
- 第三点:利用
drawArraysapi,进行绘制。
第一点其实就一句话:
gl.bindBuffer(gl.ARRAY_BUFFER, this.glbuffer); // 告诉下面的代码,我现在操作或者引用的buffer,是this.glbuffer,不是别的格子的buffer
第二点, vertex_shader 里,a_PointVertex用来接收数据元的前两个数据, a_Color 用来接收数据元的第三个数据:
this.a_PointVertex = gl.getAttribLocation(program, 'a_PointVertex');
gl.vertexAttribPointer(this.a_PointVertex, 2, gl.FLOAT, false, 12, 0);
gl.enableVertexAttribArray(this.a_PointVertex);
this.a_Color = gl.getAttribLocation(program, 'a_Color');
gl.vertexAttribPointer(this.a_Color, 1, gl.FLOAT, false, 12, 8);
gl.enableVertexAttribArray(this.a_Color);
最后就是绘制:
gl.drawArrays(gl.TRIANGLES, 0, this.pointCount);
注意上面,我们绘制的点的个数,就是 this.pointCount , 就是6个。
到了这里,我们已经把 GridObject 的大概职能搞定。
多个绘制目标
我们可以在程序初始化的时候,实例化多个 GridObject 的对象,然后分别对这些对象进行设置参数,生成数据,渲染即可。
为了观察方便,我们不妨设置两个下面的对象:
gridOne = new GridObject(0.5, 0.5, 0, 0, 0.3);
gridOne.genData(gl);
gridTwo = new GridObject(0.5, 0.5, -0.5, -0.5, 0.3);
gridTwo.genData(gl);
上面第一个格子,是(0,0)处;
第二个格子,是(-0.5, -0.5)处;
绘制之后的效果如下:
这和我们的预期是一致的。
利用滑竿来修改参数
我们绘制多个单独的对象的目的就是为了:可以单独的修改其中一个,而不影响另一个。
我们设置一个滑竿,来修改第一个格子的颜色。
html部分:
<p>
<b>第一个格子的颜色:</b>
<input id="gridcolor" type="range" min="0" max="1" value="0" step="0.05" oninput="gl_draw()" />
<b id="gridcolorvalue">0</b>
</p>
js 部分:
gridColorDom = document.getElementById("gridcolor"); // 获取dom
gridOne.color = gridColorDom.value; // 将滑竿的值设置到第一个格子中的color中去
问题就来了,重新设置颜色之后,需要清除当前的buffer,然后根据现有的各个参数,重新攒一个buffer出来,
所以GridObject应该包含这么一个方法:
genData(gl) {
this.data = [
// 第一个三角形
this.posx, this.posy, this.color, // 左下角点
this.posx + this.width, this.posy, this.color, // 右下角点
this.posx + this.width, this.posy + this.height, this.color, // 右上角点
// 第二个三角形
this.posx + this.width, this.posy + this.height, 1 - this.color, // 右上角点
this.posx, this.posy + this.height, 1 - this.color, // 左上角点
this.posx, this.posy, 1 - this.color, // 左下角点
];
this.dataArr = new Float32Array(this.data);
this.pointCount = 6; // 一个格子固定六个点 两个三角形
if (this.glbuffer != null) { // 先删掉原来的buffer
gl.deleteBuffer(this.glbuffer);
this.a_PointVertex = null;
this.a_Color = null;
}
this.glbuffer = gl.createBuffer(); // 创建新的buffer
gl.bindBuffer(gl.ARRAY_BUFFER, this.glbuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.dataArr, gl.STATIC_DRAW);
this.modelUpdated = true;
}
从上述代码可以看出,每次GridObject对象有参数修改之后,都应该调用这个方法,重新生成buffer。
所以在每次滑竿修改之后,应该这样:
gridOne.color = gridColorDom.value; // 设置颜色值
gridOne.genData(gl); // 重新生成buffer
放出效果如下:
如图所见,我们的滑竿控制了第一个格子的颜色,第二个格子岿然不动。成功~
正文结束,下面是答疑
小嘎嘎说:本文的难点在哪,为什么要搞一下这个?
- 难点就在于,设计这么一个思路,然后就是上下文的切换如果不到位的话,你是画不出来或者得不到上述结果的。
小嘎嘎说:什么上下文切换不到位?
- buffer,attribute 等等的切换和设置。