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.
Quick start
Section titled “Quick start”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 colourmaterials/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.
Where the files go
Section titled “Where the files go”| 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 maps
Section titled “The maps”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
.pngname, 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
_mto change the finish and keep a flat surface, or only_nto add relief to the fallback finish. - Alternate textures each need their own maps. If
lamp.pnghas alternateslamp2.png,lamp3.png, each needs its own_nand_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.
Rule files
Section titled “Rule files”{ "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.pngand without a domain. The domain is the folder the rule file sits in: rules inassets/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”.
Making things glow
Section titled “Making things glow”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
emissiveNitswith no colour. The light takes each texel’s colour. - For one colour across the whole light (a flame, a bulb), give
emissiveKelvinoremissiveColor. 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
lightHsvstill 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.
Overriding textures you do not own
Section titled “Overriding textures you do not own”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.
Checking your work
Section titled “Checking your work”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 problemsand 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.
What happens without maps
Section titled “What happens without maps”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, andstrongandsparklyare glossy. - A texture is metal when its path has a
metalfolder 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.
Not yet supported
Section titled “Not yet supported”attributes.vsrtxMaterialon 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.classandemissiveRefLevel, as above. A liquid’siorandabsorption: liquids keep water’s own.- A base texture whose own name ends in
_nor_m: its maps cannot be told apart from it. Rename it.
