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>
相关推荐
fruge7 分钟前
纯css制作声波扩散动画、js+css3波纹催眠动画特效、【css3动画】圆波扩散效果、雷达光波效果完整代码
javascript·css·css3
neter.asia16 分钟前
vue中如何关闭eslint检测?
前端·javascript·vue.js
光影少年36 分钟前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
Rattenking41 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
熊的猫2 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
别拿曾经看以后~3 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
川石课堂软件测试3 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
JerryXZR4 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
problc4 小时前
Flutter中文字体设置指南:打造个性化的应用体验
android·javascript·flutter
Gavin_9154 小时前
【JavaScript】模块化开发
前端·javascript·vue.js