-
Notifications
You must be signed in to change notification settings - Fork 6
en Models3D System
OBJ / glTF loading and layered 3D drawing on the
RenderHandlepipeline; includes skinned animation.
Client / GPU only: On Main.dedServ, loading returns Vault3DModel.Empty and no GPU resources are created.
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:
- Server logic or hitboxes → NPC / Projectile / Actor Entity System
- Pure 2D sprite FX → Basic PRT System
- Full-screen post-process pipelines → custom
RenderHandle(see RenderHandle Pipeline)
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 |
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.
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]
| 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 |
[VaultLoaden] + Model3DRenderer.Draw(...) (see 5-minute example). Key points:
- Check
model.IsValidbefore draw -
Positionis world pixels (framework subtractsscreenPosition) - OBJ models often need
CullBackface = false
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);
}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.
Vault3DModel model = Model3DLoadenHandle.Load(Mod, "Assets/Models/Boss");Import issues go to model.Diagnostic; tune via GltfImportOptions / ObjImportOptions.
| 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 |
_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.
| 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 |
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,
Weightordering, and SpriteBatch contracts: RenderHandle Pipeline.
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);
}
}| 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 |
| 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 |
| 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 |
| 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 |
| Format | Loader | Notes |
|---|---|---|
.obj + .mtl
|
ObjModelLoader |
Wavefront OBJ |
.gltf |
GltfModelLoader |
glTF 2.0 with skeletons/animations |
| 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 |
- Basic VaultLoaden
- RenderHandle Pipeline — shared render base and post-process
-
Basic PRT System —
PRTRenderimplementation - Actor Entity System
| Previous | Home | Next |
|---|---|---|
| Narrative System | Home | Home |