示例

在讲述本文内容之前,我希望读者先具备以下知识点:

  1. 了解WebGL的基本知识,懂得调用自定义的Shader程序;
  2. 基本的数学基础和空间几何知识;
  3. 明白GPU的渲染管线流程;

因为,本文内容主要讲述绘制的核心思路和注意事项,所以对于基本知识只能简单描述,请谅解;

前言

首先,先附上一篇至今我看到思路十分正确,图文并茂(图片真的很好看)的讲述WebGL绘制有宽度的线和箭头贴图的文章:WebGL绘制有宽度的线  https://www.cnblogs.com/dojo-lzz/p/9219290.html

链接博客的作者基本讲述本文想要讲的主要思路,但是他并没有过多的讲述其他细节。我就在上述的博文中补充一些关键的细节。

在绘制箭头线之前,我们必须掌握如何绘制有宽度的线条,因为,箭头图片的贴图坐标与此有关。

绘制有宽度的线

WebGL提供绘制线条的接口中是提供了线条宽度的属性,但是在windows系统中不管设置线宽的属性为多少,都是一条只有一像素宽度的线。因此,我们需要利用绘制三角形的方式去拟合带宽度的线。

从上述两张图片可以简单的理解,如何从一条线去构造宽带;假设我们有以下线的顶点数据 Line = [p0, p1, p2 ...],那么我们需要将顶点沿着直线方向的法线方向(垂直方向上)的正方向和反方向各平移 lineWidth / 2 的距离,这就刚好实现宽度为lineWidth的线条。原理十分的简单。

而我们的顶点数据的在每个顶点处只有一个点,若需要在法线方向上的正反方向上都平移,则我们需要提前把一个顶点复制两份,同时做上标识是正方向还是负方向(因为需要区分三角形的顶点索引顺序),然后传给Shader程序。一般上述的步骤需要在Js里面的准备GLSL顶点数据时进行。

// g 是line线条的每个顶点的数据
for (var j = 0; j < g.vertices.length; j++) {var v = g.vertices[j] // positions 是需要传到顶点着色器的顶点坐标,这里需要把原有// 顶点复制两份this.positions.push(v.x, v.y, v.z)this.positions.push(v.x, v.y, v.z)// side是表示顶点平移的方向this.side.push(1)this.side.push(-1)
}

上述的思路,还需要计算法线方向,因此,我们在准备顶点数据时,把当前的顶点的上一个节点和后一个节点的数据也存到顶点的Attribute属性中。

var l = this.positions.length / 6 // 这里得到为线的原有长度,未复制顶点前的
var v
// 判断线的起点和终点是否重合,是闭环的话,终点前一个点是起点的前一个顶点,否则前一个点就是起点自己
// compareV3 是根据输入的两个数组中索引的位置,判断是否是同一个vector3向量
// copyV3 是复制点的索引为a的向量
if (this.compareV3(0, l - 1)) {v = this.copyV3(l - 2)
} else {v = this.copyV3(0)
}
this.previous.push(v[0], v[1], v[2])
this.previous.push(v[0], v[1], v[2])
// 已经得到起点的前一个点,这个得到后面的其他点的前一个点的位置
for (var j = 0; j < l - 1; j++) {v = this.copyV3(j)this.previous.push(v[0], v[1], v[2])this.previous.push(v[0], v[1], v[2])
}
// 这个得到除终点外的其他点的下一个点的位置
for (var j = 1; j < l; j++) {v = this.copyV3(j)this.next.push(v[0], v[1], v[2])this.next.push(v[0], v[1], v[2])
}
// 判断线的终点和起点是否重合,是闭环的话,终点的后一点是起点的后一个顶点,否则后一个点就是终点自己
if (this.compareV3(l - 1, 0)) {v = this.copyV3(1)
} else {v = this.copyV3(l - 1)
}
this.next.push(v[0], v[1], v[2])
this.next.push(v[0], v[1], v[2])
// 设置矩形分割的三角形的顶点索引顺序,已经贴图的顺序集合,记得是逆时针的顺序
for (var j = 0; j < l - 1; j++) {var n = j * 2this.indices_array.push(n, n + 1, n + 2)this.indices_array.push(n + 2, n + 1, n + 3)
}

至此,我们已经得到了计算带宽度的线的所有必须的attributes属性


this._attributes = {position: new BufferAttribute(new Float32Array(this.positions), 3), previous: new BufferAttribute(new Float32Array(this.previous), 3), next: new BufferAttribute(new Float32Array(this.next), 3), side: new BufferAttribute(new Float32Array(this.side), 1), width: new BufferAttribute(new Float32Array(this.width), 1), // uv: new BufferAttribute(new Float32Array(this.uvs), 2), // uvs是一个二元组index: new BufferAttribute(new Uint16Array(this.indices_array), 1)
}

网上关于绘制有宽度的Shader程序的代码很多,这里就不贴出来了,可以参考Three.js的插件MeshLine,参考链接,https://github.com/spite/THREE.MeshLine

