OpenMontage Three.js 动画系统实战:关键帧、骨骼动画、Morph Targets 与动作混合
2026/9/10 10:55:50 网站建设 项目流程

OpenMontage Three.js 动画系统实战:关键帧、骨骼动画、Morph Targets 与动作混合

【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage

导读

本文围绕 OpenMontage 仓库中面向 AI Agent 的.agents/skills/threejs-animation/SKILL.md技能文档展开,系统讲解 Three.js 动画系统的三大核心组件(AnimationClip、AnimationMixer、AnimationAction)、六类 KeyframeTrack、GLTF 骨骼动画加载、Morph Targets 形变混合、基于权重的动作混合与叠加混合,以及平滑阻尼、弹簧物理、振荡三类程序化动画模式。这套技能在 OpenMontage 的 3D 世界中台(threejs_world工具 + HyperFrames 渲染链路)中有着直接落点:Agent 可以利用本文的知识为地形、地标与 GLTF 资产编写可编辑的浏览器原生动画,再交给视频合成管线输出成片。读完本文,你将掌握从"逐帧驱动"到"动作编排"的完整 Three.js 动画能力,并能理解这些能力如何嵌入 OpenMontage 的确定性世界生成与渲染流程。

Three.js 动画系统总览

Three.js 的动画系统由三个协同工作的核心组件构成,这也是整份技能文档的主线:

  1. AnimationClip—— 关键帧数据的容器,描述"一段动画里各属性在时间轴上的取值变化";
  2. AnimationMixer—— 动画播放器,挂在某个根对象(模型或场景节点)上,负责驱动该对象及其后代节点上的所有动画;
  3. AnimationAction—— 单个剪辑的播放控制器,管理播放/暂停、循环、速度、权重、淡入淡出等行为。

三者关系可以概括为:Clip 定义数据,Mixer 负责执行,Action 控制播放。一个 Mixer 可以同时持有多个 Action,这正是动作混合(Animation Blending)得以实现的基础。

快速开始:最简程序化动画循环

技能文档给出的 Quick Start 展示了最基本的动画骨架——用THREE.Clock提供稳定的帧间隔,而不是依赖Date.now()或浏览器时间戳:

import * as THREE from "three"; // Simple procedural animation const clock = new THREE.Clock(); function animate() { const delta = clock.getDelta(); const elapsed = clock.getElapsedTime(); mesh.rotation.y += delta; mesh.position.y = Math.sin(elapsed) * 0.5; requestAnimationFrame(animate); renderer.render(scene, camera); } animate();

这里的关键点是clock.getDelta()(本次帧与上次帧的时间差)与clock.getElapsedTime()(动画启动后的累计时间)的区分:基于 delta 的写法用于累加式运动(如旋转增量),基于 elapsed 的写法用于时间函数驱动的运动(如正弦起伏)。在 OpenMontage 的 world-runtime.js 中,这一模式被进一步演化为"时间驱动渲染":通过监听hf-seek事件按绝对时间renderAt(time)渲染每一帧,配合 GSAP 时间线(见 index.html 中的gsap.timeline),从而让动画与视频时间轴严格对齐。

AnimationClip:关键帧数据的容器

AnimationClip 存放一段动画的关键帧数据。一个最简剪辑由属性路径、关键帧时间点、对应取值三要素构成:

// Create animation clip const times = [0, 1, 2]; // Keyframe times (seconds) const values = [0, 1, 0]; // Values at each keyframe const track = new THREE.NumberKeyframeTrack( ".position[y]", // Property path times, values, ); const clip = new THREE.AnimationClip("bounce", 2, [track]);

AnimationClip构造函数签名是(name, duration, tracks),其中duration可以显式给出;如果不给,Three.js 会根据各轨道的最晚关键帧时间自动计算。属性路径支持点号与方括号两种语法(如.position[y].material.opacity),方括号可用于带下标或名称的字段,例如 Morph Target 的.morphTargetInfluences[smile]

