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

	}

效果 

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Three.js r147 已不能直接访问几何体的 vertices 属性,需要通过其他方式来获取。例如,可以使用几何体的attributes属性或使用一些插件,如 THREE.GeometryUtils.merge。请根据您的需求选择适当的方法。 ### 回答2: 在three.js的r147版本之前,可以直接使用`geometry.vertices`属性来获取几何体的顶点信息。但自从r147版本开始,Three.js对几何体的数据结构进行了重构,不再直接暴露`vertices`属性。 在现在的版本,要获取几何体的顶点信息,可以通过以下步骤来实现: 1. 首先,创建一个几何体对象,例如创建一个方形的平面: ```javascript const geometry = new THREE.PlaneGeometry(10, 10, 1, 1); ``` 2. 然后,通过调用`geometry.attributes.position`获取顶点的`BufferAttribute`。`BufferAttribute`是一个包含几何体顶点信息的缓冲区对象。 ```javascript const positions = geometry.attributes.position.array; ``` 3. 最后,根据顶点的数量,可以通过`positions`数组来访问每个顶点的坐标。假设顶点集合每个顶点拥有3个坐标分量(x、y、z),可以通过以下方式访问第0个顶点的坐标: ```javascript const x = positions[0]; const y = positions[1]; const z = positions[2]; ``` 通过以上步骤,可以获取几何体的顶点信息,以便进行后续的计算或修改。 需要注意的是,不同版本的Three.js可能有不同的用法,如果你使用的是r147版本,那么以上的步骤应该能够满足你的需求。如果你使用的是更新版本的Three.js,建议参考相应版本的官方文档或示例代码,查找正确的方法来获取几何体的顶点信息。 ### 回答3: 在three.js版本r147之前,我们可以通过直接访问几何体的vertices属性来获取其顶点信息。但从r147版本开始,three.js不再直接暴露几何体的vertices属性。 这是因为在three.js几何体(Geometry)对象和缓冲几何体(BufferGeometry)对象分别用于表示顶点数据。r147版本之前的几何体对象使用一个数组来存储顶点的位置信息,而在r147版本之后,three.js推荐使用缓冲几何体来表示顶点数据。 新的缓冲几何体通过使用属性(Attribute)来定义顶点数据。属性类似于顶点数组,但提供了更灵活的数据访问方式和更高效的渲染性能。 要获取几何体的顶点信息,我们需要使用几何体对象的相关方法。例如,可以通过调用几何体对象的getAttribute方法来获取缓冲几何体的属性对象,然后通过属性对象的array属性访问顶点数据。 具体而言,可以使用以下代码获取缓冲几何体对象的顶点信息: ```javascript // geometry是几何体对象 const bufferGeometry = new THREE.BufferGeometry().fromGeometry(geometry); const positionAttribute = bufferGeometry.getAttribute('position'); const vertices = positionAttribute.array; ``` 上述代码,我们首先将几何体对象转换为缓冲几何体对象,然后通过getAttribute方法获取顶点属性对象,最后通过属性对象的array属性获取顶点数据。 所以,在r147版本后,我们不能直接访问几何体的vertices属性,但可以通过转换为缓冲几何体对象,并使用相关方法来获取顶点信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山楂树の

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值