箭头贴图

WebGL的UV贴图(参考文章链接)可以参考上述的链接,这里不做详细介绍。在有宽度的线上绘制箭头纹理,其实十分的简单。只需要解决以下几个问题:

  1. 箭头图片
  2. 箭头的间隔(分静态和动态的)
  3. 贴图坐标与顶点坐标对应

箭头图片的选取要思考箭头的方向与贴图坐标相呼应,不然导致箭头方向不准确。

上述计算纹素大概如下,我们可以规定每隔markerDelta米在halfd(halfd = markerDelta/2)处,已uvDelta长的距离里绘制一个箭头。

因此,计算纹素坐标的方法如下:

1. 求出线段的每个顶点与线段的起始坐标点的距离,将这个距离存入纹理UV的X坐标中(利用在顶点着色器中varying变量在片段着色器的插值保证每个片段都能知道自己在当前线上的长度);

2. uvx对markerDelta取模运算得muvx,求出其所在间隔中的所占的长度,在根据规则(if(muvx >= halfd && muvx <= halfd + uvDelta))计算这个像素是否在uvDelta中,若在表示箭头在此区域,则计算纹素坐标。

3. 接下来就是计算纹素坐标,因为,箭头的长度肯定为整幅图片,因此,uvy为直接取0或者1.而uvx则可以根据muxv - half 与 uvDelta的比例获取。具体看下面的代码。

从上述内容可以得到,需要传递markerDelta和uvDelta作为uniform参数到片段着色器中参与运算,以及传递Uvs顶点坐标作为attributes参数到顶点着色器中计算;

以下是计算UVS坐标的JS代码:

var l = this.positions.length / 6 // 这里得到为线的原有长度,未复制顶点前的// 设置uv映射的纹理映射数组,根据贴图图片的总长度为1,把贴图的宽度分割为等分的份额,高度不变,所以这里会把图片给拉长// uvs数组是记录线段中每个顶点到线段第一个顶点的距离数据
for (var j = 0; j < l; j++) {this.uvs.push(uvs[j], 0)this.uvs.push(uvs[j], 1)
}

以下是渲染箭头贴图的着色器代码:

