MCME Wiki

The shader base is the set of shaders every MCME pack shares: the shaders that draw the baked 3D models, the water, no fog, the action bar and the time-of-day clock. It lives once, in ResourcePackScripts' shaderBase/, and a script copies it into each pack repository. Optional modules, such as lava, go only to the packs that turn them on. A pack adds features of its own, such as Mordor's fire eye, through hooks. It never changes its copy of a base file: changed copies drift apart, and one broken shader can stop the whole pack from loading.

What's in it

Every pack gets all of shaderBase/assets/:

Files What they do
minecraft/shaders/core/text.vsh The MCME action bar
minecraft/shaders/include/fog.glsl No fog
minecraft/shaders/core/terrain.vsh, terrain.fsh, minecraft/shaders/include/objmc_*.glsl objmc: draw the baked models' real shape
sodium/shaders/blocks/block_layer_opaque.* The same, for players running the Vanilla pack with Sodium
sodium/shaders/include/*.glsl Copies of Sodium 0.9.2's own includes (see Sodium's includes)
minecraft/shaders/include/fluid.glsl, water*.glsl Water, drawn by the terrain shaders. fluid.glsl is also what modules and a pack's own fluids build on.
minecraft/shaders/core/lightmap.fsh, include/mcme_clock.glsl Hides the time of day in the light map, for Sodium's and Distant Horizons' terrain
minecraft/shaders/include/mcme_modules.glsl, mcme_modules_main.glsl The modules the pack turned on, and drawing them
minecraft/shaders/include/mcme_lite.glsl Defines MCME_LITE in the Lite variant only
minecraft/shaders/include/mcme_hook_*.glsl Five empty hooks

shaderBase/mc26_3/ holds the terrain shaders as Minecraft 26.3 needs them (see Minecraft 26.3).

Modules

A module is a folder in shaderBase/modules/<name>/ and goes only to the packs that turn it on:

Module Files Draws On in
lava lava.glsl, lava_config.glsl block/lava_still, lava_flow Mordor, Dwarven
ice ice.glsl, ice_config.glsl block/ice None yet

A pack turns modules on in a .mcme-shaders.json at the top of its repository (names starting with a dot never go into a zip):

{
  "modules": ["lava"],
  "own": ["assets/minecraft/shaders/include/lava_config.glsl"],
  "fluids": {"powder_snow": 6}
}

A pack without the file gets the base and no modules. RP-Human's is {"modules": []}.

Getting the base into a pack

The release scripts zip a pack's repository as it is (only the Vanilla and Lite zips are rebuilt), so the base has to be committed into every pack repository. In a checkout of ResourcePackScripts:

python generateVanilla/syncShaderBase.py <pack repository>

The sync:

It prints what it did, or Already up to date. Commit everything it changed. Run it again whenever the base changes, and after you edit a fluid texture.

It refuses a pack whose copy of a base file was changed by hand, because the lock no longer matches. Move the change into a hook, or list the file under own. For a pack's first sync, over copies it kept from before, check that they had no changes of its own, then run it with --force.

In the build

generateVanilla, and so a Sodium release, stops when:

Hooks

The terrain shaders import five hook files, after the water and the modules. The base's are empty. A pack overrides one by shipping its own in its root assets/minecraft/shaders/include/, so both the Sodium and the Vanilla zips get it. The same hook file serves vanilla's terrain.* and Sodium's block_layer_opaque.*.

Hook Where it's imported For
mcme_hook_vertex_globals.glsl Vertex shader, global scope, after objmc_tools outs, imports, functions
mcme_hook_vertex_main.glsl Vertex main(), after objmc, before gl_Position is set from Pos Moving or marking faces. objmc's locals (atlasSize, isCustom, uv…), UV0, Position and texCoord are in scope.
mcme_hook_vertex_end.glsl End of vertex main() Overriding the fog distance, or gl_Position
mcme_hook_fragment_globals.glsl Fragment shader, global scope, after objmc_fragment ins, imports, functions
mcme_hook_fragment_main.glsl Fragment main(), after objmc's lighting, the water and the modules, before the alpha cutout and fog Changing color. fluid and fluidHere say which fluid the face is and where on it; shore gives its corners' occlusion.

Import with the namespace, #moj_import <minecraft:my_feature.glsl>, because Sodium's shaders are in the sodium namespace.

Before the hooks, the base defines these macros, so a hook doesn't need to know which shader it is in:

Macro Vanilla Sodium
MCME_SODIUM not defined defined
MCME_MODELVIEW ModelViewMat u_ModelViewMatrix
MCME_PROJECTION ProjMat u_ProjectionMatrix
MCME_SECONDS (vertex) Seconds into the day, from GameTime Seconds since the region was built
MCME_SECONDS (fragment) Seconds into the day The same, from the light map's clock: seamless across regions
MCME_WORLD_POS The vertex's world position Its position within its region of 128 × 64 × 128 blocks (MCME_REGION)
MCME_WORLD_POS_64 The world position mod 64 blocks The same, exactly: for patterns that must look identical with and without Sodium
MCME_SECTION_CENTRE The section's centre, relative to the camera The same
MCME_FOG_DISTANCE(p) Fogs the vertex as if it were at p The same
MCME_TEXCOORD (fragment) The fragment's atlas coordinate The same
MCME_ATLAS_SIZE (fragment) The block atlas's size in texels The same
MCME_FOG_START (fragment) Where the render-distance fog starts The same
MCME_FOG_COLOR (fragment) The fog's colour, a vec4 The same

For example, Mordor's fire eye as hooks:

// mcme_hook_vertex_main.glsl
#define FIRE_MODELVIEW MCME_MODELVIEW
#define FIRE_SECONDS MCME_SECONDS
#moj_import <minecraft:fire_eye_main.glsl>

// mcme_hook_vertex_end.glsl: fog the eye as one thing, at its centre
if (fireLayer >= 0) {
    MCME_FOG_DISTANCE(fireCentre);
}

Shaders a feature adds that the base doesn't have, such as Mordor's sky.fsh or Distant Horizons shaders, simply stay in the pack.

Water and fluid signing

The base draws water per pixel, fixed in the world: layered ripples and crests, now and then a wave with a trail of foam, streaks where it flows, and, with Sodium, foam along the shores of still water. Its settings are in water_config.glsl.

The shaders know a fluid by a code hidden in the lowest bits of its texture (block/water_still.png, water_flow.png, and the lava, ice and own-fluid textures). The code changes no colour by more than 3 steps in 255. The sync and the build write it.

Minecraft 26.3

26.3 compiles every shader to SPIR-V for Vulkan, which needs the shaders written differently: #include instead of #moj_import, no #version in an include, and a layout(location = N) on every input and output. It also draws translucent terrain, particles and text in extra transparency passes.

So that one zip serves 26.2 and 26.3, each pack keeps 26.3's copies in an overlay folder, mc26_3/, which pack.mcmeta points at for format 97. The game reads it over assets/. Only the terrain shaders are written by hand, in shaderBase/mc26_3/. The sync and the build translate the rest from the 26.2 files: every core shader the pack has that the overlay doesn't (such as Mordor's sky), and every include those shaders reach, hooks and modules included.

Checks

A shader that one player's driver refuses drops every resource pack they have on, and nobody can test every GPU by hand. checkShaders.py checks a pack's shaders the ways a driver could refuse them:

python generateVanilla/checkShaders.py <pack> [--glslang PATH] [--fetch tested|latest] [--minecraft 26.3]

The game's shaders, and Sodium's and DH's, come from their jars. By default those are the ones installed in your Minecraft folder: the client jar in versions/<version>/, and mods in mods/. --fetch tested downloads the versions in generateVanilla/shader_versions.json, the ones the shaders were tested on in game. --fetch latest downloads the newest Sodium and DH for that Minecraft version and notes every newer version.

On GitHub: each pack repository runs the check in .github/workflows/check-shaders.yml, a copy of ResourcePackScripts' ci/check-shaders.yml. It runs on every push and pull request that touches assets/*/shaders/, mc26_3/ or .mcme-shaders.*, against the tested versions and, for packs with an mc26_3/ folder, for 26.3 too. Once a week it also runs against the newest Sodium and DH, as a heads-up that never fails the repository. It uses the checks from ResourcePackScripts' development branch, so a changed check reaches the packs before production.

