需求:绘制斑点在球面上走过的路径

思路:要绘制斑点在球面上走过的路径,首先要记录上一时刻和当前时刻该斑点所在球面的位置,并且实时更新当前时刻的斑点位置和上一时刻的斑点位置。

为了方便,上一时刻斑点所在位置记为 last_point,当前时刻位置记为 cur_point,统一用球坐标系进行计算。

 last_point = [ltheta, lbeta, ldis];
cur_point = [theta, beta, dis];

那么接下来就要把这些记录下来的点(转换成直角坐标系后)和对应的索引存放到 buffer 缓冲区中,

 gl.bindBuffer(gl.ARRAY_BUFFER,  vertex_buffer);
gl.bufferSubData(gl.ARRAY_BUFFER, offset, new Float32Array(vertices)); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
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 中:

 vertices_array.push(vertices);
gl.bindBuffer(gl.ARRAY_BUFFER, vertices_buffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(vertices_array)

索引缓冲区也是一样的。

这种方式很明显的缺陷在于,vertices_array 会越来越大,而且每次都要把所有记录到的顶点全部传送到缓冲区中。

另外,在创建缓冲区的时候需要指定创建的缓冲区类型是 gl.DYNAMIC_DRAW。用于多次传输数据,多次绘制。

方法1

用 gl 的绘图 API 进行绘制即可,最简单的方法是:

 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 角的变化

 var dt = 1 * Math.PI / 180;
var db = 1 * Math.PI / 180;
var p1 = [ cur_point[0] - dt, cur_point[1] - db, cur_point[2] ];
var p2 = [ cur_point[0] - dt, cur_point[1] + db, cur_point[2] ];
var p3 = [ cur_point[0] + dt, cur_point[1] + db, cur_point[2] ];
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 就不应该一样。

 var db1 = 1 * Math.PI / 180 / Math.sin(cur_point[0] - dt);
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,就可以得到与运动轨迹相垂直的向量了。将其归一化得到单位向量,再乘上比例系数,就可以用于计算了。

周边的四个顶点分别就可以用 P1P2 加减 L 向量得到。之前说过,球心在原点,向量和坐标在值上是一样的。那么就得到周边四个顶点的直角坐标。

用 gl.TRIANGLES 绘制三角形,就可以把路径绘制成连续的平面。当然如果 L 向量的长度很小,最后得到的路径就很窄,反之路径就会很宽。

最终绘制出来的路径如果想要变成虚线的形式,就间隔几个时刻采点,然后绘制出来就像虚线的样子了。

或许也可以设置 gl.vertexAttribPointer(attributes_vertex_position, 3, gl.FLOAT, false, 0, 0); 这个函数的 stride 参数,间隔若干个点进行绘制,应该也可以实现成虚线的形式。

05-12 18:09