数字人Avatar如果只有头部模型,进入动作驱动阶段会立刻遇到身体缺失问题。全身生成需要同时解决身体比例、服装适配、纹理连续性和骨骼绑定四类问题。当前AI模型通常不是直接输出完整穿着服装的网格,而是先生成标准姿态下的身体网格,再生成或迁移服装资产,最后组合输出。下面从身体基础网格开始拆解。

身体基础网格:参数化模型与姿态归一化
全身Avatar的底层通常不直接使用AI回归出来的原始顶点,而是落在SMPL或SMPL-X这类参数化人体模型上。原因很直接:参数化模型的拓扑固定、顶点顺序一致,后续绑定骨骼、映射UV、复用服装模板都更加容易。以SMPL-X为例,模型通过betas控制高矮胖瘦,通过body_pose控制关节旋转,输出的人体网格始终是固定数量顶点。AI模型可以从单张人像预测这些参数,而不是预测几万个自由顶点,这样能大幅减少网格噪声。
姿态归一化是身体生成的前置步骤。输入图像可能是站姿、坐姿或手臂弯曲,直接预测身体形状会受到姿态干扰。常见做法是把图像先拟合到标准A-pose或T-pose,再提取形状参数。这个过程中会用到关键点检测、SMPL-X拟合和逆变换。归一化后的身体网格才能作为服装模板变形的基准,否则服装很容易在腋下、裆部出现异常拉伸。
直接生成顶点也不是完全不可行,但问题在于拓扑不一致。每次生成的顶点数量不同,三角面连接关系混乱,会导致后续骨骼绑定和动画状态机无法复用。因此工程上更稳妥的路线是参数化身体作为底层,AI生成的细节通过法线贴图、置换贴图或局部网格补充。
import smplx
import torch
model = smplx.create(model_path='./models', model_type='smplx',
gender='neutral', num_betas=10)
betas = torch.zeros(1, 10)
expression = torch.zeros(1, 10)
output = model(betas=betas, expression=expression)
vertices = output.vertices.detach().cpu().numpy()[0]
print(vertices.shape)
服装生成与身体对齐:避免穿模和接缝
服装是完整Avatar最容易出问题的部分。当前AI生成服装网格主要有两条路线:一是从多视角图像或扩散模型直接重建带衣服的粗糙网格,二是使用标准服装模板根据身体参数变形。前者能保留更多服装褶皱和轮廓,但拓扑质量差;后者拓扑稳定,适合动画和实时渲染。实际项目中通常先用扩散模型获得服装外观参考,再映射到模板网格上。
身体与服装对齐的核心是共享体型参数。如果身体使用beta参数生成,那么服装模板也应当使用同一组beta推导出胸围、腰围、臀围和肩宽。否则即使静态看起来合身,一旦身体参数改变,服装就会穿模。许多工程团队会预先为上衣、裤子、裙子建立尺寸系数表,用beta的线性组合近似每个部位的围度变化。
穿模检测不能只靠人工观察。可以在建模阶段引入三角网格碰撞检测,对每个服装顶点计算到身体表面的有符号距离。距离为负表示穿入身体内部,需要沿法线向外推。关节附近还需要增加简化的碰撞球或胶囊体,避免动作时肘部、膝盖穿透布料。下面是一段使用trimesh做穿透检查的代码。
import numpy as np
import trimesh
body = trimesh.load('body.obj', process=False)
cloth = trimesh.load('cloth_template.obj', process=False)
offset = 0.002
cloth.vertices += cloth.vertex_normals * offset
collision = trimesh.collision.ProximityQuery(body)
distances = collision.signed_distance(cloth.vertices)
print(np.min(distances), np.max(distances))
纹理烘焙与UV一致性
纹理决定了Avatar最终观感。身体和服装如果分开生成纹理,最容易出现边界色差和接缝。推荐在标准姿态下将身体与服装合并成一张UV布局,再使用多视角图像投影生成纹理。投影时正面、侧面、背面都要有对应视角,接缝处保留2到4像素的重叠,避免渲染时露出背景色。
UV展开可以选择xatlas这类自动展开工具,也可以使用Blender的智能UV。对于身体部分,SMPL-X自带标准UV,不建议重新展开,否则会丢失已有的皮肤细节。服装模板则可以在建模阶段手工展开,保证对称和拉伸均匀。最终交付时通常输出一张4K颜色贴图、一张法线贴图和一张粗糙度贴图。
材质需要分槽管理。身体和服装虽然合在一个网格里,但材质属性不同,皮肤有次表面散射,布料有纤维反光。导出GLB时可以使用多个材质槽,对合并后的网格按三角形分配材质索引。下面展示如何用xatlas为合并网格重新生成UV。
import xatlas
import trimesh
mesh = trimesh.load('merged_avatar.obj', process=False)
vmapping, indices, uvs = xatlas.parametrize(mesh.vertices, mesh.faces)
mesh.visual = trimesh.visual.TextureVisuals(uv=uvs)
mesh.export('avatar_uv.obj')
骨骼绑定与权重转移
静态网格无法直接用于动画。身体部分可以直接使用SMPL-X骨架和蒙皮权重,但服装必须共享同一套骨架,否则动起来会出现身体和衣服分离。权重转移的基本思路是:对每个服装顶点找到身体网格上的最近点,把对应蒙皮权重复制过去。这样服装在关节弯曲时会跟随身体变形。
最近邻方法简单但不够平滑,在关节周围容易出现权重突变。更稳定的做法是在最近邻基础上做拉普拉斯平滑,或者使用热扩散方法将权重传播到整个服装网格。对于裙摆、外套下摆等区域,还应当降低骨骼权重,增加布料物理模拟,使其自然摆动。
绑定完成后要检查权重总值和关节影响数量。每个顶点通常保留4到8个主要骨骼影响,权重要归一化。下面代码展示了从身体网格向服装网格转移蒙皮权重的最小实现。
import numpy as np
from scipy.spatial import cKDTree
body_verts = np.load('body_vertices.npy')
body_weights = np.load('body_skin_weights.npy')
cloth_verts = np.load('cloth_vertices.npy')
tree = cKDTree(body_verts)
dist, idx = tree.query(cloth_verts)
cloth_weights = body_weights[idx]
np.save('cloth_skin_weights.npy', cloth_weights)
完整Avatar的工程化组装
如果把所有步骤写在一个脚本里,一旦中间结果不理想,很难定位是身体参数错误、服装变形失败还是纹理投影歪了。推荐拆成五个独立阶段:人体参数估计、服装模板适配、网格合并与清理、纹理烘焙、骨骼绑定与导出。每个阶段输出中间资产,便于检查和迭代。
交付格式建议使用GLB或GLTF。GLB是二进制单文件,便于在网页和移动端加载;GLTF适合需要继续编辑的场景。导出前要检查单位是否为米、模型是否面向正Z轴、面法线方向是否正确。材质金属度、粗糙度和双面渲染参数也要在导出阶段固化。
下面是一个简化后的命令行流程,展示从单张输入图到最终Avatar资产的处理顺序。实际项目中每个脚本内部还包含错误处理和日志输出。
python estimate_body.py --image input.jpg --out body.json python generate_cloth.py --body body.json --style casual --out cloth.obj python merge_avatar.py --body body.json --cloth cloth.obj --out avatar.obj python bake_texture.py --mesh avatar.obj --views ./views --out avatar_tex.png python bind_avatar.py --mesh avatar.obj --skeleton body.json --out avatar.glb
全身数字人Avatar不是单个模型的简单输出,而是身体、服装、纹理、骨骼多个模块的协同结果。把参数化身体作为稳定底座,用同一组体型参数驱动服装,再统一处理UV和蒙皮权重,可以显著降低后续动画和实时渲染阶段的问题。理解这条链路后,再根据项目需求替换其中的生成模型或绑定工具,就能搭建出自己的全身Avatar生成管线。