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}
}
modules: the modules the pack gets. An unknown name stops the sync and the build.own(optional): base or module files the pack keeps its own version of, usually a module's settings, such as hotter lava. The sync and the build leave these alone. A file listed here must exist.fluids(optional): the pack's own fluid textures to sign, by texture name and kind 5 to 7. Mordor signs fourpowder_snowtextures as kind 6; Paths of the Dead signsfog,fog_thickandfog_cloudas kind 5. The pack draws them in its hooks.
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:
- writes the base and the pack's modules, and keeps its hooks and its
ownfiles; - writes the 26.3 copies into the pack's
mc26_3/folder, and pointspack.mcmetaat it (format 97); - deletes what an earlier sync wrote that the pack no longer gets, such as a module turned off;
- signs the pack's water, its modules' textures and its
fluids; - records every file it wrote, with its hash, in
.mcme-shaders.lock; - adds the vanilla terrain shaders and includes Sodium would warn about to
pack.mcmeta'ssodium.ignored_shaders. Sodium doesn't run them (it runs the base's own, in thesodiumnamespace), and without the list Sodium flags every pack with the base as incompatible; - checks that every shader import in the pack resolves.
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
- Vanilla and Lite zips: generateVanilla adds the newest base and the pack's modules to every pack it builds, over the synced copies.
- The Lite zip gets the base too, but its
mcme_lite.glsldefinesMCME_LITE: the terrain shaders draw no fluid, so water, lava, tar and ice show as their plain textures, and only the models' shaders and the hooks run. A pack's own shaders can checkMCME_LITEthe same way. - Sodium and vanilla-only zips are the repository as it is, so they have the base the sync last wrote.
generateVanilla, and so a Sodium release, stops when:
- the pack's copy of a base or module file was changed by hand, in
assets/orvanilla/assets/. An unchanged copy of an older base is fine: the newer one replaces it. - a shader import doesn't resolve, in any namespace. Minecraft 26.2 resolves every
#moj_importin every pack shader on loading, used or not, and one it can't find makes it drop all resource packs. That is what broke the Vanilla zips of Paths of the Dead v1.8.16. - a shader breaks a platform rule: a
#versionabove 410, or an#extensionnot every platform has (see Checks). - a texture that isn't a fluid still carries a fluid's code (see Water and fluid signing).
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.
- Editing a fluid texture loses the code. Run the sync again, or sign it by hand:
python generateVanilla/signFluids.py <pack> water(orlava,ice,all). Add--checkto only report. - A texture whose size isn't a multiple of 4, or with fully transparent texels, can't carry the code. The build warns, and that fluid shows plain.
- A copy of a signed texture would be drawn as that fluid, for example a 3D model's texture made from the water texture. The build then stops with
Textures the terrain shaders would draw over as a fluidand names each one. Edit a few texels of each, even by one step, and build again. - A pack without its own water textures shows the game's water, plain.
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.
- Each translated file says so on its first line (a core shader's second). Don't edit one: change the 26.2 file and sync again.
- A file in a pack's
mc26_3/without that line is the pack's own, written by hand, and the sync leaves it alone. - In 26.3's alpha-only passes, a hook's
minecraft_sample_lightmap(Sampler2, ...)reads white. A hook that samplesSampler2some other way won't compile there.
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]
- Rules, always (the build checks them too): no
#versionabove 410, because macOS stops at OpenGL 4.1; no ES shaders; no#extensionexceptGL_ARB_separate_shader_objects. - Compiling. With glslang (the Khronos reference compiler: given with
--glslang, in$GLSLANG, or on yourPATH), every shader the pack has is compiled with its imports filled in, in every define combination the game and Sodium use. With the Python packagemodernglinstalled, each shader is also linked on your own GPU's driver. Without either, only the rules are checked, and it saysrules OK (nothing compiled). - Distant Horizons. A pack's DH shader replaces DH's own of the same name outright, so the check compares the two and reports an input, output or uniform DH's has that the pack's lacks or types differently.
--minecraft 26.3checks the pack as 26.3 runs it: themc26_3overlay overassets/, compiled to SPIR-V in every pass, each stage checked to meet the next at the same locations.
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:
-
Link it into your
resourcepacksfolder. On Windows, incmd:mklink /J "%APPDATA%\.minecraft\resourcepacks\MCME-shaderBase" "C:\path\to\ResourcePackScripts\shaderBase"Link your pack checkout the same way.
-
In the game, put your pack above
MCME-shaderBase. -
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:
- A feature every pack should have, such as water, belongs in the base: its includes go in
shaderBase/, imported directly by the base's terrain shaders next to the hooks. - A feature some packs want belongs in a module: add its folder under
shaderBase/modules/, its entry toMODULESingenerateVanilla/shader_base.py, and its drawing tomcme_modules_main.glslunder its define. - Test with glslang (or in game) on vanilla, with Sodium, and with
ALPHA_CUTOUT. - objmc changes: the base's
objmc_*.glslmust matchgenerateVanilla/objmc.py. Change both in the same pull request. - Sodium updates:
block_layer_opaque.*is Sodium's own shader with objmc and the hooks added. Compare it with the new Sodium jar'sassets/sodium/shaders/and re-copy the includes. - After testing a new game, Sodium or DH version in game, raise it in
shader_versions.json.
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. |