Threejs(WebGL)绘制线段优化:Shader修改gl.LINES模式为gl.LINE_STRIP

news2025/1/21 11:35:59

目录

背景 

思路

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 object
  var 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;
}

注意 

  1. 自定义的顶点缓冲数据如果是int类型的,及时你js里面是int类型,在传入shader里面的时候,vertexpoint辅助函数有个参数也会给转成float类型!则需要float声明接收,后续使用int数据再次int转换即可
  2. uniform变量不能直接声明为数组类型。这是因为uniform变量是在整个渲染过程中保持不变的,而数组类型通常需要在编译时知道其大小

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1719329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线性代数|机器学习-P1课程简介

文章目录 1. 书籍下载2. 正文 1. 书籍下载 链接&#xff1a;https://pan.baidu.com/s/1QbK0enLh0x4nU1c4Tqwlkw 提取码&#xff1a;r7ft 本课程回顾线性代数在概率论、统计学、优化和深度学习中的应用。是GILBERT STRANG教授的有一个经典的课程。课程将线性代数分为如下部分&a…

Android Studio启动第一个项目的报错——笔记

零基础第一次使用Android Studio开发遇到的问题 1 新建项目下载gradle慢的问题 直接叉掉打开gradle官网手动下载自己想要的版本 gradle官网 然后在刚刚建好的项目里找到gradle-wrapper.properties&#xff0c;修改distributionUrl&#xff0c;并把自己从官网下载的包放到和gr…

SQLRecon:一款针对MSSQL的网络侦查与后渗透测试工具

关于SQLRecon SQLRecon是一款针对Microsoft SQL Server的安全研究工具&#xff0c;该工具专为红队研究人员设计&#xff0c;可以帮助广大研究人员针对MSSQL执行网络侦查和后渗透利用测试。 工具安装 广大研究人员可以直接访问该项目的【Releases页面】下载预编译的最新版本SQ…

WHAT - 用户登录系列(二)- 单点登录 SSO

目录 一、认证机制1.1 基于会话的认证&#xff08;Session-based Authentication&#xff09;1. 介绍2. 基本流程 1.2 JSON Web Tokens (JWT)1. 介绍2. jwt 组成3. 基本流程4. 阻止列表5. 刷新令牌 二、单点登录&#xff1a;SSO2.1 单系统登录2.2 SSO 介绍2.3 SSO 登录2.4 SSO …

从零开始学习Slam-旋转矩阵旋转向量四元组(二)

本文参考&#xff1a;计算机视觉life 仅作笔记用 书接上回&#xff0c;上回不清不楚的介绍了旋转矩阵&旋转向量和四元组 现在回顾一下重点&#xff1a; 本着绕谁谁不变的变则 假设绕z轴旋转θ&#xff0c;旋转矩阵为&#xff1a; 再回顾一下旋转向量的表示以及这个基本记不…

【课程总结】Day4:信息论和决策树算法

前言 本章内容主要是学习机器学习中的一个重要模型&#xff1a;决策树&#xff0c;围绕决策树的应用&#xff0c;我们展开了解到&#xff1a;熵的定义、熵的计算、决策树的构建过程(基于快速降熵)、基尼系数等&#xff0c;从而使得我们对决策树有了直观认识。 熵的介绍 因为…

discuz论坛怎么修改备案信息

大家好&#xff0c;今天给大家分享下discuz如何填写备案信息并且展示在网站首页。大家都知道国内网站都需要备案&#xff0c;不通过备案的网站上是没办法通过域名打开的。大家也可以通过搜索网创有方&#xff0c;或者直接点击网创有方 查看悬挂备案号后的效果。 首先大家可以看…

AntV F2 极坐标堆叠柱状图:可视化数据分布

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 AntV F2 极坐标堆叠柱状图&#xff1a;可视化数据分布 应用场景 极坐标堆叠柱状图适用于展示不同分类数据在某个维度上的分布情况&#xff0c;例如不同电影的票房占比、不同商品的销售额占比等。通过这种方式…

闽盾杯 2021 DNS协议分析

