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