文章目录
  • 1. 声明一个实例必要的属性`instanceMatrix`同级别的属性
  • 2. 在设置位置矩阵的时候填充这个数组
  • 3. 在shader中获取当前的索引
  • 4. 增加uniform
  • 5. 对比当前着色的实例是否是选中的实例
  • 6. 如果是选中的实例
  • 7. 影响片元着色器透明度参数
  • 其他 - 渐入渐出动画
  • 8.源码


写在前面
本文环境是 原生js 没使用框架
因为目前r167节点材质系统还不太稳定试了几个打包工具对有些特性支持不好 遂不在框架中写代码
并且直接引用 three.webgpu.js文件 更方便更改源代码 插入自己的元素 也是本文的实现方式

 官方instances案例

Three.js & WebGPU 节点材质系统 控制instances的某个实例单独的透明度,颜色等属性_html


实现效果如图 第二个实例透明度为0.1 其他的为1

Three.js & WebGPU 节点材质系统 控制instances的某个实例单独的透明度,颜色等属性_html_02

实现思路:

1. 声明一个实例必要的属性instanceMatrix同级别的属性
child.instanceIndex = new THREE.InstancedBufferAttribute(
	new Float32Array(实例数量),
	1
);
  • 1.
  • 2.
  • 3.
  • 4.
2. 在设置位置矩阵的时候填充这个数组
for (let i = 0; i < 实例数量; i++) {
	//,,,
	child.instanceIndex.array[i] = i ;
}
  • 1.
  • 2.
  • 3.
  • 4.
3. 在shader中获取当前的索引

修改InstanceNode的源码的setup函数

