MIT 许可证 NuGet

首页 / 文档 / 实例化

实例化

一个控件、一次绘制、许多副本 — 每个都有自己的变换,而对 InstancedModel 来说,还有自己的动画片段、速度和相位。参考应用中的 机器人、鸟、岩石和沙滩瓦片,每一种都只是一个实例化控件。

模式

你从 MeshInstanceTransform 派生自己的类型,把你自己的字段放上去,再把你的 对象加进 Instances。列表中的对象 就是 实例, 所以编辑它在下一帧就生效,没有复制步骤,也没有需要保持同步的句柄。

// Your own fields ride along with the engine's.
internal class Person : MeshInstanceTransform
{
    internal float Yaw { get; set; }
    internal string? Animation { get; set; }
}

robotField = new InstancedModel { ModelName = "Assets/3DGodotRobot.glb" };
AddControl(robotField);

for (var i = 0; i < 10; i++)
{
    robotField.Instances.Add(new Person
    {
        PosX = -2f, PosY = 1f, PosZ = 6f + i * 3f,
        Width = 1f, Height = 1f, Depth = 0.5f,
        Animation = "Idle-loop"
    });
}

在运行时添加和移除(包括在列表中间)会保持顺序。 没有重建步骤,也没有需要事先声明的实例数上限。

一个实例携带什么

字段说明
PosX、PosY、PosZ实例锚点的世界位置,即共享模板盒的中心。
Width、Height、Depth世界单位的目标尺寸。这里是普通 float,不是 float? — 0 意味着未设置,会在宿主更新期间收敛为模板尺寸。
Rotation一个 Quaternion,以锚点为枢轴。注意与 Model.Rotation 的区别,后者是绕 Y 轴的 float。
Enable跳过该副本而不把它从列表中移除。默认 true。
AnimationClip片段 索引,不是名字。0 是默认片段。
AnimationSpeed播放速率,1 为正常。每个副本稍微不同,是让人群看起来不机械的方法。
AnimationTimeOffset以秒计的相位偏移,让副本们不齐步走。
Selected、Highlight逐实例的拾取状态,所以两百个副本中的一个可以被描边。
ID、Name身份标识,当列表从数据重建时很有用。

动画字段由 InstancedModel 读取,并被 InstancedMesh3D 忽略,因为程序化几何没有片段。

片段名,因为实例用的是索引

实例用索引寻址片段。你的代码通常想用名字思考,所以 参考应用在它的实例类型上保留一个名字,每帧转换一次。这 就是整个转换,值得照抄而不是重新发明:

int ResolveAnimationClip(string? name)
{
    if (string.IsNullOrWhiteSpace(name))
        return 0;

    var names = robotField.AnimationNames;

    for (var i = 0; i < names.Count; i++)
        if (names[i] == name)
            return i;

    return 0;   // unmatched falls back to the default clip
}

// Per frame, per instance:
person.AnimationClip = ResolveAnimationClip(person.Animation);

InstancedModel.AnimationNames 在 GLB 加载后才填充, 所以在那之前循环找不到任何东西,每个副本都播放片段 0。null、空或不匹配的 名字会回退到默认片段而不是抛出,这正是资产还在加载时你 想要的行为。

模板边界与锚点

几何是共享的,所以边界也是。Mesh3DBase 上的 一切在这里都有模板作用域的对偶。

成员含义
TemplateLocalBounds对动画模型会被 AnimatedBoundsScale 膨胀。剔除测试的对象。
TemplateLocalBoundsRaw不膨胀。锚点和缩放计算使用的对象。
TemplateLocalSize原始模板盒的完整尺寸。
TemplateAnchorLocal原始模板盒的中心 — 与单个模型相同的中心锚点约定。
BuildInstanceMatrix(instance)一个副本的世界矩阵,构建方式与单个模型完全相同。
InstanceAnchorWorldOffset(instance)当你想钉住模板的 原点 而不是它的中心时使用的转换。
GetInstanceWorldBoundsRaw(instance)一个副本的世界边界,与实际渲染的身体匹配。
TryPickInstance(...)、TryPickInstanceSurface(...)盒级和表面级的射线测试,都会报告击中了哪个 MeshInstanceTransform。
在宿主上设置 Highlight 会级联

把一个全新的 Highlight 对象赋给实例化 控件,会把它复制进那一刻存在的每一个实例。嵌套赋值 — Highlight = { Style = ... } — 不会 级联,因为没有对属性发生赋值。后来加入的实例 保留它们自己的默认值,并被 ObjectPicker 解析为“继承宿主”,这正是让一个逐帧增长实例列表的面板 仍然能正确高亮的原因。

剔除,以及实例化不会带给你什么

每个实例都会做一次包围球视锥测试,只有每个副本都不可见时, 整批才被跳过。阴影通道完全不做视锥剔除;它改由 CastShadows 把关。

  • 缺失 间接绘制与 GPU 驱动的剔除。 剔除和提交都在 CPU 侧。 数万个实例的 CPU 开销是你可以测出来的。
  • 缺失 细节层次。 每个副本在任何距离都绘制完整网格。
  • 缺失 实例化控件的透明排序。 ITransparentSortable 只存在于 Mesh3DBase 上,所以实例化批次不会与 透明的单个对象做深度排序。