Skip to content
VSPT

Materials for your mod

How a mod or texture pack gives its textures a real material under the ray tracer: relief, shine, metal, light. Everything here is optional. A mod that ships nothing still looks right: its faces get the fallback, worked out from what the game already knows.

The files are ordinary assets, in a zip or a folder, and do nothing without this renderer. Do not add a vspt dependency to your modinfo.json: a player without the renderer could not then load your mod.

To build a pack from the images a paint program exports, without packing channels by hand, use the material kit: vspt-pack and a sample project (material-packs.md). This page is the format underneath.

A mod mymod with a lamp block whose glass glows, and a polished steel trim:

mymod.zip
modinfo.json
assets/mymod/
textures/block/lamp.png your art, as for any mod
textures/block/trim.png
materials/
textures/block/lamp_n.png normal map: relief
textures/block/lamp_m.png surface map: roughness, metal, where it glows
lamp.json rule file: how bright, what colour

materials/lamp.json:

{
"contract": "1.12",
"materials": {
"block/lamp": { "emissiveNits": 3000, "emissiveKelvin": 2700 },
"block/trim": { "roughness": 0.25, "metalness": 1 }
}
}

trim needs no maps at all: the rule makes the whole texture polished metal.

File What it is
assets/<domain>/materials/textures/<path>_n.png Normal map for the texture <domain>:<path>
assets/<domain>/materials/textures/<path>_m.png Surface map for it
assets/<domain>/materials/textures/<path>.png Optional. A base colour only the ray tracer uses, in place of your art. For a de-lit version: art with painted shadows or a painted glow taken out
assets/<domain>/materials/*.json Rule files. Any name; subfolders are fine too

materials/textures/ mirrors textures/: the map for textures/block/lamp.png is materials/textures/block/lamp_n.png. Every file is found by its path, so it can sit in any mod, not only the one that owns the texture (see Overriding).

Never put maps beside your art in textures/. The game treats every PNG there as a texture. Block types may name their textures with a wildcard (vanilla does: "block/clay/brick/clinker*"), and then a clinker1_n.png beside clinker1.png becomes one more random variant of the block, drawn as a purple-blue brick in the vanilla renderer. The renderer warns about any _n or _m file it finds there.

The full definitions are in the material contract. In short:

Map R G B A
_n normal X, +right Y, +up (OpenGL convention) Z, out of the surface Height, 128 = flat. Optional
_m surface Roughness, 0 mirror to 255 matte Metalness: 0 or 255 Emissive mask: where it glows, 0 to 255 Opacity to transmission: 255 solid, lower lets light through leaves, cloth and paper. Save 255 if unsure
  • Make each map the same size as its texture, texel for texel, because one art texel has one material. A map of another size is refused and logged.
  • Any kind of PNG works. RGB, RGBA, grey and palette PNGs all read, at any bit depth (16-bit is reduced to 8) and interlaced or not; so does a GIF saved under a .png name, as the game reads it. A missing alpha means the default (flat height, fully opaque).
  • Normal maps are OpenGL style, with Y up. A DirectX-style map (Y down) looks lit from the wrong side, so invert its green channel. (128, 128, 255) is exactly flat. Keep tilts under 60 degrees.
  • Each map works on its own. Ship only _m to change the finish and keep a flat surface, or only _n to add relief to the fallback finish.
  • Alternate textures each need their own maps. If lamp.png has alternates lamp2.png, lamp3.png, each needs its own _n and _m; a variant without them uses the fallback, never a sibling’s.

The look to aim for: the art stays yours. Keep the base colour’s texture, and keep relief fine and subtle on manufactured things, stronger on loose natural ones. Roughness around 180 to 220 suits most stone, wood and soil; 60 to 120 polished metal or glazed tiles.

{
"contract": "1.12",
"materials": {
"block/metal/**": { "roughness": 0.45, "metalness": 1 },
"block/metal/copper-shiny": { "roughness": 0.2 },
"block/lantern-glass": { "emissiveNits": 1500, "emissiveKelvin": 2200 }
}
}
  • A key is a texture path under textures/, without .png and without a domain. The domain is the folder the rule file sits in: rules in assets/game/materials/ are about vanilla textures.
  • * matches within one folder, ** across folders. Every matching entry applies, least specific first, so a family default can be refined by a more specific key.
  • Rules from mods that load later win over earlier ones, and over the renderer’s own.

What the renderer reads today:

Key Meaning
emissiveNits How bright the glowing texels are, in real units (nits, candela per square metre), at mask 255. This is scene brightness, not a multiplier. A glow with no emissiveNits uses the face’s glowLevel
emissiveColor [r, g, b], linear Rec.2020, any scale: the light’s colour, in place of the texels’ own. May be more saturated than sRGB
emissiveKelvin A blackbody temperature, in place of emissiveColor: 1,900 for a candle, 2,700 for a warm bulb, 6,500 for daylight
roughness, metalness 0 to 1, for the whole texture, where it has no _m map
ior Glass only: its refractive index, 1.0 to 3.0. Without it, 1.5. Diamond is 2.42, sapphire 1.77, flint glass 1.62, crown glass 1.52, fused quartz 1.46, ice 1.31
solid Glass only. true: a solid body, bending light in and out as a lens does. false: a thin pane, whatever the players’ Solid glass setting says. Leave it out to let the setting decide
emissiveFlicker { "amplitude": 0.15, "hz": 8 }: the light swings about its brightness by up to the amplitude (0 to 1), as smooth noise at about the rate (up to 30 Hz), the whole block together. A torch is 0.15 at 8, a candle 0.08 at 5, a failing tube about 0.45 at 1.5
absorption Glass only: { "color": [r, g, b], "distance": metres }, the colour that gets through that far, linear Rec.2020, 0 to 1 each. On a solid it deepens with thickness: a block of [0.4, 0.5, 0.92] after 0.25 is a deep blue, a chiselled sliver of it pale. On a thin pane color is what the whole pane passes. It takes the place of the texture’s tint, for the whole texture
diffuseTransmission Glass only, 0 to 1: for milky, scattering glass and stone (a texel whose _m carries translucency), the share of the light it scatters that goes on through it, so it glows with a lamp or the sun behind it. Opal and milk glass about 0.5, alabaster 0.4. Leave it out for ice, which is lit from the front only

Kept but not read yet, so they cost nothing and will start working as the renderer grows: class, emissiveRefLevel (contract). Glass and liquids are not chosen by class today: a face drawn in the game’s transparent render pass is glass, and one in the liquid pass is a liquid. A film of glass over another face (a coloured gel, a clear coat) is a decor block of glass: glass.md, “Glass films”.

A light is three things: where (_m blue), how bright (emissiveNits) and what colour.

  • For a light where each texel has its own colour (RGB LEDs, a screen, stained glass), paint the colours in the art, set blue to 255 where it glows, and give emissiveNits with no colour. The light takes each texel’s colour.
  • For one colour across the whole light (a flame, a bulb), give emissiveKelvin or emissiveColor. The art can stay pale and the light still comes out the right colour.
  • For brightness, the renderer’s own values are a guide: a torch flame 10,000; a candle 8,000; lava 3,000; a temporal gear 1,200; a rock salt lamp 200; a glowworm 48. A screen or an indicator LED is about 100 to 500. Glowing faces also light the scene around them, so a bright one lights a room.
  • The block’s lightHsv still drives the game’s light grid, which is what mobs and crops see. Keep the two roughly in step, or a room will look brighter or darker than the game thinks it is.

A texture pack or a mod may ship maps and rules for any domain, vanilla’s included: assets/game/materials/textures/block/stone/rock/granite1_m.png gives vanilla granite a new surface map.

The game reads mods in its load order, and a later one wins. The renderer follows the same order, with one rule on top, so a texture pack never shows your art with maps made for somebody else’s:

A map or rule applies only to the picture it was made for. When a texture pack repaints a texture, the maps and rules that loaded before it (the renderer’s own, and any earlier mod’s) are dropped for that texture, and it takes the fallback, unless the texture pack, or a mod after it, ships maps of its own.

A repaint here means different pixels. A mod that ships an identical copy of a texture repaints nothing.

So a texture pack author has two good options: ship maps for the repainted textures, or ship none and get the fallback. A “PBR for pack X” add-on should load after pack X (set its load order in modinfo.json with dependencies on it), and then its maps apply to X’s pictures.

On joining a world, the client log (Logs/client-main.log) says what was found, one line per mod that ships anything:

[vspt] Mod materials: mymod (mymod.zip): 2 maps, 1 rule files
[vspt] Mod materials: 23 mod asset folders read in 9 ms; 1 ship maps or rules, 4 ship textures; 0 problems

and a warning for each problem, naming your mod and file. The chat command .vspt materials shows the same in game, and .vspt materials all every problem. What it catches:

Problem What to do
A map beside its texture in textures/ Move it to materials/textures/
A map whose texture exists nowhere The path or the domain folder is misspelt
A map of another size than its texture Resize it to the texture’s size
lamp_normal.png, lamp_s.png and other renderers’ names Rename to _n or _m: other names are read as a base colour
A picture that will not read (not a PNG or a GIF, or a damaged file) Save it as a plain 8-bit RGBA PNG
A rule key with a domain, textures/ or .png in it Keys are paths like block/lamp
A misspelt or unknown key (emissivenits) Keys are case-sensitive; see the table above
A key the renderer does not read yet Nothing: it is kept for later

Before a release, check the whole mod with the renderer’s material tool, which reads every map in full (the game only reads what its atlases ask for):

VsRtx.MaterialBake check-mod <your mod's folder or zip> [--game-assets <the game's assets folder>]

Besides the problems above, it says when a normal map does not look like one (a height map or a colour picture saved as _n), when many texels tilt past 60 degrees, when texels are part metal, and when an emissive mask has no emissiveNits to say how bright. Give several mods to check them in a load order. It exits 0 when there is nothing wrong; notes (keys kept for later) do not count.

To try a change, leave the world and join it again. The renderer reads the mods’ files afresh on every join, so you don’t need to restart the game. For quick work, run your mod from a folder in Mods/ rather than a zip.

Faces with no maps and no rules use the fallback, from the game’s own labels:

  • Shine comes from a face’s reflectiveMode: none is matte, and strong and sparkly are glossy.
  • A texture is metal when its path has a metal folder in it: block/metal/... is metal in any domain.
  • Glow comes from a face’s glowLevel, in the texel’s own colour.

A mod that sets these well already looks reasonable, with no maps at all.

  • attributes.vsrtxMaterial on a block, item or entity type, for per-block rules on a shared texture. Until it lands, a rule is per texture: give the block its own texture if it needs its own light.
  • class and emissiveRefLevel, as above. A liquid’s ior and absorption: liquids keep water’s own.
  • A base texture whose own name ends in _n or _m: its maps cannot be told apart from it. Rename it.