Working on shaders without building

shaderBase/ is a resource pack itself, so you can see shader changes straight away, on vanilla or with Sodium:

  1. Link it into your resourcepacks folder. On Windows, in cmd:

    mklink /J "%APPDATA%\.minecraft\resourcepacks\MCME-shaderBase" "C:\path\to\ResourcePackScripts\shaderBase"
    

    Link your pack checkout the same way.

  2. In the game, put your pack above MCME-shaderBase.

  3. Edit the base or your hooks, then press F3 + T to reload.

With Sodium, the pack's .obj models render through Special Model Loader and its hooks run in Sodium's terrain shader. On vanilla, the .obj models don't show until the pack is converted, but its hooks do.

Changing the base itself

This is work in ResourcePackScripts, through a pull request into development:

Sodium's includes

Sodium compiles its shaders through Minecraft's own preprocessor, which only expands #moj_import. So block_layer_opaque.* imports <sodium:globals.glsl> and so on, like Sodium's originals. Without Sodium those files don't exist and Minecraft would drop the pack, so the base ships copies of them. Writing #include instead loads without Sodium, but breaks the shader with Sodium.

Other shader scripts

Script What it does
generateVanilla/applyShaderBase.py <pack> [<output>] Adds the newest base to a pack that generateVanilla doesn't build, and signs its water. Without an output folder it changes the pack in place, so run it on a copy.
generateVanilla/signFluids.py <pack> lava\|water\|ice\|all [--check] Writes the fluids' codes into a pack's fluid textures.