Skip to content

en Models3D System

HoCha113 edited this page Jun 21, 2026 · 4 revisions

中文版本

Models3D System

OBJ / glTF loading and layered 3D drawing on the RenderHandle pipeline; includes skinned animation.

Client / GPU only: On Main.dedServ, loading returns Vault3DModel.Empty and no GPU resources are created.


What is this? When to use?

Models3D imports .obj / .gltf assets into Vault3DModel, then draws them via Model3DRenderer (a RenderHandle) in layered passes. Use for boss props, vehicles, weapons, environmental 3D objects — client visuals.

Use when:

  • Skinned glTF characters / boss models
  • Grounded or floating 3D decor (NPC / projectile PostDraw)
  • 3D content that must sort with tiles / players (Model3DLayer)

Don't use when:

Transient vs persistent

Transient (Draw / Submit) Persistent (RegisterPersistent)
Lifetime Current frame only Until UnregisterPersistent
Typical use Projectile trails, one-shot FX Model attached to NPC for entire life
Updates Pass params each frame Mutate Model3DInstance fields
Performance Low frequency, short life Long-lived, per-frame transform updates

Quick start (5 min)

using InnoVault;
using InnoVault.Models3D.Runtime;
using Microsoft.Xna.Framework;

// 1) Autoload model
public static class MyAssets {
    [VaultLoaden("Assets/Models/MyModel")]
    public static Vault3DModel MyModel;
}

// 2) Draw each frame (e.g. projectile PostDraw)
public class MyProjectile : ModProjectile {
    public override void PostDraw(Color lightColor) {
        if (!MyAssets.MyModel.IsValid) return;
        Model3DRenderer.Draw(
            MyAssets.MyModel,
            Projectile.Center,
            new Vector3(0, Projectile.rotation, 0),
            Vector3.One * 0.5f,
            Model3DLayer.AfterPlayers,
            Color.White);
    }
}

Extension may be omitted; .obj then .gltf are tried. See Basic VaultLoaden.


Architecture

flowchart LR
    VL[VaultLoaden / manual Load] --> Model[Vault3DModel]
    Model --> Inst[Model3DInstance]
    Inst -->|Submit transient| Renderer[Model3DRenderer]
    Inst -->|RegisterPersistent| Renderer
    Renderer -->|extends| RH[RenderHandle]
    RH -->|Weight=5| Pipeline[After PRT in pipeline]
    Pipeline --> Screen[Composited to game]
Loading
Component Description
Vault3DModel Static asset (mesh groups, materials, skeletons, clips)
Model3DInstance Per-draw description (transform, layer, effect, etc.)
Model3DRenderer Renderer (RenderHandle), draws by Model3DLayer
Model3DLoadenHandle VaultLoaden loader
AnimationPlayer Per-instance animation head (skinning / cross-fade)
ObjModelLoader / GltfModelLoader Manual load entry points

Level 1 — Hello World

[VaultLoaden] + Model3DRenderer.Draw(...) (see 5-minute example). Key points:

  • Check model.IsValid before draw
  • Position is world pixels (framework subtracts screenPosition)
  • OBJ models often need CullBackface = false

Level 2 — Common patterns

Persistent instance (NPC / world object)

private Model3DInstance _instance;

public override void OnSpawn() {
    _instance = new Model3DInstance(MyAssets.MyModel) {
        Layer = Model3DLayer.AfterTiles,
        Scale = Vector3.One * 2f,
    };
    Model3DRenderer.RegisterPersistent(_instance);
}

public override void AI() {
    _instance.Position = NPC.Center;
    _instance.Rotation = new Vector3(0, NPC.rotation, 0);
}

public override void OnKill() {
    Model3DRenderer.UnregisterPersistent(_instance);
}

Skinned animation

var anim = new AnimationPlayer(model);
anim.Play("idle", fadeIn: 0.3f);
anim.Loop = true;

_instance = new Model3DInstance(model) {
    Animation = anim,
    Layer = Model3DLayer.BeforePlayers,
};
Model3DRenderer.RegisterPersistent(_instance);

Each instance needs its own AnimationPlayer; the renderer auto-calls Update.

Manual load

Vault3DModel model = Model3DLoadenHandle.Load(Mod, "Assets/Models/Boss");

Import issues go to model.Diagnostic; tune via GltfImportOptions / ObjImportOptions.

Model3DLayer

Value Description
BeforeTiles Before tiles — background scenery
AfterTiles After tiles — ground objects
BeforePlayers Before players — occluded by player
AfterPlayers After players — vehicles, weapons
BeforeInfernoRings Default general world FX layer

Level 3 — Advanced

Custom Effect and lighting