// uniforms 参数
this.uniforms = {lineWidth: { type: 'f', value: this.lineWidth },map: { type: 't', value: this.map },useMap: { type: 'f', value: this.useMap },color: { type: 'c', value: this.color },resolution: { type: 'v2', value: this.resolution }, // 处理顶点的sizeAttenuation: { type: 'f', value: this.sizeAttenuation },// 是否设置稀疏处理near: { type: 'f', value: this.near },far: { type: 'f', value: this.far },repeat: { type: 'v2', value: this.repeat },// 自定义贴图的间隔和总长// 如果是想要静态的效果,可以写死为某个常量uvdelta: { type: 'f', value: this.uvdelta }, markerdelta: { type: 'f', value: this.markerdelta }}
var fragmentShaderSource = '#extension GL_OES_standard_derivatives : enable','precision mediump float;','','uniform sampler2D map;','uniform float useMap;','uniform float uvdelta;', // 代表应该绘制箭头的长度'uniform float markerdelta;', // 代表线段分割的平均距离'','varying vec2 vUV;', 'varying vec4 vColor;','','void main() {','','    vec4 c = vColor;','  if (useMap > 0.0) {','    vec4 tc = vec4(1.0, 1.0, 1.0, 0.0);','    float uvx = vUV.x', // 获取uv映射的x坐标'    float muvx = mod(uvx, markerdelta);','    float halfd = markerdelta / 2.0;','    if(muvx >= halfd && muvx <= halfd + uvdelta) {','      float s = (muvx - halfd) / uvdelta;','      tc = texture2D( map, vec2(s, vUV.y));','      c.xyzw = tc.w >= 0.5 ? tc.xyzw : c.xyzw;', // 如果tc的景深超过0.5就不再变化了,这里可以发挥现象去拓展一下'    }','  }','    gl_FragColor = c;','}'

总结

文章只讲述了文章开头给出的博客没有提及的部分,建议先阅读其他博客,最后再来阅读本文。

在实际的应用中,因为上述的代码及思路都是以最简单的形式给出,会存在以下需要优化的地方:

1. 线条在拐角处,需要进行插值去使拐角更光滑。参考文献中也给出了一种常见的方法。也可以使用样条曲线去拟合;

2. 箭头图片的使用建议考虑去锯齿,方法百度有很多;

3. glsl代码里有一处计算屏幕宽高比的aspect,这一步不要省略;

关于后面线条描边或者光滑曲线的文章,后面有时间再更新。

参考文献

https://www.cnblogs.com/dojo-lzz/p/9219290.html

https://www.cnblogs.com/dojo-lzz/p/9461506.html

https://blog.csdn.net/a23366192007/article/details/51264454

WebGL绘制带箭头贴图的线相关推荐

  1. 生成有箭头的流向Line.shp 并通过ArcMap绘制带箭头的图,根据D8算法,输入Flow Direction

    简单说明:代码C++,配置GDAL环境(必须),ArcMap 附一个配置GDAL环境的教程,感谢这位大佬 VS2017编译配置GDAL库 输入流向数据的 asc文件,附网盘测试链接 链接:https: ...

  2. Echarts绘制带箭头的线

    Echarts绘制带箭头的线 这个图主要参考echarts官网的关系图绘制的,难点在于去掉其余导向图的箭头,只保留最后一个节点的箭头,以及处理值为null时箭头的指向问题,代码如下: <temp ...

  3. R语言使用rnorm函数生成正太分布数据、使用plot函数可视化折线图、使用arrows函数在可视化图像中绘制箭头曲线、绘制带箭头线段,可以设置箭头角度,有几个箭头(1起点箭头、2终点箭头,3双箭头)

    R语言使用rnorm函数生成正太分布数据.使用plot函数可视化折线图.使用arrows函数在可视化图像中绘制箭头曲线.绘制带箭头线段,可以设置箭头角度,有几个箭头(1起点箭头.2终点箭头,3双箭头) ...

  4. Matlab如何绘制带箭头图形

    Matlab如何绘制带箭头图形 带箭头坐标轴 带箭头曲线 实例演示 例1 带箭头曲线 例2 带箭头曲线+带箭头坐标轴 联系作者 带箭头坐标轴 前段时间推出了绘制箭头坐标轴曲线的方法,许多网友联系我索取 ...

  5. Android Canvas绘制带箭头的直线

    先看下效果图: 下面我们直接看代码 我自定义了一个View,代码如下: package com.davis.drawtrangle;import android.content.Context; im ...

  6. cad net 绘制带圆弧的多段线

    //绘制带弧线的多段线 输入每个点和它后面圆弧的角度(180°的圆弧就是 Math.PI),如果没有圆弧则用null public static ObjectId MakePolylineWithAr ...

  7. Matlab任意两点之间绘制带箭头的直线

    Matlab任意两点之间绘制带箭头的直线 简单绘制任意两点之间.本来不想自己写的,可是网上的代码用起来不舒服,所以简单看看原理,原来就是个坐标变换而已.索性自己写了一份,分享如下: function ...

  8. opencv之绘制带箭头的线段---arrowedLine

    核心函数 arrowedLine(img, pt1, pt2, color, thickness=None, line_type=None, shift=None, tipLength=None) i ...

  9. 为图片绘制带箭头的坐标轴

    需求说明 有时候,我们需要对一张图片进行坐标轴绘制,直观观察图片的长宽分布,并根据坐标轴中心点的选取来实现不同位置的坐标轴. 常见需求: 中心点自定义 坐标间隔自定义 坐标轴颜色自定义 透明度自定义 ...

最新文章

  1. 使用blowery.Web.HttpCompress.dll对aspx压缩
  2. Android移动开发之【Android实战项目】渐变的TextView小漂亮代码
  3. Android消息机制基本原理和使用
  4. 微架构设计:微博计数器的设计
  5. 等差数列划分 II - 子序列(动态规划)
  6. 再聊一次值类型和引用类型
  7. CSS3的常用属性(一)
  8. 扩展欧几里得算法 POJ 1061
  9. bash 别名_必不可少的Bash别名
  10. 2017数学建模b题回顾_12月热门文章和2017年回顾
  11. 数据库笔记07:实施数据完整性
  12. Snap 程序包管理器被爆Linux 提权漏洞
  13. python设置excel套打_你不一定知道这个用 Python 快速设置 Excel 表格边框的技巧
  14. [渝粤教育] 徐州工业职业技术学院 橡胶原材料 参考 资料
  15. 国庆怎么玩?国庆去面试!
  16. CE-植物大战僵尸-僵尸-关卡-金币
  17. 计算机大类和三不限哪个好考,上岸经验 !公考千万别报“三不限”职位!
  18. c/c++ 计算屏幕的PPI
  19. poj2965The Pilots Brothers' refrigerator
  20. DenseFuse :A Fusion Approach to Infrared and Visible Images解读

热门文章

  1. 浪漫的情侣头像 | 太爱了(文末赠书)
  2. 关于MFC中CListCtrl::InsertColumn()和InsertItem()和SetItemText()的一些认识
  3. 手机学习android,对于Android系统手机学习探讨
  4. 科技赋能中医,推动行业现代化发展
  5. linux服务器 更新显卡驱动
  6. 是时候来薅京东的羊毛了,自动化签到脚本详解!
  7. 《跨界杂谈》华为印象之混搭儿(六)情侣装
  8. 关于访问服务器视频资源没有声音的问题
  9. [计算机专业英文术语]Malware Payload, 正确的中文名应该是什么?
  10. 超级巡警(Anti-Spyware Toolkit)3.0.0