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

注意 

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cdo | 常用命令

整理一下平时经常会使用的cdo命令 如何来更改netcdf数据中的变量名呢&#xff1f; 假设我现在有一个sst月平均数据,希望将里面的变量名称sst修改为sst_new netcdf oisst_monthly { dimensions:lat 180 ;lon 360 ;time UNLIMITED ; // (476 currently)nbnds 2 ; variable…

音视频开发14 FFmpeg 视频 相关格式分析 -- H264 NALU格式分析

H264简介-也叫做 AVC H.264&#xff0c;在MPEG的标准⾥是MPEG-4的⼀个组成部分–MPEG-4 Part 10&#xff0c;⼜叫Advanced Video Codec&#xff0c;因此常常称为MPEG-4 AVC或直接叫AVC。 原始数据YUV,RGB为什么要压缩-知道就行 在⾳视频传输过程中&#xff0c;视频⽂件的传输…

Element快速入门

Vue组件库Element 1 Element介绍 vue是侧重于VM开发的&#xff0c;主要用于数据绑定到视图的&#xff0c;ElementUI就是一款侧重于V开发的前端框架&#xff0c;主要用于开发美观的页面的。 Element&#xff1a;是饿了么公司前端开发团队提供的一套基于 Vue 的网站组件库&…

使用pytorch搭建textCNN、BERT、transformer进行文本分类

首先展示数据处理后的类型&#xff1a; 第一列为文本&#xff0c;第二类为标注的标签&#xff0c;数据保存在xlsx的表格中&#xff0c;分为训练集和验证集。 textCNN 直接上整个工程代码&#xff1a; import pandas as pd import numpy as np import torch from torch.util…

SAPUI5基础知识3 - 引导过程(Bootstrap)

1. 背景 在上一篇博客中&#xff0c;我们已经建立出了第一个SAPUI5项目&#xff0c;接下来&#xff0c;我们将为这个项目添加引导过程。 在动手练习之前&#xff0c;让我们先解释一下什么引导过程。 1.1 什么是引导过程&#xff1f; 在计算机科学中&#xff0c;引导过程也称…

Presto 从提交SQL到获取结果 源码详解(3)

物理执行计划 回到SqlQueryExecution.startExecution() &#xff0c;执行计划划分以后&#xff0c; // 初始化连接&#xff0c;获取Connect 元数据&#xff0c;添加会话&#xff0c;初始ConnectId metadata.beginQuery(getSession(), plan.getConnectors()); // 构建物理执行…

你真的会用收藏夹吗?可道云teamOS收藏夹,竟能缩短多层级文件夹的路径,实现快速访问

在日常工作中&#xff0c;我们时常会面临一个让人头疼的问题&#xff1a;如何在海量的文件和资料中快速找到我们需要的那一份&#xff1f; 尤其是在团队协作中&#xff0c;每个人都在不断地上传、更新文件……导致文件目录层级复杂&#xff0c;搜索也变得繁琐。 这时候&#x…

编程学习 (C规划) 6 {24_4_18} 七 ( 简单扫雷游戏)

首先我们要清楚扫雷大概是如何实现的&#xff1a; 1.布置雷 2.扫雷&#xff08;排查雷&#xff09; &#xff08;1&#xff09;如果这个位置是雷就炸了&#xff0c;游戏结束 &#xff08;2&#xff09;如果不是雷&#xff0c;就告诉周围有几个雷 3.把所有不是雷的位置都找…

一周学会Django5 Python Web开发 - Django5内置Admin系统二次开发

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计56条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

【C++】Vector的简易模拟与探索

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

模型 FABE(特性 优势 好处 证据)法则

说明&#xff1a;系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。特性、优势、好处、证据&#xff0c;一气呵成。 1 FABE法则的应用 1.1 FABE法则营销商用跑步机 一家高端健身器材公司的销售代表正在向一家新开的健身房推销他们的商用跑步机。以下…

Microsoft Dynamics 365 Business Central 讲解VAT RATE CHANGE TOOL(增值税税率更改工具)

学习目标&#xff1a; 如果使用VAT RATE CHANGE TOOL&#xff08;增值税税率更改工具&#xff09; 过程演示&#xff1a; 1.创建新的VAT产品过账组 2.创建新的总账科目以过账采购、销售和逆向征收增值税。 3.给新的VAT产品过账设置过账设置 4.创建一个新的一般产品过账组 5…

CUDA学习(2)

什么是CUDA CUDA&#xff08;Compute Unified Device Architecture&#xff09;&#xff0c;统一计算设备架构&#xff0c;英伟达推出的基于其GPU的通用高性能计算平台和编程模型。 借助CUDA&#xff0c;开发者可以充分利用英伟达GPU的强大计算能力加速各种计算任务。 软件生…

手写HTML字符串解析成对应的 AST语法树

先看效果 展示如下&#xff1a; HTML模版 转成ast语法树后 在学习之前&#xff0c;我们需要了解这么一个问题&#xff0c;为什么要将HTML字符串解析成对应的 AST语法树。 为什么&#xff1f; 语法分析&#xff1a;HTML字符串是一种标记语言&#xff0c;其中包含了大量的标签…

chap5 CNN

卷积神经网络&#xff08;CNN&#xff09; 问题描述&#xff1a; 利用卷积神经网络&#xff0c;实现对MNIST数据集的分类问题 数据集&#xff1a; MNIST数据集包括60000张训练图片和10000张测试图片。图片样本的数量已经足够训练一个很复杂的模型&#xff08;例如 CNN的深层…

gcc 内建函数示例 __builtin_return_address

1,理论未动&#xff0c;示例先行 hello_gcc_callstack.c #include <stdio.h>void do_backtrace() {void *pc0 __builtin_return_address(0);void *pc1 __builtin_return_address(1);void *pc2 __builtin_return_address(2);void *pc3 __builtin_return_address(3);…

低边驱动与高边驱动

一.高边驱动和低边驱动 低边驱动(LSD): 在电路的接地端加了一个可控开关&#xff0c;低边驱动就是通过闭合地线来控制这个开关的开关。容易实现&#xff08;电路也比较简单&#xff0c;一般由MOS管加几个电阻、电容&#xff09;、适用电路简化和成本控制的情况。 高边驱动&am…

JVM哪些区域可能出现内存溢出,哪些地方需要GC?

GC顾名思义也就是垃圾回收&#xff0c;有人的地方就有江湖&#xff0c;那有数据的地方也理应有垃圾回收&#xff0c;所以思考一下&#xff0c;沿着之前提到过的JVM内存分区&#xff0c;堆&#xff0c;栈&#xff0c;程序计数器&#xff0c;方法区 堆、栈、方法区…

一键安装 HaloDB 之 Ansible for Halo

↑ 关注“少安事务所”公众号&#xff0c;欢迎⭐收藏&#xff0c;不错过精彩内容~ 前倾回顾 前面介绍了“光环”数据库的基本情况和安装办法。 哈喽&#xff0c;国产数据库&#xff01;Halo DB! 三步走&#xff0c;Halo DB 安装指引 以及 HaloDB 的 Oracle 和 MySQL 兼容模式: …

ChatGPT-4o 有何特别之处?

文章目录 多模态输入&#xff0c;多模态输出之前的模型和现在模型对比 大家已经知道&#xff0c;OpenAI 在 GPT-4 发布一年多后终于推出了一个新模型。它仍然是 GPT-4 的一个变体&#xff0c;但具有前所未见的多模态功能。 有趣的是&#xff0c;它包括实时视频处理等强大功能&…