ajhahn.de
← Theria
plain text 61 lines
shader_type spatial;
render_mode specular_disabled;

// The cel / toon treatment every field unit wears (heroes, creeps, structures). It
// reproduces a model's own surface albedo — the imported texture, its base colour, and
// its vertex colours where the source used them — so a stylised model still reads as
// itself, then quantises the directional light into three flat tones for the low-poly
// look and folds in the unit's team colour. Driven per surface by HeroModelLibrary,
// which copies the source material's albedo into these uniforms.

// The source surface's albedo: a texture (white when the model carries none), a base
// colour multiplied over it, and a flag for whether the source fed vertex colours into
// its albedo (the low-poly animals do; the textured structures do not).
uniform sampler2D albedo_tex : source_color, hint_default_white;
uniform vec4 albedo : source_color = vec4(1.0);
uniform float use_vertex = 0.0;

// The team wash: the colour the albedo is mixed toward, and how far (0 keeps the model's
// own colour, 1 replaces it). A hero takes a light mix to keep its species; a prop more.
uniform vec4 team_tint : source_color = vec4(1.0);
uniform float tint_strength : hint_range(0.0, 1.0) = 0.0;

// A hard-edged fresnel rim that lights the model's silhouette, so a unit pops off the
// jungle ground it stands on instead of blending into it. `rim_sharpness` is where the
// rim starts toward the edge (higher = thinner band); `rim_amount` how bright it burns.
uniform vec4 rim_color : source_color = vec4(1.0);
uniform float rim_sharpness : hint_range(0.0, 1.0) = 0.45;
uniform float rim_amount : hint_range(0.0, 2.0) = 0.8;

// The two light levels the matte (shadowed) tone steps up through, giving three flat
// bands in all. Eyeball-calibrated against the single key light and the ambient fill.
const float MID_TONE = 0.5;
const float LOW_CUT = 0.25;
const float HIGH_CUT = 0.6;

varying vec3 v_color;

void vertex() {
	v_color = COLOR.rgb;
}

void fragment() {
	vec3 base = albedo.rgb * texture(albedo_tex, UV).rgb;
	base = mix(base, base * v_color, use_vertex);
	ALBEDO = mix(base, team_tint.rgb, tint_strength);
	ROUGHNESS = 1.0;
	METALLIC = 0.0;
	float fresnel = 1.0 - clamp(dot(NORMAL, VIEW), 0.0, 1.0);
	float rim = smoothstep(rim_sharpness, 1.0, fresnel);
	EMISSION += rim_color.rgb * rim * rim_amount;
}

// Bands the key light into three flat tones with hard edges — the toon step. The
// shadow side is left to the environment's ambient fill (added by the engine), so it
// reads as a deliberate matte tone rather than going black.
void light() {
	float ndl = max(dot(NORMAL, LIGHT), 0.0);
	float tone = step(LOW_CUT, ndl) * MID_TONE + step(HIGH_CUT, ndl) * (1.0 - MID_TONE);
	DIFFUSE_LIGHT += ALBEDO * LIGHT_COLOR * ATTENUATION * tone;
}