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

Jedi Hongbin 2024-09-05 09:03:01 阅读 55

文章目录

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

写在前面

本文环境是 原生js 没使用框架

因为目前r167节点材质系统还不太稳定试了几个打包工具对有些特性支持不好 遂不在框架中写代码

并且直接引用 <code>three.webgpu.js文件 更方便更改源代码 插入自己的元素 也是本文的实现方式

官方instances案例

在这里插入图片描述

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

在这里插入图片描述

实现思路:

1. 声明一个实例必要的属性<code>instanceMatrix同级别的属性

child.instanceIndex = new THREE.InstancedBufferAttribute(

new Float32Array(实例数量),

1

);

2. 在设置位置矩阵的时候填充这个数组

for (let i = 0; i < 实例数量; i++) {

//,,,

child.instanceIndex.array[i] = i ;

}

3. 在shader中获取当前的索引

修改InstanceNode的源码的setup函数

if(instanceMesh.instanceIndex){

const indexBuffer = new InstancedBufferAttribute( 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 = new InstancedBufferAttribute( 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' );

// OPACITY

const 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

<!DOCTYPE html>

<html lang="en">code>

<head>

<title>three.js webgpu - skinning instancing</title>

<meta charset="utf-8" />code>

<meta

name="viewport"code>

content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"code>

/>

<link type="text/css" rel="stylesheet" href="../main.css" />code>

</head>

<body>

<div id="info">code>

<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a>code>

webgpu - skinning instancing

</div>

<script type="importmap">code>

{

"imports": {

"three": "../../build/three.webgpu.js",

"three/tsl": "../../build/three.webgpu.js",

"three/addons/": "../jsm/"

}

}

</script>

<script type="module">code>

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>

两个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 );

}

}

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

})

}

}



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。