three.js之动画Animation
动画
<canvas id="mainCanvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox, addSphere, addCylinder } from "./init.js";
const gui = new GUI();
function init() {
window.addEventListener("resize", onResize, false);
document.addEventListener("mousedown", onDocumentMouseDown, false);
document.addEventListener("mousemove", onDocumentMouseMove, false);
const renderer = initRenderer("mainCanvas");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const spotLight = initSpotLight();
scene.add(spotLight);
const plane = addPlane();
scene.add(plane);
const cube = addBox({ color: 0xff0000, width: 4, height: 4, depth: 4 });
cube.position.set(-10, 5, 0);
scene.add(cube);
const sphere = addSphere({ color: 0x7777ff, radius: 4 });
sphere.position.set(20, 5, 0);
scene.add(sphere);
const cylinder = addCylinder({ color: 0x77ff77, tr: 2, br: 2, height: 20 });
scene.add(cylinder);
let step = 0;
let scalingStep = 0;
const controls = new (function () {
this.rotationSpeed = 0.02;
this.bouncingSpeed = 0.03;
this.scalingSpeed = 0.03;
this.showRay = false;
})();
gui.add(controls, "rotationSpeed", 0, 0.5);
gui.add(controls, "bouncingSpeed", 0, 0.5);
gui.add(controls, "scalingSpeed", 0, 0.5);
gui.add(controls, "showRay").onChange(function (e) {
if (tube) scene.remove(tube);
});
const trackballControls = new TrackballControls(camera, renderer.domElement);
const clock = new THREE.Clock();
let tube;
render();
function render() {
trackballControls.update(clock.getDelta());
cube.rotation.x += controls.rotationSpeed;
cube.rotation.y += controls.rotationSpeed;
cube.rotation.z += controls.rotationSpeed;
step += controls.bouncingSpeed;
sphere.position.x = 20 + 10 * Math.cos(step);
sphere.position.y = 2 + 10 * Math.abs(Math.sin(step));
scalingStep += controls.scalingSpeed;
const scaleX = Math.abs(Math.sin(scalingStep / 4));
const scaleY = Math.abs(Math.cos(scalingStep / 5));
const scaleZ = Math.abs(Math.sin(scalingStep / 7));
cylinder.scale.set(scaleX, scaleY, scaleZ);
requestAnimationFrame(render);
renderer.render(scene, camera);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function onDocumentMouseDown(event) {
let vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
vector = vector.unproject(camera); //将此向量(坐标)从相机的标准化设备坐标 (NDC) 空间投影到世界空间
// 光线投射Raycaster 用于进行raycasting(光线投射)。光线投射用于进行鼠标拾取
const raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
const intersects = raycaster.intersectObjects([sphere, cylinder, cube]); //检测所有在射线与这些物体之间,包括或不包括后代的相交部分。
if (intersects.length > 0) {
console.log(intersects[0]);
intersects[0].object.material.transparent = true; //未生效 ???
intersects[0].object.material.opacity = 0.1;
}
}
function onDocumentMouseMove(event) {
if (controls.showRay) {
let vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0.5);
vector = vector.unproject(camera);
const raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
const intersects = raycaster.intersectObjects([sphere, cylinder, cube]);
if (intersects.length > 0) {
const points = [];
points.push(new THREE.Vector3(-30, 29.8, 30));
points.push(intersects[0].point);
const mat = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.6 });
const tubeGeometry = new THREE.TubeGeometry(new THREE.CatmullRomCurve3(points), 60, 0.001);
if (tube) scene.remove(tube);
if (controls.showRay) {
tube = new THREE.Mesh(tubeGeometry, mat);
scene.add(tube);
}
}
}
}
}
init();
</script>
TWEEN
<canvas id="mainCanvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import * as TWEEN from "three/addons/libs/tween.module.js";
import { PLYLoader } from "three/addons/loaders/PLYLoader.js";
import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight } from "./init.js";
function init() {
const renderer = initRenderer("mainCanvas");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
scene.add(camera);
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const spotLight = initSpotLight();
scene.add(spotLight);
const posSrc = { pos: 1 };
const tween = new TWEEN.Tween(posSrc).to({ pos: 0 }, 2000);
tween.easing(TWEEN.Easing.Bounce.InOut);
const tweenBack = new TWEEN.Tween(posSrc).to({ pos: 1 }, 2000);
tweenBack.easing(TWEEN.Easing.Bounce.InOut);
tweenBack.chain(tween);
tween.chain(tweenBack);
tween.start();
let group;
const loader = new PLYLoader();
loader.load("./models/ply/ascii/dolphins.ply", function (geometry) {
const origPosition = geometry.attributes["position"].clone();
geometry.origPosition = origPosition;
const material = new THREE.PointsMaterial({
color: 0xffffff,
size: 1,
opacity: 0.6,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
map: generateSprite(),
});
group = new THREE.Points(geometry, material);
group.scale.set(0.05, 0.05, 0.05);
scene.add(group);
});
const trackballControls = new TrackballControls(camera, renderer.domElement);
const clock = new THREE.Clock();
render();
function render() {
trackballControls.update(clock.getDelta());
TWEEN.update();
if (typeof group != "undefined") {
const positionArray = group.geometry.attributes["position"];
const origPosition = group.geometry.origPosition;
for (let i = 0; i < positionArray.count; i++) {
const oldPosX = origPosition.getX(i);
const oldPosY = origPosition.getY(i);
const oldPosZ = origPosition.getZ(i);
positionArray.setX(i, oldPosX * posSrc.pos);
positionArray.setY(i, oldPosY * posSrc.pos);
positionArray.setZ(i, oldPosZ * posSrc.pos);
}
positionArray.needsUpdate = true;
}
requestAnimationFrame(render);
renderer.render(scene, camera);
}
}
function generateSprite() {
const canvas = document.createElement("canvas");
canvas.width = 16;
canvas.height = 16;
const context = canvas.getContext("2d");
const gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
gradient.addColorStop(0, "rgba(255,255,255,1)");
gradient.addColorStop(0.2, "rgba(0,255,255,1)");
gradient.addColorStop(0.4, "rgba(0,0,64,1)");
gradient.addColorStop(1, "rgba(0,0,0,1)");
context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);
const texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return texture;
}
init();
</script>
AnimationMixer
<canvas id="mainCanvas"></canvas>
<script type="importmap">
{
"imports": {
"three": "./js/build/three.module.js",
"three/addons/": "./js/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { TrackballControls } from "three/addons/controls/TrackballControls.js";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox, addSphere, addCylinder } from "./init.js";
function init() {
const gui = new GUI();
const renderer = initRenderer("mainCanvas");
const scene = new THREE.Scene();
const camera = initPerspectiveCamera();
camera.position.set(0, 15, 70);
scene.add(camera);
const ambientLight = initAmbientLight();
scene.add(ambientLight);
const spotLight = initSpotLight();
scene.add(spotLight);
const trackballControls = new TrackballControls(camera, renderer.domElement);
const clock = new THREE.Clock();
let mixer = new THREE.AnimationMixer();
let animationClip;
let clipAction;
let mesh;
let controls;
const mixerControls = {
time: 0,
timeScale: 1,
stopAllAction: function () {
mixer.stopAllAction();
},
};
const loader = new GLTFLoader();
loader.load("./models/Horse.glb", function (res) {
res.scene.scale.set(0.1, 0.1, 0.1);
res.scene.translateY(-3);
res.scene.rotateY(-0.3 * Math.PI);
scene.add(res.scene);
// AnimationMixer 动画混合器是用于场景中特定对象的动画的播放器。当场景中的多个对象独立动画时,每个对象都可以使用同一个动画混合器。
mixer = new THREE.AnimationMixer(res.scene);
animationClip = res.animations[0];
clipAction = mixer.clipAction(animationClip).play();
animationClip = clipAction.getClip();
enableControls();
});
render();
function render() {
const delta = clock.getDelta();
trackballControls.update(delta);
requestAnimationFrame(render);
renderer.render(scene, camera);
if (mixer && clipAction) {
mixer.update(delta);
controls.time = mixer.time;
controls.effectiveTimeScale = clipAction.getEffectiveTimeScale();
controls.effectiveWeight = clipAction.getEffectiveWeight();
}
}
function enableControls() {
const mixerFolder = gui.addFolder("AnimationMixer");
mixerFolder.add(mixerControls, "time").listen();
mixerFolder.add(mixerControls, "timeScale", 0, 5).onChange(function (timeScale) {
mixer.timeScale = timeScale;
});
mixerFolder.add(mixerControls, "stopAllAction").listen();
controls = addClipActionFolder("ClipAction 1", gui, clipAction, animationClip);
}
}
init();
function computeSize(obj) {
const cbox = new THREE.Box3().setFromObject(obj);
const size = cbox.getSize(new THREE.Vector3());
if (obj instanceof THREE.Scene) {
console.log("scene size", size);
} else {
console.log("obj size", size);
}
}
function addClipActionFolder(folderName, gui, clipAction, animationClip) {
const actionControls = {
keyframe: 0,
time: 0,
timeScale: 1,
repetitions: Infinity,
// warp
warpStartTimeScale: 1,
warpEndTimeScale: 1,
warpDurationInSeconds: 2,
warp: function () {
clipAction.warp(actionControls.warpStartTimeScale, actionControls.warpEndTimeScale, actionControls.warpDurationInSeconds);
},
fadeDurationInSeconds: 2,
fadeIn: function () {
clipAction.fadeIn(actionControls.fadeDurationInSeconds);
},
fadeOut: function () {
clipAction.fadeOut(actionControls.fadeDurationInSeconds);
},
effectiveWeight: 0,
effectiveTimeScale: 0,
};
const actionFolder = gui.addFolder(folderName);
actionFolder.add(clipAction, "clampWhenFinished").listen();
actionFolder.add(clipAction, "enabled").listen();
actionFolder.add(clipAction, "paused").listen();
actionFolder.add(clipAction, "loop", { LoopRepeat: THREE.LoopRepeat, LoopOnce: THREE.LoopOnce, LoopPingPong: THREE.LoopPingPong }).onChange(function (e) {
if (e == THREE.LoopOnce || e == THREE.LoopPingPong) {
clipAction.reset();
clipAction.repetitions = undefined;
clipAction.setLoop(parseInt(e), undefined);
} else {
clipAction.setLoop(parseInt(e), actionControls.repetitions);
}
});
actionFolder
.add(actionControls, "repetitions", 0, 100)
.listen()
.onChange(function (e) {
if (clipAction.loop == THREE.LoopOnce || clipAction.loop == THREE.LoopPingPong) {
clipAction.reset();
clipAction.repetitions = undefined;
clipAction.setLoop(parseInt(clipAction.loop), undefined);
} else {
clipAction.setLoop(parseInt(e), actionControls.repetitions);
}
});
actionFolder.add(clipAction, "time", 0, animationClip.duration, 0.001).listen();
actionFolder.add(clipAction, "timeScale", 0, 5, 0.1).listen();
actionFolder.add(clipAction, "weight", 0, 1, 0.01).listen();
actionFolder.add(actionControls, "effectiveWeight", 0, 1, 0.01).listen();
actionFolder.add(actionControls, "effectiveTimeScale", 0, 5, 0.01).listen();
actionFolder.add(clipAction, "zeroSlopeAtEnd").listen();
actionFolder.add(clipAction, "zeroSlopeAtStart").listen();
actionFolder.add(clipAction, "stop");
actionFolder.add(clipAction, "play");
actionFolder.add(clipAction, "reset");
actionFolder.add(actionControls, "warpStartTimeScale", 0, 10, 0.01);
actionFolder.add(actionControls, "warpEndTimeScale", 0, 10, 0.01);
actionFolder.add(actionControls, "warpDurationInSeconds", 0, 10, 0.01);
actionFolder.add(actionControls, "warp");
actionFolder.add(actionControls, "fadeDurationInSeconds", 0, 10, 0.01);
actionFolder.add(actionControls, "fadeIn");
actionFolder.add(actionControls, "fadeOut");
return actionControls;
}
</script>
合集:
three.js基础
分类:
javascript
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!