zoukankan      html  css  js  c++  java
  • 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,就可以得到与运动轨迹相垂直的向量了。将其归一化得到单位向量,再乘上比例系数,就可以用于计算了。

    周边的四个顶点分别就可以用 P1P2 加减 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 协议规定转载,请在正文中标注并保留本人信息。
  • 相关阅读:
    map.entry<k,v>小用法(转)
    zookeeper实现分布式锁服务
    组播协议
    OSPF
    Tomcat默认工具manager管理页面访问配置
    将web应用部署到Tomcat的三种方式
    运行startup.bat的启动过程
    IDEA创建简单servlet程序
    setvlet基础知识
    NIO基本操作
  • 原文地址:https://www.cnblogs.com/brifuture/p/8268091.html
Copyright © 2011-2022 走看看