WebGL画粗线

目录

前言 

基本思路

求左右端点

实现

组织数据

顶点着色器计算端点坐标 

效果


前言 

WebGL绘制模式有点、线、面三种;通过点的绘制可以实现粒子系统等,通过线可以绘制一些连线关系;面就强大了,通过面,我们可以绘制我们想绘制的所有的三维对象。然而,WebGL在绘制线条的时候,存在一个缺陷,那就是在一些机器的一些浏览器上面(大多数情况下)线宽只能设置为1,而不能设置成其他的值。

http://alteredqualia.com/tmp/webgl-linewidth-test/,可以测试自己的电脑是否可以绘制粗线

可以看出,我的Chrome有这个 问题

 

基本思路

既然画线实现不了加粗,那就使用面来模拟粗线。

如下,一条线 p0 p1 构成,可算出两个点的左右两个端点0 1 、 2 3,这四个端点组成两个三角形

求左右端点

  1. 二维向量(x, y)的法向量为(-y, x),为方便后续平移操作,将法向量标准化为单位向量;
  2. 通过平移变换将p0、p1沿上述得到法向量正向平移width/2得到点0、1,沿法向量反向平移width/2得到点2、3。
     

但是,这样简单粗暴的求法向量会有问题,如下图,多个线段间会有冗余分割线条

为了消除节点处的可见分割,有必要将相邻节段的相邻点组合起来,以保持相邻节段两个节段的厚度。要做到这一点,需要更进一步的求法向量

如下图,用last、current、next表示前、当前、后三个点,算出当前now点的切线方向(黄色),切线方向乘以复数i,逆时针旋转90就为法线方向,乘以复数-i,顺时针旋转90度为负法线方向,则有了Normal和-Normal(紫色)。Normal和-Normal分别乘以宽度的一半,即可求出每个点的左右端点

将端点坐标写入WebGL顶点坐标缓冲区,另外通过索引[0, 1, 3, 1, 2, 3]来表示两个三角形的顶点序号写入WebGL索引缓冲区,调用gl.drawElements绘制出带线宽的直线。 

实现

事实上,计算左右端点的操作应该发生在顶点着色器中的,而且也只能在着色器中计算,因为最终显示到屏幕上的顶点与镜头相关,上述只是简单的用了2维的情况模拟,如果在js端计算,将极大消耗性能。 

组织数据

组织要传给着色器的数据:position、prevPositions、nextPositions、side

比如,三个点 p0 p1 p2,要组织数据的形式:

position = [p0, p0, p1, p1, p2, p2]

prevPositions = [p0, p0, p0, p0, p1, p1]

nextPositions= [p1, p1, p2, p2, p2, p2]

side = [1, -1, 1, -1, 1, -1]

    let geometry = new THREE.BufferGeometry()
    let vertices = []
    let count = vertices.length
    let prevPositions = new Float32Array(count * 3 * 2)
    let nextPositions = new Float32Array(count * 3 * 2)
    let side = new Float32Array(count * 2)
    let position = new Float32Array(count * 3 * 2)
    let indexes = new Uint16Array(6 * (count - 1))
    for (let j = 0; j < count * 2; j += 2) {

      // side
      side[j] = 1
      side[j + 1] = -1

      // index
      let current = vertices[j / 2]
      let prev = vertices[(j === 0 ? j : j - 2) / 2]
      let next = vertices[(j === (count - 1) * 2 ? j : j + 2) / 2]
      // position
      position[j * 3] = current.x
      position[j * 3 + 1] = current.y
      position[j * 3 + 2] = current.z
      position[j * 3 + 3] = current.x
      position[j * 3 + 4] = current.y
      position[j * 3 + 5] = current.z

      // prev
      prevPositions[j * 3] = prev.x
      prevPositions[j * 3 + 1] = prev.y
      prevPositions[j * 3 + 2] = prev.z
      prevPositions[j * 3 + 3] = prev.x
      prevPositions[j * 3 + 4] = prev.y
      prevPositions[j * 3 + 5] = prev.z

      // next
      nextPositions[j * 3] = next.x
      nextPositions[j * 3 + 1] = next.y
      nextPositions[j * 3 + 2] = next.z
      nextPositions[j * 3 + 3] = next.x
      nextPositions[j * 3 + 4] = next.y
      nextPositions[j * 3 + 5] = next.z
    }
    for (let i = 0; i < count * 2 - 2; i += 2) {
      indexes[6 * (i - i / 2)] = i
      indexes[6 * (i - i / 2) + 1] = i + 1
      indexes[6 * (i - i / 2) + 2] = i + 2
      indexes[6 * (i - i / 2) + 3] = i + 2
      indexes[6 * (i - i / 2) + 4] = i + 1
      indexes[6 * (i - i / 2) + 5] = i + 3
    }

    geometry.setAttribute('position', new THREE.BufferAttribute(position, 3))
    geometry.setAttribute('prevPositions', new THREE.BufferAttribute(prevPositions, 3))
    geometry.setAttribute('nextPositions', new THREE.BufferAttribute(nextPositions, 3))
    geometry.setAttribute('side', new THREE.BufferAttribute(side, 1))
    geometry.index = new THREE.BufferAttribute(indexes, 1)

 

顶点着色器计算端点坐标 

正如上述 计算左右端点方法  两个点为单位轮回;算出法向量乘以side,得到最终的正负法线方向

