文章目录
写在前面
本文环境是 原生js 没使用框架
因为目前r167节点材质系统还不太稳定试了几个打包工具对有些特性支持不好 遂不在框架中写代码
并且直接引用three.webgpu.js
文件 更方便更改源代码 插入自己的元素 也是本文的实现方式
官方instances案例
实现效果如图 第二个实例透明度为0.1 其他的为1
实现思路:
1. 声明一个实例必要的属性
instanceMatrix
同级别的属性
child.instanceIndex =newTHREE.InstancedBufferAttribute(newFloat32Array(实例数量),1);
2. 在设置位置矩阵的时候填充这个数组
for(let i =0; i < 实例数量; i++){//,,,
child.instanceIndex.array[i]= i ;}
3. 在shader中获取当前的索引
修改
InstanceNode
的源码的
setup
函数
if(instanceMesh.instanceIndex){const indexBuffer =newInstancedBufferAttribute( instanceMesh.instanceIndex.array,1);const _index =instancedBufferAttribute( indexBuffer )}
_index
就是当前着色的实例索引
4. 增加uniform
// 提供uniform// 选中的实例索引
child.selectInstanceIndex =uniform(1,"float");// 选中的实例索引的透明度
child.selectInstanceIndexOpacity =uniform(0.1,"float");
5. 对比当前着色的实例是否是选中的实例
if(instanceMesh.instanceIndex){const indexBuffer =newInstancedBufferAttribute( instanceMesh.instanceIndex.array,1);const _index =instancedBufferAttribute( indexBuffer )If(_index.equal(instanceMesh.selectInstanceIndex),()=>{//... })}
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 );})}
7. 影响片元着色器透明度参数
在
NodeMaterial
对象的
setupDiffuseColor
方法中将透明度乘以
vInstanceIndexOpacity
的值或者直接设置为
vInstanceIndexOpacity
的值
const vInstanceIndexOpacity =varyingProperty('float','vInstanceIndexOpacity');// OPACITYconst opacityNode = this.opacityNode ?float( this.opacityNode ): materialOpacity;
diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ).mul(vInstanceIndexOpacity));
如此便可通过更改uniform来决定某个实例的透明度了
以此思路其他材质属性也均可单独指定
其他 - 渐入渐出动画
如果想让透明度的值 自动变化 可以如下
const oscNode =abs(oscSine(timerLocal(0.1)));// 选中的实例索引的透明度- child.selectInstanceIndexOpacity =uniform(0.1,"float");+ child.selectInstanceIndexOpacity = oscNode;
ocsNode
的值就是时间放慢10倍并且使用sin函数约束值[-1,1 ]再使用abs取绝对值 使之在[0-1-0]之间循环
这样渐入渐出的动画就巧妙的完成了 这也是 节点材质系统的优越性和趣味性的体现
8.源码
html
<!DOCTYPEhtml><htmllang="en"><head><title>three.js webgpu - skinning instancing</title><metacharset="utf-8"/><metaname="viewport"content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"/><linktype="text/css"rel="stylesheet"href="../main.css"/></head><body><divid="info"><ahref="https://threejs.org"target="_blank"rel="noopener">three.js</a>
webgpu - skinning instancing
</div><scripttype="importmap">{"imports":{"three":"../../build/three.webgpu.js","three/tsl":"../../build/three.webgpu.js","three/addons/":"../jsm/"}}</script><scripttype="module">import*asTHREEfrom"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();functioninit(){THREE.RectAreaLightNode.setLTC(RectAreaLightTexturesLib.init());
camera =newTHREE.PerspectiveCamera(50,
window.innerWidth / window.innerHeight,0.01,40);// camera.position.set( 1, 2, 3 );
camera.position.set(0,0,0);
scene =newTHREE.Scene();
scene.add(newTHREE.AxesHelper(1));
camera.lookAt(0,1,0);
clock =newTHREE.Clock();// lightsconst centerLight =newTHREE.PointLight(0xff9900,2,100);
centerLight.position.y =4.5;
centerLight.power =400;// scene.add(centerLight);const cameraLight =newTHREE.PointLight(0xffffff,1,100);
cameraLight.power =400;
cameraLight.position.set(0,2,3);// camera.add(cameraLight);// scene.add(camera);// scene.add(cameraLight);const rectLight1 =newTHREE.RectAreaLight(0xffffff,10,10,0.5);
rectLight1.position.set(0,2,0);
rectLight1.lookAt(0,-1,0);
scene.add(rectLight1);{const rectLight1 =newTHREE.RectAreaLight(0xffffff,10,10,0.1);
rectLight1.position.set(0,0,2);
rectLight1.lookAt(0,0,0);
scene.add(rectLight1);}
scene.add(newRectAreaLightHelper(rectLight1));const thickness =10;const geometry =newTHREE.BoxGeometry(100,2, thickness);
geometry.translate(0,0,-thickness /2);
geometry.rotateX(-Math.PI/2);const plane =newTHREE.Mesh(
geometry,newTHREE.MeshStandardMaterial({color:0x000000,roughness:1,metalness:0.6,}));
scene.add(plane);const loader =newGLTFLoader();
loader.load("../models/gltf/Michelle.glb",function(gltf){const object = gltf.scene;
mixer =newTHREE.AnimationMixer(object);const action = mixer.clipAction(gltf.animations[0]);
action.play();const instanceCount =3;const dummy =newTHREE.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(newTHREE.Color(0x0000),newTHREE.Color(0xffffff));const randomMetalness =range(0,1);const prevMap = child.material.map;
child.material =newTHREE.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 =newTHREE.InstancedBufferAttribute(newFloat32Array(instanceCount *16),16);
child.instanceIndex =newTHREE.InstancedBufferAttribute(newFloat32Array(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 =newTHREE.WebGPURenderer({antialias:true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
document.body.appendChild(renderer.domElement);
controls =newOrbitControls(camera, renderer.domElement);
controls.target.set(0,1,0);
controls.object.position.set(0,1,4);// post processingconst 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);}functiononWindowResize(){
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);}functionanimate(){const delta = clock.getDelta();if(mixer) mixer.update(delta);// postProcessing.render();
renderer.render(scene, camera);}</script></body></html>
两个three模块核心函数修改后的代码
NodeMaterial.setupDiffuseColor
setupDiffuseColor({ object, geometry }){let colorNode =this.colorNode ?vec4(this.colorNode ): materialColor;// VERTEX COLORSif(this.vertexColors ===true&& geometry.hasAttribute('color')){
colorNode =vec4( colorNode.xyz.mul(attribute('color','vec3')), colorNode.a );}// Instanced colorsif( object.instanceColor ){const instanceColor =varyingProperty('vec3','vInstanceColor');
colorNode = instanceColor.mul( colorNode );}const vInstanceIndexOpacity =varyingProperty('float','vInstanceIndexOpacity');// COLOR
diffuseColor.assign( colorNode );// OPACITYconst opacityNode =this.opacityNode ?float(this.opacityNode ): materialOpacity;
diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ).mul(vInstanceIndexOpacity));// ALPHA TESTif(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);}}
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 =newInstancedInterleavedBuffer( 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 =newInstancedBufferAttribute( instanceColorAttribute.array,3);const bufferFn = instanceColorAttribute.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute;this.bufferColor = buffer;
instanceColorNode =vec3(bufferFn( buffer,'vec3',3,0));this.instanceColorNode = instanceColorNode;}// POSITIONconst instancePosition = instanceMatrixNode.mul( positionLocal ).xyz;// NORMALconst 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 );// COLORif(this.instanceColorNode !==null){varyingProperty('vec3','vInstanceColor').assign(this.instanceColorNode );}if(instanceMesh.instanceIndex){const indexBuffer =newInstancedBufferAttribute( 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 );})}}
版权归原作者 Jedi Hongbin 所有, 如有侵权,请联系我们删除。