Skip to content

Instantly share code, notes, and snippets.

@gigaherz
Last active April 16, 2024 09:52
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gigaherz/b8756ff463541f07a644ef8f14cb10f5 to your computer and use it in GitHub Desktop.
Save gigaherz/b8756ff463541f07a644ef8f14cb10f5 to your computer and use it in GitHub Desktop.
Custom shader rendertype mini-howto example (forge 37.0.15+)

Making a custom render type with a custom shader requires a number of things to exist at once:

  1. A ShaderInstance which references your shader json. The RegisterShadersEvent lets you define a ShaderInstance, and has a callback for when the shader is fully loaded from disk.
  2. A ShaderStateShard with a supplier that returns the ShaderInstance. The supplier exists so that shaders can reload themselves when you change resourcepacks or do a F3+T reload.
  3. A RenderType which uses the ShaderStateShard as its shader state.
  4. A shader json, which declares the shader properties and points to the shader programs (vsh and fsh).
  5. A vertex shader program, which describes how the vertex data is transformed before passing into the rasterizer and being turned into pixels.
  6. A fragment shader program, which describes how the interpolated values from the vertices get turned into color values before being passed into the output blending stage.

Note: The vanilla logic does not normally allow namespaces in the shader names. Forge patches both the ShaderInstance location and the shader program locations to allow it, meaning it's legal to specify "mymod:myshader".

public class MyRenderTypes
{
// Accessor functon, ensures that you don't use the raw methods below unintentionally.
public static RenderType brightSolid(ResourceLocation texture)
{
return CustomRenderTypes.BRIGHT_SOLID.apply(texture);
}
@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = GuidebookMod.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
public static class ModClientEvents
{
@SubscribeEvent
public static void shaderRegistry(RegisterShadersEvent event) throws IOException
{
// Adds a shader to the list, the callback runs when loading is complete.
event.registerShader(new ShaderInstance(event.getResourceManager(), new ResourceLocation("gbook:rendertype_bright_solid"), DefaultVertexFormat.NEW_ENTITY), shaderInstance -> {
CustomRenderTypes.brightSolidShader = shaderInstance;
});
}
}
// Keep private because this stuff isn't meant to be public
private static class CustomRenderTypes extends RenderType
{
// Holds the object loaded via RegisterShadersEvent
private static ShaderInstance brightSolidShader;
// Shader state for use in the render type, the supplier ensures it updates automatically with resource reloads
private static final ShaderStateShard RENDERTYPE_BRIGHT_SOLID_SHADER = new ShaderStateShard(() -> brightSolidShader);
// Dummy constructor needed to make java happy
private CustomRenderTypes(String s, VertexFormat v, VertexFormat.Mode m, int i, boolean b, boolean b2, Runnable r, Runnable r2)
{
super(s, v, m, i, b, b2, r, r2);
throw new IllegalStateException("This class is not meant to be constructed!");
}
// The memoize caches the output value for each input, meaning the expensive registration process doesn't have to rerun
public static Function<ResourceLocation, RenderType> BRIGHT_SOLID = Util.memoize(CustomRenderTypes::brightSolid);
// Defines the RenderType. Make sure the name is unique by including your MODID in the name.
private static RenderType brightSolid(ResourceLocation locationIn)
{
RenderType.CompositeState rendertype$state = RenderType.CompositeState.builder()
.setShaderState(RENDERTYPE_BRIGHT_SOLID_SHADER)
.setTextureState(new RenderStateShard.TextureStateShard(locationIn, false, false))
.setTransparencyState(NO_TRANSPARENCY)
.setLightmapState(NO_LIGHTMAP)
.setOverlayState(NO_OVERLAY)
.createCompositeState(true);
return create("gbook_bright_solid", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, false, rendertype$state);
}
}
}
#version 150
#moj_import <fog.glsl>
uniform sampler2D Sampler0;
uniform vec4 ColorModulator;
uniform float FogStart;
uniform float FogEnd;
uniform vec4 FogColor;
in float vertexDistance;
in vec4 vertexColor;
in vec2 texCoord0;
in vec4 normal;
out vec4 fragColor;
void main() {
vec4 color = texture(Sampler0, texCoord0) * vertexColor * ColorModulator;
fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor);
}
{
"blend": {
"func": "add",
"srcrgb": "srcalpha",
"dstrgb": "1-srcalpha"
},
"vertex": "gbook:rendertype_bright_solid",
"fragment": "gbook:rendertype_bright_solid",
"attributes": [
"Position",
"Color",
"UV0",
"UV1",
"UV2",
"Normal"
],
"samplers": [
{ "name": "Sampler0" }
],
"uniforms": [
{ "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
{ "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },
{ "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] },
{ "name": "Light0_Direction", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0] },
{ "name": "Light1_Direction", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0] },
{ "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] },
{ "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] },
{ "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }
]
}
#version 150
#moj_import <light.glsl>
vec4 custom_mix_light(vec3 lightDir0, vec3 lightDir1, vec3 normal, vec4 color) {
lightDir0 = normalize(lightDir0);
lightDir1 = normalize(lightDir1);
float light0 = max(0.0, dot(lightDir0, normal));
float light1 = max(0.0, dot(lightDir1, normal));
float lightAccum = min(1.0, light0 * 0.6 + light1 * 0.7 + 0.4);
return vec4(color.rgb * lightAccum, color.a);
}
in vec3 Position;
in vec4 Color;
in vec2 UV0;
in ivec2 UV1;
in ivec2 UV2;
in vec3 Normal;
uniform sampler2D Sampler1;
uniform sampler2D Sampler2;
uniform mat4 ModelViewMat;
uniform mat4 ProjMat;
uniform vec3 Light0_Direction;
uniform vec3 Light1_Direction;
out float vertexDistance;
out vec4 vertexColor;
out vec2 texCoord0;
out vec4 normal;
void main() {
gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);
vertexDistance = length((ModelViewMat * vec4(Position, 1.0)).xyz);
vertexColor = custom_mix_light(Light0_Direction, Light1_Direction, Normal, Color);
texCoord0 = UV0;
normal = ProjMat * ModelViewMat * vec4(Normal, 0.0);
}
@banan4phon3
Copy link

hey, quick question, in my case I got a core shader particle.json and a post shader effect.json. My particle.json shader is supposed to overwrite the default particle.json core shader. Does this get done automatically or do I also need to do the above for it?

@gigaherz
Copy link
Author

gigaherz commented Mar 8, 2024

hey, quick question, in my case I got a core shader particle.json and a post shader effect.json. My particle.json shader is supposed to overwrite the default particle.json core shader. Does this get done automatically or do I also need to do the above for it?

shaders are like any other file in a resource pack, if it's in the same folder with the same filename as vanilla, it overwrites the file from resource packs below it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment