Threejs(WebGL)绘制线段优化:Shader修改gl.LINES模式为gl.LINE_STRIP
目录
背景
思路
Threejs实现
记录每条线的点数
封装原始裁剪索引数据
封装合并几何体的缓冲数据:由裁剪索引组成的 IntArray
守住该有的线段!
修改顶点着色器
修改片元着色器
完整代码
WebGL实现类似功能(简易版,便于测验)
注意
背景
场景中有大量的非连续线段,每条线段由大量的点构成(曲率较大),并且需要合并渲染,这时,一般考虑使用LineSegments画线,因为LineSegments底层是基于 gl.LINES 的WebGL标准进行绘制,v0 v1、 v1 v2、 v2 v3、 v3 v4.......
但是,这种方法会有一定代价。假设,一条曲线由5个点构成,除了首尾两个点,我们需要对中间的每个点额外拷贝一份 用于下个list段的起点,5个点要拷贝3个点,10个点要拷贝8个点,n个点要拷贝n-2个点,当点数较多时,这是一笔不小的额外开销
遵守WebGL性能优化第一原则:尽可能的减少点的数量,每个顶点都要执行顶点着色器,进行各种矩阵变换,及插值后到片元着色器的相应操作,点数太多会极大的影响性能
设想:能否不复制这些点,就能达到非连续线段的效果?
思路
使用Line类,即gl.LINE_STRIP模式绘制一条连续的线段,v0 v1、v2 v3、v4 v5......
每条线段结尾到下条线段开头 多出的折线 在片元着色器中 discard
Threejs实现
记录每条线的点数
每条线是一个独立的geometry,记录每条线的点数,得到 [line1_vertex_count, line2_vertex_count...],添加到合并后的Geometry
const stripIndexs = geometrys.map(item => item.attributes.position.count)
mergeGeometry.stripIndexs = stripIndexs
封装原始裁剪索引数据
记录每条线的最后一个点的索引及其索引+1,也就是这个每个折线处的两个点的索引所在合并后的mergeGeometry的顶点中的位置,比如,有三条线,每条线仅有首尾两个点(举例说明,实际n个点),则需要记录 1 2 3 4 这四个索引,如下
let cumulativeIndex = -1;let originCropIndexes: Array<number> = [];for (let i = 0; i < geometry.stripIndexs.length - 1; i++) {cumulativeIndex += geometry.stripIndexs[i];originCropIndexes.push(cumulativeIndex);originCropIndexes.push(cumulativeIndex + 1);}
封装合并几何体的缓冲数据:由裁剪索引组成的 IntArray
将上一步得到的原始裁剪索引数组,每个裁剪索引按序映射到 缓冲数组 initCroppingIndexes 中,如下,遍历合并线段的所有顶点,当前索引与裁剪索引相同则按序映射,没有则默认-1,得到 [-1, 1, 2, 3, 4, -1](依然拿上述举例)
let vertexCount = geometry.attributes.position.count;let initCroppingIndexes = new Int16Array(vertexCount).fill(-1);if (originCropIndexes.length) {for (let i = 0; i < vertexCount; i++) {for (let j = 0; j < originCropIndexes.length; j++) {if (i == originCropIndexes[j]) {initCroppingIndexes[i] = originCropIndexes[j];break;}}}}geometry.setAttribute('initCroppingIndex', new BufferAttribute(initCroppingIndexes, 1));
守住该有的线段!
数据处理并没有结束!如果现在直接到着色器中按裁剪索引去插值直接做discard,会把 1 2 3 4 顶点中的 2 3所组成的线段也discard掉,当然,这不是我们想要的,需要进一步封装相关数据用于后续着色器使用
如下图,需要再次记录 索引 2 3 4 5 6 7 ,并且连续的两对点都有不同的标识,这个至关重要,因为这些索引是要保留的所组成的线段,如果这些要保留的索引又是连续,又是相同的标识,是不是 2 ~ 7顶点间的线段又会都保留?2 3 、4 5、6 7组成的线段你是保留了,3 4、5 6线段是不是又没有剔除?陷入了无止境循环的局面....
拿上图举例,最终 continuousCroppingIndexes 所成型的数据是 [-1, -1, 0, 0, 1, 1, 0, 0, -1, -1。]如下代码
let stripIdentCount = 0;let continuousCroppingIndexes = new Int16Array(vertexCount).fill(-1);if (originCropIndexes.length) {for (let i = 1; i < vertexCount - 1; i++) {if (initCroppingIndexes[i] != -1 && initCroppingIndexes[i - 1] + 1 == initCroppingIndexes[i + 1] - 1) {continuousCroppingIndexes[i] = stripIdentCount % 4 < 2 ? 0 : 1;stripIdentCount++;} }}geometry.setAttribute('continuousCroppingIndex', new BufferAttribute(continuousCroppingIndexes, 1));
修改顶点着色器
这步很简单,将init裁剪索引缓冲数据和要保留的裁剪索引数据分别给赋顶点插值颜色,用于后续片元根据插值颜色做判断。
注意,这里continuousCroppingIndex缓冲数据是双重标识,0 0 1 1 0 0...
material.onBeforeCompile = (shader) => {shader.vertexShader = shader.vertexShader.replace('void main() {',['attribute float initCroppingIndex;','attribute float continuousCroppingIndex;','varying vec4 vColor;','varying vec4 vStripCrop;','void handleVaryingColor() {','int initIndex = int(initCroppingIndex);','if (gl_VertexID == initIndex) {','vColor = vec4(vec3(1.), 0.);','}','vStripCrop = vec4(vec2(1.), continuousCroppingIndex, 0.);','}','void main() {','handleVaryingColor();'].join('\n'));};
修改片元着色器
可能举例更形象些:如下,1 2、3 4、5 6、7 8都会被裁剪,而并不会裁剪 2 3 、4 5 、6 7,因为该有的索引都做了成对的颜色标识,并且会区分奇偶对顶点的颜色!
如下,按需裁剪
material.onBeforeCompile = (shader) => {shader.fragmentShader = shader.fragmentShader.replace('void main() {',['varying vec4 vColor;', 'varying vec4 vStripCrop;', 'void main() {'].join('\n'));shader.fragmentShader = shader.fragmentShader.replace('vec4 diffuseColor = vec4( diffuse, opacity );',['vec4 diffuseColor = vec4( diffuse, opacity );','vec4 vUnivCropColor = vec4(vec3(1.), 0.);','vec4 vEvenCropColor = vec4(vec2(1.), vec2(0.));','if(vColor == vUnivCropColor && vStripCrop != vUnivCropColor && vStripCrop != vEvenCropColor) {','discard;','}',].join('\n'));};
完整代码
geometry和material分别是合并的几何体及其材质
const handleLineGeometryShader = (geometry, material) => {let stripIdentCount = 0;let cumulativeIndex = -1;let vertexCount = geometry.attributes.position.count;let originCropIndexes: Array<number> = [];let initCroppingIndexes = new Int16Array(vertexCount).fill(-1);let continuousCroppingIndexes = new Int16Array(vertexCount).fill(-1);for (let i = 0; i < geometry.stripIndexs.length - 1; i++) {cumulativeIndex += geometry.stripIndexs[i];originCropIndexes.push(cumulativeIndex);originCropIndexes.push(cumulativeIndex + 1);}if (originCropIndexes.length) {for (let i = 0; i < vertexCount; i++) {for (let j = 0; j < originCropIndexes.length; j++) {if (i == originCropIndexes[j]) {initCroppingIndexes[i] = originCropIndexes[j];break;}}}for (let i = 1; i < vertexCount - 1; i++) {if (initCroppingIndexes[i] != -1 && initCroppingIndexes[i - 1] + 1 == initCroppingIndexes[i + 1] - 1) {continuousCroppingIndexes[i] = stripIdentCount % 4 < 2 ? 0 : 1;stripIdentCount++;} }}// console.log(originCropIndexes, initCroppingIndexes, continuousCroppingIndexes);geometry.setAttribute('initCroppingIndex', new BufferAttribute(initCroppingIndexes, 1));geometry.setAttribute('continuousCroppingIndex', new BufferAttribute(continuousCroppingIndexes, 1));beforeCompileLineMaterial(material);};const beforeCompileLineMaterial = (material) => {material.onBeforeCompile = (shader) => {shader.vertexShader = shader.vertexShader.replace('void main() {',['attribute float initCroppingIndex;','attribute float continuousCroppingIndex;','varying vec4 vColor;','varying vec4 vStripCrop;','void handleVaryingColor() {','int initIndex = int(initCroppingIndex);','if (gl_VertexID == initIndex) {','vColor = vec4(vec3(1.), 0.);','}','vStripCrop = vec4(vec2(1.), continuousCroppingIndex, 0.);','}','void main() {','handleVaryingColor();'].join('\n'));shader.fragmentShader = shader.fragmentShader.replace('void main() {',['varying vec4 vColor;', 'varying vec4 vStripCrop;', 'void main() {'].join('\n'));shader.fragmentShader = shader.fragmentShader.replace('vec4 diffuseColor = vec4( diffuse, opacity );',['vec4 diffuseColor = vec4( diffuse, opacity );','vec4 vUnivCropColor = vec4(vec3(1.), 0.);','vec4 vEvenCropColor = vec4(vec2(1.), vec2(0.));','if(vColor == vUnivCropColor && vStripCrop != vUnivCropColor && vStripCrop != vEvenCropColor) {','discard;','}',].join('\n'));};}
WebGL实现类似功能(简易版,便于测验)
var VSHADER_SOURCE ='attribute vec4 a_Position;\n' +'attribute float indexes;\n' +'attribute float oneIndex;\n' +'attribute float twoIndex;\n' +'varying vec4 vColor;\n' + 'varying vec4 vStripCrop;\n' + 'void main() {\n' +'int index = int(indexes);\n' +'int oIndex = int(oneIndex);\n' +// 'int tIndex = int(twoIndex);\n' +'if (index == oIndex) {\n' +'vColor = vec4(vec3(1.), 0.);\n' +'}\n' +'vStripCrop = vec4(vec2(1.), twoIndex, 0.);\n' +'gl_Position = a_Position;\n' +'}\n';// Fragment shader program
var FSHADER_SOURCE ='precision mediump float;\n' +'varying vec4 vColor;\n' + 'varying vec4 vStripCrop;\n' + 'void main() {\n' +' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +'if(vColor == vec4(vec3(1.), 0.) && vStripCrop != vec4(vec3(1.), 0.) && vStripCrop != vec4(vec2(1.), 0., 0.)) {\n' +'discard;\n' +'}\n' + '}\n';function main() {var canvas = document.getElementById('webgl');var gl = getWebGLContext(canvas);if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {console.log('Failed to intialize shaders.');return;}var n = initVertexBuffers(gl);gl.clearColor(0.0, 0.0, 0.0, 1.0);gl.clear(gl.COLOR_BUFFER_BIT);gl.drawArrays(gl.LINE_STRIP, 0, n);
}function initVertexBuffers(gl) {var vertices = new Float32Array([-0.6, -0.8, 0.6, -0.8, -0.6, -0.5, 0.6, -0.5, -0.6, -0.2, 0.6, -0.2, -0.6, 0.1, 0.6, 0.1, -0.6, 0.4, 0.6, 0.4]);var indexes = new Float32Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);var arr1 = new Float32Array([-1, 1, 2, 3, 4, 5, 6, 7, 8, -1]);var arr2 = new Float32Array([-1, -1, 0, 0, 1, 1, 0, 0, -1, -1]);var n = 10;// Create a buffer objectvar vertexBuffer = gl.createBuffer(); var indexBuffer = gl.createBuffer();var oneBuffer = gl.createBuffer();var twoBuffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);var a_Position = gl.getAttribLocation(gl.program, 'a_Position');gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);gl.enableVertexAttribArray(a_Position);gl.bindBuffer(gl.ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ARRAY_BUFFER, indexes, gl.STATIC_DRAW);var indexes = gl.getAttribLocation(gl.program, 'indexes');gl.vertexAttribPointer(indexes, 1, gl.FLOAT, false, 0, 0);gl.enableVertexAttribArray(indexes);gl.bindBuffer(gl.ARRAY_BUFFER, oneBuffer);gl.bufferData(gl.ARRAY_BUFFER, arr1, gl.STATIC_DRAW);var oneIndex = gl.getAttribLocation(gl.program, 'oneIndex');gl.vertexAttribPointer(oneIndex, 1, gl.FLOAT, false, 0, 0);gl.enableVertexAttribArray(oneIndex);gl.bindBuffer(gl.ARRAY_BUFFER, twoBuffer);gl.bufferData(gl.ARRAY_BUFFER, arr2, gl.STATIC_DRAW);var twoIndex = gl.getAttribLocation(gl.program, 'twoIndex');gl.vertexAttribPointer(twoIndex, 1, gl.FLOAT, false, 0, 0);gl.enableVertexAttribArray(twoIndex);return n;
}
注意
- 自定义的顶点缓冲数据如果是int类型的,及时你js里面是int类型,在传入shader里面的时候,vertexpoint辅助函数有个参数也会给转成float类型!则需要float声明接收,后续使用int数据再次int转换即可
- uniform变量不能直接声明为数组类型。这是因为uniform变量是在整个渲染过程中保持不变的,而数组类型通常需要在编译时知道其大小
相关文章:

