ludic/packages/ludic.render3d/shaders/tersun.frag
Orkuncakilkaya f25289db20
Some checks failed
ci / build-and-test (push) Waiting to run
commit-lint / conventional-commits (push) Waiting to run
bootstrap / cfree-fixpoint (push) Has been cancelled
docs / build-and-deploy (push) Successful in 34s
feat(gl): OpenGL 4.1 and the ludic.render3d renderer
`Gl.*` binds the whole OpenGL 4.1 core API — every entry point of the
platform gl3.h with every GL_* constant, generated by `ludic-dev glgen`
with per-call ABI thunks. Windowed builds get an NSOpenGLContext on the
existing window at Retina resolution; headless builds render into an
offscreen CGL context, so a program that uses Gl.* renders and
screenshots identically under the test harness. It links gl.ll, the
thunks and OpenGL.framework only when used; every other build stays
byte-identical.

packages/ludic.render3d is a physically based renderer written on that
surface: HDRI image-based lighting, GPU-generated terrain with scanned
PBR materials, CDLOD, cascaded shadows, glTF with skinning, instanced
vegetation with impostors, procedural grass, water, SSAO, and an HDR
pipeline with bloom, auto-exposure and ACES.

It also carries this session's work on it: the terrain at half its cost
(10.3 -> 5.4 ms of frame), the streaming hitch that got worse the longer
you played, a resize that emptied the world, and the packaging that lets
a game use the renderer from its own repository — `ludic assets`, the
material manifest shipping with the package, and shader lookup falling
back to the install root. See changes/ for each, with its numbers.

The camping game that drove all of it has moved out to its own
repository, Maroon Lake; examples/rendering/smooth.ludic stays as the
renderer's example here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 03:31:12 +03:00

31 lines
1.6 KiB
GLSL

// tersun.frag — the terrain's sun visibility, on its own, one screen-sized R8 buffer.
//
// The ground's shading shader is large: it blends four scanned materials, a photograph
// and a dozen noise fields. Adding a read of the cascade shadow map to it costs about
// six milliseconds a frame on this driver — and costs the same whether the map is tapped
// once or eight times, filtered or texelFetched, compared in hardware or by hand. It is
// a cliff the big shader falls off, not work it performs. The same read from a small
// shader is nearly free, so the read happens here instead: this pass rasterises the same
// CDLOD patches, evaluates the cascades once per pixel, and writes the answer for
// terrain.frag to look up by fragment coordinate.
in vec3 v_wpos;
in vec2 v_huv;
out float o_sh;
uniform sampler2D u_height;
uniform mat4 u_view;
uniform float u_far_split;
uniform float u_far_band;
void main() {
vec3 p = v_wpos;
if (p.y < u_clip_y) discard;
vec3 N = normalize(texture(u_height, v_huv).gba);
float dist = length(p - u_cam_pos);
float viewDepth = -(u_view * vec4(p, 1.0)).z;
// The same tier choice the ground makes, cross-faded over the same band: the near tier
// keeps its filtered penumbra, the far tier its single tap, and the boundary between
// them is not a contour you can find on the hillside.
if (dist > u_far_split + u_far_band) o_sh = sunShadowCheap(p, N, viewDepth);
else if (dist < u_far_split - u_far_band) o_sh = sunShadow(p, N, viewDepth);
else o_sh = mix(sunShadow(p, N, viewDepth), sunShadowCheap(p, N, viewDepth),
smoothstep(u_far_split - u_far_band, u_far_split + u_far_band, dist));
}