Skip to content

A material format for Luanti

How LabPBR's channels line up with glTF 2.0's material model, what a file naming convention cannot settle on its own, and a proposed shape for a material description a game could ship. This is a proposal for discussion, not something Goanna or Luanti implements today. What Goanna reads now is in materials.

How LabPBR maps onto glTF 2.0

Upstream discussion favours taking glTF 2.0 material semantics as the starting point. The two standards overlap on the core of a PBR material and diverge at the edges in both directions, so neither is a superset.

LabPBR glTF 2.0 Notes
Smoothness, _s R roughnessFactor or roughness texture The GGX roughness is (1 - smoothness) squared. glTF's roughnessFactor is perceptual, like Godot's ROUGHNESS, so it carries 1 - smoothness
F0 and metal, _s G metallicFactor or metallic texture glTF has no metal table. Its model matches the LabPBR 255 case, albedo as F0
Material AO, _n B occlusionTexture Direct equivalent
Normal, _n RG normalTexture glTF specifies Y up, LabPBR stores Y down. Whether a flip is needed depends on the mesh's V direction
Emission, _s A emissiveTexture and emissiveStrength LabPBR is a scalar mask read against albedo, glTF carries an emissive colour
Height, _n A Nothing in core glTF KHR_materials_displacement was never ratified
Porosity and SSS, _s B Partly KHR_materials_volume, KHR_materials_diffuse_transmission No single equivalent channel
Nothing transmission, ior LabPBR carries neither
Nothing Anisotropy, clearcoat, sheen glTF extensions with no LabPBR equivalent

Read across, adopting LabPBR costs transmission and refraction and gains height and scattering. The normal convention has to be reconciled either way.

What a naming convention does not settle

A file name says which file. It says nothing about what the bytes mean, so two clients can both support LabPBR and still disagree. These are the points an agreement has to pin down, all of which we have hit in practice.

Normal orientation. As above, and note that the answer is not a property of the texture alone. It cost us a day: first relief that did nothing we could see, then, once we 'fixed' the orientation, black block sides.

Colour space. Which companions are sRGB and which are linear. Getting this wrong is subtle, pervasive and hard to see in a screenshot.

How texture modifiers propagate. This is the Luanti specific question, and the one no existing standard can answer, because Minecraft has no equivalent. Luanti textures are expressions, not file names: default_stone.png^[colorize:#ff0000, ^[crack:1:4:2, [combine:16x16:0,0=a.png, and the inventory cube form. An agreement has to say what the companion of an expression is. Reasonable answers exist: a colour only modifier leaves the companions untouched, which is what makes one normal map serve every recolour of a texture, and [combine: has to composite the companions in the same layout as the diffuse. Until that is written down, every client will guess differently.

That question also answers the standing objection to the naming convention route, which is that it forces one normal map per texture and makes recoloured variants duplicate their companions. Under Luanti's modifier syntax the base image keeps its name, so the variants already share one companion for free.

Palette interaction. Luanti tints nodes per instance through palette and paramtype2 = color. Whether that tint modulates only albedo, or also F0 and emission, is undefined.

Precedence. What wins when the server serves _n for a texture and the player's own client side texture pack also has one.

Discovery. Whether a client probes for companions, which is what Goanna does and which needs nothing from the engine, or whether something declares them up front. Probing works against every server that exists today. Declaring is friendlier to a client that wants to plan its uploads or fall back cleanly.

A proposed shape

If the naming convention were formalised as the material agreement, the smallest useful version is:

  1. Companions are <base>_n.png and <base>_s.png beside <base>.png, with LabPBR channel assignments.
  2. Normals are stored Y down as LabPBR has them, which matches the V direction of Luanti's tile UVs, so no client has to flip anything. State it explicitly rather than leaving it to be inferred from glTF.
  3. All companions are linear. Only the diffuse is sRGB.
  4. Companions attach to the base image of a texture expression. Modifiers that only change colour leave them alone. Modifiers that change layout apply the same layout to the companions.
  5. Server media takes precedence over a client side pack, and a client may offer the player an override.

Worth flagging before this is proposed anywhere: Luanti already does the opposite. Client::loadMedia inserts every media file with prefer_local = true (client/texturesource.cpp:534), so a player's texture_path overrides the server's art. That is what makes a texture pack a texture pack. Goanna matches upstream rather than this point, and the point should probably be rewritten to match reality: the local pack wins, and the interesting question is only whether a companion may be taken from a different source to the diffuse it dresses. 6. A client discovers companions by probing. No declaration is required.

Point 3 is the only place this departs from LabPBR, and it is one line in a converter.

What this does not cover, and what a Lua side API would still be needed for, is anything keyed to a node rather than to a texture: chamfer profiles, mote emission, waving amplitude, whether a surface should be displaced at all. Those are node properties, not surface properties, and no texture naming scheme reaches them.