Three.js-硬要自学系列31之专项学习动画混合

动画混合核心原理

动画混合(Animation Blending) 就像一位 DJ 同时播放多首歌,并将它们自然过渡拼接成一首新曲。它让 3D 模型(比如游戏角色)能在不同动作之间平滑切换(如从"走路"渐变到"跑步"),不会出现卡顿或突兀的跳跃。

想象你的角色有两个动作:

  1. 站立不动(动画 A)
  2. 挥手打招呼(动画 B)

直接切换会显得僵硬🤖,就像突然从静止跳到挥手。动画混合的作用是:

  • 权重(Weight)控制:给每个动作分配一个"音量旋钮"(权重值 0~1),比如:

    • 初始:动画 A 权重=1(站立),动画 B 权重=0(挥手关闭)
    • 过渡期:动画 A 权重从 1 → 0,动画 B 权重从 0 → 1
    • 最终:动画 A 权重=0,动画 B 权重=1(完全挥手)
  • 实时混合计算 :每一帧,Three.js 会根据权重比例,同时播放两个动作并叠加效果,生成中间过渡状态。

动画混合的常见用途

场景 效果说明 混合策略
角色动作切换 走路 → 跑步 → 跳跃 自然过渡 权重渐变(0→1/1→0)
复合动作 边走路边挥手 多个动作权重同时为 1
情绪叠加 受伤时跛脚行走 正常行走 + 受伤姿态混合

使用注意

  1. 权重总和 ≤ 1:所有动作的权重相加不超过 1,否则动作会"变形"
  2. 时间同步:混合的动画需时间轴对齐(如都是从左脚起步)。
  3. 性能优化:过多动画混合会增加计算量,需合理控制数量

向量值定义动画案例

效果如图

需要了解的API

动画混合器(Animation Mixer)

  • 角色动画的"总控台",负责管理所有动画片段和混合效果

动画行为(Animation Action)

  • 每个动作(如走路、跑步)都是一个独立的 AnimationAction 对象,其用于创建并控制动画行为

动画剪辑 (AnimationClip)

  • 用于存储和管理关键帧动画数据,整合多个轨道,形成完整的动画片段,其本身不执行动画

实现思路

使用VectorKeyframeTrack来定义动画运动轨迹,

js 复制代码
const track = new THREE.VectorKeyframeTrack('.position', [0,1],[
    5, 0, -5,
    -5, 0, 5
])

这里定义了两个时间节点,相对应的是两个坐标点,这意味着0时对应在第一个坐标位置,1时在第二个坐标位置

js 复制代码
const clip = new THREE.AnimationClip('move', -1, [track]);
const mixer = new THREE.AnimationMixer(mesh);
const action = mixer.clipAction(clip);
action.play();

这里定义了一个名为move的动画剪辑,其作用于mesh模型上,并将无限循环播放

js 复制代码
const update = (frame, frameMax) => {
    const a_frame = frame / frameMax; // 当前帧占总帧数的比例
    const a_framesin = ( Math.sin( Math.PI *2 * a_frame) + 1) / 2; 
    mixer.setTime( 1 * a_framesin); // 设置动画时间
}

mixer.setTime用于精确控制动画的播放时间点

JSON格式的动画效果案例

效果如图

需要了解的API

AnimationClip.parse

该api是一个静态方法,用于将JSON格式的动画数据反序列化为 AnimationClip 对象

