Threejs Shader动态修改Merge合并几何体中单个Mesh的颜色

目录

Merge合并

现象

思路

实现

为单个geometry添加映射

通过id检索Merge后的Geometry映射属性,获取顶点坐标

onBeforeCompile修改编译前材质的着色代码

编译前材质的顶点着色代码

编译前材质的片元着色代码

着色器代码

注意

效果 


Merge合并

mergeBufferGeometries 是用于合并多个几何体(BufferGeometry)为一个几何体的工具。这种方法适用于当有大量的几何体需要渲染时,可以将它们合并为一个几何体来减少渲染调用,从而提高性能。合并后的几何体将会生成一个大的缓冲区,包含了所有合并的几何体的数据,这样在渲染时只需一次性加载这个大缓冲区即可,减少了渲染调用和资源占用。

现象

mergeBufferGeometries 是将每个小geometry的顶点信息做合并,所有的顶点坐标按序存放在合并后的缓冲对象 position数组中。一个大的geometry对应一个材质生成一个合并后的物体

由于没有单个的概念,也就无法通过直接修改材质实现对单个geometry的控制

思路

  1. 给每个geometry添加缓冲属性,存储 id和geometry顶点数量。merge合并后,每个geometry的自定义映射属性会同position一样push在一个数组中
  2. 要单个控制时:通过id检索映射数组,可以得到当前geometry的顶点数量,从而得到这段顶点在merge的position中的位置
  3. 根据当前geometry顶点坐标,通过onBeforeCompile,修改材质的着色代码

实现

为单个geometry添加映射

每执行次函数创建一个geometry,为当前几何体添加自定义属性customId,存储当前id和顶点数量,每两个为1组

function createLineGeometry(points: Array<Vector3>, id: number) {const geometry = new BufferGeometry();geometry.setFromPoints(points);const position = geometry.getAttribute('position')const customIdAttribute = new BufferAttribute(new Int16Array([id, position.count]), 2)geometry.setAttribute('customId', customIdAttribute);return geometry;
}

如下图,id为0,geometry顶点数量为24 

当前几何体的postion(24*3)

通过id检索Merge后的Geometry映射属性,获取顶点坐标

如下,Merge后的Geometry,每个geometry的id和顶点数依次存放在customId中(奇数id,偶数顶点数量)

当前合并了32个geometry,每个几何体的顶点数都是24(合并时,顶点数量不一定一致,这也是要映射顶点数的关键)

Merge后的position

如下函数,检索customId数组,根据id获取当前顶点在总顶点中的开始索引,结束索引