KeyframeTrack 类型:六种常用轨道

技能文档系统罗列了六种轨道类型,分别对应不同的属性数据类型:

// Number track (single value) new THREE.NumberKeyframeTrack(".opacity", times, [1, 0]); new THREE.NumberKeyframeTrack(".material.opacity", times, [1, 0]); // Vector track (position, scale) new THREE.VectorKeyframeTrack(".position", times, [ 0, 0, 0, // t=0 1, 2, 0, // t=1 0, 0, 0, // t=2 ]); // Quaternion track (rotation) const q1 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0)); const q2 = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, Math.PI, 0)); new THREE.QuaternionKeyframeTrack( ".quaternion", [0, 1], [q1.x, q1.y, q1.z, q1.w, q2.x, q2.y, q2.z, q2.w], ); // Color track new THREE.ColorKeyframeTrack(".material.color", times, [ 1, 0, 0, // red 0, 1, 0, // green 0, 0, 1, // blue ]); // Boolean track new THREE.BooleanKeyframeTrack(".visible", [0, 0.5, 1], [true, false, true]); // String track (for morph targets) new THREE.StringKeyframeTrack( ".morphTargetInfluences[smile]", [0, 1], ["0", "1"], );

各轨道的数据长度规则值得注意:Vector 轨道每个关键帧 3 个分量,Quaternion 轨道每个关键帧 4 个分量(x/y/z/w),Color 轨道每个关键帧 3 个分量(r/g/b),数值数组必须与times长度严格对应。Rotatiton 必须使用四元数轨道而非欧拉角轨道,以避免万向锁与插值歧义。

插值模式

轨道默认线性插值,也可切换为样条平滑或离散跳变:

const track = new THREE.VectorKeyframeTrack(".position", times, values); // Interpolation track.setInterpolation(THREE.InterpolateLinear); // Default track.setInterpolation(THREE.InterpolateSmooth); // Cubic spline track.setInterpolation(THREE.InterpolateDiscrete); // Step function
  • InterpolateLinear:默认值,相邻关键帧直线过渡;
  • InterpolateSmooth:三次样条插值,曲线平滑、无拐点突变,适合相机路径与有机形变;
  • InterpolateDiscrete:阶跃函数,只在关键帧处切换取值,适合布尔开关或离散状态动画。

AnimationMixer:动画的执行器

Mixer 将一段(或多段)剪辑应用到对象及其后代节点上,是动画系统的心脏:

const mixer = new THREE.AnimationMixer(model); // Create action from clip const action = mixer.clipAction(clip); action.play(); // Update in animation loop function animate() { const delta = clock.getDelta(); mixer.update(delta); // Required! requestAnimationFrame(animate); renderer.render(scene, camera); }

最容易踩的坑是忘记调用mixer.update(delta)。Mixer 内部基于累计时间推进,update参数是帧间隔(秒),而不是绝对时间;只有每一帧都调用它,Action 的播放、循环、权重混合才会被真正计算并写回对象的属性。技能的 GLTF 示例中同样强调"Store mixer for update loop",并在动画循环里以if (window.mixer) window.mixer.update(delta)的方式更新。

Mixer 事件

Mixer 在播放到关键节点时会派发事件,可用于编排后续逻辑(如播完开场动画后切换状态机):

mixer.addEventListener("finished", (e) => { console.log("Animation finished:", e.action.getClip().name); }); mixer.addEventListener("loop", (e) => { console.log("Animation looped:", e.action.getClip().name); });

事件对象e.action指向触发事件的 Action,通过getClip().name可取得对应剪辑名,便于区分多个动画。

AnimationAction:播放控制中枢

Action 是日常开发中接触最多的对象,技能文档对其 API 做了全景式归纳,可分为五组:

播放控制