JSON 数据结构要求
字段 类型 说明 是否必需
name string 动画名称
tracks Array<Object> 关键帧轨迹数据
duration number 动画总时长(秒)
blendMode number 混合模式(如 NormalAnimationBlendMode
uuid string 唯一标识符

在这个案例中,使用AnimationClip.parse将给定的json数据转换为AnimationClip对象

js 复制代码
const str_json = `{
    "name": "scale",
    "duration":3,
    "tracks":[
        {
        "name":".scale",
        "times":[0,1.5,3],
        "values":[ 1,1,1, 0.5,3,0.5, 1,1,1],
        "type":"vector"
        },
        {
        "name":".position",
        "times":[0, 0.5, 1.5, 2.5, 3],
        "values":[ 0,0.5,0, 2,1.5,0, -2,1.5,0, -2,1.5,2, 0,0.5,0],
        "type":"vector"
        }
    ],
    "uuid":"eff507b6-8c41-47d4-a62e-77119a5ee288",
    "blendMode":2500
}`
const clip = THREE.AnimationClip.parse( JSON.parse(str_json) )

const mixer = new THREE.AnimationMixer(mesh);
const action = mixer.clipAction(clip);
action.play();

从代码中可以看到定义了两个轨道动画状态,一个是缩放,一个是位置

加载json格式场景数据案例

如图效果

需要了解的API

ObjectLoader().parse 该方法将JSON格式的 3D 场景数据实时解析为 Three.js 可操作对象(场景、模型、动画等),实现动态场景加载与重建。

数据结构如下

js 复制代码
const str_json = `{
    "metadata": {
        "version": 4.3,
        "type": "Object",
        "generator": "Hand Coded"
    },
    "textures": [],
    "images": [],
    "geometries": [
        {
            "uuid": "bce89f32-4cab-41c8-b3f6-15e04b1dd68e",
            "type": "BufferGeometry",
            "data": {
                "attributes": {
                    "position": {
                        "itemSize": 3,
                        "type": "Float32Array",
                        "array": [0,0,0, 4,0,0, 0,0,4 ],
                        "normalized": false
                    }
                },
                "morphTargetsRelative": true,
                "morphAttributes": {
                    "position": [
                        {
                            "itemSize":3,
                            "type":"Float32Array",
                            "array":[
                                0.0, 3.0, 0.0,
                                -4.0, 3.0, 0.0,
                                0.0, 3.0,-4.0
                            ],
                            "normalized":false
                        }
                    ]
                }
            }
        }
    ],
    "materials": [
        {
            "uuid": "0246dafa-bf34-4460-a7eb-f5098b2120af",
            "type": "PointsMaterial",
            "size": 1,
            "color": 65480
        }
    ],
    "animations": [
        {
            "name": "converge",
            "duration": 1,
            "tracks": [
                {
                    "name": ".morphTargetInfluences[0]", 
                    "times": [0, 0.5, 1],
                    "values": [0, 1, 0],
                    "type": "number"
                }
            ],
            "uuid": "584702c7-efb2-4fa1-ae37-43d18b5f7fb5",
            "blendMode": 2500
        }
    ],
    "object": {
        "uuid": "ad1ecebd-b665-4e10-9ead-6d0205bec011",
        "type": "Scene",
        "matrix": [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ],
        "children": [
            {
                "uuid": "6b95325d-5bcc-4f85-a330-fbca8f271287",
                "name": "tri_one",
                "type": "Points",
                "geometry": "bce89f32-4cab-41c8-b3f6-15e04b1dd68e",
                "material": "0246dafa-bf34-4460-a7eb-f5098b2120af",
                "matrix": [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ],
                "animations": ["584702c7-efb2-4fa1-ae37-43d18b5f7fb5"]
            }
        ]
    }
}`;

这是一个场景数据结构,包含了几何体、材质、动画,模型对象等内容

js 复制代码
scene = new THREE.ObjectLoader().parse( JSON.parse(str_json) );
const points = scene.getObjectByName('tri_one');
const mixer = new THREE.AnimationMixer(points);
const action = mixer.clipAction(points.animations[0]);
action.play();

蝴蝶动画案例

先看效果

需要了解的API

BufferGeometryLoader

该api 用于加载预序列化的BufferGeometry数据,跳过完整场景解析,直接获取几何体对象。

NumberKeyframeTrack

用于在动画中精准控制单一数值属性的关键帧变化(如透明度、强度、进度条等)

js 复制代码
const loader = new THREE.BufferGeometryLoader();
loader.load(
    './json/tri12-butterfly/set1-buffergeometry/0.json', 
    (geometry) =>{
    // 创建一个网格,使用几何体和基本材质
    state.mesh = new THREE.Mesh(
        geometry,
        new THREE.MeshBasicMaterial({vertexColors: true, side: THREE.DoubleSide})
    )
    // 将网格添加到场景中
    scene.add(state.mesh)
    // 创建一个数字关键帧轨道,用于控制网格的变形
    const track = new THREE.NumberKeyframeTrack('.morphTargetInfluences[0]', 
        [ 0, 0.25, 0.5, 0.75, 1], // 时间点
        [ 0, 0.30, 0.5, 0.15, 0]  // 变形程度
    )
    // 创建一个动画剪辑,包含一个关键帧轨道
    const clip = new THREE.AnimationClip('flap', -1, [ track ] );  
    // 将动画剪辑添加到网格的动画数组中
    state.mesh.animations.push(clip)
    // 创建一个动画混合器,用于控制网格的动画
    state.mixer = new THREE.AnimationMixer(state.mesh)
    // 获取网格的第一个动画剪辑
    const action = state.mixer.clipAction( state.mesh.animations[0] );
    // 播放动画
    action.play();

    animation()
})

这里要注意使用NumberKeyframeTrack创建了一个数字关键帧轨道,来控制变形强弱

总结动画实现步骤

  1. 创建动画剪辑
  2. 创建混合器并播放
  3. 在动画循环中更新

以上便是本篇的所有内容,自己动手练一练,感受一下实际效果

相关推荐
代码搬运媛6 小时前
Jest 测试框架详解与实现指南
前端
counterxing7 小时前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程
wangqiaowq7 小时前
windows下nginx的安装
linux·服务器·前端
之歆8 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
发现一只大呆瓜8 小时前
Vite凭什么这么快?3分钟带你彻底搞懂 Vite 热更新的幕后黑手
前端·面试·vite
Maimai108088 小时前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
kyriewen10 小时前
产品经理把PRD写成“天书”,我用AI半小时重写了一遍,他当场愣住
前端·ai编程·cursor
humcomm10 小时前
元框架的工作原理详解
前端·前端框架
canonical_entropy10 小时前
Attractor Before Harness: AI 大规模开发的方法论
前端·aigc·ai编程
贵州数擎科技有限公司11 小时前
曼德勃罗集的 Three.js 实现
webgl·three.js