需要注意的是,为了能够计算顶点在屏幕上的最终位置,需要把canvans的尺寸大小传递给着色器

    <script id="vertexShader" type="x-shader/x-vertex">
      attribute float side;
      attribute vec3 prevPositions;
      attribute vec3 nextPositions;
      uniform float width;
      uniform vec2 resolution;
  
      void main(){
        float aspect = resolution.x / resolution.y;
  
        mat4 pvm = projectionMatrix * modelViewMatrix;
        vec4 currentV4 = pvm * vec4(position, 1.0);
        vec4 prevV4 = pvm * vec4(prevPositions, 1.0);
        vec4 nextV4 = pvm * vec4(nextPositions, 1.0);
  
        vec2 currentV2 = currentV4.xy / currentV4.w;
        vec2 prevV2 = prevV4.xy / prevV4.w;
        vec2 nextV2 = nextV4.xy / nextV4.w;
  
        vec2 dir1 = normalize(nextV2 - currentV2);
        vec2 dir2 = normalize(currentV2 - prevV2);
        vec2 dir = normalize(dir1 + dir2);

        vec2 normal = vec2( -dir.y, dir.x );
        
        normal.x /= aspect;
  
        normal *= width;
        normal /= resolution.y;
         
        currentV4.xy += (normal * side) * currentV4.w;
        
        gl_Position = currentV4;
  
      }
  
    </script>

效果

export default [  
    {x:  0.2980022405076852, y:  0.0007317477689525731, z:  1},
    {x:  1.1869354597466781, y:  0.0009180096778322877, z:  0},
    {x:  2.0758235116605874, y:  0.0012041573064607292, z:  1},
    {x:  2.9647108701853995, y:  0.0016485209298480186, z:  1},
    {x:  3.8535979009456014, y:  0.002361701064614863, z:  0},
    {x:  4.7424853104305384, y:  0.00351269568443513, z:  0},
    {x:  5.631373663413683, y:  0.005294665597602943, z:  1},
    {x:  6.520261987572894, y:  0.007922778765419025, z:  0},
    {x:  7.409147752210174, y:  0.01167311915872915, z:  2},
    {x:  8.298025451981857, y:  0.016846610763536773, z:  0},
    {x:  9.186886934688118, y:  0.023772568206652522, z:  -1},
    {x:  10.07571907490967, y:  0.0328001040410868, z:  0},
    {x:  10.964496398756296, y:  0.044288140005107834, z:  5},
    {x:  11.85319602294021, y:  0.05862768318564804, z:  10},
    {x:  12.741786244815671, y:  0.07613330348408454, z:  0},
    {x:  13.630226377381064, y:  0.09712025624207854, z:  20},
    {x:  14.518501144938796, y:  0.12186988355398398, z:  1},
    {x:  15.406618008779674, y:  0.15055661742513848, z:  0},
    {x:  16.294597333615457, y:  0.18321126049687564, z:  1},
    {x:  17.182483414117996, y:  0.21982671919579388, z:  0},
    {x:  18.07027996070667, y:  0.26025710620086784, z:  0},
    {x:  18.957985046609565, y:  0.3042638844103749, z:  2},
    {x:  19.845588805245143, y:  0.35157055005709026, z:  20},
    {x:  20.733061996025413, y:  0.40187981369570025, z:  12},
    {x:  21.620398578083382, y:  0.4548763144693453, z:  0},
    {x:  22.50759421058183, y:  0.510277754478011, z:  0},
    {x:  23.394649626125783, y:  0.5678463420269395, z: 10},
    {x:  24.28156938481652, y:  0.6273966818090457, z:  10},
    {x:  25.16835632444645, y:  0.6887999401383809, z:  0},
    {x:  26.055014124105355, y:  0.7520044141307949, z:  2},
    {x:  26.94154278405199, y:  0.8170242843056599, z:  0},
    {x:  27.827935344684192, y:  0.8839563938809647, z:  2},
    {x:  28.71418144341419, y:  0.9530044273309386, z:  4},
    {x:  29.60025448188958, y:  1.0244047577025412, z:  0},
    {x:  30.486116503444237, y:  1.0984849046006389, z: 50},
    {x:  31.371726404611536, y:  1.175603041504246, z:  6},
    {x:  32.25701896209273, y:  1.2562969882843618, z:  4},
    {x:  33.14193521146046, y:  1.3409977805800963, z:  10},
    {x:  34.0263999228323, y:  1.4302624318554535, z:  0},
    {x:  34.91033330314474, y:  1.5245805770430252, z:  0},
    {x:  35.79361266847275, y:  1.6247205249796934, z:  0},
    {x:  36.676130186271166, y:  1.7312884009689355, z:  0},
    {x:  37.557760528031736, y:  1.844910698533738, z:  2},
    {x:  38.43832338116283, y:  1.9663458748508447, z:  0},
    {x:  39.31764605054343, y:  2.0958906489368587, z:  0},
    {x:  40.19564230123137, y:  2.234175946683024, z:  0},
    {x:  41.07190059501124, y:  2.3830193460015607, z:  10},
    {x:  41.946324364051975, y:  2.5416768732814035, z:  -20},
    {x:  42.81851559265749, y:  2.712179223072269, z:  0},
    {x:  43.688235247276, y:  2.8943826198482725, z:  -10},
    {x:  44.55509274214762, y:  3.089389858893753, z:  -10},
    {x:  45.41866612330193, y:  3.298229478551491, z:  -20}
    ]

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山楂树の

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值