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 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变量是在整个渲染过程中保持不变的,而数组类型通常需要在编译时知道其大小

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

极验4点选逆向 JS逆向分析 最新版验证码

目录 声明&#xff01; 一、请求流程分析 二、加密参数w与payload 三、参数w生成位置 四、结果展示&#xff1a; 原创文章&#xff0c;请勿转载&#xff01; 本文内容仅限于安全研究&#xff0c;不公开具体源码。维护网络安全&#xff0c;人人有责。 声明&#xff01; 本文章…

mirth Connect 自定义JAVA_HOME

mirth Connect 自定义JAVA_HOME 1、背景 服务器上安装了两个不同版本的Java&#xff0c;我希望Mirth服务使用与默认系统不同的版本。自定义指定java版本 2、解决方法 2.1 优先级说明 系统变量JAVA_HOME (设置后&#xff0c;mirth会根据这个进行启动运行服务&#xff0c;优先级…

家政预约小程序10公众号集成

目录 1 使用测试号3 工作流配置4 配置关注事件脚本5 注册开放平台6 获取公众号access_token6 实现关注业务逻辑总结 我们本次实战项目构建的相当于一个预约平台&#xff0c;既有家政企业&#xff0c;也有家政服务人员还有用户。不同的人员需要收到不同的消息&#xff0c;比如用…

根据状态转移图实现时序电路 (三段式状态机)

看图编程 * ** 代码 module seq_circuit(input C ,input clk ,input rst_n,output wire Y ); reg [1:0] current_stage ; reg [1:0] next_stage ; reg Y_reg; //输出//第一段 &#xff1a; 初始化当前状态和…

vmware esxi虚拟化数据迁移

1、启用esxi的ssh 登录esxi的web界面&#xff0c;选择主机-》操作——》服务——》启动ssh 2.xshell登录esxi 3、找到虚拟机所在目录 blog.csdnimg.cn/direct/d57372536a4145f2bcc1189d02cc7da8.png)#### 3在传输数据前需关闭防火墙服务 查看防火墙状态&#xff1a;esxcli …

vue3学习(六)

前言 接上一篇学习笔记&#xff0c;今天主要是抽空学习了vue的状态管理&#xff0c;这里学习的是vuex&#xff0c;版本4.1。学习还没有学习完&#xff0c;里面有大坑&#xff0c;难怪现在官网出的状态管理用Pinia。 一、vuex状态管理知识点 上面的方式没有写全&#xff0c;还有…

如何修改开源项目中发现的bug?

如何修改开源项目中发现的bug&#xff1f; 目录 如何修改开源项目中发现的bug&#xff1f;第一步&#xff1a;找到开源项目并建立分支第二步&#xff1a;克隆分支到本地仓库第三步&#xff1a;在本地对项目进行修改第四步&#xff1a;依次使用命令行进行操作注意&#xff1a;Gi…

OAK相机如何将 YOLOv9 模型转换成 blob 格式?

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是Ashely。 专…

逆天工具一键修复图片,视频去码。简直不要太好用!

今天&#xff0c;我要向您推荐一款功能强大的本地部署软件&#xff0c;它能够在您的计算机上一键修复图片和视频&#xff0c;去除令人不悦的码赛克&#xff08;轻度马赛克&#xff09;。这款软件是开源的&#xff0c;并在GitHub上公开可用&#xff0c;您可以免费下载并使用。 …

智能制造案例专题|与MongoDB一起解锁工业4.0转型与增长的无限潜力!

MongoDB 智能制造 数字化技术的洪流在各个产业链的主干和枝节涌现。在工业制造领域&#xff0c;能否通过数字化技术实现各生产要素、生产环节之间的紧密配合&#xff0c;高效规划、管理整个生产流程&#xff0c;是企业提升韧性、赢得竞争的关键。随着工业4.0的深入发展和智能…

Kafka自定义分区器编写教程

