Art is not simply dropped in. A model is registered, with its materials
described, and the game then resolves it by name. Registration is where most art
problems live.
Registering a model
<model name="Ark" source="ark.fbx" format="Standard" collision-source="Ark-Collision"
group="Vessels" rotation="0 0 0" scale="0.7559546">
<mesh id="0" format="Standard"
texture="ark.jpg"
texture-normal="ark_norm.jpg" texture-emissive="ark_light.jpg"
texture-arm="ark_arm.jpg" blending="Color"
metalness="0" roughness="1" emissive-intensity="1" />
</model>
<model name="Ark-Collision" source="ark_collision.fbx" format="Collision"
group="Vessels" scale="0.7559546" />
The record names the file, a scale, a rotation correction, and one entry per mesh
describing that mesh's material. name is how everything else refers to it.
FBX and glTF/GLB are both supported. Scale is almost always needed — modeling
tools and the game do not agree on units, and the scale figures in real content are
arbitrary numbers found by eye.
A <mesh> entry's own name binds it to the surface of
that name in the file, and it is the reliable way to address one. An entry with no name
falls back to its id as an index, but the server and the renderer can count
a file's surfaces differently, or in a different order, so an index can point at a
surface you did not mean. Where the two disagree, the editor's Mesh tab says so: give
those entries names. A named entry that matches nothing paints nothing rather
than guessing. A model with a single entry is treated as one material, and that entry
applies to every surface.
Two model attributes control how the art takes part in the scene.
cast-shadows="false" keeps a model out of the shadow pass on a map that draws
shadows, while it still receives them: use it for sky sheets and other geometry the sun
should ignore. travel-stretch="true" is for streaks and tracers: the model is
never drawn longer behind its origin than its object has traveled, so a missile streak
grows out of the launcher instead of poking out of the back of whatever fired it.
The format chooses the shading model:
- Basic — unlit. For anything that should not respond to light:
projectiles, effects, glowing elements.
- Phong — classic specular shading, driven by specular and normal
maps. Older art that has not moved to Standard: in the base game, mostly asteroids,
debris, weapons and a few small craft.
- Standard — physically based, with metalness and roughness. What
to use for new work.
Texture slots are diffuse, specular, normal, emissive, and a packed
ambient‑occlusion / roughness / metalness (ARM) map. Standard reads the ARM map and
ignores specular; Phong reads specular and ignores ARM. Emissive is what makes a ship's
windows and running lights read at distance, and it does more for a model's presence
in space than any other single map.
The numeric settings apply on top of the maps: metalness and
roughness on Standard, shininess and reflectivity
on Phong, and emissive-intensity on both. A value left at its default
(metalness 0, roughness 1, shininess 30, reflectivity 1, emissive intensity 1) changes
nothing, which is what lets an ARM map's own metalness and roughness stand.
blending selects how the mesh composites — Color for
ordinary solids, Additive for anything that should glow through what is
behind it, Multiply to darken what is behind it.
Compressed textures (KTX2)
A JPEG or PNG is unpacked to full size in video memory, so one 4096 × 4096 map costs
about 85 MB of VRAM however small the file is. A KTX2 file holds GPU block‑compressed
data that is uploaded as it is: a quarter to an eighth of the memory, at the same
resolution, and decoded off the main thread.
You do not change models.xml to use one. Put a .ktx2 with the
same base name beside the texture — ark_norm.ktx2 next to
ark_norm.jpg — and the server hands the client the KTX2 instead, for every map
slot on a model's mesh entries. A slot can also name a .ktx2 directly.
The engine reads KTX2 but does not make it. Encode with Khronos's KTX‑Software
(ktx create), and follow four rules, most of which fail quietly:
- Bake the orientation in. A compressed texture cannot be flipped as it
loads. Maps used by an FBX model must be written flipped vertically; maps used by a glTF
model must not be. A texture both formats use cannot be converted at all: give each
format its own copy.
- Match the color space to the slot. Diffuse and emissive maps are color
and want the sRGB transfer function; normal, specular and ARM maps are data and want
linear. The file's own setting wins, and a mismatch is reported in the browser
console.
- Use UASTC for normal and ARM maps. The smaller ETC1S encoding is fine
for color and specular, but it blends channels that a normal or ARM map needs kept
apart, and a damaged normal map renders as black facets. UASTC files are larger to
download, so 2048 is usually the right size for those two slots. Do not use the
two‑channel normal‑map mode: the renderer does not rebuild the third channel.
- Generate mipmaps in the file. The renderer uses the file's own mip
chain and cannot build one, so a file without mipmaps shimmers on a distant
surface.
Collision
A separate collision-source mesh is worth providing for anything large.
Collision against full display geometry is expensive, and a simplified hull is both
faster and better behaved.
collision-source names another registered model, not a file. Register the
simplified hull as a model of its own, with format="Collision" and its own
scale, and name that, as the Ark example does. A name that matches no model
is not reported: the object silently collides with its display mesh instead.
Shaders and effects
A model or mesh entry can carry an effect attribute naming a shader
registered in World/shaders.xml. The current renderer does not read it: every
model draws through the material formats above, and the attribute is kept only so that
it survives a save. Do not rely on it to change how a model looks.
What a model can carry is effect placements: child <effect>
elements, each naming an effect bank and where it hangs in model
space.
<model name="CT1" source="ct1.glb" format="Phong" group="Scenery" scale="1">
<effect class="Smoke Stack" name="Stack" position="0 38.5 0" />
</model>
They play wherever the model is placed as scenery, so a piece of set dressing that
smokes or flares does not need a second model placed on top of it in every map. Any other
attribute on the element is handed to the bank's renderers as a parameter override. A
ship's effects belong on its vessel class instead.
Traps
A glTF with its own maps keeps them. When a glTF file carries texture
maps, its materials win, and the mesh entries' texture and material settings are
ignored; only colorize still applies, and the editor's Mesh tab says so. A glTF exported
as geometry only, and every FBX, takes its materials from its mesh entries. Which side
owns the material is the first thing to check when a model imports looking nothing like
it did in your modeling tool.
glTF and FBX disagree about texture orientation. The vertical axis
runs the other way, so the game loads a glTF model's maps unflipped and an FBX model's
flipped. For ordinary images that is automatic, and one image can serve both formats. A
KTX2 file has one orientation baked in, so there it is your job (see above). Get it wrong
and the normal map lights the surface backwards — the symptom is black facets and a
model that looks flat and dead rather than obviously broken.
Cloned models share materials. Changing a material at runtime
affects every instance, and texture caching is keyed on load settings, so a change
that appears to work on one ship can silently apply to all of them.
Do not assume a path exists. Assets resolve through the merged
index of every active module, so a texture your module references may be coming from
somewhere else entirely — and a path that works on your machine may not exist for
someone with a different module set. See Modules for the resolution order. Base
game art moves too: its backdrops are WebP files now, and several textures have been
removed, so a reference to a base game file by an old name finds nothing.
Previewing
In Hydra, a module's Models section is a browser: the list, grouped
and searchable, with a live 3D view of the selected model beside it. Edit
opens the model in its own tab, with Details, Mesh and Animations tabs beside a full‑size
view. Mesh edits show in the view as you type them, built by the same material code the
game uses, so what you see is what will load. The Gallery station also shows registered
art without launching a mission.
Use them: the loop of registering, looking, and adjusting scale and materials is much
faster there than in the game.