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

news2024/11/18 9:45:27

目录

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 = 0
    const customId = merge.geometry.getAttribute('customId')  
    if(!customId || !mergeId.value.length) return
    for (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 - 1
        return { 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 none

			if ( 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 different

			for ( 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 attributes

			if ( 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 different

			if ( 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 .userData

			mergedGeometry.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 indices

		if ( 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 attributes

		for ( 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 attributes

		for ( 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.coloradmin.cn/o/1664946.html

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

相关文章

Postgresql中JIT函数能否inline的依据function_inlinable

相关 《Postgresql源码&#xff08;128&#xff09;深入分析JIT中的函数内联llvm_inline》 《LLVM的ThinLTO编译优化技术在Postgresql中的应用》 前置阅读&#xff1a;《Postgresql源码&#xff08;128&#xff09;深入分析JIT中的函数内联llvm_inline》 在JIT inline函数的过…

2024高安全个人密码本程序源码,贴身密码管家-随机密码备忘录二代密码

项目概述&#xff1a; 在这个网络高度发展的时代&#xff0c;每个人都需要上网&#xff0c;而上网就不可避免地需要使用账号和密码。 在众多账号的情况下&#xff0c;你是否还在为复杂难记的密码感到烦恼&#xff1f;现在只需要记录一次&#xff0c; 就可以随时查看你的密码…

用websocket实现一个简单的im聊天功能

WebSocket前后端建立以及使用-CSDN博客 经过我上一篇的基本理解websocket的建立以及使用后&#xff0c;这篇就写一个简单的demo 实现im聊天 首先就是后端代码&#xff0c;详细解释我都放到了每一句的代码解析了&#xff0c;文章最后我会说怎么运行流程 放置后端代码 packa…

半小时搞懂STM32面经知识点——系统架构与启动流程

1.Cortex-M系统 1.1系统结构 1.处理器核心&#xff1a; Cortex-M3 2.存储器系统&#xff1a; Flash&#xff0c;SRAM&#xff0c;FSMC等 3.总线接口&#xff1a; 核心通过总线接口与外设设备和存储器进行通信。 总线矩阵&#xff1a;总线矩阵是一种硬件结构&#xff0c;用于连…

libcity笔记:

1 __init__ 2 encode 得到的内容如下&#xff1a; data_feature的内容&#xff1a; 一共有多少个location1【包括pad的一个】最长的时间间隔&#xff08;秒&#xff09;最长的距离间隔&#xff08;千米&#xff09;多少个useer idpadding 的locationidpad_item的内容 location…

社交媒体数据恢复:飞书

飞书数据恢复过程包括以下几个步骤&#xff1a; 确认数据丢失&#xff1a;首先要确认数据是否真的丢失&#xff0c;有时候可能只是被隐藏或者误操作删除了。 检查回收站&#xff1a;飞书中删除的文件会默认保存在回收站中&#xff0c;用户可以通过进入回收站找到被删除的文件&…

【北京迅为】《iTOP-3588从零搭建ubuntu环境手册》-第5章 安装SSH

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

C++中调用python函数(VS2017+WIN10+Anaconda虚拟环境)

1.利用VS创建C空项目 step1 文件——新建——项目 step2 Visual C—— Windows桌面——Windows桌面向导 step3 选择空项目 step4 源文件——新建项——添加 step5 Visual C——C文件&#xff08;.cpp&#xff09; 2.配置环境 Step1. 更换成Release与X64 Step2. 打开项目属性&…

2 GPIO控制

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

20240511,谓词,内建函数对象

拜托铠甲勇士真的帅好不好&#xff01;&#xff01;&#xff01; STL案例2-员工分组 10个员工&#xff0c;指派部门&#xff0c;员工信息&#xff08;姓名&#xff0c;工资组成&#xff0c;部门&#xff1a;策划&#xff0c;美术&#xff0c;研发&#xff09;&#xff0c;随机…

量子波函数白话解释

关键词&#xff1a;Quantum Wave Function 文章目录 一、说明二、什么是波函数&#xff1f;三 量子波的可视化四、量子波的概率解释 一、说明 在量子力学中&#xff0c;粒子是我们只有在测量它们时才能看到的东西。其中运动模式由满足薛定谔方程的波函数描述。波函数并非量子…

基于Huffman编码的字符串统计及WPL计算

一、问题描述 问题概括&#xff1a; 给定一个字符串或文件&#xff0c;基于Huffman编码方法&#xff0c;实现以下功能&#xff1a; 1.统计每个字符的频率。 2.输出每个字符的Huffman编码。 3.计算并输出WPL&#xff08;加权路径长度&#xff09;。 这个问题要求对Huffman编码算…

AppBuilder低代码体验:构建雅思大作文组件

AppBuilder低代码体验&#xff1a;构建雅思大作文组件 ​ 在4月14日&#xff0c;AppBuilder赢来了一次大更新&#xff0c;具体更新内容见&#xff1a;AppBuilder 2024.04.14发版上线公告 。本次更新最大的亮点就是**新增了工作流&#xff0c;低代码制作组件。**具体包括&#x…

[Cmake Qt]找不到文件ui_xx.h的问题?有关Qt工程的问题,看这篇文章就行了。

前言 最近在开发一个组件&#xff0c;但是这个东西是以dll的形式发布的界面库&#xff0c;所以在开发的时候就需要上层调用。 如果你是很懂CMake的话&#xff0c;ui_xx.h的文件目录在 ${CMAKE_CURRENT_BINARY_DIR} 下 然后除了有关这个ui_xx.h&#xff0c;还有一些别的可以简…

付费文章合集第二期

☞☞付费文章合集第一期 感谢大家一年来的陪伴与支持&#xff01; 对于感兴趣的文章点标题能跳转原文阅读啦~~ 21、Matlab信号处理——基于LSB和DCB音频水印嵌入提取算法 22、CV小目标识别——AITOD数据集&#xff08;已处理&#xff09; 23、Matlab信号发生器——三角波、…

OSError: [WinError 1455] 页面文件太小,无法完成操作 的问题

实质问题是报错&#xff1a;caffe2_detectron_ops.dll“ or one of its dependencies 还需要安装一个包&#xff1a; pip install intel-openmp 安装之后顺利测试通过。

设计模式之数据访问对象模式

在Java编程的浩瀚星海中&#xff0c;有一个模式低调却强大&#xff0c;它像是一位默默无闻的超级英雄&#xff0c;支撑起无数应用的数据脊梁——那就是数据访问对象&#xff08;DAO, Data Access Object&#xff09;模式&#xff01;想象一下&#xff0c;如果你能像操纵魔法一样…

三大消息传递机制区别与联系

目录 总结放开头 1、定义区别&#xff1a; EventBus Broadcast Receiver Notification 2、使用区别: EventBus Broadcast Receiver Notification 3、补充通知渠道&#xff1a; 通知渠道重要程度 总结放开头 BroadCast Receiver:属于安卓全局监听机制&#xff0c;接收…

Linux下安装mysql8.0(以rpm包安装)

前言&#xff1a;原文在我的博客网站中&#xff0c;持续更新数通、系统方面的知识&#xff0c;欢迎来访&#xff01; Linux下安装mysql8.0&#xff08;以rpm包安装&#xff09;https://myweb.myskillstree.cn/125.html 目录 1、查操作系统信息 2、下载mysql 8.0.34的rpm包 …