当前位置: 首页 > news >正文

Three.js 单/多模型动画教程

单/多模型动画 ·Multi Clip Animation· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • 同一模型多个 clip 切换多 clip 同时播放
  • actionIndexs布尔数组控制播放哪些动画
  • 多个模型各自mixerAnimateRender挂到统一 rAF

效果说明

Soldier 模型,dat.GUI 按钮:单动画 0/1/2…切换单个动作;1,2 动画同时播放让两个 clip 并行 4 秒后 stop。

核心概念

与 modelAnimation 的 crossFade 不同,本案例用多 Action 同时 play + 权重默认混合

const actions = group.actionIndexs.map((enabled, k) => {

if (!enabled) return; const action = mixer.clipAction(group.animations[k]); action.loop = THREE.LoopRepeat; action.play(); return action; }).filter(Boolean);

mixerFrames数组收集各模型的mixerAnimateRender,在 animate 里统一forEach更新。

代码要点

import * as THREE from 'three'

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' import * as dat from 'dat.gui'

const box = document.getElementById('box')

const scene = new THREE.Scene()

const camera = new THREE.PerspectiveCamera(75, box.clientWidth / box.clientHeight, 0.1, 1000)

camera.position.set(5, 5, 5)

const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true })

renderer.setSize(box.clientWidth, box.clientHeight)

box.appendChild(renderer.domElement)

new OrbitControls(camera, renderer.domElement)

window.onresize = () => {

renderer.setSize(box.clientWidth, box.clientHeight)

camera.aspect = box.clientWidth / box.clientHeight

camera.updateProjectionMatrix()

}

const mixerFrames = []

animate()

function animate() {

requestAnimationFrame(animate)

mixerFrames.forEach(i => i?.mixerAnimateRender?.())

renderer.render(scene, camera)

}

scene.add(new THREE.AmbientLight(0xffffff, 4))

scene.add(new THREE.AxesHelper(1000))

// 加载模型 gltf/ glb draco解码器 const loader = new GLTFLoader()

loader.setDRACOLoader(new DRACOLoader().setDecoderPath(FILE_HOST + 'js/three/draco/'))

loader.load(

FILE_HOST + 'files/model/Soldier.glb',

gltf => {

const group = gltf.scene

group.animations = gltf.animations

scene.add(group)

group.actionIndexs = new Array(group.animations.length).fill(false)

createModeAnimates(group)

}

)

const GUI = new dat.GUI()

// 模型加载完成 const createModeAnimates = model => {

model.animations.forEach((_, k) => {

GUI.add({

fn: () => {

model.actionIndexs.forEach((_, _k, arr) => arr[_k] = _k === k)

modelAnimationPlay(model, model.animations)

}

}, 'fn').name(单动画${k})

});

// 多动画 GUI.add({

fn: () => {

const _actions = [1, 2] // 同时播放 第三个和第四个动画

model.actionIndexs.forEach((_, k, arr) => arr[k] = _actions.includes(k))

const { actions } = modelAnimationPlay(model, model.animations)

setTimeout(() => actions.forEach((v => v.stop())), 4000)

} }, 'fn').name('1, 2动画同时播放')

}

function modelAnimationPlay(group) {

const clock = new THREE.Clock()

const mixer = new THREE.AnimationMixer(group)

group.mixerAnimateRender = () => {

const deltaTime = clock.getDelta()

mixer.update(deltaTime)

}

const actions = group.actionIndexs.map((i, k) => {

if(i) {

const animationAction = mixer.clipAction(group.animations[k])

animationAction.loop = THREE.LoopRepeat animationAction.time = 0 animationAction.timeScale = 1 // 播放速度 animationAction.clampWhenFinished = true //停留到最后一帧 animationAction.play() return animationAction

}

}).filter(i => i)

!mixerFrames.find(i => i === group) && mixerFrames.push(group)

return { actions, mixer }

}

完整源码:GitHub

小结

  • 本文提供单/多模型动画完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
  • 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库
http://www.jsqmd.com/news/1101134/

相关文章:

  • 2026数据中心EC风机能效之争
  • 二维码修复技术深度解析:如何利用QrazyBox从零恢复损坏的二维码
  • 二阶段项目抖粉智算实战知识点:RabbitMQ异步消息队列
  • Windows微信QQ防撤回原理与实现:Hook技术与本地信息留存方案详解
  • MCP协议全面落地:AI Agent如何改变软件开发流程
  • 告别DOM污染!用CSS Custom Highlight API给你的网页搜索功能做个性能大升级
  • Mac Mouse Fix终极指南:释放普通鼠标在macOS上的全部潜能
  • 保姆级图解:从差分信号到8b/10b编码,手把手拆解PCIe物理层数据收发全流程
  • 2026年ABS吸塑包装定制,靠谱厂家这样选
  • 【VMware快照管理黄金法则】:20年资深架构师亲授5大避坑指南与3步极速回滚术
  • 国茂硬齿面减速机传动配件精度匹配标准拆解,维保必看
  • TOF模组:智能感知的核心测距引擎
  • 深度解析glogg:高性能日志分析工具的技术实现与实战指南
  • 别再只看Datasheet了!手把手教你读懂MOSFET的SOA曲线(以英飞凌IPW60R045C7为例)
  • vSphere 8.0环境下厚置备延迟清零与精简置备元数据膨胀(真实生产事故复盘+容量预测公式)
  • 计算机毕业设计之基于Web的就业管理系统
  • VMware虚拟机磁盘膨胀失控,如何安全压缩并规避快照损坏?(附PowerShell自动化脚本+校验清单)
  • Postman便携版:解锁Windows API开发的终极自由,告别安装烦恼的强力工具
  • ARM汇编里BL和BLR到底啥区别?用C语言函数指针一对比就懂了
  • Flutter异步编程避坑指南:为什么你的Future.microtask()没按预期执行?
  • SPC统计过程控制:半导体质量管控的核心利器
  • openEuler构建工具扩展开发:自定义构建步骤与插件编写终极指南
  • 扩容失败导致业务中断?VMware虚拟机磁盘扩容的7个关键检查点,第5项90%工程师都忽略!
  • 保姆级图解:用4机32卡环境,手把手拆解NCCL的三种Tree拓扑(附避坑指南)
  • TikTok 网红营销怎么做?从达人筛选到合作流程详细解析
  • 避开‘倒π’现象:为什么实际通信系统更偏爱2DPSK而非2PSK?
  • 别再乱用parallelStream了!Java8并行流实战避坑指南(附性能对比测试)
  • Java内存马技术解析:MemShellParty框架原理与攻防实践
  • 医学影像智能分析革命:FAE如何重塑放射组学研究范式
  • 【毕业设计】车辆管理系统设计与实现 SpringBoot+Vue 完整源码(含论文+数据库,可运行)