-
Notifications
You must be signed in to change notification settings - Fork 13
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"
}
}
}
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 | .csh |
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.
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 net.minecraft.resources.ResourceLocation;
import org.joml.Matrix4f;
public class RenderClass {
private static final ResourceLocation CUSTOM_SHADER = Veil.veilPath("test_shader");
public static void render(PoseStack stack, MultiBufferSource source, float partialTicks) {
ShaderProgram shader = VeilRenderSystem.setShader(CUSTOM_SHADER);
if (shader == null) {
return;
}
shader.setFloat("CustomValue", 37.2F);
shader.setMatrix("CustomProjection", new Matrix4f().ortho(0, 10, 10, 0, 0.3F, 100.0F, false));
shader.bind();
// rendering code here
ShaderProgram.unbind();
}
}
import foundry.veil.api.client.render.VeilRenderSystem;
import foundry.veil.api.client.render.shader.program.ShaderProgram;
public class MainModClass {
private static final ResourceLocation CUSTOM_POST_PIPELINE = Veil.veilPath("test_pipeline");
private static final ResourceLocation CUSTOM_POST_SHADER = Veil.veilPath("test_post_shader");
public MainModClass() {
// This works for pipeline-specific uniforms
VeilEventPlatform.INSTANCE.preVeilPostProcessing((pipelineName, pipeline, context) -> {
if (CUSTOM_POST_PIPELINE.equals(pipelineName)) {
ShaderProgram shader = context.getShader(CUSTOM_POST_SHADER);
if (shader != null) {
shader.setInt("Secret", 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.render.pipeline.VeilRenderSystem;
import foundry.veil.render.pipeline.VeilRenderer;
import foundry.veil.render.shader.definition.ShaderPreDefinitions;
import foundry.veil.render.shader.program.ShaderProgram;
import net.minecraft.resources.ResourceLocation;
public class Foo {
private static final ResourceLocation SHADER_ID = new ResourceLocation("veil", "example");
// Some event fired before rendering
public static void onPreRender() {
VeilRenderer renderer = VeilRenderSystem.renderer();
ShaderPreDefinitions definitions = renderer.getDefinitions();
// This adds #define EXAMPLE_DEFINITION to all shaders that depend on it
definitions.define("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.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.inventory.InventoryMenu;
public class Foo {
public void render() {
RenderSystem.setShaderTexture(0, new ResourceLocation(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"
},
"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);
}