例如,要控制id为1的geometry,此函数应该返回 24、47

  const getGeometryVextexHeadTailIndex = (merge) => {// 当前几何体的顶点数量let vertexCount = 0// 顶点起始索引let vertexStartIndex = 0// 顶点结束索引let vertexEndIndex = 0const customId = merge.geometry.getAttribute('customId')  if(!customId || !mergeId.value.length) returnfor (let i = 0; i < customId.array.length; i+=2) {if (customId.array[i] == mergeId.value) {// 检索到id,+1 偶 则为当前顶点数vertexCount = customId.array[i + 1]vertexEndIndex = vertexStartIndex + vertexCount - 1return { vertexStartIndex, vertexEndIndex }}vertexStartIndex += customId.array[i + 1]}  }

onBeforeCompile修改编译前材质的着色代码

根据顶点索引,顶点着色器动态传递Varying类型的高亮色,片元着色器会收到插值后的Varying Color,判断当前片元的插值颜色是否和uniform的高亮色一致,一致则修改,效果达成(要高亮的所有顶点组成的每个图元一个色,所以插值后的每个片元也是这个色)

编译前材质的顶点着色代码

对 void main 进行修改

编译前材质的片元着色代码

对 void main 和 vec4 diffuseColor = vec4( diffuse, opacity ); 进行修改

着色器代码

  const beforeCompileMaterial = (merge, { vertexStartIndex, vertexEndIndex }) => {// console.log(vertexStartIndex, vertexEndIndex);merge.material.onBeforeCompile = (shader) => {/* 三个uniform开始索引结束索引高亮色*/shader.uniforms.vertexStartIndex = { value: vertexStartIndex };shader.uniforms.vertexEndIndex = { value: vertexEndIndex };shader.uniforms.highLightColor = { value: merge.highLightColor };// 修改顶点着色器shader.vertexShader = shader.vertexShader.replace('void main() {',['uniform int vertexStartIndex;','uniform int vertexEndIndex;',   'uniform vec3 highLightColor;',   'varying vec3 vColor;','void main() {',// 如果当前顶点索引在 起止索引 间,varing向片元传递高亮色`if(gl_VertexID >= vertexStartIndex && gl_VertexID <= vertexEndIndex) {`,'vColor = highLightColor;','}'].join('\n'))// 修改片元着色器shader.fragmentShader = shader.fragmentShader.replace('void main() {',['uniform vec3 highLightColor;',   // 如果顶点着色器与片元着色器中有类型和命名都相同的varying变量,那么顶点着色器赋给该变量的值就会被自动地传入片元着色器'varying vec3 vColor;','void main() {'].join('\n'))shader.fragmentShader = shader.fragmentShader.replace('vec4 diffuseColor = vec4( diffuse, opacity );',['vec4 diffuseColor;',  // 插值后的vColor,当前片元的vColor如果和高亮色一致'if(vColor == highLightColor) {',// 修改当前片元为高亮色'diffuseColor = vec4( vColor, 1.0 );','} else {',// 别的片元不变'diffuseColor = vec4( diffuse, opacity );','}'].join('\n'))}}

注意

为每个小geometry添加映射时,需要添加缓冲属性,而不是直接类js添加属性,因为Merge的源码是循环geometry数组,逐个push每个geometry的缓冲属性(源码38 ~ 53行),无需修改源码,性能消耗也比较友好

mergeBufferGeometries 源码 

	/*** @param  {Array<BufferGeometry>} geometries* @param  {Boolean} useGroups* @return {BufferGeometry}*/mergeBufferGeometries: function ( geometries, useGroups ) {var isIndexed = geometries[ 0 ].index !== null;var attributesUsed = new Set( Object.keys( geometries[ 0 ].attributes ) );var morphAttributesUsed = new Set( Object.keys( geometries[ 0 ].morphAttributes ) );var attributes = {};var morphAttributes = {};var morphTargetsRelative = geometries[ 0 ].morphTargetsRelative;var mergedGeometry = new BufferGeometry();var offset = 0;for ( var i = 0; i < geometries.length; ++ i ) {var geometry = geometries[ i ];var attributesCount = 0;// ensure that all geometries are indexed, or noneif ( isIndexed !== ( geometry.index !== null ) ) {console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.' );return null;}// gather attributes, exit early if they're differentfor ( var name in geometry.attributes ) {if ( ! attributesUsed.has( name ) ) {console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. All geometries must have compatible attributes; make sure "' + name + '" attribute exists among all geometries, or in none of them.' );return null;}if ( attributes[ name ] === undefined ) attributes[ name ] = [];attributes[ name ].push( geometry.attributes[ name ] );attributesCount ++;}// ensure geometries have the same number of attributesif ( attributesCount !== attributesUsed.size ) {console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. Make sure all geometries have the same number of attributes.' );return null;}// gather morph attributes, exit early if they're differentif ( morphTargetsRelative !== geometry.morphTargetsRelative ) {console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. .morphTargetsRelative must be consistent throughout all geometries.' );return null;}for ( var name in geometry.morphAttributes ) {if ( ! morphAttributesUsed.has( name ) ) {console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '.  .morphAttributes must be consistent throughout all geometries.' );return null;}if ( morphAttributes[ name ] === undefined ) morphAttributes[ name ] = [];morphAttributes[ name ].push( geometry.morphAttributes[ name ] );}// gather .userDatamergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || [];mergedGeometry.userData.mergedUserData.push( geometry.userData );if ( useGroups ) {var count;if ( isIndexed ) {count = geometry.index.count;} else if ( geometry.attributes.position !== undefined ) {count = geometry.attributes.position.count;} else {console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed with geometry at index ' + i + '. The geometry must have either an index or a position attribute' );return null;}mergedGeometry.addGroup( offset, count, i );offset += count;}}// merge indicesif ( isIndexed ) {var indexOffset = 0;var mergedIndex = [];for ( var i = 0; i < geometries.length; ++ i ) {var index = geometries[ i ].index;for ( var j = 0; j < index.count; ++ j ) {mergedIndex.push( index.getX( j ) + indexOffset );}indexOffset += geometries[ i ].attributes.position.count;}mergedGeometry.setIndex( mergedIndex );}// merge attributesfor ( var name in attributes ) {var mergedAttribute = this.mergeBufferAttributes( attributes[ name ] );if ( ! mergedAttribute ) {console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed while trying to merge the ' + name + ' attribute.' );return null;}mergedGeometry.setAttribute( name, mergedAttribute );}// merge morph attributesfor ( var name in morphAttributes ) {var numMorphTargets = morphAttributes[ name ][ 0 ].length;if ( numMorphTargets === 0 ) break;mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {};mergedGeometry.morphAttributes[ name ] = [];for ( var i = 0; i < numMorphTargets; ++ i ) {var morphAttributesToMerge = [];for ( var j = 0; j < morphAttributes[ name ].length; ++ j ) {morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] );}var mergedMorphAttribute = this.mergeBufferAttributes( morphAttributesToMerge );if ( ! mergedMorphAttribute ) {console.error( 'THREE.BufferGeometryUtils: .mergeBufferGeometries() failed while trying to merge the ' + name + ' morphAttribute.' );return null;}mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute );}}return mergedGeometry;}

效果 

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

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

相关文章

redis深入理解之数据存储

1、redis为什么快 1&#xff09;Redis是单线程执行&#xff0c;在执行时顺序执行 redis单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#xff0c;Redis在处理客户端的请求时包括获取(socket 读)、解析、执行、内容返回 (socket 写)等都由一个顺序串行的主线…

sqli-labs第一关

1、提示我们需要传递一个id的参数 ?id1 2、判断是什么类型的注入&#xff08;字符or整形&#xff09;结果&#xff1a;存在字符型注入 ?id1 ?id1 -- 3、使用联合查询&#xff0c;查看有几列。结果&#xff1a;有3列 ?id1 order by 4 -- 4、查看这3列中哪几列会在页面显…

Lombok介绍、使用方法和安装

目录 1 Lombok背景介绍 2 Lombok使用方法 2.1 Data 2.2 Getter/Setter 2.3 NonNull 2.4 Cleanup 2.5 EqualsAndHashCode 2.6 ToString 2.7 NoArgsConstructor, RequiredArgsConstructor and AllArgsConstructor 3 Lombok工作原理分析 4. Lombok的优缺点 5. 总结 1 …

QueryPerformanceCounter实现高精度uS(微妙)延时

参考连接 C# 利用Kernel32的QueryPerformanceCounter封装的 高精度定时器Timer_kernel32.dll queryperformancecounter-CSDN博客https://blog.csdn.net/wuyuander/article/details/111831973 特此记录 anlog 2024年5月11日

土地档案管理关系参考论文(论文 + 源码)

【免费】javaEE土地档案管理系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89296786 土地档案管理关系 摘 要 研究土地档案管理关系即为实现一个土地档案管理系统。土地档案管理系统是将现有的历史纸质档案资料进行数字化加工处理&#xff0c;建成标准化的…

探索Linux:深入理解各种指令与用法

文章目录 cp指令mv指令cat指令more指令less指令head指令tail指令与时间相关的指令date指令 cal指令find指令grep指令zip/unzip指令总结 上一个Linux文章我们介绍了大部分指令&#xff0c;这节我们将继续介绍Linux的指令和用法。 cp指令 功能&#xff1a;复制文件或者目录 语法…

基于Qt的Model-View显示树形数据

目标 用qt的模型-视图框架实现树型层次节点的显示&#xff0c;从QAbstractItemModel派生自己的模型类MyTreeItemModel&#xff0c;用boost::property_tree::ptree操作树型数据结构&#xff0c;为了演示&#xff0c;此处只实现了个只读的模型 MyTreeItemModel的定义 #pragma o…

张驰咨询:AI与六西格玛——携手共进,非彼此替代

在历史的洪流中&#xff0c;技术与方法的演进如同波澜壮阔的画卷&#xff0c;不断书写着人类文明的篇章。六西格玛&#xff0c;作为一种追求极致品质与效率的方法论&#xff0c;是现代工业文明中的瑰宝。而当我们面对AI&#xff08;人工智能&#xff09;这一新时代的产物时&…

[leetcode] 68. 文本左右对齐

文章目录 题目描述解题方法贪心java代码复杂度分析 题目描述 给定一个单词数组 words 和一个长度 maxWidth &#xff0c;重新排版单词&#xff0c;使其成为每行恰好有 maxWidth 个字符&#xff0c;且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单词&#xff…

视频监控系统中,中心录像服务器的录像文件实际大小和理论值相差很大的问题解决

目录 一、现象描述 二、视频监控的录像文件计算 &#xff08;一&#xff09;计算方法 1、仅视频部分 2、视频和音频部分 3、使用平均码率 &#xff08;二&#xff09;计算工具 1、关注威迪斯特公众号 2、打开“计算容量”的小工具 三、原因分析 &#xff08;一&…

Redis 实战之命令请求的执行过程

命令请求的执行过程 发送命令请求读取命令请求命令执行器&#xff08;1&#xff09;&#xff1a;查找命令实现命令执行器&#xff08;2&#xff09;&#xff1a;执行预备操作命令执行器&#xff08;3&#xff09;&#xff1a;调用命令的实现函数命令执行器&#xff08;4&#x…

【回溯算法】【Python实现】最大团问题

文章目录 [toc]问题描述回溯算法Python实现时间复杂性 问题描述 给定无向图 G ( V , E ) G (V , E) G(V,E)&#xff0c;如果 U ⊆ V U \subseteq V U⊆V&#xff0c;且对任意 u u u&#xff0c; v ∈ U v \in U v∈U有 ( u , v ) ∈ E (u , v) \in E (u,v)∈E&#xff0c;则称…

Navicat导出表结构到Excel或Word

文章目录 sql语句复制到excel复制到Word sql语句 SELECTcols.COLUMN_NAME AS 字段,cols.COLUMN_TYPE AS 数据类型,IF(pks.CONSTRAINT_TYPE PRIMARY KEY, YES, NO) AS 是否为主键,IF(idxs.INDEX_NAME IS NOT NULL, YES, NO) AS 是否为索引,cols.IS_NULLABLE AS 是否为空,cols.…

13.Netty组件EventLoopGroup和EventLoop介绍

EventLoop 是一个单线程的执行器&#xff08;同时维护了一个Selector&#xff09;&#xff0c;里面有run方法处理Channel上源源不断的io事件。 1.继承java.util.concurrent.ScheduledExecutorService因此包含了线程池中所有的方法。 2.继承netty自己的OrderedEventExecutor …

JDBC调用MogDB存储过程返回ref_cursor的方法和注意事项

MogDB在处理存储过程的时候&#xff0c;有时候需要返回结果集&#xff0c;类型为ref_cursor&#xff0c;但有时候可能会报错。而大部分应用程序都是使用Java JDBC. 根据我们这几年的数据库国产化改造经验&#xff0c;给大家分享一下JDBC调用 MogDB存储过程返回ref_cursor的方法…

算法设计与分析 例题解答 解空间与搜索

1.请画出用回溯法解n3的0-1背包问题的解空间树和当三个物品的重量为{20, 15, 10}&#xff0c;价值为{20, 30, 25}&#xff0c;背包容量为25时搜索空间树。 答&#xff1a; 解空间树&#xff1a; 搜索空间树&#xff1a; 2. 考虑用分支限界解0-1背包问题 给定n种物品和一背包…

2 GPIO控制

ESP32的GPIO的模式&#xff0c;一共有输入和输出模式两类。其中输入模式&#xff1a;上拉输入、下拉输入、浮空输入、模拟输入&#xff1b;输出模式&#xff1a;输出模式、开漏输出&#xff08;跟stm32八种输入输出模式有所不同&#xff09;。库函数中控制引脚的函数如下&#…

行业新应用:电机驱动将成为机器人的动力核心

电机已经遍布当今社会人们生活的方方面面&#xff0c;不仅应用范围越来越广&#xff0c;更新换代的速度也日益加快。按照工作电源分类&#xff0c;可以将它划分为直流电机和交流电机两大类型。直流电机中&#xff0c;按照线圈类型分类&#xff0c;又可以分为有铁芯的电机、空心…

FFmpeg常用API与示例(四)——过滤器实战

1.filter 在多媒体处理中&#xff0c;filter 的意思是被编码到输出文件之前用来修改输入文件内容的一个软件工具。如&#xff1a;视频翻转&#xff0c;旋转&#xff0c;缩放等。 语法&#xff1a;[input_link_label1]… filter_nameparameters [output_link_label1]… 1、视…

书生浦语训练营第四次课作业

基础作业 环境配置 拷贝internlm开发机内的环境 studio-conda xtuner0.1.17# 激活环境 conda activate xtuner0.1.17 # 进入家目录 &#xff08;~的意思是 “当前用户的home路径”&#xff09; cd ~ # 创建版本文件夹并进入&#xff0c;以跟随本教程 mkdir -p /root/xtuner0…