WebGL 踩坑系列-2
需求:绘制斑点在球面上走过的路径
思路:要绘制斑点在球面上走过的路径,首先要记录上一时刻和当前时刻该斑点所在球面的位置,并且实时更新当前时刻的斑点位置和上一时刻的斑点位置。
为了方便,上一时刻斑点所在位置记为 last_point,当前时刻位置记为 cur_point,统一用球坐标系进行计算。
1 last_point = [ltheta, lbeta, ldis];
2 cur_point = [theta, beta, dis];
那么接下来就要把这些记录下来的点(转换成直角坐标系后)和对应的索引存放到 buffer 缓冲区中,
1 gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
2 gl.bufferSubData(gl.ARRAY_BUFFER, offset, new Float32Array(vertices));
3
4 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
5 gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, offset, new Uint16ArrayBuffer(indices));
vertex_buffer 和 index_buffer 分别是创建的 gl 缓冲区。 offset 根据已经存入的 vertices 和 indices 来计算,
注意:这里的 offset 都应该是字节数。
比如当前已经向缓冲区中存入了 100 个顶点数据(也就是100 * 3 个浮点数),200 个索引数据
那么 vertices 的 offset 应该是 100 * 3 * 4 = 1200,浮点数的字节数是 4,所以这里需要乘上 4。
而 Uint 的字节数是 2,索引的 offset 应该是 200 * 2 = 400。
如果不想计算这里的偏移量,可以将每次记录下来的顶点 push 一个数组里,比如 vertices_array,然后直接将该数组的所有数据更新到 buffer 中:
1 vertices_array.push(vertices);
2 gl.bindBuffer(gl.ARRAY_BUFFER, vertices_buffer);
3 gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(vertices_array)
索引缓冲区也是一样的。
这种方式很明显的缺陷在于,vertices_array 会越来越大,而且每次都要把所有记录到的顶点全部传送到缓冲区中。
另外,在创建缓冲区的时候需要指定创建的缓冲区类型是 gl.DYNAMIC_DRAW。用于多次传输数据,多次绘制。
方法1
用 gl 的绘图 API 进行绘制即可,最简单的方法是:
1 gl.drawElements(gl.LINES, index.length, gl.UNSIGNED_SHORT, 0);
index.length 就是记录的索引长度。
方法2:
第一种方法绘制的是线条,我之前想到的复杂一点的方式是记录 cur_point 周围的顶点,
然后用这些顶点绘制黎曼矩形,也就是将路径绘制成面。
那么这样就需要计算 cur_point 的周围点。
最开始用的方法是简单的计算 cur_point 周围四个点的坐标,注意到 cur_point 用的是球坐标系,
若用 dt 表示 theta 角的变化,db 表示 beta 角的变化
1 var dt = 1 * Math.PI / 180;
2 var db = 1 * Math.PI / 180;
3 var p1 = [ cur_point[0] - dt, cur_point[1] - db, cur_point[2] ];
4 var p2 = [ cur_point[0] - dt, cur_point[1] + db, cur_point[2] ];
5 var p3 = [ cur_point[0] + dt, cur_point[1] + db, cur_point[2] ];
6 var p4 = [ cur_point[0] + dt, cur_point[1] - db, cur_point[2] ];
这样就得到了周围的四个点,再和 last_point 周围的四个点一起,绘制三角形,就可以形成路径了。
然而这种方法有个非常难受的地方,当 p1[0] = p2[0] 的时候,把球坐标转换成直角坐标的时候,就会发现,p1 和 p2 重合了,路径的宽度在不同的 theta 角处是不一样的。
在极点附近的路径很窄,而赤道处附近的路径很宽。
这样的路径绘制出来看上去就非常难受。
方法3:
后来我想了一种方法,因为路径的宽度会随着当前顶点的 theta 角变化,那么p1,p2,p3,p4 四个点用的 db 就不应该一样。
1 var db1 = 1 * Math.PI / 180 / Math.sin(cur_point[0] - dt);
2 var db2 = 1 * Math.PI / 180 / Math.sin(cur_point[0] + dt);
天知道当时怎么想到的把 db 除上一个 Sin 值,然后通过不断的修改系数,让记录下来绘制的路径看上去宽度大致是相同的。
方法4:
前面那个方法完全是凑出来的,虽然最后的结果还行。
后来我重新思考周边顶点的计算方式。找到与路径垂直,而且在球面上的点就可以实现功能。
令 S = P1 - P2 ,P1 和 P2 是 cur_point 和 last_point 的直角坐标,如果球心在原点的话,那么它们的直角坐标就是各自的方向向量了。
n = (P1 + P2) / 2,计算得到一个垂直于 S 的向量,计算 n x S 的结果 L,就可以得到与运动轨迹相垂直的向量了。将其归一化得到单位向量,再乘上比例系数,就可以用于计算了。
周边的四个顶点分别就可以用 P1 和 P2 加减 L 向量得到。之前说过,球心在原点,向量和坐标在值上是一样的。那么就得到周边四个顶点的直角坐标。
用 gl.TRIANGLES 绘制三角形,就可以把路径绘制成连续的平面。当然如果 L 向量的长度很小,最后得到的路径就很窄,反之路径就会很宽。
最终绘制出来的路径如果想要变成虚线的形式,就间隔几个时刻采点,然后绘制出来就像虚线的样子了。
或许也可以设置 gl.vertexAttribPointer(attributes_vertex_position, 3, gl.FLOAT, false, 0, 0); 这个函数的 stride 参数,间隔若干个点进行绘制,应该也可以实现成虚线的形式。
本博客由 BriFuture 原创,并在个人博客(WordPress构建) BriFuture's Blog 上发布。欢迎访问。
欢迎遵照 CC-BY-NC-SA 协议规定转载,请在正文中标注并保留本人信息。