glTF 2.0 是唯一的模型和动画格式。不是首选的那个——是唯一的那个。 这个决定移除了一层抽象、一条导入管线,以及一类两种格式在绑定姿势上 不一致的 bug。
Model 使用引擎统一的摆放约定把一个 GLB
放进世界:位置是锚点的世界位置,宽、高、深是目标包围盒尺寸。
var robot = new Model
{
Name = "Assets/3DGodotRobot.glb",
PosX = -2f, PosY = 1f, PosZ = 6f,
Width = 1f, Height = 1f, Depth = 0.5f,
Rotation = MathF.PI / 2f
};
AddControl(robot);
Width、Height 和
Depth 是以世界单位计的目标尺寸,不是倍数。
写 Width = 1f 会让模型正好一个单位宽,
不管文件里怎么说。而 Rotation 是绕 Y 轴的
弧度——写 90 得到的是 90 弧度,
归一化后大约是 117 度,看起来几乎是对的,这是最糟糕的一类 bug。
摆放、锚点与相机 讲解了锚点数学,包括
AnchorWorldOffset——用于你想要钉住模型自身原点、
而不是其包围盒中心的情形。
蒙皮、变形目标和命名片段全都来自文件。片段按名称寻址, 这些名称就是美术使用的名称。
// After Load has completed:
foreach (var name in robot.GetAnimationNames())
app.AddLog(LogType.Info, name);
robot.PlayAnimation("Walk");
// Or cycle, which is what a debug key usually wants:
robot.SwitchToNextAnimation();
// Advance the clock yourself; Time is the animation clock.
robot.Update(time);
| 成员 | 说明 |
|---|---|
GetAnimationNames() | 片段名,资产加载完成后即可用。 |
PlayAnimation(string) | 切换片段。返回实际选中的名称。 |
GetCurrentAnimationName() | 当前正在播放什么。 |
SwitchToNextAnimation() | 在片段列表中前进,循环回绕。 |
Time | 动画时钟,单位为秒。 |
SetModel(name, forceReload) | 替换底层 GLB,保持摆放不变。 |
切换片段是切刀,不是交叉淡入。没有状态机,没有混合树, 没有叠加层。如果一个角色需要一边走路一边瞄准, 这是引擎目前不会替你做的部分。
一个 InstancedModel 声明一次,并绑定到一列实例变换。
蒙皮和变形目标按实例独立,所以二十个角色可以在一次绘制调用里各播放不同的片段。
参考应用中的机器人、鸟、岩石和海滩用的都是这个模式。
robotField = new InstancedModel { ModelName = "Assets/3DGodotRobot.glb" };
AddControl(robotField);
for (var i = 0; i < 10; i++)
{
var person = new Person
{
PosX = -2f, PosY = 1f, PosZ = 6f + i * 3f,
Width = 1f, Height = 1f, Depth = 0.5f,
Animation = animations[i]
};
robotField.Instances.Add(person);
}
Person 派生自
MeshInstanceTransform,所以列表里的对象
就是实例本身。编辑它的字段下一帧就生效,没有拷贝步骤;
在列表中间插入或删除会保持顺序。
MeshInstanceTransform 上的字段 | 说明 |
|---|---|
PosX/Y/Z、Width、Height、Depth、Rotation | 与单个模型语义相同,相对于共享的模板包围盒。 |
AnimationClip | 此副本的片段索引。父对象上的 AnimationClipCount 和 AnimationNames 告诉你取值范围。 |
AnimationSpeed | 播放速率。给每个副本稍作变化,才能让人群看起来不机械。 |
AnimationTimeOffset | 相位偏移,让副本不至于齐步走。 |
Enable | 跳过该副本而不把它移出列表。 |
Selected、Highlight | 逐实例的拾取状态,让单个副本可以被描边。 |
ID、Name | 身份标识,当列表由数据重建时很有用。 |
不是所有东西都来自文件。Mesh3D 接受一列
Surface 对象——顶点、索引和一种材质——
然后就像任何其他控件一样工作。参考应用中的海面、沙滩地砖和立方体田
都是这样构建的。
| 类型 | 用途 |
|---|---|
Mesh3D | 程序化几何,带四元数 Rotation、ColorTint,以及用于不应接收遮蔽的表面的 ExcludeFromAo。 |
InstancedMesh3D | 同样的东西,实例化版。没有逐实例动画——程序化几何没有片段。 |
GLTFAnimationPlayer | 片段求值,如果你想自己驱动骨架。 |
GLTFTools | 在运行时从单个 GLB 中取出各个网格。 |
PickMesh | 用于表面精确拾取的 CPU 侧几何。 |
视锥剔除和光空间阴影剔除都在 CPU 上针对包围体运行。
微妙之处在于:蒙皮网格可能移出它自己的绑定姿势包围盒,
所以用于剔除的包围盒由 AnimatedBoundsScale 放大,
默认为 1.5。
| 成员 | 哪个包围盒 |
|---|---|
LocalBounds | 膨胀后的。剔除测试用的就是它,并且刻意保持保守。 |
LocalBoundsRaw | 未膨胀的。拾取测试用的就是它,因为一次拾取不该命中空气。 |
LocalSize | 原始包围盒的尺寸,用它去除以尺寸就能算出目标缩放。 |
GetWorldBounds() | 世界矩阵之后的包围盒,用于你自己的空间查询。 |
CullingEnabled | 逐控件的退出开关。对任何合法超出自身包围盒的东西都有用。 |
一段动画如果把肢体甩出绑定姿势 50 % 以上,就会在视锥边缘突然消失。
调高 AnimatedBoundsScale,或者在该控件上清掉
CullingEnabled;两者都比每帧计算精确蒙皮包围盒
的替代方案便宜得多。
glTF 文件可以携带 KHR_lights_punctual 光源,
Model 会把它们作为
ImportedPunctualLights 呈现出来,并通过
AppendWorldLights 追加进帧的光照。
要记住两件事:帧预算是总共八个光源,所以一个携带十二个光源的资产
会挤掉你自己的光源;并且 glTF 强度是光度学的,这就是
Model.LightIntensityScale 和
RenderQuality.KhrLightIntensityScale 存在的原因。参见
光照与阴影。