Changing the render pipeline
Put your own GLSL into the shader every model is drawn with, add coloured lights, hand per-model values to the shader and run code around the draw, all from your mod and without a copy of Strut's renderer.
Everything here is client side and applies to the GPU path's own shader. Under a shader pack the pack draws the model with its own program, and these hooks are not used (see Shader packs).
Shader hooks
A hook is a piece of GLSL registered under an id at a named place in the vertex or fragment shader. Strut builds the shader with every hook in place the first time a model is drawn, and again whenever hooks are added or taken out. Hooks at one place run in the order they were registered.
StrutShaderHooks.register(MyMod.id("wobble"), StrutShaderHooks.Point.VERTEX_POSE,
"posedPosition.y += 0.02 * sin(posedPosition.x * 4.0 + StrutParams[0].x) * instData.custom[0];");
| Point | Where | In reach |
|---|---|---|
VERTEX_DECLS | Top level of the vertex shader | Put functions and constants here for the other vertex hooks. |
VERTEX_POSE | After the vertex is posed, before it is projected | Change posedPosition (a vec4, relative to the camera, not yet rotated by it) and worldNormal. Read instData (instData.custom[0..3] are your floats), gl_InstanceID, BoneIndex. |
VERTEX_COLOR | End of the vertex shader | Change vertexColor (the shading) and lightTint (the colour of the light). Set strutVarying0 and strutVarying1, a vec4 each, which reach the fragment shader interpolated. |
FRAGMENT_DECLS | Top level of the fragment shader | Functions and constants for the other fragment hooks. |
FRAGMENT_TOON | First thing in toonColor(vec3 albedo, float shade, float rim) | return a colour to replace the toon look, or change albedo, shade (0.55 in the shadow, 1 in the light) and rim (0 facing the eye, 1 edge on) to adjust it. |
FRAGMENT_ALBEDO | After the texture is read | Change color, the model's own colour before any light. Read texCoord, fragPos, strutCustom (a vec4), strutVarying0, strutVarying1. |
FRAGMENT_FINAL | After the light, before the fog | Change color, what would be drawn. |
Everything declared with uniform vec4 StrutParams[8] is in both stages. A hook that does not compile is left out and logged with the ids registered, and the models are drawn as if none was; it is not tried again until the hooks change. StrutShaderHooks.unregister(id) takes an id out at every point.
Values for the shader
- For every model:
StrutShaderParams.set(slot, x, y, z, w)sets one of eightvec4s, read in the shader asStrutParams[slot]. They are uploaded once a frame as they stand then: a time, a colour, a strength. - For one model: in a
StrutRenderHooks.SUBMITlistener,context.setCustom(index, value)sets one of four floats that go with that model, read asinstData.custom[i]in the vertex hooks andstrutCustom[i]in the fragment ones.
Coloured lights
A model is lit by up to four lights: the sky's (the sun or the moon), and the three strongest of the glowing blocks round it and the lights mods add; the weaker ones are added together into the last. Each light shapes the side of the model that faces it, and a light with a colour tints that side the way a coloured lamp does to a white wall.
// a mob that glows blue lights what is near it
StrutLighting.addProvider((animatable, world, x, y, z, sink) -> {
for (GlowMob mob : world.getEntitiesByClass(GlowMob.class, new Box(x - 6, y - 6, z - 6, x + 6, y + 6, z + 6), e -> true))
sink.point(mob.getX(), mob.getEyeY(), mob.getZ(), 6.0, 1.0f, 0x4080FF);
});
// a lamp of one colour
StrutLighting.setBlockLightColour(Blocks.SOUL_LANTERN, 0x40C0FF);
sink.point(x, y, z, radius, brightness, rgb): a light in the world; it reachesradiusblocks and fades out toward the edge. Abrightnessof 1 is a torch's.sink.directional(dx, dy, dz, strength, rgb): a light from a direction, like the sun.- A provider runs on the render thread, a few times a second for each model: keep it cheap. One that throws is skipped for that frame.
- The invisible
lightblock is not counted as a light.
The F9 overlay shows the lights on you and on the model drawn last, each as [direction |strength| #colour], and the block that is the strongest light.
Events along the draw
| Event | When |
|---|---|
StrutRenderHooks.onSubmit(listener) | A model is about to be queued. The SubmitContext gives its animatable() and lets you change its colour, how it is shaded (setShading) and the four custom floats. Register with onSubmit, not SUBMIT.register, so Strut knows there is a listener to ask. |
StrutRenderHooks.BEFORE_DRAW | The frame's models are about to be drawn and Strut's shader is not yet bound. Draw your own things, or set state; put back what you change. |
StrutRenderHooks.AFTER_DRAW | They have been drawn and Strut has put its own state back. |
StrutRenderHooks.onSubmit(context -> {
if (context.animatable() instanceof Entity entity && entity.hurtTime > 0)
context.setCustom(0, entity.hurtTime / 10f);
});
// in the fragment shader, a hook reads it
StrutShaderHooks.register(MyMod.id("hurt"), StrutShaderHooks.Point.FRAGMENT_ALBEDO,
"color.rgb = mix(color.rgb, vec3(1.0, 0.1, 0.1), strutCustom.x * 0.6);");
The toon look
The toon look is a function in the fragment shader, so it is changed with the FRAGMENT_TOON hook: shade is how lit the surface is, albedo the texture's own colour. This gives two flat bands and no rim:
StrutShaderHooks.register(MyMod.id("two_band"), StrutShaderHooks.Point.FRAGMENT_TOON,
"return albedo * mix(0.55, 1.0, step(0.7, shade));");