const action = mixer.clipAction(clip); // Playback control action.play(); action.stop(); action.reset(); action.halt(fadeOutDuration); // Playback state action.isRunning(); action.isScheduled();

play()只负责"计划播放",真正的状态由isScheduled()(已进入播放队列)与isRunning()(当前帧实际生效)区分;reset()将动作重置到剪辑起点再播放,halt(duration)则在一个给定的淡出时长后停止。

时间控制

// Time control action.time = 0.5; // Current time action.timeScale = 1; // Playback speed (negative = reverse) action.paused = false;

timeScale支持负值实现倒放,也支持 0.5、2.0 等变速效果;paused用于临时冻结。

权重(混合的基石)

// Weight (for blending) action.weight = 1; // 0-1, contribution to final pose action.setEffectiveWeight(1);

weight表示该 Action 对最终姿态的贡献比例(0 到 1),多个 Action 同时播放时,最终姿态是各 Action 按权重加权的结果。setEffectiveWeight会同时考虑全局权重开关与淡入淡出系数,返回实际生效的权重。

循环模式

// Loop modes action.loop = THREE.LoopRepeat; // Default: loop forever action.loop = THREE.LoopOnce; // Play once and stop action.loop = THREE.LoopPingPong; // Alternate forward/backward action.repetitions = 3; // Number of loops (Infinity default) // Clamping action.clampWhenFinished = true; // Hold last frame when done
  • LoopRepeat:默认模式,无限循环(repetitions默认Infinity);
  • LoopOnce:只播放一次;
  • LoopPingPong:正向播放后反向播放,适合呼吸、摇晃等往复运动;
  • clampWhenFinished:配合LoopOnce使用,播完后钉在最后一帧,而不是跳回起点。

混合模式

// Blending action.blendMode = THREE.NormalAnimationBlendMode; action.blendMode = THREE.AdditiveAnimationBlendMode;

NormalAnimationBlendMode是常规的加权混合,AdditiveAnimationBlendMode是叠加混合(在基础姿态上叠加差值),详见下文"叠加混合"。

淡入淡出与交叉过渡

// Fade in action.reset().fadeIn(0.5).play(); // Fade out action.fadeOut(0.5); // Crossfade between animations const action1 = mixer.clipAction(clip1); const action2 = mixer.clipAction(clip2); action1.play(); // Later, crossfade to action2 action1.crossFadeTo(action2, 0.5, true); action2.play();

fadeIn/fadeOut是带时长的权重渐变;crossFadeTo(otherAction, duration, warp)实现两段动作间的平滑过渡,第三个参数warptrue时还会对时间比例做 warp 校正,避免过渡期间动作速度不一致导致的跳变。这是角色从"待机"切到"走路"再切到"奔跑"的标准做法。

加载 GLTF 骨骼动画

GLTF/GLB 是骨骼动画最常见的来源。技能文档给出了从加载、取剪辑、按名播放到接入循环的完整流程:

import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; const loader = new GLTFLoader(); loader.load("model.glb", (gltf) => { const model = gltf.scene; scene.add(model); // Create mixer const mixer = new THREE.AnimationMixer(model); // Get all clips const clips = gltf.animations; console.log( "Available animations:", clips.map((c) => c.name), ); // Play first animation if (clips.length > 0) { const action = mixer.clipAction(clips[0]); action.play(); } // Play specific animation by name const walkClip = THREE.AnimationClip.findByName(clips, "Walk"); if (walkClip) { mixer.clipAction(walkClip).play(); } // Store mixer for update loop window.mixer = mixer; }); // Animation loop function animate() { const delta = clock.getDelta(); if (window.mixer) window.mixer.update(delta); requestAnimationFrame(animate); renderer.render(scene, camera); }

要点在于:Mixer 必须挂在gltf.scene这个根对象上,而不是挂在单个网格上,否则骨骼驱动链路(Mixer → 骨骼 → SkinnedMesh 顶点)不会生效。AnimationClip.findByName(clips, "Walk")是查找命名剪辑的标准方式。

OpenMontage 中的 GLTF 加载实践

在 OpenMontage 的 3D 世界中台里,GLTF 资产的加载不是手写loader.load,而是由 threejs_asset_catalog.py 先安装权属清晰的本地资产目录(内置 Kenney Nature Kit、Fantasy Town Kit、Survival Kit 等 CC0 目录),再由 world-runtime.js 在production质量档下用GLTFLoader.loadAsync异步加载调色板模型:

async function loadProductionPalette() { if (qualityTier !== "production") return; const loader = new GLTFLoader(); const prototypes = new Map(); const palette = WORLD_SPEC.asset_palette || []; await Promise.all(palette.map(async (entry) => { const key = `${entry.catalog_id}:${entry.model_id}`; const path = catalogModels.get(key); if (!path || prototypes.has(key)) return; const gltf = await loader.loadAsync(path); gltf.scene.traverse((node) => { if (!node.isMesh) return; node.castShadow = renderMode === "cinematic"; node.receiveShadow = renderMode === "cinematic"; if (node.material) node.material.envMapIntensity = 0.8; }); prototypes.set(key, gltf.scene); })); // ... 按世界 spec 的 asset_palette 布局克隆实例 }

这段源码体现了动画技能在真实工程中的两个重要补充:其一,加载完成后用traverse统一设置阴影与材质参数,避免模型自带材质与环境不匹配;其二,prototype.clone(true)深拷贝同一份加载结果生成大量实例,避免为每个散布点重复走网络加载。这与技能文档"Share clips"的性能建议一脉相承——同一份资源(Clip 或 Scene 原型)可以在多个对象上复用。

骨骼动画(Skeletal Animation)

访问骨架与骨骼

// Access skeleton from skinned mesh const skinnedMesh = model.getObjectByProperty("type", "SkinnedMesh"); const skeleton = skinnedMesh.skeleton; // Access bones skeleton.bones.forEach((bone) => { console.log(bone.name, bone.position, bone.rotation); }); // Find specific bone by name const headBone = skeleton.bones.find((b) => b.name === "Head"); if (headBone) headBone.rotation.y = Math.PI / 4; // Turn head // Skeleton helper const helper = new THREE.SkeletonHelper(model); scene.add(helper);

SkeletonHelper用于调试——它会画出骨骼的可视化连线,方便确认骨骼层级与动画作用范围。

程序化骨骼动画

不依赖剪辑,直接逐帧修改骨骼姿态,适合"活着感"的动态(呼吸、头部跟随等):

function animate() { const time = clock.getElapsedTime(); // Animate bone const headBone = skeleton.bones.find((b) => b.name === "Head"); if (headBone) { headBone.rotation.y = Math.sin(time) * 0.3; } // Update mixer if also playing clips mixer.update(clock.getDelta()); }

注意:程序化修改骨骼与 Mixer 播放剪辑可以并存,但 Mixer 的update仍须调用,否则剪辑驱动的骨骼会停在原地、与程序化骨骼产生冲突。

骨骼挂点(附件系统)

武器、道具等物体可以挂到骨骼上,随骨骼一起运动:

// Attach object to bone const weapon = new THREE.Mesh(weaponGeometry, weaponMaterial); const handBone = skeleton.bones.find((b) => b.name === "RightHand"); if (handBone) handBone.add(weapon); // Offset attachment weapon.position.set(0, 0, 0.5); weapon.rotation.set(0, Math.PI / 2, 0);

把网格addBone节点后,它的变换就进入骨骼局部坐标系,骨骼旋转时武器自然跟随;偏移量通过挂点自身的position/rotation调整。

Morph Targets:形状混合动画

Morph Targets(形变目标/混合变形)在两个或多个网格形状之间做顶点级混合,常用于面部表情:

// Morph targets are stored in geometry const geometry = mesh.geometry; console.log("Morph attributes:", Object.keys(geometry.morphAttributes)); // Access morph target influences mesh.morphTargetInfluences; // Array of weights mesh.morphTargetDictionary; // Name -> index mapping // Set morph target by index mesh.morphTargetInfluences[0] = 0.5; // Set by name const smileIndex = mesh.morphTargetDictionary["smile"]; mesh.morphTargetInfluences[smileIndex] = 1;

morphTargetInfluences是每个形变目标的权重数组(0~1),morphTargetDictionary提供"名称 → 索引"的映射,按名操作比按裸索引更抗重构。

两种驱动方式

Morph 权重既可以用代码实时驱动,也可以用关键帧轨道驱动:

// Procedural function animate() { const t = clock.getElapsedTime(); mesh.morphTargetInfluences[0] = (Math.sin(t) + 1) / 2; } // With keyframe animation const track = new THREE.NumberKeyframeTrack( ".morphTargetInfluences[smile]", [0, 0.5, 1], [0, 1, 0], ); const clip = new THREE.AnimationClip("smile", 1, [track]); mixer.clipAction(clip).play();

程序化方式的优势是实时反馈(例如根据音频响度驱动张嘴幅度);关键帧方式则适合与整体动画时间轴严格同步。技能文档中的 String 轨道写法(.morphTargetInfluences[smile]"0"/"1")是兼容某些 GLTF 导出器将 morph 名称写入 string track 的另一种路径。

动画混合(Animation Blending)

基于权重的状态混合

典型场景:根据角色移动速度在 idle/walk/run 三个动作间连续过渡:

// Setup actions const idleAction = mixer.clipAction(idleClip); const walkAction = mixer.clipAction(walkClip); const runAction = mixer.clipAction(runClip); // Play all with different weights idleAction.play(); walkAction.play(); runAction.play(); // Set initial weights idleAction.setEffectiveWeight(1); walkAction.setEffectiveWeight(0); runAction.setEffectiveWeight(0); // Blend based on speed function updateAnimations(speed) { if (speed < 0.1) { idleAction.setEffectiveWeight(1); walkAction.setEffectiveWeight(0); runAction.setEffectiveWeight(0); } else if (speed < 5) { const t = speed / 5; idleAction.setEffectiveWeight(1 - t); walkAction.setEffectiveWeight(t); runAction.setEffectiveWeight(0); } else { const t = Math.min((speed - 5) / 5, 1); idleAction.setEffectiveWeight(0); walkAction.setEffectiveWeight(1 - t); runAction.setEffectiveWeight(t); } }

这种写法的核心思想是:所有候选动作始终保持play()状态,只动态调整权重,权重之和恒为 1,从而得到数学上连续的姿态插值。相比"先 fadeOut 一个再 fadeIn 另一个",权重混合更平滑、无重叠权重缺口。

叠加混合(Additive Blending)

叠加层用于在基础动作之上"叠加"细碎动态,而不干扰基础姿态:

// Base pose const baseAction = mixer.clipAction(baseClip); baseAction.play(); // Additive layer (e.g., breathing) const additiveAction = mixer.clipAction(additiveClip); additiveAction.blendMode = THREE.AdditiveAnimationBlendMode; additiveAction.play(); // Convert clip to additive THREE.AnimationUtils.makeClipAdditive(additiveClip);

典型用法是基础剪辑播"跑步",叠加剪辑播"呼吸起伏",两者互不干扰。AnimationUtils.makeClipAdditive(clip)会把一段普通剪辑原地转换为叠加剪辑(可传入参考剪辑/参考帧指定差值基准)。

动画工具函数

技能文档还汇总了AnimationUtils的常用工具,适合剪辑复用与后处理:

import * as THREE from "three"; // Find clip by name const clip = THREE.AnimationClip.findByName(clips, "Walk"); // Create subclip const subclip = THREE.AnimationUtils.subclip(clip, "subclip", 0, 30, 30); // Convert to additive THREE.AnimationUtils.makeClipAdditive(clip); THREE.AnimationUtils.makeClipAdditive(clip, 0, referenceClip); // Clone clip const clone = clip.clone(); // Get clip duration clip.duration; // Optimize clip (remove redundant keyframes) clip.optimize(); // Reset clip to first frame clip.resetDuration();

其中subclip(clip, name, startFrame, endFrame, fps)从原剪辑截取一段(按帧号换算时间);optimize()删除冗余关键帧(同一取值区间内的多余帧),在保证视觉不变的前提下压缩数据量;resetDuration()duration回到按轨道重新计算的正确值,常用于对剪辑做编辑之后。

程序化动画模式

除了剪辑驱动的"录制式"动画,技能文档提供了三类高频复用的程序化运动模式,它们不依赖动画系统,直接修改对象变换,特别适合相机跟随、UI 弹性和装饰物运动。

平滑阻尼(Smooth Damping)

Unity 风格SmoothDamp的三维实现,特点是加速度随时间自然衰减、无过冲且帧率无关

// Smooth follow/lerp const target = new THREE.Vector3(); const current = new THREE.Vector3(); const velocity = new THREE.Vector3(); function smoothDamp(current, target, velocity, smoothTime, deltaTime) { const omega = 2 / smoothTime; const x = omega * deltaTime; const exp = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x); const change = current.clone().sub(target); const temp = velocity .clone() .add(change.clone().multiplyScalar(omega)) .multiplyScalar(deltaTime); velocity.sub(temp.clone().multiplyScalar(omega)).multiplyScalar(exp); return target.clone().add(change.add(temp).multiplyScalar(exp)); } function animate() { current.copy(smoothDamp(current, target, velocity, 0.3, delta)); mesh.position.copy(current); }

velocity对象必须在多次调用间持续持有(否则失去阻尼记忆),smoothTime越小跟随越快。

弹簧物理(Spring Physics)

带刚度与阻尼的二阶弹簧,用于弹性回弹、受击震动、UI 弹跳等:

class Spring { constructor(stiffness = 100, damping = 10) { this.stiffness = stiffness; this.damping = damping; this.position = 0; this.velocity = 0; this.target = 0; } update(dt) { const force = -this.stiffness * (this.position - this.target); const dampingForce = -this.damping * this.velocity; this.velocity += (force + dampingForce) * dt; this.position += this.velocity * dt; return this.position; } } const spring = new Spring(100, 10); spring.target = 1; function animate() { mesh.position.y = spring.update(delta); }

stiffness决定回复力大小(越高越"硬"),damping决定能量损耗(越高越早静止);当damping² < 4·stiffness时系统会出现欠阻尼振荡,这是制造弹性的关键区间。

振荡与轨迹运动

用时间函数直接合成运动轨迹,是装饰物、粒子与相机微动的最轻量方案:

function animate() { const t = clock.getElapsedTime(); // Sine wave mesh.position.y = Math.sin(t * 2) * 0.5; // Bouncing mesh.position.y = Math.abs(Math.sin(t * 3)) * 2; // Circular motion mesh.position.x = Math.cos(t) * 2; mesh.position.z = Math.sin(t) * 2; // Figure 8 mesh.position.x = Math.sin(t) * 2; mesh.position.z = Math.sin(t * 2) * 1; }

技能文档给出的四个模式——正弦起伏、取绝对值制造"弹跳"、正余弦合成圆环、倍频合成"8 字"——覆盖了从波动到轨迹巡航的大多数装饰性需求。在 OpenMontage 的 world-runtime.js 中同样可以看到这类时间函数:水面透明度0.73 + Math.sin(time * 0.42) * 0.035、太阳强度0.96 + Math.sin(time * 0.09) * 0.04,都是"振荡模式"在场景氛围动画上的直接应用。

性能优化建议

技能文档给出了五条动画性能原则,对任何规模的三维场景都适用:

  1. 共享剪辑:同一份AnimationClip可以被多个 Mixer 复用,不要在每次使用时重新构造;
  2. 优化剪辑:对冗余关键帧调用clip.optimize()压缩;
  3. 离屏暂停:不可见对象停止mixer.update
  4. 按距离使用 LOD:远景角色使用简化骨骼/低骨骼数模型;
  5. 控制 Mixer 数量:每个mixer.update()都有遍历开销,尽量合并对象、减少活跃 Mixer。

配套的工程化写法包括按视锥裁剪暂停动作,以及用 Map 做剪辑缓存:

// Pause animation when not visible mesh.onBeforeRender = () => { action.paused = false; }; mesh.onAfterRender = () => { // Check if will be visible next frame if (!isInFrustum(mesh)) { action.paused = true; } }; // Cache clips const clipCache = new Map(); function getClip(name) { if (!clipCache.has(name)) { clipCache.set(name, loadClip(name)); } return clipCache.get(name); }

在 OpenMontage 的 3D 世界中台中,性能治理还有更系统化的一层:ThreeJSWorld工具(threejs_world.py)在构建阶段就把场景统计写入诊断报告(terrain_trianglesenvironment_instances等,见其_report方法),并在production档通过保真度闸门(_fidelity_gate)约束资产调色板与地形材质数量。密集环境物统一走InstancedMesh合批渲染(makeInstanced),而散布点由确定性种子生成,保证不同渲染轮次结果一致。

在 OpenMontage 中的完整落点:从技能到可渲染工作区

将本文的动画知识放入 OpenMontage 的全局上下文,可以看到一条完整的调用链:

  1. 技能选路:Agent 依据 skills/INDEX.md 的"3D Graphics"分类定位threejs-animation(动画能力)、threejs-loaders(GLTF 加载)与threejs-world-generation(OpenMontage 语义世界工作流);
  2. 世界规范:Agent 向threejs_world工具提交结构化world_spec(区域、地标、相机路径、资产调色板),输入约束记录在 schemas/tools/threejs_world.schema.json(operationbuild/validaterender_modecinematic/semantic/wireframequality_tierblockout/production);
  3. 资产就绪:通过 threejs_asset_catalog.py 安装 CC0 许可的 GLTF 目录(install 时校验 SHA-256、生成catalog-manifest.json与模型清单);
  4. 工作区物化:工具把模板 index.html、world-runtime.js 与规范化后的world.json一起写入可编辑工作区;运行时可响应hf-seek时间事件、暴露window.__worldRenderAt(time)供时间轴驱动逐帧渲染;
  5. 确定性验证:test_threejs_world.py 断言同一 spec 两次 build 的world-spec.jsSHA-256 完全一致,并验证相机路径首尾时间、区域唯一性、生产档闸门等契约;
  6. 成片输出:工作区最终交由video_compose/hyperframes_compose以"atelier"模式渲染为视频,动画(无论是剪辑驱动的 GLTF 动作,还是renderAt(time)时间函数驱动的镜头运动)都在这一环节成为成片内容。

这条链路表明:threejs-animation技能不只是孤立的 API 速查,它直接服务于"确定性 3D 世界生成 → 可编辑工作区 → 时间轴驱动渲染"的生产体系。当你需要让 GLTF 模型播放动画、用骨骼程序化驱动角色细节、用 Morph Targets 做表情混合,或为镜头与装饰物编写程序化运动时,本文覆盖的 API 与模式就是 Agent 落地这些需求的执行依据。

相关技能延伸

  • threejs-loaders—— 加载带动画的 GLTF 模型;
  • threejs-fundamentals—— Clock 与动画循环基础;
  • threejs-shaders—— 在着色器中实现顶点动画。

【免费下载链接】OpenMontageWorld's first open-source, agentic video production system. 12 production pipelines, 100+ tools, 700+ agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询