Skip to content
github-actions[bot] edited this page Feb 13, 2026 · 16 revisions

Veil adds a custom shader format that supports all OpenGl shader stages. It also has additional features that make shaders more data-driven.

Defining Shaders

Shader programs are located in assets/modid/pinwheel/shaders/program. These are used as actual shader programs that can be used during runtime.

Shader includes are located in assets/modid/pinwheel/shaders/include. These are glsl source files, but they cannot be used as programs. Instead, they can be referenced in shader programs to share code between multiple shader programs.

Programs

Shader programs define what shader stages should be used, what definitions to have, and what textures to bind. Compute shaders are supported, but must be called from Java code. See glDispatchCompute for more information.

Syntax

{
  // Optional
  "vertex": "modid:shaderid",
  // Optional
  "tesselation_control": "modid:shaderid",
  // Optional
  "tesselation_evaluation": "modid:shaderid",
  // Optional
  "geometry": "modid:shaderid",
  // Optional
  "fragment": "modid:shaderid",
  // Optional
  "compute": "modid:shaderid",
  // Optional
  "definitions": [
    "foo",
    "bar",
    {
      "defaultValue": 4
    }
  ],
  // Optional
  "textures": {
    "LocationTexture": "veil:textures/gui/item_shadow.png",
    "AlternateLocationTexture": {
      "type": "location",
      "location": "minecraft:textures/atlas/particles.png"
    },
    "ExampleFramebuffer": {
      "type": "framebuffer",
      "name": "veil:deferred",
      // This is used to identify what color buffer to sample from
      "sampler": 4
    },
    "ExampleFramebufferColor": {
      "type": "framebuffer",
      "name": "veil:deferred"
    },
    "ExampleFramebufferDepth": {
      "type": "framebuffer",
      "name": "veil:deferred:depth",
      "filter": {
        ...
      }
    }
  },
  // Optional
  "blend": {
    // Optional
    "func": "ADD",
    // Optional
    "alphafunc": "ADD",
    // Optional
    "srcrgb": "ONE",
    // Optional
    "dstrgb": "ZERO",
    // Optional
    "srcalpha": "ONE",
    // Optional
    "dstalpha": "ZERO"
  },
  // Optional
  "required_features": [
    "BINDLESS_TEXTURE",
    ...
    "See ShaderFeature.java for all supported features"
  ],
}

Each shader stage has a different file extension to allow each stage to share the same name as the shader. The only exception is include shaders which always use .glsl as the file extension.

Shader Stage Extension
Vertex .vsh
Tesselation Control .tcsh
Tesselation Evaluation .tesh
Geometry .gsh
Fragment .fsh
Compute .comp

Includes

Include shaders define extra shader code that can be imported into programs and other includes. Included shaders are allowed to include code from other include shaders as long as there are no circular references.

Example

#include domain:includeid

out vec4 fragColor;

void main() {
    fragColor = vec4(1, 0, 1, 1);
}

Double Include Example

assets/veil/pinwheel/shaders/include/foo.glsl

vec3 foo(vec4 color) {
    return color.rgb;
}

assets/veil/pinwheel/shaders/include/bar.glsl

#include veil:foo

vec3 bar(vec4 color) {
    return foo(color) / 2.0;
}

Uniforms

Unlike vanilla shaders, no extra work is required to add uniforms to shaders. They will automatically be detected by name when the methods in the shader are called.

Custom post-shader uniforms can be added by uploading uniforms during the VeilPostProcessingEvent.Pre event.

Built-in Uniforms