Threejs(WebGL)绘制线段优化:Shader修改gl.LINES模式为gl.LINE_STRIP
目录 背景 思路 Threejs实现 记录每条线的点数 封装原始裁剪索引数据 封装合并几何体的缓冲数据:由裁剪索引组成的 IntArray 守住该有的线段! 修改顶点着色器 修改片元着色器 完整代码 WebGL实现类似功能(简易版,便于测…...

继承-进阶
父子类成员共享 普通成员对象/父子间不共享, 成员独立 函数成员共享(函数不存储在对象中) 子类由两部分构成:父类中继承的成员和子类中新定义成员 继承方式 子类中存在父类private成员但不可直接访问(及时在类中&am…...

探索k8s集群的配置资源(secret和configmap)
目录 ConfigMap ConfigMap(主要是将配置目录或者文件挂载到k8s里面使用) 与Secret类似,区别在于ConfigMap保存的是不需要加密配置的信息。(例如:配置文件) ConfigMap 功能在 Kubernetes1.2 版本中引入&…...

如何设置vue3项目中默认的背景为白色
方法1:通过CSS全局样式 在全局CSS文件中设置: 如果你的项目中有全局的CSS文件(如App.vue或专门的CSS文件),你可以直接设置body或html标签的背景颜色。 在src/assets文件夹中(或者任何你存放CSS文件的地方&a…...

MS1112驱动开发
作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生在读,研究方向无线联邦学习 擅长领域:驱动开发,嵌入式软件开发,BSP开发 作者主页:一个平凡而乐于分享的小比特的个人主页…...

K8s存储对象的使用
背景和概念 容器中的文件在磁盘上是临时存放的,这给在容器中运行较重要的应用带来一些问题: 当容器崩溃或停止时,此时容器状态未保存, 因此在容器生命周期内创建或修改的所有文件都将丢失。另外 在崩溃期间,kubelet 会…...

构建自动化API数据抓取系统
构建一个自动化API数据抓取系统是一个涉及多个技术领域的复杂任务。这样的系统不仅要求高效的数据获取能力,还需要有稳定的数据处理、存储和错误处理机制。 1. 需求分析 在开始构建之前,明确你的需求至关重要。你需要确定要抓取的API、数据的频率、数据的…...

【Qt知识】部分QWidget属性表格
QWidget是Qt库中所有图形用户界面组件的基类,它提供了大量属性以供自定义和配置控件的行为和外观。下面列出了一些主要的QWidget属性及其作用。 属性 作用 accessibleName 控件的辅助技术名称,用于无障碍访问。 accessibleDescription 控件的辅助技…...

【ARM64 常见汇编指令学习 19.1 -- ARM64 跳转指令 b.pl 详细介绍】
文章目录 ARM64 跳转指令 b.pl使用场景语法示例总结 ARM64 跳转指令 b.pl 在 ARMv8 架构中,b.pl 是一条条件分支(Branch)指令,它根据当前的状态寄存器中的条件标志执行跳转。b.pl 的全称是 Branch if Plus,即如果条件…...

WWDC24即将到来,ios18放大招
苹果公司即将在下周开全球开发者大会(WWDC),大会上将展示其人工智能技术整合到设备和软件中的重大进展,包括与OpenAI的历史性合作。随着大会的临近,有关iOS 18及其据称采用AI技术支持的应用程序和功能的各种泄露信息已经浮出水面。 据报道,苹果将利用其自主研发的大…...

C#中的空合并运算符与空合并赋值运算符:简化空值处理
在C#编程中,处理可能为null的值是一项常见的任务,尤其是在涉及数据库查询、Web服务调用或任何可能返回缺失数据的场景中。为了简化这类操作并提高代码的可读性,C# 8 引入了两个非常实用的运算符:空合并运算符 (??) 和 空合并赋值…...

数据结构:哈夫曼树及其哈夫曼编码
目录 1.哈夫曼树是什么? 2.哈夫曼编码是什么? 3.哈夫曼编码的应用 4.包含头文件 5.结点设计 6.接口函数定义 7.接口函数实现 8.哈夫曼编码测试案列 哈夫曼树是什么? 哈夫曼树(Huffman Tree)是一种特殊的二叉树…...

微信如何防止被对方拉黑删除?一招教你解决!文末附软件!
你一定不知道,微信可以防止被对方拉黑删除,秒变无敌。只需一招就能解决!赶快来学!文末有惊喜! 惹到某些重要人物(比如女朋友),被删除拉黑一条龙,那真的是太令人沮丧了&a…...

jar增量打包
jar增量打包 Linux环境下: 1.解压缩 jar -xvf jarname.jar(解压)2.打包 这时可以把要替换的lib包的内容粘帖进去,然后重新打jar包 jar -cvf0M jarname.jar .(重新压缩,-0是主要的)jar命令: …...

智慧医院物联网建设-统一管理物联网终端及应用
近年来,国家卫健委相继出台的政策和评估标准体系中,都涵盖了强化物联网建设的内容。物联网建设已成为智慧医院建设的核心议题之一。 作为医院高质量发展的关键驱动力,物联网的顶层设计与网络架构设计规划,既需要结合现代信息技术的…...

Debian的常用命令
Debian作为一个稳定、安全且高效的Linux发行版,被广泛应用于服务器和桌面操作系统中。对于系统管理员和开发者来说,熟练掌握Debian的常用命令能够大大提升工作的效率和系统的管理水平。本文将详细介绍一些常见且实用的Debian命令,帮助新手更好地管理和操作Debian系统。 系统…...

矩阵1-范数与二重求和的求和可交换
矩阵1-范数与二重求和的求和可交换 1、矩阵1-范数 A [ a 11 a 12 ⋯ a 1 n a 21 a 22 ⋯ a 2 n ⋮ ⋮ ⋱ ⋮ a n 1 a n 2 ⋯ a n n ] A \begin{bmatrix} a_{11} &a_{12} &\cdots &a_{1n} \\ a_{21} &a_{22} &\cdots &a_{2n} \\ \vdots &\vdots …...

Python笔记 - *args和**kwargs
探索Python的*args和**kwargs 在Python中,函数可以接受任意数量的参数,而这要归功于*args和**kwargs的强大功能。这两个特性使得函数在处理不同数量的输入时变得更加灵活和高效。在这篇博客中,我们将详细介绍*args和**kwargs,并展…...

微信小程序实现图片转base64
在微信小程序中,图片转base63可以引入第三方插件; 也可以通过下边的方法转base64。 转换方法: imgToBase64(filePath) {return new Promise((resolve, reject) > {let baseFormat data:image/png;base64,let base64 wx.getFileSystem…...

os和os.path模块
自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 目录也称文件夹,用于分层保存文件。通过目录可以分门别类地存放文件。我们也可以通过目录快速找到想要的文件。在Python中,并…...

链表题目练习----重排链表
这道题会联系到前面写的一篇文章----快慢指针相关经典问题。 重排链表 指针法 这道题乍一看,好像有点难处理,但如果仔细观察就会发现,这道题是查找中间节点反转链表链表的合并问题,具体细节有些不同,这个在反装中间链…...

【杂记-浅谈XSS跨站脚本攻击】
一、什么是XSS? XSS,Cross-site Scripting,跨站脚本攻击,是一种典型的Web程序漏洞利用攻击,攻击者利用Web程序对用户输入检查不足的漏洞将可执行恶意脚本注入网站或Web应用,当用户访问网页时触发恶意脚本的…...

VMware虚拟机与MobaXterm建立远程连接失败
VMware虚拟机与MobaXterm建立远程连接失败 首先可以检查一下是不是虚拟机的ssh服务并不存在 解决方法: 1.更新镜像源 yum -y update 这个过程会有点久,请耐心等待 2.安装ssh yum install openssh-server 3.启动ssh systemctl restart sshd 4.查…...

mysql undolog管理
在MySQL中,Undo Log(撤销日志)用于支持事务的回滚和MVCC(多版本并发控制)。为了避免Undo Log不断增长,影响系统性能,需要进行合理的清理。MySQL的Undo Log清理策略主要依赖于系统的配置参数和后…...

【Linux】进程2——管理概念,进程概念
1.什么是管理? 那在还没有学习进程之前,就问大家,操作系统是怎么管理进行进程管理的呢? 很简单,先把进程描述起来,再把进程组织起来! 我们拿大学为例子 最典型的管理者——校长最典型的被管理…...

【C++】植物大战僵尸杂交版自动存档——防闪退存档消失
植物大战僵尸杂交版现已更新到v2.0.88,闪退问题还是偶有发生,参考网上现有的方案,简单实现了一个。 原理就是监控存档目录的文件变化,一旦有新的存档,则将其备份。如发生闪退,则还原备份即可。 原目录&…...

通过Excel,生成sql,将A表数据插入B表
文章目录 投机取巧的方式,进行表数据初始化通过navicat搜索A表数据,然后复制进excel中通过excel的函数方式,将该批量数据自动生成插入B表的sql语句然后一次性拷贝生成的sql语句,放进navicat中一次执行,直接完成数据初始化...

如何在MySQL中实现upsert:如果不存在则插入?
目录 1 使用 REPLACE 2 使用 INSERT ... ON DUPLICATE KEY UPDATE 使用 INSERT IGNORE 有效会导致 MySQL 在尝试执行语句时忽略执行错误 INSERT 。这意味着 包含 索引或 字段 INSERT IGNORE 中重复值的语句 不会 产生错误,而只是完全忽略该特定 命令。其明显目的是…...

MyBatis中 set标签
1、set标签特点: set标签用于更新语句中set标签解析为set关键字set可以去除跟新语句中无用的逗号通常是和if标签一起使用 2、set标签的使用 编写接口方法编写sql语句 注意 当set标签中有条件成立时就会附加set关键字,字段为null时该列不会被更新。se…...

mysql自带分页
select 查询列表 from 表 limit offset,pagesize; offset代表的是起始的条目索引,默认从0开始size代表的是显示的条目数offset(n-1)*pagesize -- 第-页 limit 0 5 -- 第二页 limit 5,5 -- 第三页 limit 10,5 -- 第n页limit(n-1)*pagesize,pagesize -- pages…...