if(instanceMesh.instanceIndex){

	const indexBuffer = new InstancedBufferAttribute( instanceMesh.instanceIndex.array, 1 );
	
	const _index = instancedBufferAttribute( indexBuffer ) 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

_index就是当前着色的实例索引

4. 增加uniform
// 提供uniform
// 选中的实例索引
child.selectInstanceIndex = uniform(1, "float");
// 选中的实例索引的透明度
child.selectInstanceIndexOpacity = uniform(0.1, "float");
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
5. 对比当前着色的实例是否是选中的实例
if(instanceMesh.instanceIndex){

	const indexBuffer = new InstancedBufferAttribute( instanceMesh.instanceIndex.array, 1 );
	
	const _index = instancedBufferAttribute( indexBuffer ) 
	
	If(_index.equal(instanceMesh.selectInstanceIndex),() => {
		//...			
	})
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
6. 如果是选中的实例

加入一个varying变量vInstanceIndexOpacity影响选中的实例的透明度(也可以影响其他材质参数 这里以透明度为例)

if(instanceMesh.instanceIndex){

	const indexBuffer = new InstancedBufferAttribute( instanceMesh.instanceIndex.array, 1 );
	
	const _index = instancedBufferAttribute( indexBuffer ) 
	
	If(_index.equal(instanceMesh.selectInstanceIndex),() => {
+		varyingProperty( 'float', 'vInstanceIndexOpacity' ).assign(instanceMesh.selectInstanceIndexOpacity );
	})
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
7. 影响片元着色器透明度参数

NodeMaterial对象的setupDiffuseColor方法中将透明度乘以vInstanceIndexOpacity的值或者直接设置为vInstanceIndexOpacity的值

const vInstanceIndexOpacity = varyingProperty( 'float', 'vInstanceIndexOpacity' ); 

// OPACITY

const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;

diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ).mul(vInstanceIndexOpacity) );
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

如此便可通过更改uniform来决定某个实例的透明度了
以此思路其他材质属性也均可单独指定

其他 - 渐入渐出动画

如果想让透明度的值 自动变化 可以如下

const oscNode = abs(oscSine(timerLocal(0.1)));
// 选中的实例索引的透明度
- child.selectInstanceIndexOpacity = uniform(0.1, "float");
+ child.selectInstanceIndexOpacity = oscNode;
  • 1.
  • 2.
  • 3.
  • 4.

ocsNode的值就是时间放慢10倍并且使用sin函数约束值[-1,1 ]再使用abs取绝对值 使之在[0-1-0]之间循环
这样渐入渐出的动画就巧妙的完成了 这也是 节点材质系统的优越性和趣味性的体现

8.源码

html

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgpu - skinning instancing</title>
		<meta charset="utf-8" />
		<meta
			name="viewport"
			content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
		/>
		<link type="text/css" rel="stylesheet" href="../main.css" />
	</head>
	<body>
		<div id="info">
			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a>
			webgpu - skinning instancing
		</div>

		<script type="importmap">
			{
				"imports": {
					"three": "../../build/three.webgpu.js",
					"three/tsl": "../../build/three.webgpu.js",
					"three/addons/": "../jsm/"
				}
			}
		</script>

		<script type="module">
			import * as THREE from "three";
			import {
				pass,
				mix,
				range,
				color,
				oscSine,
				timerLocal,
				texture,
				TextureNode,
				normalLocal,
				min,
				max,
				abs,
				uniform
			} from "three/tsl";

			import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
			import { OrbitControls } from "three/addons/controls/OrbitControls.js";
			import { RectAreaLightHelper } from "three/addons/helpers/RectAreaLightHelper.js";
			import { RectAreaLightTexturesLib } from "three/addons/lights/RectAreaLightTexturesLib.js";

			let camera, scene, renderer, controls;
			let postProcessing;

			let mixer, clock;

			init();

			function init() {
				THREE.RectAreaLightNode.setLTC(RectAreaLightTexturesLib.init());

				camera = new THREE.PerspectiveCamera(
					50,
					window.innerWidth / window.innerHeight,
					0.01,
					40
				);
				// camera.position.set( 1, 2, 3 );
				camera.position.set(0, 0, 0);

				scene = new THREE.Scene();
				scene.add(new THREE.AxesHelper(1));
				camera.lookAt(0, 1, 0);

				clock = new THREE.Clock();

				// lights

				const centerLight = new THREE.PointLight(0xff9900, 2, 100);
				centerLight.position.y = 4.5;
				centerLight.power = 400;
				// scene.add(centerLight);

				const cameraLight = new THREE.PointLight(0xffffff, 1, 100);
				cameraLight.power = 400;
				cameraLight.position.set(0, 2, 3);
				// camera.add(cameraLight);
				// scene.add(camera);
				// scene.add(cameraLight);

				const rectLight1 = new THREE.RectAreaLight(0xffffff, 10, 10, 0.5);
				rectLight1.position.set(0, 2, 0);
				rectLight1.lookAt(0, -1, 0);
				scene.add(rectLight1);
				{
					const rectLight1 = new THREE.RectAreaLight(0xffffff, 10, 10, 0.1);
					rectLight1.position.set(0, 0, 2);
					rectLight1.lookAt(0, 0, 0);
					scene.add(rectLight1);
				}
				scene.add(new RectAreaLightHelper(rectLight1));

				const thickness = 10;
				const geometry = new THREE.BoxGeometry(100, 2, thickness);
				geometry.translate(0, 0, -thickness / 2);
				geometry.rotateX(-Math.PI / 2);

				const plane = new THREE.Mesh(
					geometry,
					new THREE.MeshStandardMaterial({
						color: 0x000000,
						roughness: 1,
						metalness: 0.6,
					})
				);
				scene.add(plane);

				const loader = new GLTFLoader();
				loader.load("../models/gltf/Michelle.glb", function (gltf) {
					const object = gltf.scene;

					mixer = new THREE.AnimationMixer(object);

					const action = mixer.clipAction(gltf.animations[0]);
					action.play();

					const instanceCount = 3;
					const dummy = new THREE.Object3D();

					object.traverse((child) => {
						if (child.isMesh) {
							// const oscNode = max(0,oscSine(timerLocal(0.1)));
							const oscNode = abs(oscSine(timerLocal(0.1)));
							// const oscNode = oscSine(timerLocal(0.1));

							const randomColors = range(
								new THREE.Color(0x0000),
								new THREE.Color(0xffffff)
							);

							const randomMetalness = range(0, 1);
							const prevMap = child.material.map;
							child.material = new THREE.MeshStandardNodeMaterial({
								transparent: true,
							});

							// child.material.onBeforeCompile = (shader) => {
							// 	console.log("onBeforeCompile:", shader);
							// };

							// roughnessNode是变化的 roughness是固定的
							child.material.roughnessNode = oscNode;

							child.material.metalnessNode =
								0.5 || mix(0.0, randomMetalness, oscNode);

							child.material.colorNode = mix(
								texture(prevMap),
								randomColors,
								oscNode
							);

							child.isInstancedMesh = true;
							child.instanceMatrix = new THREE.InstancedBufferAttribute(
								new Float32Array(instanceCount * 16),
								16
							);
							child.instanceIndex = new THREE.InstancedBufferAttribute(
								new Float32Array(instanceCount),
								1
							);

							// 提供uniform
							// 选中的实例索引
							child.selectInstanceIndex = uniform(1, "float");
							// 选中的实例索引的透明度
							child.selectInstanceIndexOpacity = uniform(0.1, "float");
							
							child.count = instanceCount;

							for (let i = 0; i < instanceCount; i++) {
								dummy.position.x = i * 70;

								dummy.position.y = Math.floor(i / 5) * -200;

								dummy.updateMatrix();

								dummy.matrix.toArray(child.instanceMatrix.array, i * 16);

								child.instanceIndex.array[i] = i ;
							}
							// child.instanceIndex.array[0] = 0 ;
							// child.instanceIndex.array[1] = 1 ;
							// child.instanceIndex.array[2] = 5 ;
						}
					});

					scene.add(object);
				});

				// renderer

				renderer = new THREE.WebGPURenderer({ antialias: true });
				renderer.setPixelRatio(window.devicePixelRatio);
				renderer.setSize(window.innerWidth, window.innerHeight);
				renderer.setAnimationLoop(animate);
				document.body.appendChild(renderer.domElement);

				controls = new OrbitControls(camera, renderer.domElement);

				controls.target.set(0, 1, 0);
				controls.object.position.set(0, 1, 4);

				// post processing

				const scenePass = pass(scene, camera);
				const scenePassColor = scenePass.getTextureNode();
				const scenePassDepth = scenePass
					.getLinearDepthNode()
					.remapClamp(0.15, 0.3);

				const scenePassColorBlurred = scenePassColor.gaussianBlur();
				scenePassColorBlurred.directionNode = scenePassDepth;

				// postProcessing = new THREE.PostProcessing(renderer);
				// postProcessing.outputNode = scenePassColorBlurred;

				// events

				window.addEventListener("resize", onWindowResize);
			}

			function onWindowResize() {
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();

				renderer.setSize(window.innerWidth, window.innerHeight);
			}

			function animate() {
				const delta = clock.getDelta();

				if (mixer) mixer.update(delta);

				// postProcessing.render();
				renderer.render(scene, camera);
			}
		</script>
	</body>
</html>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.

两个three模块核心函数修改后的代码
NodeMaterial.setupDiffuseColor

setupDiffuseColor( { object, geometry } ) {

		let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor;

		// VERTEX COLORS

		if ( this.vertexColors === true && geometry.hasAttribute( 'color' ) ) {

			colorNode = vec4( colorNode.xyz.mul( attribute( 'color', 'vec3' ) ), colorNode.a );

		}

		// Instanced colors

		if ( object.instanceColor ) {

			const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' );

			colorNode = instanceColor.mul( colorNode );
			
		}

		const vInstanceIndexOpacity = varyingProperty( 'float', 'vInstanceIndexOpacity' ); 

		// COLOR

		diffuseColor.assign( colorNode );

		// OPACITY

		const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;
		diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ).mul(vInstanceIndexOpacity) );

		// ALPHA TEST

		if ( this.alphaTestNode !== null || this.alphaTest > 0 ) {

			const alphaTestNode = this.alphaTestNode !== null ? float( this.alphaTestNode ) : materialAlphaTest;

			diffuseColor.a.lessThanEqual( alphaTestNode ).discard();

		}

		if ( this.transparent === false && this.blending === NormalBlending && this.alphaToCoverage === false ) {

			diffuseColor.a.assign( 1.0 );

		}

	}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.

