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];");
PointWhereIn reach
VERTEX_DECLSTop level of the vertex shaderPut functions and constants here for the other vertex hooks.
VERTEX_POSEAfter the vertex is posed, before it is projectedChange 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_COLOREnd of the vertex shaderChange vertexColor (the shading) and lightTint (the colour of the light). Set strutVarying0 and strutVarying1, a vec4 each, which reach the fragment shader interpolated.
FRAGMENT_DECLSTop level of the fragment shaderFunctions and constants for the other fragment hooks.
FRAGMENT_TOONFirst 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_ALBEDOAfter the texture is readChange color, the model's own colour before any light. Read texCoord, fragPos, strutCustom (a vec4), strutVarying0, strutVarying1.
FRAGMENT_FINALAfter the light, before the fogChange 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

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);

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

EventWhen
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_DRAWThe 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_DRAWThey 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));");