glTF 2.0 是引擎唯一读取的模型格式。一个
Model 和其他控件一样:你给它一个文件名和
一个目标尺寸,把它加到一个面板里,每帧更新它。
robot = new Model
{
Name = "Assets/3DGodotRobot.glb",
PosX = -2f, PosY = 1f, PosZ = 6f,
Width = 1f, Height = 1f, Depth = 0.5f,
Rotation = MathF.PI / 2f,
Highlight = { Style = HighlightStyle.Wireframe }
};
AddControl(robot);
AddControl 会排队加载。光照、阴影、
环境光遮蔽或天空这些都没有出现在那段代码里,因为那些是管线的事,而不是
逐对象的事 — 一个已添加的模型,被这一帧正在做的一切所照亮和投影。
Width = 1f 让模型宽一个单位,无论
美术导出的是什么,因为引擎从资产自身的边界推导缩放。
Model 上的 Rotation 是一个
绕 Y 轴的弧度 float — 写 90 会给你 90 弧度,
归一化后大约是 117 度,看起来 几乎 是对的。参见
放置、锚点与相机。
文件必须同时在 MauiAsset 和
Content 项组里,否则它会在桌面上加载,而在
打包构建中静默失败。项目布局解释了
原因。
片段来自文件,用美术使用的名字寻址。在控件
Ready 之前,下面的一切都返回 null 或空列表,
所以请在加载之后而不是在构造函数里读取片段列表。
// Advancing the clock is what plays the animation.
robot.Update(time);
// Once Ready:
foreach (var name in robot.GetAnimationNames())
DeviceServices.BaseApp?.AddLog(LogType.Info, name);
robot.PlayAnimation("Walk"); // returns the name actually activated
robot.SwitchToNextAnimation(); // wraps; what a debug key usually wants
var playing = robot.GetCurrentAnimationName();
| 成员 | 说明 |
|---|---|
GetAnimationNames() | 来自 animations[].name 的片段名。资产没有时为空列表。 |
GetAnimations() | 同样的列表,附带时长,作为 ModelAnimationInfo。 |
PlayAnimation(string) | 切换片段并返回实际生效的名称,或 null。 |
SwitchToNextAnimation() | 在列表中前进,循环回绕。 |
GetCurrentAnimationName() | 当前正在播放什么。 |
Time | 以秒计的动画时钟,如果你想自己拖动的话。 |
Model 有自己的 Update 重载
Update(float time, string? name = null, float? alpha = null)。
它转发到基类控件更新,当
name 非 null 时交换资产,而且 — 关键的部分 —
当控件 Ready 且有内容时,把
time 推入蒙皮路径。一个你从不更新的模型会
正确绘制,并保持完全静止。
没有交叉淡化,没有混合树,没有叠加层,也没有状态机。一个 需要同时走路和瞄准的角色,是你自己要做的工作,或者 是等待路线图的工作。
SetModel(name, forceReload = false) 保留控件及其
摆放,替换几何体:它清除
Ready,把控件标记为已更改,并重新排队加载。当名称未变且
forceReload 为 false 时,它
返回 false 且什么都不做。
// Same position, size and rotation; different geometry.
robot.SetModel("Assets/3DGodotRobotAlt.glb");
// Equivalent, inside your update:
robot.Update(time, name: "Assets/3DGodotRobotAlt.glb");
从调用到下一次 Ready 之间,控件什么都不绘制,
这与首次加载的规则相同。如果交换不能闪烁,就保留两个控件并交叉它们的
Alpha。
存在一小撮一次性覆盖,用于你想给导入的材质上色或变暗、又不想编辑文件的情况。 它们会被下一次更新消费,并把自己重置为 null,所以设置一个覆盖是一次请求,而不是一个你必须记得清除的状态。
| 覆盖 | 效果 |
|---|---|
MetallicOverride | 金属度系数,0 到 1。 |
RoughnessOverride | 粗糙度系数,0 到 1。 |
EmissiveFactorOverride | 自发光颜色,作为 RGB 强度。 |
LightIntensityScale | 不是覆盖 — 是文件携带的光照上的持久缩放。见下文。 |
glTF 可以携带 KHR_lights_punctual 光源。
Model 把它们暴露为
ImportedPunctualLights,而
AppendWorldLights(ref SceneLightParams) 把它们折入
这一帧。
在导入室内场景之前,有两个值得知道的后果。帧预算总共是八个
点光源,所以一个携带了十二个的资产会挤掉你自己的。而且 glTF
强度是光度学的,这就是
Model.LightIntensityScale 和
RenderQuality.KhrLightIntensityScale 存在的原因 — 后者
默认为 0.05,因为原始的坎德拉值在引擎自身的
强度约定旁边大得离谱。
蒙皮网格可能摆出绑定姿势的边界之外,所以用于剔除的盒会被
RenderQuality.AnimatedBoundsScale 放大,默认 1.5。这
留下两个盒,而选错一个是一个可见的 bug,而不是微妙的。
| 用途 | 读哪个 |
|---|---|
| 视锥与阴影剔除 | LocalBounds / GetWorldBounds() — 膨胀的,有意保守。 |
| 拾取、选择框、布局吸附 | LocalBoundsRaw / GetWorldBoundsRaw() — 与实际渲染的身体匹配。 |
| 计算你自己的缩放 | LocalSize,原始盒的尺寸。 |
| 完全退出 | CullingEnabled = false,用于任何合法超出其边界的对象。 |
一个把肢体甩出绑定姿势 50 % 以上的动画会在
视锥边缘突然消失;对那个控件调高 AnimatedBoundsScale 或清除
CullingEnabled。与每帧计算精确的蒙皮边界相比,
这两者都便宜,而后者是没人愿意付出的替代方案。
Mesh3D 接受一个
Surface 对象列表 — 顶点、索引、一个材质 —
并且从那以后表现得像一个 Model,包括同样的
放置约定和同样的阴影与 GI 参与。参考应用中的海、沙滩瓦片和
立方体田都是用这种方式构建的。它的
Rotation 是四元数而不是 float。
完整类型列表见 模型、动画与实例化,
包括从共享 GLB 中取出一个网格的 GLTFTools,以及
为表面级精确拾取提供 CPU 侧几何的 PickMesh。