Source Java Code GLSL Code Notes
Minecraft RenderSystem#getShaderTexture uniform sampler2D Sampler#; Vanilla Minecraft supports samplers 0-11. Generally, the color texture is bound to Sampler0, the overlay is bound to Sampler1, and the lightmap is bound to Sampler2.
Minecraft RenderSystem#getModelViewStack uniform mat4 ModelViewMat;
Minecraft RenderSystem#getProjectionMatrix uniform mat4 ProjMat;
Minecraft RenderSystem#getTextureMatrix uniform mat4 TextureMat;
Minecraft (Window#getWidth, Window#getHeight) uniform vec2 ScreenSize;
Minecraft RenderSystem#getShaderColor uniform vec4 ColorModulator;
Minecraft VeilRenderSystem#getLight0Direction uniform vec3 Light0_Direction;
Minecraft VeilRenderSystem#getLight1Direction uniform vec3 Light1_Direction;
Minecraft RenderSystem#getShaderGlintAlpha uniform float GlintAlpha;
Minecraft RenderSystem#getShaderFogStart uniform float FogStart;
Minecraft RenderSystem#getShaderFogEnd uniform float FogEnd;
Minecraft RenderSystem#getShaderFogColor uniform vec4 FogColor;
Minecraft RenderSystem#getShaderFogShape uniform int FogShape;
Minecraft RenderSystem#getShaderLineWidth uniform float LineWidth; Only present when using LINES or LINE_STRIP render mode
Minecraft RenderSystem#getShaderGameTime uniform float GameTime; Vanilla Minecraft sets this uniform to a value between 0 and 1 depending on the level game time. For example, 0 means the tick time is 0 and 0.5 means the game time is 12,000.
Minecraft Set Manually per renderer uniform vec3 ChunkOffset; This uniform is used during chunk rendering to offset the chunks in the shader.
Veil The current client time in seconds, looping every hour uniform float VeilRenderTime;
Veil The normal transformation from the projection matrix. Matrix4f#normal uniform mat3 NormalMat;
Veil ClientLevel#getShade uniform float VeilBlockFaceBrightness[#]; Only set if the player is currently in a level. This should also be accessed using #import veil:light in a Veil shader.

Uniform Blocks

Veil has an API for creating simple uniform blocks, see VeilShaderBufferRegistry#REGISTRY. The data layout can then be constructed with VeilShaderBufferLayout, see CameraMatrices for an example.

The registry key used for a shader buffer can then be used in any veil shader to import the GLSL code to access that block.

Veil fully supports registering custom uniform blocks. See VeilShaderBufferLayout#Builder for more details.

Example

#veil:buffer veil:camera

This will include all the fields from CameraMatrices#createLayout in the top level of the shader. In most cases you should also include an interface name to make the shader more robust.

#veil:buffer veil:camera FooBar

You can name the interface anything you want, but for best practice it should be related to what the block contains. When a shader block interface name is set, all fields are accessed via InterfaceName.FieldName in the GLSL code.

Built-in Uniform Blocks

Java Code GLSL Code
VeilRenderer#getCameraMatrices #veil:buffer veil:camera VeilCamera
VeilRenderer#getGuiInfo #veil:buffer veil:gui_info VeilGuiInfo

Example

import com.mojang.blaze3d.vertex.PoseStack;
import foundry.veil.Veil;
import foundry.veil.api.client.render.VeilRenderSystem;
import foundry.veil.api.client.render.shader.program.ShaderProgram;
import foundry.veil.api.client.render.shader.uniform.ShaderUniformAccess;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;

public class RenderClass {

    private static final ResourceLocation CUSTOM_SHADER = ResourceLocation.fromNamespaceAndPath(Veil.MODID, "test_shader");

    public static void render(PoseStack stack, MultiBufferSource source, float partialTicks) {
        ShaderProgram shader = VeilRenderSystem.setShader(CUSTOM_SHADER);
        if (shader == null) {
            return;
        }

        ShaderUniformAccess customValue = shader.getUniform("CustomValue");
        // Always check if the uniform for nullability
        // because it will be null if it doesn't exist
        if (customValue != null) {
            customValue.setFloat(32.2F);
        }
        ShaderUniformAccess customProjection = shader.getUniform("CustomProjection");
        if (customProjection != null) {
            Matrix4f projection = new Matrix4f().ortho(0, 10, 10, 0, 0.3F, 100.0F, false);
            customProjection.setMatrix(projection);
        }

        shader.bind();
        // rendering code here
        ShaderProgram.unbind();
    }
}

Post-Processing Example

import foundry.veil.Veil;
import foundry.veil.api.client.render.shader.uniform.ShaderUniformAccess;
import foundry.veil.platform.VeilEventPlatform;
import net.minecraft.resources.ResourceLocation;

public class MainModClass {

    private static final ResourceLocation CUSTOM_POST_PIPELINE = ResourceLocation.fromNamespaceAndPath(Veil.MODID, "test_pipeline");

    public MainModClass() {
        // This works for pipeline-specific uniforms
        VeilEventPlatform.INSTANCE.preVeilPostProcessing((pipelineName, pipeline, context) -> {
            if (CUSTOM_POST_PIPELINE.equals(pipelineName)) {
                ShaderUniformAccess secret = pipeline.getUniform("Secret");
                if (secret != null) {
                    secret.setInt(42);
                }
            }
        });
    }
}

Definitions

Definitions allow shaders to use flags specified in Java code. Values can be defined by calling VeilRenderSystem.renderer().getDefinitions().define(). Any shader that depends on a value will be automatically recompiled when the value updates or is removed. Each definition is inserted as a #define name value in shader code at the top of the file.

If a default value is specified, then that value will be used if no definition is specified in Java code.

Global Definitions

Global definitions are similar to regular definitions, but they are added to all shaders and do not schedule a shader recompile when changed. They should be set to constants defined in Java code.

Syntax

{
  "definitions": [
    "foo",
    "bar",
    {
      "defaultValue": 4
    }
  ]
}

Example

assets/veil/pinwheel/shaders/program/example.json

{
  "vertex": "veil:example",
  "fragment": "veil:example",
  "definitions": [
    "example_definition"
  ]
}

Foo.java

import foundry.veil.api.client.render.VeilRenderSystem;
import foundry.veil.api.client.render.VeilRenderer;
import foundry.veil.api.client.render.shader.ShaderPreDefinitions;
import net.minecraft.resources.ResourceLocation;

public class Foo {

    private static final ResourceLocation SHADER_ID = ResourceLocation.fromNamespaceAndPath("veil", "example");

    // Some event fired before rendering
    public static void onPreRender() {
        VeilRenderer renderer = VeilRenderSystem.renderer();
        ShaderPreDefinitions definitions = renderer.getShaderDefinitions();

        // This adds #define EXAMPLE_DEFINITION to all shaders that depend on it
        definitions.set("example_definition");
    }
}

Textures

All shader programs can define an arbitrary number of textures. There are currently two supported texture sources:

  • Locations
  • Framebuffers

The name used as the key in the json is bound to the uniform name in the shader files.

Locations

This will use the name of any registered texture in TextureManager or load a texture from file if it does not exist. This works exactly like binding any other texture in Minecraft:

import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.world.inventory.InventoryMenu;

public class Foo {

    public void render() {
        RenderSystem.setShaderTexture(0, InventoryMenu.BLOCK_ATLAS);
    }
}

Framebuffers

This uses textures defined in framebuffers as texture sources. Since framebuffers can have multiple attachments, there is an optional parameter defining what sampler to use. If the specified sampler doesn't exist or is not a texture attachment, then no texture is bound.

Using :depth at the end of the name will use the depth attachment. If the framebuffer doesn't have a depth attachment, then no texture is bound.

See the framebuffer documentation for more information.

Syntax

{
  "textures": {
    "LocationTexture": "veil:textures/gui/item_shadow.png",
    "AlternateLocationTexture": {
      "type": "location",
      "location": "minecraft:textures/atlas/particles.png"
    },
    "CubemapTexture": {
      "type": "location",
      "location": "veil:textures/somecubemap.png"
    },
    "ExampleFramebuffer": {
      "type": "framebuffer",
      "name": "veil:deferred",
      "sampler": 4
    },
    "ExampleFramebufferColor": {
      "type": "framebuffer",
      "name": "veil:deferred"
    },
    "ExampleFramebufferDepth": {
      "type": "framebuffer",
      "name": "veil:deferred:depth"
    }
  }
}

Example

assets/veil/pinwheel/shaders/program/example.json

{
  "vertex": "example",
  "fragment": "example",
  "textures": {
    "CustomTexture": "veil:textures/gui/item_shadow.png"
  }
}

assets/veil/pinwheel/shaders/program/example.fsh

uniform sampler2D CustomTexture;

in vec2 texCoord;

out vec4 fragColor;

void main() {
    fragColor = texture(CustomTexture, texCoord);
}

Texture Filters

All textures can have their sampling parameters specified on a per-definition basis. If specified, this overrides any parameters specified by the OpenGL texture object.

Syntax

{
  "textures": {
    "TextureName": {
      "type": "location",
      "location": "minecraft:textures/atlas/particles.png",
      "filter": {
        "blur": false,
        // Whether the texture should use linear or nearest filtering
        "mipmap": false,
        // Whether the texture should use mipmaps
        "anisotropy": 1.0,
        // The maximum allowed level of "anisotropy". Any value > 1 enables anisotropic filtering (https://en.wikipedia.org/wiki/Anisotropic_filtering)
        "compareFunction": "never|always|less|lequal|equal|not_equal|gequal|greater",
        // Optional parameter. Indicates the type of depth comparison to make if a depth texture. Mostly used for shadow-mapping to allow correct interpolation when using blur (https://registry.khronos.org/OpenGL-Refpages/gl4/html/glTexParameter.xhtml)
        "wrapX|wrapY|wrapZ": "repeat|clamp_to_edge|clamp_to_border|mirrored_repeat|mirror_clamp_to_edge",
        // Default is repeat. Indicates how the texture should be sampled if the texture coordinates fall outside the range of 0 to 1 (for X/Y/Z S/T/R respectively)
        "borderColor": "0xFF000000",
        // Custom color when using clamp_to_border,
        "borderType": "float|int|uint",
        // Must be set when using an integer texture
        "seamless": false
        // Specifies cubemap textures to be sampled as seamless textures
      }
    }
  }
}

Shader Blending

Normally, blending must be specified in java code and set per render type. However, Veil provides access to override the currently set blend mode in the shader. This is mostly useful for post-processing, but it works in all scenarios.

See glBlendEquation and glBlendFuncSeparate for more information on changing the blend func.

Syntax

{
  "blend": {
    // Optional
    "func": "ADD|SUBTRACT|REVERSE_SUBTRACT|MIN|MAX",
    // Optional
    "alphafunc": "ADD|SUBTRACT|REVERSE_SUBTRACT|MIN|MAX",
    // Optional
    "srcrgb": "CONSTANT_ALPHA|CONSTANT_COLOR|DST_ALPHA|DST_COLOR|ONE|ONE_MINUS_CONSTANT_ALPHA|ONE_MINUS_CONSTANT_COLOR|ONE_MINUS_DST_ALPHA|ONE_MINUS_DST_COLOR|ONE_MINUS_SRC_ALPHA|ONE_MINUS_SRC_COLOR|SRC_ALPHA|SRC_ALPHA_SATURATE|SRC_COLOR|ZERO",
    // Optional
    "dstrgb": "CONSTANT_ALPHA|CONSTANT_COLOR|DST_ALPHA|DST_COLOR|ONE|ONE_MINUS_CONSTANT_ALPHA|ONE_MINUS_CONSTANT_COLOR|ONE_MINUS_DST_ALPHA|ONE_MINUS_DST_COLOR|ONE_MINUS_SRC_ALPHA|ONE_MINUS_SRC_COLOR|SRC_ALPHA|SRC_COLOR|ZERO",
    // Optional
    "srcalpha": "CONSTANT_ALPHA|CONSTANT_COLOR|DST_ALPHA|DST_COLOR|ONE|ONE_MINUS_CONSTANT_ALPHA|ONE_MINUS_CONSTANT_COLOR|ONE_MINUS_DST_ALPHA|ONE_MINUS_DST_COLOR|ONE_MINUS_SRC_ALPHA|ONE_MINUS_SRC_COLOR|SRC_ALPHA|SRC_ALPHA_SATURATE|SRC_COLOR|ZERO",
    // Optional
    "dstalpha": "CONSTANT_ALPHA|CONSTANT_COLOR|DST_ALPHA|DST_COLOR|ONE|ONE_MINUS_CONSTANT_ALPHA|ONE_MINUS_CONSTANT_COLOR|ONE_MINUS_DST_ALPHA|ONE_MINUS_DST_COLOR|ONE_MINUS_SRC_ALPHA|ONE_MINUS_SRC_COLOR|SRC_ALPHA|SRC_COLOR|ZERO"
  }
}

Shader Feature

Veil supports optional shader features through extensions. Instead of forcing the shader to attempt compilation and fail, Veil requires all required features to be present before trying to load and compile the shader.

Requiring a feature will also enable all required GLSL extensions to use the feature.

Syntax

{
  "required_features": [
    "A list of enum constants found in ShaderFeature.java"
  ]
}

Example

assets/veil/pinwheel/shaders/program/example.json

{
  "vertex": "veil:example",
  "fragment": "veil:example",
  "required_features": [
    "BINDLESS_TEXTURE"
  ]
}

assets/veil/pinwheel/shaders/program/example.vsh

layout(location = 0) in vec3 Position;
layout(location = 1) in vec2 UV0;

uniform mat4 ModelViewMat;
uniform mat4 ProjMat;

out vec2 texCoord0;

void main() {
    gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);
    texCoord0 = UV0;
}

assets/veil/pinwheel/shaders/program/example.fsh

in vec2 texCoord0;

layout(std140) uniform CustomTextures {
    sampler2D textures[128];
};

uniform uint TextureIndex;

out vec4 Color;

void main() {
    Color = texture(textures[TextureIndex], texCoord0);
}

Because bindless texture is a required extension, the shader will only load and compile if the GPU supports it.

Clone this wiki locally