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.hqwc.cn/news/689992.html

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

相关文章

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

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

蛋糕店做配送小程序的作用是什么

蛋糕烘焙除了生日需要&#xff0c;对喜吃之人来说往往复购率较高&#xff0c;除线下实体店经营外&#xff0c;更多的商家选择线上多种方式获客转化、持续提高生意营收&#xff0c;而除了进驻第三方平台外&#xff0c;构建品牌私域自营店铺也同样重要。 运用【雨科】平台搭建蛋…

用友畅捷通T+ keyEdit sql注入漏洞

产品介绍 畅捷通 T 是一款灵动&#xff0c;智慧&#xff0c;时尚的基于互联网时代开发的管理软件&#xff0c;主要针对中小型工贸与商贸企业&#xff0c;尤其适合有异地多组织机构&#xff08;多工厂&#xff0c;多仓库&#xff0c;多办事处&#xff0c;多经销商&#xff09;的…

ue引擎游戏开发笔记(37)——实现造成伤害

1.需求分析&#xff1a; 在游戏中已经能够射击&#xff0c;并且能得到实际的落点反馈&#xff0c;但本质上这种射击没有任何实际数值伤害&#xff0c;为射击添加实际的子弹伤害数值。 2.操作实现&#xff1a; 1.思路&#xff1a;ue本身函数FPointDamageEvent就可以解决&#x…

stata空间计量模型基础+检验命令LM检验、sem、门槛+arcgis画图

目录 怎么安装stata命令 3怎么使用已有的数据 4数据编辑器中查看数据 4怎么删除不要的列 4直接将字符型变量转化为数值型的命令 4改变字符长度 4描述分析 4取对数 5相关性分析 5单位根检验 5权重矩阵标准化 6计算泰尔指数 6做核密度图 7Moran’s I 指数 8空间计量模型 9LM检验…

node——使用localtunnel做内网穿透

前言 内网穿透是一种允许外网用户访问内网主机的技术。 将您的本地主机公开到世界各地&#xff0c;使之能访问&#xff0c;无需混淆DNS或部署。 内网穿透技术通常涉及以下几个关键步骤&#xff1a; 使用公网服务器或NAT&#xff08;网络地址转换&#xff09;设备&#xff1a;这…

【小红书采集工具】根据搜索关键词批量采集小红书笔记,含笔记正文、笔记链接、发布时间、转评赞藏等

一、背景介绍 1.1 爬取目标 熟悉我的小伙伴都了解&#xff0c;我之前开发过2款软件&#xff1a; 【GUI软件】小红书搜索结果批量采集&#xff0c;支持多个关键词同时抓取&#xff01; 【GUI软件】小红书详情数据批量采集&#xff0c;含笔记内容、转评赞藏等&#xff0c;支持…

在 Kubernetes 上运行 Apache Spark 进行大规模数据处理的实践

在刚刚结束的 Kubernetes Community Day 上海站&#xff0c;亚马逊云科技在云原生分论坛分享的“在 Kunernets 上运行 Apache Spark 进行大规模数据处理实践”引起了现场参与者的关注。开发者告诉我们&#xff0c;为了充分利用 Kubernetes 的高可用设计、弹性&#xff0c;在越来…

【Linux】-IP地址、主机名配置[5]

目录 一、IP和主机名 1、IP地址 2、特殊IP地址 3、主机名 4、在Linux中修改主机名 5、配置主机名映射 二、虚拟机配置固定IP 1、为什么需要固定IP 2、在VMware Workstation中配置固定ip 一、IP和主机名 1、IP地址 每一台联网的电脑都会有一个地址&#xff0c;用于和…

AGV混合型电机驱动器|伺服控制器CNS-MI50H系列对电机的要求

混合型电机驱动器 CNS-MI50H系列涵盖CNS-MI50HB-A、CNS-MI50HBN-A、CNS-MI50HDN-A、CNS-MI50HSN-A型号&#xff0c;专为 AGV 舵轮控制需求设计&#xff0c;集成舵轮转向角度控制和驱动电机闭环控制。支持增量式编码器&#xff0c;霍尔传感器&#xff0c; 角度电位计&#xff0c…

网络运维故障排错思路!!!!!(稳了!!!)

1 网络排错的必备条件 为什么要先讲必备条件&#xff1f;因为这里所讲的网络排错并不仅仅是停留在某一个小小命令的使用上&#xff0c;而是一套系统的方法&#xff0c;如果没有这些条件&#xff0c;我真的不能保证下面讲的这些你可以听得懂&#xff0c;并且能运用到实际当中&a…

前端笔记-day03

文章目录 01-初始CSS02-CSS引入方式03-标签选择器04-类选择器05-id选择器06-通配符选择器07-画盒子08-字体大小09-文字粗细10-字体倾斜11-行高12-行高垂直居中13-字体族14-font复合属性15-文本缩进16-文本对齐方式17-图片对齐方式18-文本修饰线19-文字颜色20-调试工具21-综合案…