Threejs&WebGPU运动残影demo


功能点

实例化SkinnedMesh

修改NodeMaterial着色器

节点材质系统 shader 语言 使用uniform和attribute

中合其他几篇博客中的内容

代码仓库

克隆后需要放到three源码同级别目录下 运行 three源码部分不在git仓库中(太大了)

使用vscode的live-server启动后访问

http://127.0.0.1:5501/demo/webgpu_skinning_instancing/webgpu_skinning_instancing.html

html部分

html 复制代码
<!--
 * @Author: hongbin
 * @Date: 2024-07-30 07:48:37
 * @LastEditors: hongbin
 * @LastEditTime: 2024-08-12 22:03:13
 * @Description: 
-->
<!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="../../three.js/examples/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": "./three.webgpu.js",
                    "three/tsl": "./three.webgpu.js",
                    "three/addons/": "../../three.js/examples/jsm/"
                }
            }
        </script>

        <script type="module">
            import * as THREE from "three";
            import {
                pass,
                mix,
                range,
                color,
                oscSine,
                timerLocal,
                texture,
                TextureNode,
                normalLocal,
                min,
                max,
                abs,
                uniform,
                floor,
                float,
                nodeObject,
                instanceIndex,
                buffer,
                varyingProperty,
                int,
                instancedBufferAttribute,
                instancedDynamicBufferAttribute,
                vec3,
                reference,
            } from "three/tsl";

            import { GUI } from "three/addons/libs/lil-gui.module.min.js";
            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";
            import TWEEN from "three/addons/libs/tween.module.js";

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

            let clock, delta;

            let dir = 1;

            let mixer, skinningInstanceMatrices;
            let mixer2, skinningInstanceMatrices2;
            let mixer3, skinningInstanceMatrices3;

            const tt = uniform(0, "float");
            const ttI = uniform(0, "float");

            class SkinningInstanceMatrices {
                constructor(mesh, count = 2, delay = 5,useTween = false) {
                    this.mesh = mesh;
                    this.count = count;
                    this.delay = 0.016 * delay;
                    this.time = 0;
                    this.useTween = useTween;

                    // const params = {opacity:1};
                    //     new TWEEN.Tween(params)
                    //         .to({ opacity: 0 }, 1000)
                    //         .onUpdate(function () {
                    //             console.log(new Date().getSeconds(),params.opacity)

                    //         })
                    //         .start();
                }

                setStatus(status = 1) {
                    this.isFinished = !status;

                    // this.mesh.setOmitOneOpacity && this.mesh.setOmitOneOpacity(status);

                    const params = { opacity: 1 - status };
                    new TWEEN.Tween(params)
                        .to({ opacity: status }, 16 * 10)
                        .onUpdate(() => {
                            this.mesh.setOmitOneOpacity && this.mesh.setOmitOneOpacity(params.opacity);
                        })
                        .start();
                }

                start() {
                    this.setStatus(1);
                }

                isFinished = false;

                /* 动画播放完 隐藏其他实例 */
                finished() {
                    this.setStatus(0);
                }

                prevAction(delta) {
                    this.time += delta;
                    if (this.time > this.delay && !this.isFinished) {
                        for (let i = this.count; i >= 1; i--) {
                            this.syncOnes(i, i - 1);

                            // if (this.useTween) {
                            //     const params = { opacity: 1 };

                            //     new TWEEN.Tween(params)
                            //         .to({ opacity: 0 }, this.delay * 1000)
                            //         .onUpdate(() => {
                            //             this.mesh.setOpacity && this.mesh.setOpacity(i, params.opacity);
                            //         })
                            //         .start();
                            // }
                        }

                        // this.syncOnes(3, 2);
                        // this.syncOnes(2, 1);
                        // this.syncOnes(1);
                        this.time = 0;

                        // const params = {opacity:1};
                        // new TWEEN.Tween(params)
                        //     .to({ opacity: 0 }, this.delay * 1000)
                        //     .onUpdate(function () {
                        //         // console.log(params.opacity)
                        //         window.setInstanceOpacity && window.setInstanceOpacity(1,params.opacity)
                        //         window.setInstanceOpacity && window.setInstanceOpacity(2,params.opacity)
                        //     })
                        //     .start();
                    }
                }

                syncOnes(index, copyIndex = 0) {
                    const length = this.mesh.skeleton.bones.length;

                    const copyIndexLeft = 16 * copyIndex * length;

                    // const end = 16 * (index + 1) * length;
                    const left = 16 * index * length;

                    // for (let index = left, i = 0; index < end; index++, i++) {
                    //     this.mesh.skeleton.boneMatrices[index] = this.mesh.skeleton.boneMatrices[i + copyIndexLeft];
                    // }

                    //使用内置方法 避免遍历 TypedArray.copyWithin(target start end) https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/copyWithin
                    // this.mesh.skeleton.boneMatrices.copyWithin(copyIndexLeft, 0, length * 16);
                    this.mesh.skeleton.boneMatrices.copyWithin(left, copyIndexLeft, copyIndexLeft + length * 16);
                }

                syncIndex(index) {
                    requestAnimationFrame(() => {
                        this.syncIndex(index);
                    });
                    this.syncOnes(index);
                    // const { count } = this.mesh;
                    // const length = this.mesh.skeleton.bones.length;

                    // const end = 16 * (index + 1) * length;
                    // const left = 16 * index * length;

                    // for (let index = left, i = 0; index < end; index++, i++) {
                    //     this.mesh.skeleton.boneMatrices[index] = this.mesh.skeleton.boneMatrices[i];
                    // }
                }
            }

            init();

            window.offsetCount = 1040;

            function init() {
                gui = new GUI();
                window.stopAnimation = false;
                gui.add(window, "stopAnimation");
                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, 100, 0.5);
                    rectLight1.position.set(0, 2, 0);
                    rectLight1.lookAt(0, -1, 0);
                    scene.add(rectLight1);
                    {
                        const rectLight1 = new THREE.RectAreaLight(0xffffff, 1, 10, 0.5);
                        rectLight1.position.set(0, 2.1, 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 floorMesh = new THREE.Mesh(
                    geometry,
                    new THREE.MeshStandardMaterial({
                        color: 0x000000,
                        roughness: 1,
                        metalness: 0.6,
                    })
                );

                scene.add(floorMesh);

                // {
                //     const floorMesh = new THREE.Mesh(
                //         new THREE.BoxGeometry(0.1, 0.1, 0.1),
                //         new THREE.MeshStandardMaterial({
                //             color: 0xff0000,
                //             roughness: 0,
                //             metalness: 0,
                //         })
                //     );
                //     floorMesh.position.set(0, 1, 1);
                //     scene.add(floorMesh);
                // }

                {
                    const geometry = new THREE.BoxGeometry(100, 2.4, 2);

                    const map = new THREE.TextureLoader().load("../../three.js/examples/textures/carbon/Carbon.png");
                    const normalMap = new THREE.TextureLoader().load(
                        "../../three.js/examples/textures/carbon/Carbon_Normal.png"
                    );

                    map.colorSpace = THREE.SRGBColorSpace;
                    normalMap.wrapS = map.wrapS = THREE.RepeatWrapping;
                    normalMap.wrapT = map.wrapT = THREE.RepeatWrapping;
                    map.repeat.set(60, 1);
                    normalMap.repeat.copy(map.repeat);

                    const box = new THREE.Mesh(
                        geometry,
                        new THREE.MeshStandardMaterial({
                            color: 0xffffff,
                            // color: 0x666666,
                            roughness: 0.3,
                            metalness: 0.7,
                            map,
                            normalMap,
                        })
                    );
                    box.position.z = -2;
                    box.position.y = 1.2;
                    scene.add(box);
                }

                const loader = new GLTFLoader();

                loader.load("../../three.js/examples/models/gltf/Michelle.glb", function (gltf) {
                    const danceGUI = gui.addFolder("跳舞");
                    const object = gltf.scene;

                    mixer = new THREE.AnimationMixer(object);

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

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

                    object.traverse(child => {
                        if (child.isMesh) {
                            skinningInstanceMatrices = new SkinningInstanceMatrices(child);

                            const oscNode = abs(oscSine(timerLocal(0.1)));
                            // const oscNode = abs(oscSine(timerLocal(1)));
                            // const oscNode = abs(timerLocal(1).sin());
                            const indexNode = floor(oscNode.div(0.3333334));
                            // const oscNode = oscSine(timerLocal(0.1));

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

                            const randomMetalness = range(0, 1);
                            const prevMap = child.material.map;

                            // 设置 false 不计入 视椎体可见范围计算 避免被隐藏  椎体计算 一般使用object.boundingSphere
                            child.frustumCulled = false;

                            // 显示卡通边框
                            child.CartoonBorder = true;
                            child.cartoonBorder = uniform(0.2);
                            child.cartoonBorderColor = uniform(new THREE.Color("#ffffff"));
                            child.CartoonBorderLight = uniform(vec3(0, 0, 1), "vec3");
                            // child.CartoonBorderLight = uniform(camera.position,'vec3');

                            danceGUI.add(child.cartoonBorder, "value", 0, 1).name("cartoonBorder");
                            danceGUI.add(skinningInstanceMatrices, "delay", 0, 0.2).name("speed");

                            danceGUI
                                .addColor({ color: "#" + child.cartoonBorderColor.value.getHexString() }, "color")
                                .onChange(c => {
                                    const newColor = new THREE.Color(c);
                                    child.cartoonBorderColor.value.copy(newColor);
                                });

                            child.material = new THREE.MeshStandardNodeMaterial({
                                transparent: true,
                                side: 0,
                            });

                            // 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.material.colorNode = texture(prevMap);

                            child.isInstancedMesh = true;

                            child.instanceMatrix = new THREE.InstancedBufferAttribute(
                                new Float32Array(instanceCount * 16),
                                16
                            );

                            // 透明度
                            const floatOpacity = new Float32Array(instanceCount);

                            const buffer = new THREE.InstancedBufferAttribute(floatOpacity, 1);
                            console.log(buffer);
                            // instancedDynamicBufferAttribute 每次写入buffer
                            // instancedBufferAttribute 手动更新 needsUpdate = true
                            child.instanceOpacity = instancedBufferAttribute(buffer);

                            window.setInstanceOpacity = (index, opacity) => {
                                floatOpacity[index] = opacity;
                                buffer.needsUpdate = true;
                            };

                            // child.instanceOpacity = new THREE.InstancedBufferAttribute(
                            //     new Float32Array(instanceCount),
                            //     1
                            // );
                            // child.instanceOpacity.setUsage(THREE.DynamicDrawUsage);

                            // 索引 attribute
                            const floatIndex = new Float32Array(instanceCount);

                            const indexBuffer = new THREE.InstancedBufferAttribute(floatIndex, 1);
                            child.instanceIndex = instancedBufferAttribute(indexBuffer);

                            // 索引
                            // child.instanceIndex = new THREE.InstancedBufferAttribute(
                            //     new Float32Array(instanceCount),
                            //     1
                            // );

                            // 提供uniform
                            // 选中的实例索引
                            // child.selectInstanceIndex = ttI;
                            // child.selectInstanceIndex = indexNode;
                            child.selectInstanceIndex = uniform(-1, "float");

                            // 选中的实例索引的透明度
                            child.selectInstanceIndexOpacity = tt;
                            // child.selectInstanceIndexOpacity = uniform(0.5, "float");

                            // child.selectInstanceIndexOpacity = abs(oscSine(timerLocal(0.33334)));
                            // child.selectInstanceIndexOpacity = oscNode
                            child.count = instanceCount;
                            //重新设置 实例矩阵长度 为原长度的instanceCount倍
                            child.skeleton.setInstanceCount(instanceCount);

                            // gui.add(
                            //     {
                            //         f: () => {
                            //             mixer.update(delta);
                            //             console.log(child.skeleton.boneMatrices);
                            //         },
                            //     },
                            //     "f"
                            // ).name("mixer.update()");

                            for (let i = 0; i < instanceCount; i++) {
                                // dummy.position.y = Math.floor(i / 5) * -200;
                                dummy.position.x = 70;
                                // dummy.position.x = i * 70;
                                dummy.position.z = i * -0.1;

                                dummy.updateMatrix();

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

                                floatIndex[i] = i;

                                floatOpacity[i] = 1;
                            }

                            const setIndexFadeOut = i => {
                                // child.selectInstanceIndex.value = i;
                                // dir = -1;
                                // tt.value = 1;
                            };

                            // gui.add({ f: () => skinningInstanceMatrices.syncOnes(1) }, "f").name("1 ONES");
                            // gui.add({ f: () => skinningInstanceMatrices.syncOnes(2) }, "f").name("2 ONES");
                            // gui.add({ f: () => skinningInstanceMatrices.syncOnes(3) }, "f").name("3 ONES");
                            // gui.add({ f: () => skinningInstanceMatrices.syncOnes(4) }, "f").name("4 ONES");
                            // gui.add({ f: () => skinningInstanceMatrices.syncOnes(5) }, "f").name("5 ONES");
                            // gui.add({ f: () => skinningInstanceMatrices.syncOnes(6) }, "f").name("6 ONES");
                            // gui.add({ f: () => skinningInstanceMatrices.syncOnes(7) }, "f").name("7 ONES");
                            // gui.add({ f: () => skinningInstanceMatrices.syncOnes(8) }, "f").name("8 ONES");
                            // gui.add({ f: () => skinningInstanceMatrices.syncOnes(9) }, "f").name("9 ONES");
                            // gui.add({ f: () => skinningInstanceMatrices.syncIndex(1) }, "f").name("1 ASYNC");
                            // gui.add({ f: () => skinningInstanceMatrices.syncIndex(2) }, "f").name("2 ASYNC");
                            // gui.add({ f: () => skinningInstanceMatrices.syncIndex(3) }, "f").name("3 ASYNC");
                            // gui.add({ f: () => skinningInstanceMatrices.syncIndex(4) }, "f").name("4 ASYNC");
                            // gui.add({ f: () => skinningInstanceMatrices.syncIndex(5) }, "f").name("5 ASYNC");
                            // gui.add({ f: () => skinningInstanceMatrices.syncIndex(6) }, "f").name("6 ASYNC");
                            // gui.add({ f: () => skinningInstanceMatrices.syncIndex(7) }, "f").name("7 ASYNC");
                            // gui.add({ f: () => skinningInstanceMatrices.syncIndex(8) }, "f").name("8 ASYNC");
                            // gui.add({ f: () => skinningInstanceMatrices.syncIndex(9) }, "f").name("9 ASYNC");
                        }
                    });

                    scene.add(object);
                });

                loader.load("./avoid.glb", function (gltf) {
                    const object = gltf.scene;
                    const avoidGUI = gui.addFolder("躲避");

                    mixer2 = new THREE.AnimationMixer(object);

                    const action = mixer2.clipAction(gltf.animations[0]);
                    // 播放一次
                    // action.loop = THREE.LoopOnce;
                    // 播完保持当前姿势
                    action.clampWhenFinished = true;
                    action.play();

                    // mixer2.addEventListener("finished", c => {
                    //     skinningInstanceMatrices2 && skinningInstanceMatrices2.finished();
                    // });

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

                    object.traverse(child => {
                        if (child.isMesh) {
                            skinningInstanceMatrices2 = new SkinningInstanceMatrices(child);
                            skinningInstanceMatrices2.start();

                            const oscNode = abs(oscSine(timerLocal(0.1)));
                            const indexNode = floor(oscNode.div(0.3333334));

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

                            const randomMetalness = range(0, 1);
                            const prevMap = child.material.map;

                            // 设置 false 不计入 视椎体可见范围计算 避免被隐藏  椎体计算 一般使用object.boundingSphere
                            child.frustumCulled = false;

                            // 显示卡通边框
                            child.CartoonBorder = true;
                            child.cartoonBorder = uniform(0.2);
                            child.cartoonBorderColor = uniform(new THREE.Color("#ffffff"));
                            child.CartoonBorderLight = uniform(vec3(0, 0, 1), "vec3");

                            avoidGUI.add(child.cartoonBorder, "value", 0, 1).name("cartoonBorder");
                            avoidGUI.add(skinningInstanceMatrices2, "delay", 0, 0.2).name("speed");

                            avoidGUI
                                .addColor({ color: "#" + child.cartoonBorderColor.value.getHexString() }, "color")
                                .onChange(c => {
                                    const newColor = new THREE.Color(c);
                                    child.cartoonBorderColor.value.copy(newColor);
                                });
                            // 重新播放
                            avoidGUI
                                .add(
                                    {
                                        f: () => {
                                            action.reset();
                                            skinningInstanceMatrices2.start();
                                        },
                                    },
                                    "f"
                                )
                                .name("Reset Action");

                            child.material = new THREE.MeshStandardNodeMaterial({
                                transparent: true,
                                side: 0,
                            });

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

                            child.isInstancedMesh = true;

                            child.instanceMatrix = new THREE.InstancedBufferAttribute(
                                new Float32Array(instanceCount * 16),
                                16
                            );

                            // 透明度
                            const floatOpacity = new Float32Array(instanceCount);

                            const buffer = new THREE.InstancedBufferAttribute(floatOpacity, 1);

                            child.instanceOpacity = instancedBufferAttribute(buffer);

                            child.hideOther = child.setOmitOneOpacity = (op = 0) => {
                                floatOpacity.fill(op);
                                floatOpacity[0] = 1;
                                buffer.needsUpdate = true;
                            };

                            child.showOther = () => {
                                floatOpacity.fill(1);
                                buffer.needsUpdate = true;
                            };

                            const floatIndex = new Float32Array(instanceCount);

                            const indexBuffer = new THREE.InstancedBufferAttribute(floatIndex, 1);
                            child.instanceIndex = instancedBufferAttribute(indexBuffer);

                            child.selectInstanceIndex = uniform(-1, "float");

                            // 选中的实例索引的透明度
                            child.selectInstanceIndexOpacity = tt;

                            child.count = instanceCount;
                            //重新设置 实例矩阵长度 为原长度的instanceCount倍
                            child.skeleton.setInstanceCount(instanceCount);

                            for (let i = 0; i < instanceCount; i++) {
                                // dummy.position.y = Math.floor(i / 5) * -200;
                                dummy.position.x = -70;
                                // dummy.position.x = i * 70;
                                // dummy.position.y = i * -2;

                                dummy.updateMatrix();

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

                                floatIndex[i] = i;

                                floatOpacity[i] = 1;
                            }
                        }
                    });

                    scene.add(object);
                });

                loader.load("./AirEvasion.glb", function (gltf) {
                    const object = gltf.scene;
                    const AirEvasionGUI = gui.addFolder("空中躲避");

                    mixer3 = new THREE.AnimationMixer(object);

                    const action = mixer3.clipAction(gltf.animations[0]);
                    // 播放一次
                    action.loop = THREE.LoopOnce;
                    // 播完保持当前姿势
                    action.clampWhenFinished = true;
                    action.play();

                    mixer3.addEventListener("finished", c => {
                        skinningInstanceMatrices3 && skinningInstanceMatrices3.finished();
                    });

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

                    object.traverse(child => {
                        if (child.isMesh) {
                            skinningInstanceMatrices3 = new SkinningInstanceMatrices(child, 2, 29, true);
                            skinningInstanceMatrices3.start();

                            const oscNode = abs(oscSine(timerLocal(0.1)));
                            const indexNode = floor(oscNode.div(0.3333334));

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

                            const randomMetalness = range(0, 1);
                            const prevMap = child.material.map;

                            // 设置 false 不计入 视椎体可见范围计算 避免被隐藏  椎体计算 一般使用object.boundingSphere
                            child.frustumCulled = false;

                            // 显示卡通边框
                            child.CartoonBorder = true;
                            child.cartoonBorder = uniform(0.2);
                            child.cartoonBorderColor = uniform(new THREE.Color("#ffffff"));
                            child.CartoonBorderLight = uniform(vec3(0, 0, 1), "vec3");

                            AirEvasionGUI.add(child.cartoonBorder, "value", 0, 1).name("cartoonBorder");
                            AirEvasionGUI.add(skinningInstanceMatrices3, "delay", 0, 0.5).name("speed");

                            AirEvasionGUI.addColor(
                                { color: "#" + child.cartoonBorderColor.value.getHexString() },
                                "color"
                            ).onChange(c => {
                                const newColor = new THREE.Color(c);
                                child.cartoonBorderColor.value.copy(newColor);
                            });
                            // 重新播放
                            AirEvasionGUI.add(
                                {
                                    f: () => {
                                        action.reset();
                                        skinningInstanceMatrices3.start();
                                    },
                                },
                                "f"
                            ).name("Reset Action");

                            child.material = new THREE.MeshStandardNodeMaterial({
                                transparent: true,
                                side: 0,
                            });

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

                            child.isInstancedMesh = true;

                            child.instanceMatrix = new THREE.InstancedBufferAttribute(
                                new Float32Array(instanceCount * 16),
                                16
                            );

                            // 透明度
                            const floatOpacity = new Float32Array(instanceCount);

                            const buffer = new THREE.InstancedBufferAttribute(floatOpacity, 1);

                            child.instanceOpacity = instancedBufferAttribute(buffer);

                            child.hideOther = child.setOmitOneOpacity = (op = 0) => {
                                floatOpacity.fill(op);
                                floatOpacity[0] = 1;
                                buffer.needsUpdate = true;
                            };

                            child.setOpacity = (index, op = 0) => {
                                floatOpacity[index] = op;
                                buffer.needsUpdate = true;
                            };

                            child.showOther = () => {
                                floatOpacity.fill(1);
                                buffer.needsUpdate = true;
                            };

                            const floatIndex = new Float32Array(instanceCount);

                            const indexBuffer = new THREE.InstancedBufferAttribute(floatIndex, 1);
                            child.instanceIndex = instancedBufferAttribute(indexBuffer);

                            child.selectInstanceIndex = uniform(-1, "float");

                            // 选中的实例索引的透明度
                            child.selectInstanceIndexOpacity = tt;

                            child.count = instanceCount;
                            //重新设置 实例矩阵长度 为原长度的instanceCount倍
                            child.skeleton.setInstanceCount(instanceCount);

                            for (let i = 0; i < instanceCount; i++) {
                                // dummy.position.y = Math.floor(i / 5) * -200;
                                dummy.position.x = -140;
                                // dummy.position.x = i * 70;
                                // dummy.position.y = i * -2;
                                dummy.position.y = 60;

                                dummy.updateMatrix();

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

                                floatIndex[i] = i;

                                floatOpacity[i] = 1;
                            }
                        }
                    });

                    scene.add(object);
                });

                // renderer

                renderer = new THREE.WebGPURenderer({ antialias: true });
                renderer.setPixelRatio(2);
                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() {
                delta = clock.getDelta();

                if (skinningInstanceMatrices) {
                    TWEEN.update();
                }
                if (!window.stopAnimation) {
                    skinningInstanceMatrices && skinningInstanceMatrices.prevAction(delta);
                    if (mixer) mixer.update(delta);
                    skinningInstanceMatrices2 && skinningInstanceMatrices2.prevAction(delta);
                    if (mixer2) mixer2.update(delta);
                    skinningInstanceMatrices3 && skinningInstanceMatrices3.prevAction(delta);
                    if (mixer3) mixer3.update(delta);
                }

                // tt.value = tt.value + 0.01 * dir;

                // if (tt.value >= 1 || tt.value <= 0) {
                //     dir *= -1;
                // }

                // ttI.value = Math.floor(tt.value / 0.333334);

                // postProcessing.render();
                renderer.render(scene, camera);
            }
        </script>
    </body>
</html>
相关推荐
李长渊哦4 小时前
深入理解 JavaScript 中的全局对象与 JSON 序列化
开发语言·javascript·json
Senar6 小时前
如何判断浏览器是否开启硬件加速
前端·javascript·数据可视化
codingandsleeping6 小时前
一个简易版无缝轮播图的实现思路
前端·javascript·css
拉不动的猪7 小时前
简单回顾下插槽透传
前端·javascript·面试
爱吃鱼的锅包肉8 小时前
Flutter路由模块化管理方案
前端·javascript·flutter
风清扬雨8 小时前
Vue3具名插槽用法全解——从零到一的详细指南
前端·javascript·vue.js
海盗强8 小时前
Vue 3 常见的通信方式
javascript·vue.js·ecmascript
oscar9999 小时前
JavaScript与TypeScript
开发语言·javascript·typescript
橘子味的冰淇淋~9 小时前
【解决】Vue + Vite + TS 配置路径别名成功仍爆红
前端·javascript·vue.js
leluckys10 小时前
flutter 专题 六十三 Flutter入门与实战作者:xiangzhihong8Fluter 应用调试
前端·javascript·flutter