文本驱动3D角色动作创建并不是直接生成一段3D动画视频,而是把自然语言描述映射到动作参数空间。一条输入文本先经过文本编码器得到语义向量,这个向量作为条件送入动作生成模型。模型在动作特征空间中进行采样,输出一组姿态序列。之后这组姿态序列通过骨骼映射和运动学约束重定向到具体角色上,最终在引擎或DCC工具中渲染。链路核心是动作生成模型,而不是文本编码本身。

目前的文本编码器大多复用CLIP或T5。CLIP的文本特征对动作语义有一定对齐能力,但训练时并没有针对动作数据微调,所以在描述复杂动作时会出现偏差。T5的语义理解更强,很多文本驱动动作模型会把T5编码结果作为条件输入。动作生成侧,MDM和MotionDiffuse等扩散模型在HumanML3D和KIT-ML数据集上表现较好;T2M-GPT则用自回归方式生成离散动作token,生成速度更快,但多样性略弱。选择哪一类模型取决于项目对生成质量和实时性的要求。
从工程角度看,文本到3D动作生成链路通常被拆成四个部分:文本编码、动作生成、动作解码与骨骼重定向、渲染预览。文本编码负责把一句自然语言压缩成语义条件;动作生成模型在条件引导下采样动作特征;动作解码负责把特征转回旋转或位置数据;骨骼重定向则解决不同角色骨架之间的映射问题。任何一环缺失,都会导致最终动画出现语义不符、动作僵硬或脚部滑动。
一、文本编码与动作生成模型的选型
文本编码器决定了模型能否准确理解动作描述。CLIP文本分支在图像-文本对齐任务上很成熟,但动作数据的语义与静态图像差异很大,例如walk forward and wave right hand这样的连续动作描述,CLIP可能只捕捉到walk和wave两个孤立概念,丢失了动作的时序关系。因此,后续工作开始引入T5、BERT甚至动作专用文本编码器,将动作提示词映射到更适合运动生成的语义空间。
动作生成模型主要分为扩散类和自回归类。扩散类模型如MDM、MotionDiffuse,通过逐步去噪生成动作序列,生成质量较高,支持无分类器引导来增强文本一致性。自回归类模型如T2M-GPT,把动作序列转成离散token,再用GPT式结构逐帧预测,推理速度更快,适合低延迟场景。两类模型通常都在HumanML3D或KIT-ML数据集上训练,训练目标是最小化生成动作与真实动作之间的重建误差,同时让文本条件与动作特征对齐。
实际选型时,如果项目对动作多样性要求高,可以优先考虑扩散模型;如果需要在普通GPU甚至CPU上快速出结果,自回归模型更具优势。下面的代码演示了如何用文本编码器获取动作生成所需的条件特征,其中文本编码器可替换为T5或CLIP。
import torch
from transformers import AutoTokenizer, AutoModel
text = "the person walks forward and waves right hand"
tokenizer = AutoTokenizer.from_pretrained("t5-base")
text_encoder = AutoModel.from_pretrained("t5-base")
inputs = tokenizer(text, return_tensors="pt")
with torch.no_grad():
text_features = text_encoder(**inputs).last_hidden_state.mean(dim=1)
print(text_features.shape) # 输出文本条件特征,形状为 [1, hidden_dim]
二、关键动作表示:SMPL、旋转与运动学约束
动作生成模型通常不直接输出骨骼的欧拉角。欧拉角存在万向锁问题,输出范围也不连续,会导致训练不稳定。常见做法是使用SMPL姿态参数,将每个关节的旋转表示为三维轴角向量,再通过Rodrigues公式转换为旋转矩阵。SMPL的标准人体模板包含24个关节,姿态参数形状为(T, 24, 3),其中T是帧数。全局旋转控制人体朝向,其余关节表示局部旋转,形状参数控制高矮胖瘦。模型只需预测姿态参数,形状参数可以固定或由角色设定决定。
不过直接使用轴角向量也有问题,因为旋转的周期性和范数不唯一会给扩散模型带来噪声。因此很多工作会把轴角转换为6D旋转表示或四元数,并加入正交化损失。HumanML3D还构造了一个动作特征空间,把人体的运动特征压缩到约263维联合向量,同时包含根节点速度、关节位置和脚部接触信息。训练和评估时直接在这个特征空间中进行扩散,可以比较好地约束足部滑动问题。
从模型输出到目标骨骼还需要处理坐标系差异。例如SMPL的Y轴通常向上,而Unity的Z轴可能向上,Blender的坐标系又与两者不同。重定向时要统一根节点位移、关节旋转顺序和单位比例。下面代码展示了如何将轴角向量批量转换为旋转矩阵,这是把生成结果映射到DCC工具前的常见预处理步骤。
import torch
def axis_angle_to_rotation_matrix(axis_angle):
angle = torch.norm(axis_angle, dim=-1, keepdim=True)
axis = axis_angle / (angle + 1e-8)
K = torch.zeros(axis_angle.shape[0], axis_angle.shape[1], 3, 3, device=axis_angle.device)
K[..., 0, 1] = -axis[..., 2]
K[..., 0, 2] = axis[..., 1]
K[..., 1, 0] = axis[..., 2]
K[..., 1, 2] = -axis[..., 0]
K[..., 2, 0] = -axis[..., 1]
K[..., 2, 1] = axis[..., 0]
I = torch.eye(3, device=axis_angle.device).expand(axis_angle.shape[0], axis_angle.shape[1], 3, 3)
R = I + torch.sin(angle).unsqueeze(-1) * K + (1 - torch.cos(angle).unsqueeze(-1)) * (K @ K)
return R
pose = torch.randn(1, 24, 3)
R = axis_angle_to_rotation_matrix(pose)
print(R.shape) # torch.Size([1, 24, 3, 3])
三、扩散模型如何生成动作序列
MDM等运动扩散模型把动作特征当作数据样本。前向过程不断给真实动作加高斯噪声,直到变成纯噪声;反向过程则从噪声出发,每步用文本条件引导去噪,逐步恢复出符合语义的动作。MDM使用的网络是一个轻量级Transformer,输入包括当前噪声动作、去噪步长和文本特征,输出预测的动作噪声或原始动作。为了提升生成质量,模型可以在推理时使用无分类器引导,将无文本条件的结果与有文本条件的结果线性组合,增强文本一致性。
与图像扩散不同,动作数据是时序连续的,帧与帧之间存在强相关性。因此加噪不能只用空间维度,还要考虑时间一致性。通常的做法是对整个动作序列同时加噪,而不是逐帧独立处理。这样可以迫使模型学习人体运动在时间上的平滑性和物理约束。扩散步数在推理时可以压缩,配合DDIM采样器,几十步就能得到不错的结果,对交互式应用比较友好。
下面是一个简化版扩散采样循环,展示了从随机噪声逐步生成动作特征的核心结构。实际项目中还需要加入噪声调度器、时间步嵌入和文本交叉注意力,但整体流程基本一致。
import torch
def sample_motion(model, text_condition, steps=50, motion_length=120, feature_dim=263):
device = next(model.parameters()).device
x = torch.randn(1, motion_length, feature_dim, device=device)
timesteps = torch.linspace(1, 0, steps, device=device)
for t in timesteps:
t_batch = torch.full((1,), int(t * 1000), device=device, dtype=torch.long)
text_condition = text_condition.to(device)
predicted_noise = model(x, t_batch, text_condition)
x = x - predicted_noise * 0.1
return x
四、工程落地中的关键问题
生成动作之后还要做骨骼重定向。模型输出通常基于SMPL骨架,而项目中的角色可能使用不同骨骼层级和单位比例。重定向可以通过HumanIK、Maya的HIK或Blender的Rigify完成。需要把SMPL姿态重新解算到目标骨骼,同时保持根节点位移和脚部接触。脚部滑动是文本驱动动作生成中最容易暴露的问题,单靠模型输出很难完全避免,一般要在后处理阶段加入足部接触检测和IK修正。
性能优化也很关键。扩散模型在GPU上单次推理通常需要1秒到数秒,而实时对话场景希望控制在300毫秒以内。可以采用动作缓存、提前生成候选动作、降低去噪步数、把模型量化到FP16或INT8等方法。如果项目允许离线生成,可以一次性为高频文本描述生成动作库,运行时只做匹配和混合,这样能把延迟降到接近零。另一个常见问题是文本覆盖率,模型对训练集中未出现的动作组合理解较弱,需要允许用户输入简短关键词,并在提示词中加入动作节奏、方向、情感等辅助信息。
下面的代码演示了后处理中的两个常用操作:动作平滑和足部高度修正。动作平滑可以减少生成结果中的高频抖动,足部修正则强制接触地面的关节不会穿透地面。
import numpy as np
def smooth_motion(positions, window=5):
kernel = np.ones(window) / window
smoothed = np.apply_along_axis(lambda m: np.convolve(m, kernel, mode='same'), axis=0, arr=positions)
return smoothed
def enforce_foot_contact(positions, contact_joints, ground_height=0.0):
for joint in contact_joints:
positions[:, joint, 1] = np.minimum(positions[:, joint, 1], ground_height)
return positions
五、从文本到动画的最小可运行服务
如果要在自有项目中验证文本驱动动画,建议先不要从零训练模型。可以直接使用开源的预训练权重,如MDM或T2M-GPT,配合HumanML3D的特征提取器。服务端用FastAPI或Flask启动一个GPU推理接口,客户端发送中文或英文文本,服务端返回动作参数或BVH文件。对于中文输入,先用翻译模型或中文文本编码器统一为模型训练语言,避免语义漂移。动作结果可以输出为BVH,便于导入Blender、Maya和Unity。
服务端处理流程是:接收文本,编码文本条件,初始化高斯噪声,执行采样,反归一化到动作特征空间,再将动作特征解码为SMPL姿态或BVH关节位置。BVH不包含形状信息,只存储层次骨骼和每帧旋转,因此从SMPL输出到BVH时要提取标准骨骼的旋转值并调整坐标系。如果目标角色不是SMPL拓扑,还需要做一步重定向;很多团队会用Mixamo或自研自动绑定工具来完成这一步。
以下代码展示了生成BVH文件时头部和层次结构的基本输出逻辑。实际项目中还需要把每帧旋转数据写入对应的关节通道。
frames = 120
frame_time = 1 / 30
joint_names = ["Hips", "Spine", "Neck", "RightShoulder", "RightArm", "LeftShoulder", "LeftArm"]
print("HIERARCHY")
print("ROOT Hips")
print("{")
print(" OFFSET 0.0 0.0 0.0")
print(" CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation")
for name in joint_names[1:]:
print(" JOINT " + name)
print(" {")
print(" OFFSET 0.0 1.0 0.0")
print(" CHANNELS 3 Zrotation Xrotation Yrotation")
print(" End Site")
print(" {")
print(" OFFSET 0.0 1.0 0.0")
print(" }")
print(" }")
print("}")
print("MOTION")
print(f"Frames: {frames}")
print(f"Frame Time: {frame_time}")
整体来看,文本描述驱动3D角色动作创建已经具备落地条件,但距离自然语言与角色动画完全无缝连接还有距离。实际项目可以先用预训练模型做原型验证,再根据自家角色骨架和动作风格微调生成模型,同时配合后处理与缓存策略,把生成质量和实时性控制在可接受范围内。