1.创建java类MyPartitioner并实现Partitioner接口 点击灯泡选择实现方法&#xff0c;导入需要实现的抽象方法 2.实现方法 3.自定义分区器的使用 在自定义生产者消息发送时&#xff0c;属性配置上加入自定义分区器 properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,&q…

stack和queue(1)

一、stack的简单介绍和使用 1.1 stack的介绍 1.stack是一种容器适配器&#xff0c;专门用在具有先进后出&#xff0c;后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入和弹出操作。 2.stack是作为容器适配器被实现的&#xff0c;容器适配器即是…

C++ 混合运算的类型转换

一 混合运算和隐式转换 257 整型2 浮点5 行吗&#xff1f;成吗&#xff1f;中不中&#xff1f; C 中允许相关的数据类型进行混合运算。 相关类型。 尽管在程序中的数据类型不同&#xff0c;但逻辑上进行这种运算是合理的相关类型在混合运算时会自动进行类型转换&#xff0c;再…

EXCEL数据透视图中的日期字段,怎样自动分出年、季度、月的功能?

在excel里&#xff0c;这个果然是有个设置的地方&#xff0c;修改后就好了。 点击文件选项卡&#xff0c;选项&#xff0c;在高级里&#xff0c;将图示选项的勾选给取消&#xff0c;然后再创建数据透视表或透视图&#xff0c;日期就不会自动组合了&#xff1a; 这个选项只对新…

Scroll 生态明星项目Pencils Protocol,发展潜力巨大

近日&#xff0c;完成品牌升级的 Pencils Prtocol 结束了 Season 2 并无缝开启了 Season 3&#xff0c;在 Season 3 中&#xff0c;用户可以通过质押系列资产包括 $ETH、$USDT、$USDC、$STONE 、$wrsETH、$pufETH 等来获得可观收益&#xff0c;并获得包括 Scroll Marks、 Penci…

html+CSS部分基础运用9

项目1 参会注册表 1.设计参会注册表页面&#xff0c;效果如图9-1所示。 图9-1 参会注册表页面 项目2 设计《大学生暑期社会实践调查问卷》 1.设计“大学生暑期社会实践调查问卷”页面&#xff0c;如图9-2所示。 图9-2 大学生暑期社会调查表页面 2&#xff0e;调查表前导语的…

C/C++中互斥量(锁)的实现原理探究

互斥量的实现原理探究 文章目录 互斥量的实现原理探究互斥量的概念何为原子性操作原理探究 互斥量的概念 ​ 互斥量&#xff08;mutex&#xff09;是一种同步原语&#xff0c;用于保护多个线程同时访问共享数据。互斥量提供独占的、非递归的所有权语义&#xff1a;一个线程从成…

LeetCode374猜数字大小

题目描述 我们正在玩猜数字游戏。猜数字游戏的规则如下&#xff1a;我会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。如果你猜错了&#xff0c;我会告诉你&#xff0c;我选出的数字比你猜测的数字大了还是小了。你可以通过调用一个预先定义好的接口 int guess(int n…

代码随想录算法训练营第四十四天 | 01背包问题 二维、 01背包问题 一维、416. 分割等和子集

01背包问题 二维 代码随想录 视频讲解&#xff1a;带你学透0-1背包问题&#xff01;| 关于背包问题&#xff0c;你不清楚的地方&#xff0c;这里都讲了&#xff01;| 动态规划经典问题 | 数据结构与算法_哔哩哔哩_bilibili 1.dp数组定义 dp[i][j] 下标为[0,i]之间的物品&…

2024盘古石初赛(服务器部分)

赛后总结 这次初赛就有20道服务器部分赛题&#xff0c;做的情况一般&#xff0c;错了5道题这样&#xff0c;主要原因就是出在第二个网站服务器没有重构起来 今天来复现一下 这次的服务器部分我直接用仿真仿起来就开找了 第一台IM前期配置 先把网配置好&#xff0c;然后ssh…