InstanceNode.setup

setup( /*builder*/ ) {

		let instanceMatrixNode = this.instanceMatrixNode;
		let instanceColorNode = this.instanceColorNode;
		let instanceIndexNode;
		
		const instanceMesh = this.instanceMesh;
		

		if ( instanceMatrixNode === null ) {

			const instanceAttribute = instanceMesh.instanceMatrix;

			// Both WebGPU and WebGL backends have UBO max limited to 64kb. Matrix count number bigger than 1000 ( 16 * 4 * 1000 = 64kb ) will fallback to attribute.

			if ( instanceMesh.count <= 1000 ) {

				instanceMatrixNode = buffer( instanceAttribute.array, 'mat4', instanceMesh.count ).element( instanceIndex );
				console.log('instanceMatrixNode:',instanceMatrixNode)

			} else {

				const buffer = new InstancedInterleavedBuffer( instanceAttribute.array, 16, 1 );

				this.buffer = buffer;

				const bufferFn = instanceAttribute.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;

				const instanceBuffers = [
					// F.Signature -> bufferAttribute( array, type, stride, offset )
					bufferFn( buffer, 'vec4', 16, 0 ),
					bufferFn( buffer, 'vec4', 16, 4 ),
					bufferFn( buffer, 'vec4', 16, 8 ),
					bufferFn( buffer, 'vec4', 16, 12 )
				];

				instanceMatrixNode = mat4( ...instanceBuffers );

			}

			this.instanceMatrixNode = instanceMatrixNode;

			if( instanceMesh.instanceIndex ){

				const insertInstanceIndex = instanceMesh.instanceIndex;
				
				// instanceIndexNode = buffer(insertInstanceIndex.array, "float", instanceMesh.count).element(instanceIndex);
				// console.log("插入实例索引:",instanceIndexNode)
			}
		}

		const instanceColorAttribute = instanceMesh.instanceColor;

		if ( instanceColorAttribute && instanceColorNode === null ) {

			const buffer = new InstancedBufferAttribute( instanceColorAttribute.array, 3 );

			const bufferFn = instanceColorAttribute.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;

			this.bufferColor = buffer;

			instanceColorNode = vec3( bufferFn( buffer, 'vec3', 3, 0 ) );

			this.instanceColorNode = instanceColorNode;

		}

		// POSITION

		const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;

		// NORMAL

		const m = mat3( instanceMatrixNode );

		const transformedNormal = normalLocal.div( vec3( m[ 0 ].dot( m[ 0 ] ), m[ 1 ].dot( m[ 1 ] ), m[ 2 ].dot( m[ 2 ] ) ) );

		const instanceNormal = m.mul( transformedNormal ).xyz;

		// ASSIGNS

		positionLocal.assign( instancePosition );
		normalLocal.assign( instanceNormal );

		// COLOR

		if ( this.instanceColorNode !== null ) {

			varyingProperty( 'vec3', 'vInstanceColor' ).assign( this.instanceColorNode );
			
		}
		
		if(instanceMesh.instanceIndex){

			const indexBuffer = new InstancedBufferAttribute( instanceMesh.instanceIndex.array, 1 );
			
			const _index = instancedBufferAttribute( indexBuffer ) 

			// 当前的索引
			varyingProperty( 'float', 'vInstanceIndexOpacity' ).assign( 1 );
			// 当前index是uniform selectInstanceIndex 的实例
			If(_index.equal(instanceMesh.selectInstanceIndex),() => {
				varyingProperty( 'float', 'vInstanceIndexOpacity' ).assign( instanceMesh.selectInstanceIndexOpacity );
			})

		}


	}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.