功能点
实例化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>