当前位置:   article > 正文

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

three webgpu.js

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

官方instances案例
在这里插入图片描述
实现效果如图 第二个实例透明度为0.1 其他的为1
在这里插入图片描述

实现思路:

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
  • 250

两个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
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/978836
推荐阅读
相关标签
  

闽ICP备14008679号