_instance = new Model3DInstance(model) {
    EffectProvider = ctx => myCustomEffect,
    ConfigureEffect = (ctx, effect) => {
        // Non-BasicEffect: set tint / texture / lighting yourself
    },
    LightingEnabled = true,
};

Effect priority: explicit override > instance Effect > instance EffectProvider > material Effect > material EffectProvider > default BasicEffect.

Global: Model3DRenderer.GlobalLighting; per-instance: Model3DRenderer.ResolveLighting event.

Global draw hooks

Event Description
ResolveLighting Per-instance lighting resolve
PreDrawInstance / PostDrawInstance Instance draw hooks
PreDrawGroup / PostDrawGroup Mesh group hooks
OnLayerRendered Layer RT ready (post-process)
CompositeOverride Replace default RT composite

RenderHandle pipeline

Model3DRenderer extends RenderHandle, hooked into multiple Terraria draw stages. Weight = 5 — after PRT (lower weight) to avoid clobbering particle canvas state.

Concept Description
RenderHandle Abstract base managing RTs and stage hooks; PRT and Models3D use this
Model3DRenderer.Instance Active singleton
Atomic API DrawInstance, DrawMeshGroup, BuildWorldMatrix, etc. — reuse in custom RenderHandle
vs PRT Same pipeline; see RenderHandle Pipeline

Full-screen post-process, custom capture stages, Weight ordering, and SpriteBatch contracts: RenderHandle Pipeline.

Full example (animated boss decor)

public class BossDecor : ModNPC
{
    private Model3DInstance _model;
    private AnimationPlayer _anim;

    [VaultLoaden("Assets/Models/Boss")]
    private static Vault3DModel BossModel;

    public override void OnSpawn() {
        if (!BossModel.IsValid) return;
        _anim = new AnimationPlayer(BossModel);
        _anim.Play("spawn");
        _model = new Model3DInstance(BossModel) {
            Layer = Model3DLayer.BeforePlayers,
            Scale = Vector3.One * 3f,
            LightingEnabled = true,
            Animation = _anim,
        };
        Model3DRenderer.RegisterPersistent(_model);
    }

    public override void AI() {
        if (_model != null) {
            _model.Position = NPC.Center;
            _model.Rotation = new Vector3(0, NPC.rotation, 0);
        }
    }

    public override void OnKill() {
        Model3DRenderer.UnregisterPersistent(_model);
    }
}

API reference

Vault3DModel

Member Description
Empty Placeholder on failure / dedicated server
Name / SourcePath Display name and source path
Groups / Materials Mesh groups and materials
Skeletons / Clips Skeletons and animation clips
Diagnostic Import diagnostics
Bounds / Pivot / RootTransform Bounds, pivot, root transform
IsValid / IsSkinned Drawable / has skeleton
TryGetClip(name, out clip) Lookup clip by name

Model3DInstance (core)

Property Description
Model Associated Vault3DModel
Position / Depth World position and Z offset
Rotation / Scale Euler radians (X/Y/Z), scale
Tint / Opacity Color and opacity
Layer / SortKey / Visible Layer, sort, visibility
LightingEnabled / LightingOverride Lighting
DepthEnabled / CullBackface / ForceTransparent Depth, cull, transparent bucket
Animation AnimationPlayer
Effect / EffectProvider / ConfigureEffect Custom shader
RenderStateOverride Blend / depth / rasterizer override

Model3DRenderer (convenience)

Method Description
Submit(instance) Submit to current-frame queue
Draw(...) One-shot draw
RegisterPersistent / UnregisterPersistent Register / unregister persistent
ClearPersistent() Clear all persistent
Instance Renderer singleton
GlobalLighting Global lighting config

AnimationPlayer

Member Description
Play(clipName, fadeIn, restart) Play by name with cross-fade
Stop() Stop; revert to bind pose
Update(delta) / Tick(delta) Advance time
Time / Speed / Loop / Paused Playback control
Current / IsFading Current clip / cross-fade state

Import formats

Format Loader Notes
.obj + .mtl ObjModelLoader Wavefront OBJ
.gltf GltfModelLoader glTF 2.0 with skeletons/animations

Common mistakes / FAQ

Issue Cause / fix
Model not visible IsValid false; check path, logs, Diagnostic
Dedicated server empty model Expected; Empty on server, GPU on client
Forgot UnregisterPersistent Model keeps drawing after entity dies; unregister in Kill
OBJ missing faces Set CullBackface = false
Animation frozen No Animation; or clip name mismatch
Custom shader all black Non-BasicEffect needs ConfigureEffect params
Draw order vs PRT wrong Models3D Weight=5 after PRT; restore RenderState if mixing
CastShadow does nothing Reserved — shadows not implemented yet

Related modules


Navigation

Previous Home Next
Narrative System Home Home

Clone this wiki locally