今年CISCN的Tough DNS 的前戏就是DNS协议分析 直接可以查找到flag的base64形式Zmxh 发现就是请求的dnslog 携带的数据 过滤器就是 dns tshark -r dns.pcapng -T json -Y "dns" >1.json 字段选择 dns.qry.name tshark -r dns.pcapng -T json -Y "dns"…

C# try catch异常捕获

异常捕获 执行过程&#xff1a;try中的代码没有出现异常&#xff0c;则catch里面不会自行&#xff0c;如果try中代码出现异常&#xff0c;则后面的代码都不执行&#xff0c;直接跳到catch中的代码执行。 // try catch 可以捕获多个错误&#xff0c; try...catch...catch.... …

【ETAS CP AUTOSAR基础软件】EcuM模块详解

文章包含了AUTOSAR基础软件&#xff08;BSW&#xff09;中EcuM模块相关的内容详解。本文从AUTOSAR规范解析&#xff0c;ISOLAR-AB配置以及模块相关代码分析三个维度来帮读者清晰的认识和了解EcuM。文中涉及的SOLAR-AB配置以及模块相关代码都是依托于ETAS提供的工具链来配置与生…

桃金娘T2T基因组-文献精读17

Gap-free genome assembly and comparative analysis reveal the evolution and anthocyanin accumulation mechanism of Rhodomyrtus tomentosa 无缺口基因组组装及比较分析揭示了桃金娘的进化和花青素积累机制 摘要 桃金娘&#xff08;Rhodomyrtus tomentosa&#xff09;是…

鸿蒙工程目录介绍

鸿蒙构建完毕生成hhvp文件。 项目结构&#xff1a; .hvigor : 是存储构建配置文件的 .idea : 是开发工具拥有的目录 AppScope : 是全局的公共资源存放位置 hvigor &#xff1a;存放前端构建配置信息 oh_modules : 存放项目用到的第三方包 build-profile.json5 : 应用级别的构…

Git基本配置,使用Gitee(一)

1、设置Giter的user name和email 设置提交用户的信息 git config --global user.name "username" git config --global user.email "Your e-mail"查看配置 git config --list2、生成 SSH 公钥 通过命令 ssh-keygen 生成 SSH Key -t key 类型 -C 注释 ssh-…

客户文章|难能可贵,非模式生物的功能研究与创新

菜豆&#xff08;Phaseolus vulgaris&#xff09;&#xff0c;又名四季豆、芸豆、油豆角&#xff0c;是全球第一大豆类蔬菜&#xff0c;我国是世界上最主要的菜豆生产国和销售国。在田间生产过程中&#xff0c;菜豆常面临着各种生物和非生物逆境的胁迫&#xff0c;对其产量品质…

匹配字符串

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Python提供了re模块&#xff0c;用于实现正则表达式的操作。在实现时&#xff0c;可以使用re模块提供的方法&#xff08;如search()、match()、finda…

elementui中的el-checkbox-group添加全选按钮

//多选子组件 <template><div class"multiple-choice"><el-checkbox class"no1" v-if"isShowAllBtn" :indeterminate"isIndeterminate1" v-model"checkAll1" border :style"{borderColor:isIndetermina…

鸿蒙ArkTS声明式开发:跨平台支持列表【显隐控制】 通用属性

显隐控制 控制组件是否可见。 说明&#xff1a; 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本…

微型导轨在自动化制造中有哪些优势?

微型导轨在自动化制造中发挥重要作用&#xff0c;能够满足自动化设备制造中对精度要求较高的工艺环节。适用于自动装配线、自动检测设备和机器人操作等环节&#xff0c;推动了行业的进步与发展。那么&#xff0c;微型导轨在使用中有哪些优势呢&#xff1f; 1、精度高和稳定性强…

基于鲲鹏服务器搭建简单的开源论坛系统(LAMP)实践分享

LAMPLinux apache mysql( mariadb) PHP 结合利用华为云弹性负载均衡ELB弹性伸缩AS服务 优点&#xff1a; 将访问流量自动分发到多台云服务器&#xff0c;扩展应用系统对外的服务能力&#xff0c;实现更高水平的应用容错&#xff1b; 根据不同的业务、访问需求和预设策略&…