-
Notifications
You must be signed in to change notification settings - Fork 36
Shader
Veil adds a custom shader format that supports all OpenGl shader stages. It also has additional features that make shaders more data-driven.
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.
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.
{
// 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 |
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.
#include domain:includeid
out vec4 fragColor;
void main() {
fragColor = vec4(1, 0, 1, 1);
}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;
}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.
| 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. |
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.
#veil:buffer veil:cameraThis 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 FooBarYou 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.
| Java Code | GLSL Code |
|---|---|
VeilRenderer#getCameraMatrices |
#veil:buffer veil:camera VeilCamera |
VeilRenderer#getGuiInfo |
#veil:buffer veil:gui_info VeilGuiInfo |
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();
}
}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 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 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.
{
"definitions": [
"foo",
"bar",
{
"defaultValue": 4
}
]
}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");
}
}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.
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);
}
}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.
{
"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"
}
}
}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);
}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.
{
"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
}
}
}
}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.
{
"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"
}
}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.
{
"required_features": [
"A list of enum constants found in ShaderFeature.java"
]
}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.