Making material packs
VSPT renders Vintage Story with a path tracer, so a surface’s look depends on what it is made of: how rough or polished it is, whether it is metal, where it has relief, and whether it glows. A material pack describes that for a set of textures, with a few images per texture.
This kit has everything to make one:
vspt-pack.exe |
Builds a material pack from the images your paint program exports, checks it the way the game will, and zips it for the Mod DB |
sample/ |
A complete pack project: dirt, the chest, granite, the temporal gear, the glass pane, the anvil and the iron pickaxe |
vsptpbrsample_1.0.0.zip |
The sample, built: drop it into your Mods folder to see it in the game |
vspt-pack needs the .NET 10 runtime, which Vintage Story itself needs, so if the game runs, the tool runs.
A material pack is an ordinary mod. It does nothing without VSPT, and the game loads it fine without VSPT.
Do not make your pack depend on vspt: a player without the path tracer could not then use your pack’s
textures either.
Quick start
Section titled “Quick start”vspt-pack new mypackmakes a project folder. Put images in it, named after the texture they belong to:
mypack/ pack.json your mod's name, id, version: like modinfo.json textures/game/block/soil/fertmedium.normal.png vanilla's medium soil: its normal map textures/game/block/soil/fertmedium.height.png its height map textures/game/block/soil/fertmedium.roughness.png its roughnessthen:
vspt-pack build mypack builds mypack/build/mypack/ and checks itvspt-pack install mypack builds and copies it into the game's Mods foldervspt-pack zip mypack builds, checks and zips it: mypack/build/mypack_1.0.0.zipJoin a world and type .vspt materials in chat: it lists the packs it read and anything wrong with them.
After changing an image, build or install again, then leave the world and join it again; there is no need to
restart the game.
Which texture an image belongs to
Section titled “Which texture an image belongs to”An image’s path is the texture’s path under the game’s assets/<folder>/textures/, with its domain in front,
and the channel before .png:
textures/<domain>/<path>.<channel>.png| The game’s texture | Your image |
|---|---|
assets/survival/textures/block/stone/rock/granite1.png (domain game) |
textures/game/block/stone/rock/granite1.roughness.png |
assets/survival/textures/item/resource/temporalgear.png |
textures/game/item/resource/temporalgear.emissive.png |
Another mod’s assets/coolmod/textures/block/lamp.png |
textures/coolmod/block/lamp.normal.png |
Vanilla’s textures are all domain game, whether they sit in the game’s survival, creative or game folder.
Alternate textures each need their own images. Granite is granite1 to granite4, so give all four, or the ones
without images keep the default look.
The channels
Section titled “The channels”Every image of a texture is the texture’s size, texel for texel: a 32x32 texture has 32x32 maps. One texel of art, one material. An image of 1x1 pixel is the same value everywhere (handy for “all of it is metal”).
| Channel | What it is | Values |
|---|---|---|
.normal.png |
Which way each texel’s surface faces | RGB tangent-space normal, Y up (OpenGL). A DirectX map (Y down)? Set "normalY": "down" in pack.json, or name it .normaldx.png |
.height.png |
Relief | Grey, 128 is the surface, the full range is 4 texels of depth. With no normal map, the normals are worked out from it |
.roughness.png |
How rough | Grey: 0 a mirror, 255 fully matte. Polished metal about 60 to 120; stone, wood and soil about 180 to 230 |
.smoothness.png |
The same upside down | For tools that export smoothness or gloss. Give one of the two |
.metallic.png |
Metal or not | 255 metal, 0 not. A texel is metal or it is not: avoid the grey in between |
.emissive.png |
Where it glows | Grey, 0 none to 255 full. How bright and in what colour comes from a rule (below) |
.translucency.png |
Light passing through thin things | Grey, 0 none (the default) to 255. Leaves, cloth, paper. Not glass: glass is glass because the game draws it as glass |
.tracedalbedo.png |
Base colour, path tracer only | A version of the texture for the path tracer alone: shadows or a painted glow taken out of the art, since the path tracer makes its own. The game without the path tracer keeps the normal texture |
.albedo.png |
Base colour, for everyone | Replaces the texture itself, as a texture pack does, in both renderers |
Your tool’s names work too: .basecolor, .diffuse, .nrm, .displacement, .bump, .gloss, .metalness,
.emission and a few more are read as the channel they mean.
Leave out what you do not need. A texture with only .roughness.png gets flat normals; one with only a normal map
keeps the default finish. Missing parts of the surface map default to roughness 0.85, not metal, no glow and no
translucency.
Ready-made maps in this renderer’s own format, <name>_n.png and <name>_m.png, are copied as they are. Their
layout is at the end of this guide.
pack.json
Section titled “pack.json”It is your mod’s modinfo.json, and becomes it, with a few settings of the tool’s own:
{ "modid": "mypack", "name": "My PBR pack", "version": "1.0.0", "description": "Materials for VSPT.", "authors": ["you"], "type": "theme", "side": "Client", "dependencies": { "game": "1.22.0" }, "normalY": "up"}| Key | |
|---|---|
modid |
Lowercase letters and digits only, as the game requires |
type |
theme for a pack of maps, which changes how things look and nothing about the game. content if it also changes game content |
side |
Client: a material pack is for the player’s own screen, so a server never asks for it and it works on any server |
normalY |
up (OpenGL, the default) or down (DirectX), for .normal.png images |
heightScale |
How deep relief worked out from a height map is: 1 (the default) is the 4 texels of the height range |
heightEdges |
wrap (the default) for block faces, which tile; clamp for item and shape textures, which do not |
Anything else (website, more dependencies) is passed through to modinfo.json. Put a modicon.png
beside pack.json and it goes in too.
Glow, and other rules
Section titled “Glow, and other rules”How bright a glowing texture is, and its colour, goes in a .material.json beside its images:
textures/game/item/resource/temporalgear.material.json{ "emissiveNits": 1200 }| Key | Meaning |
|---|---|
emissiveNits |
How bright the brightest texels are (emissive 255), in real units: candela per square metre. For scale, in the path tracer: a torch flame 10,000; a candle 8,000; lava 3,000; the temporal gear 1,200; a rock salt lamp 200; a glowworm 48. A screen or a small indicator light is about 100 to 500 |
emissiveColor |
[r, g, b], the light’s colour in place of the texels’ own. Linear Rec.2020, any scale; it may be more saturated than an ordinary picture can show |
emissiveKelvin |
A colour temperature in place of emissiveColor: 1,900 a candle, 2,700 a warm bulb, 6,500 daylight |
roughness, metalness |
0 to 1, the whole texture at once, for a texture with no roughness, metallic or _m image |
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 |
Without a colour, each glowing texel glows in its own colour: paint an RGB strip and it lights the room in
rainbow. Glowing surfaces light their surroundings, so a bright one lights a whole room. The block’s lightHsv
still sets the game’s light level, which is what plants and creatures go by, so keep the two roughly in step.
For many textures at once, put rule files in rules/<domain>/:
rules/game/metals.json{ "contract": "1.12", "materials": { "block/metal/sheet-plain/**": { "roughness": 0.3, "metalness": 1 }, "block/metal/sheet-plain/gold*": { "roughness": 0.15 } }}A key is a texture path, without .png and without the domain (the folder the file is in gives that). * matches
within one folder name, ** across folders. Every matching entry applies, the least specific first, so a
family-wide value can be refined by a more specific one.
Not read yet, but kept for when the path tracer learns them: class, emissiveRefLevel. The tool and the game
mention them as notes, not as problems.
Checking
Section titled “Checking”vspt-pack build checks what it built, and stops zip and install when anything is wrong. It looks for:
| It says | What to do |
|---|---|
| An image of another size than its texture | Resize it, nearest neighbour, to the texture’s size |
| No channel in a file name, or an unknown one | Name it <texture>.<channel>.png, with a channel from the table above |
| A normal map that does not look like one | A height map named .normal.png? Rename it .height.png |
| Texels tilting more than 60 degrees | Soften the relief: the path tracer flattens them to 60 |
| Part-metal texels | Use 0 or 255 for metallic |
A glow with no emissiveNits |
Add a .material.json saying how bright |
| A map whose texture does not exist | The path or the domain folder is misspelt |
Give it the game’s folder with --game <install folder> if it cannot find the game by itself (it looks at
%VINTAGE_STORY% and the usual install folder). With the game, it sizes and checks maps for vanilla’s textures
against the real ones.
A pack for another mod’s textures (a texture pack’s, say) names that mod with --with <folder or zip>, once per
mod: vspt-pack build mypack --with D:\mods\conquest. Its textures then count as present and your maps are sized
and checked against them; without it the tool can only see the game’s own textures and reports your maps as unused.
Where such a mod replaces a vanilla texture (a 64x64 repaint of a 32x32 plant, say), its picture is the one the
tool sizes and checks against, as it is the one the game loads; with several --with mods, the last one given wins.
vspt-pack check <mod folder or zip> checks any built pack, yours or someone else’s.
Load order and texture packs
Section titled “Load order and texture packs”Packs load in the game’s mod load order, and a later one wins, image by image: your normal map can replace the path tracer’s own while its roughness stays.
One rule on top keeps texture packs safe: a map applies only to the picture it was made for. If a texture pack repaints a texture, the maps made for the old picture (the path tracer’s own, and any pack’s that loads before the repaint) are dropped for that texture, so they are never drawn over art they do not fit. Maps from the texture pack itself, or from a pack that loads after it, apply. An identical copy of a texture is not a repaint.
So a texture pack with PBR should ship its maps with its art, in the same mod. A PBR add-on for somebody else’s
texture pack should list that pack in dependencies, which makes it load after.
Where it all goes in the built mod
Section titled “Where it all goes in the built mod”What vspt-pack writes, for anyone making maps another way:
modinfo.jsonassets/<domain>/materials/textures/<path>_n.png normal map: RGB normal (Y up), alpha heightassets/<domain>/materials/textures/<path>_m.png surface map: R roughness, G metallic, B emissive, A opacityassets/<domain>/materials/textures/<path>.png path traced base colour (from .tracedalbedo)assets/<domain>/materials/*.json rulesassets/<domain>/textures/<path>.png replaced texture (from .albedo)The surface map’s alpha is opacity to light passing through: 255 none passes (the default), 0 all of it. It is
upside down from .translucency.png so that an image editor’s usual opaque alpha means “solid”.
The maps never go in textures/ beside the art. The game treats every picture there as a texture, and block
types that pick their textures by wildcard (vanilla’s bricks do) would draw a normal map as a block.
Editing maps that already exist
Section titled “Editing maps that already exist”vspt-pack split <assets folder> game:block/soil/fertmedium --into mypacksplits a built pack’s _n and _m maps into channel images in your project, for editing in any paint program.
The sample was made this way from the path tracer’s own maps.
