render3d: shadow_fit, water_reflection_pass, layer_partition_lods and the GPU cull's scratch are made with the state; v3_dist is scalar; the pushes into lists sized at start-up, the caps probe, the table growth, the loads and the constructors declared with their bounds (one statement a line); the renderer's name made once with the device; the two error messages given back; the dead lupine models removed. runtime: a component's text is held interned in its value cell (one copy per distinct text), so the getter's own text goes with its frame instead of being kept by ludic.ui's model - 80 of the 83 keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14 KiB
ludic.render3d — what is implemented, where, and how it compares
Every item below names the file (and function or shader) that implements it, so
the claim can be checked in the tree. Paths are relative to packages/ludic.render3d
unless stated; Gl.* lives in runtime/native/.
The OpenGL surface (Gl.*)
- Every OpenGL 4.1 core entry point — 478 of 478
GLAPIdeclarations in the platformgl3.h, generated byludic-dev glgen(tools/ludic-cli/glgen.ludic) intoruntime/native/gl_api.ludic(grep -c '^extern function gl_'= 478) with one ABI thunk each inruntime/native/gl_thunks.ll; 901GL_*constants. Float and double parameters takefixed; 64-bit sizes takelong;GLbooleanreturns are widened. macOS exposes 4.1 core as its ceiling, so this is the whole API the platform has. - Context on the window at Retina resolution (
runtime/native/cocoa.ll,win_gl_attach), offscreen CGL context for headless runs (runtime/native/gl.ll), screenshots, shader/program helpers, IEEE-float helpers over Q16.16 (runtime/native/gl.ludic).
Rendering features (numbered so they can be counted)
Lighting and materials
- Cook-Torrance GGX specular, Smith height-correlated visibility, Schlick Fresnel —
shaders/lighting.glslshade() - Diffuse image-based lighting from a convolved irradiance map —
sky.ludicsky_precompute,shaders/ibl_irradiance.frag - GGX-prefiltered specular environment, 6 roughness levels (importance sampled, mip-filtered) —
shaders/ibl_prefilter.frag - Split-sum BRDF lookup table —
shaders/ibl_brdf.frag - HDRI sky with the sun extracted from the map and its irradiance integrated from the clipped texels —
texture.ludichdr_decode,sky.ludic - Sky rotation with all convolutions rebuilt (time-of-azimuth control) —
sky.ludicsky_set_yaw - Normal mapping with derivative-reconstructed tangent frames (models) and a heightfield tangent basis (terrain) —
shaders/model.fragcotangentFrame,shaders/terrain.frag - Distant-grass carpet: the clump cards baked straight down into a tiling tile (alpha = coverage) and draped on the ground beyond the blade rings with stochastic anti-tiling, so the far meadow carries the near cover's clumps and gaps —
scatter.ludiccarpet_bake,shaders/terrain.fragsampleCarpet(replaced the parallax pass, whose sampler slot it took) - Stochastic (triangle-grid) anti-tiling texture sampling with rotated taps and
textureGrad—shaders/terrain.fragsampleMat - Triplanar mapping for steep rock —
shaders/terrain.fragsampleTri - Foliage translucency and wrapped diffuse (thin-leaf subsurface approximation) —
shaders/model.fragFOLIAGE - Two-sided lighting for thin cards —
shaders/model.fragCARD - Ground-bounce fill light (one bounce from the sunlit meadow onto undersides) —
shaders/lighting.glslbounce - Screen-space global illumination: one indirect diffuse bounce gathered from the previous anti-aliased frame, plus ambient occlusion, half resolution, depth-aware blur —
shaders/ssgi.frag,shaders/ssao_blur.frag,post.ludicpost_ssao_pass - Specular occlusion from ambient occlusion —
shaders/lighting.glsl - Cloud shadows projected along the sun from an animated cloud layer —
shaders/lighting.glslcloudShadow - Regional vegetation colouring (aspen groves, dry ridges) and slope-aspect colouring —
shaders/lighting.glslregionTint,shaders/terrain.frag
Shadows
18. Cascaded shadow maps, 5 cascades (16 / 60 / 250 / 1100 / 6000 m) fitted to frustum-slice bounding spheres, texel-snapped, PCF radius widened on the far cascades — shadow.ludic shadow_fit
19. PCF with rotated Poisson taps and interleaved-gradient noise, normal-offset and world-unit slope bias, cascade edge blending — shaders/lighting.glsl sunShadow
20. Per-layer caster culling (grass in near cascades, coarse terrain meshes for far cascades) — scatter.ludic scatter_draw_casters, terrain.ludic
21. Sun-facing impostor cards in the shadow pass with mip-biased coverage — shaders/impostor.vert, shaders/impostor.frag
Geometry, terrain and world
22. GPU-generated terrain height field (ridged multifractal, fbm) — shaders/heightgen.frag
23. Real-world terrain from the Copernicus GLO-30 elevation model with slope-scaled fractal detail; a lake bed carved under the surface the model records — the survey window in the Maroon Lake game (git.workshopsoft.io/workshopsoft/maroon-lake), shaders/heightgen.frag DEM, terrain.ludic terrain_use_dem, terrain_lake
24. Satellite orthophoto drape (Sentinel-2 true colour) sampled bicubically, blended in past the middle ground, keeping the scanned materials' luminance grain plus three-scale procedural grain — the survey window in the Maroon Lake game (git.workshopsoft.io/workshopsoft/maroon-lake), shaders/terrain.frag u_ortho
25. Satellite-classified materials and placement: bare ground (cobble shore near, talus far), snow, meadow, dense conifer stands (tree density and forest-floor duff follow the photograph) — shaders/terrain.frag, terrain.ludic terrain_ortho_green/scree/forest
26. Terrain material splatting by slope, altitude and a track mask; height-map CPU read-back for placement queries — shaders/terrain.frag, terrain.ludic terrain_height
27. Snow by altitude and slope, gully snow, forest-floor material under stands — shaders/terrain.frag
28. glTF 2.0 loading of photogrammetry models by byte-range reads from the .bin — gltf.ludic
29. GPU instancing with divisor attributes and per-instance transforms — scatter.ludic, shaders/model.vert
30. Baked impostors (16-view atlas with coverage and normals, hull-normal blending, LOD switch) — scatter.ludic impostor_bake, shaders/impostor.*
31. Baked branch-card trees: wedge slices of a scanned crown (sectors × height bands) on radial cards over the real trunk mesh — scatter.ludic impostor_bake_wedges, model_branch_cards
32. Card atlases baked from scanned grass clumps and a dense procedural lupine, instanced as crossed cards — scatter.ludic layer_cards
33. Procedural grass blades with wind, dryness and base occlusion; distant blades widen, lie flat to the ground normal and sink into the carpet at the ring's edge — scatter.ludic model_blade, shaders/model.vert, shaders/model.frag BLADE
34. Wind animation (height-weighted sway, per-instance phase) — shaders/model.vert WIND
35. Chunk-streamed ground cover with distance bands (blades in four rings, densest underfoot), deterministic per-chunk generation, nearest-first amortised over frames with an instance budget — stream.ludic
36. Multi-scale noise clustering for natural placement, forest density by elevation/slope/aspect, avalanche chutes, shoreline scrub — the Maroon Lake game (git.workshopsoft.io/workshopsoft/maroon-lake)
37. Level-of-detail by distance: full scan mesh → branch cards → single impostor — scatter.ludic layer_update
38. Texture edge dilation for cut-out atlases; 16-bit PNG decoding to RGB16/R16; RGBE HDR decoding — texture.ludic
39. Anisotropic filtering and mipmapping on all scanned maps — texture.ludic tex_upload
40. Still water: planar reflection pass through a reflection matrix with clip-below discard, depth-tinted body, soft shores, ripple normals, foam streaks, sun glitter; the surface writes depth so occlusion and temporal passes see it — water.ludic, shaders/water.frag
Anti-aliasing, camera and post-processing
41. Temporal anti-aliasing: Halton-jittered projection, depth reprojection, YCoCg neighbourhood clamping, history ping-pong — camera.ludic cam_set_jitter, shaders/taa.frag
42. Optional MSAA with alpha-to-coverage and per-mip alpha sharpening — post.ludic, shaders/impostor.frag
43. HDR pipeline (RGBA16F) with NaN/inf sanitising at every stage — post.ludic, shaders/lighting.glsl sane
44. Bloom: 13-tap downsample pyramid with soft threshold, tent upsample — shaders/bloom_down.frag, shaders/bloom_up.frag
45. Auto-exposure by mean luminance with temporal adaptation — post.ludic post_measure
46. ACES filmic tonemapping with log-space contrast — shaders/tonemap.frag
47. Colour grading: white balance, lift/gain, saturation — shaders/tonemap.frag
48. Vignette, dithering, film grain — shaders/tonemap.frag, shaders/sharpen.frag
49. Luma unsharp sharpening — shaders/sharpen.frag
50. Height-based exponential fog / aerial perspective from the prefiltered sky with sun inscatter — shaders/lighting.glsl applyFog
51. Specular anti-aliasing (highlight cap) and firefly prevention — shaders/lighting.glsl
52. Fly camera with mouse look — the Maroon Lake game (git.workshopsoft.io/workshopsoft/maroon-lake)
53. Profiling switches per stage and a frame benchmark — render.ludic r3d_env_flags
Characters and moving objects
54. glTF skins: node hierarchy, joints and inverse bind matrices loaded per model, four-influence GPU skinning, poses set per node in the model's frame — skin.ludic, shaders/skin.vert, quat.ludic
55. Actors: models placed and moved per frame (skinned or rigid), lit and cast into every cascade — actor.ludic
56. The drawn terrain height on the CPU (cubic B-spline of the texels) for things that stand on it — terrain.ludic terrain_height_smooth
57. A third-person character controller with a distance-driven procedural gait (walk / run / idle), slope and water limits, and a settling orbit camera — the Maroon Lake game (git.workshopsoft.io/workshopsoft/maroon-lake)
58. A 2D overlay over the finished frame: batched rectangles, images and proportional text from a baked font atlas (tools/blender/font_build.py) — overlay.ludic, shaders/overlay.*
59. Time of day over the HDRI: a solar arc drives the sun's direction and colour, the sky's light scales toward a deep-blue starry night, a moon lights the night, the visible sky turns with the sun and its convolutions rebake when far off, the height-field shadow rebakes as the sun moves, auto-exposure is capped at night — daylight.ludic, shaders/sky.frag, shaders/lighting.glsl
60. One point light (the campfire) in the shading — shaders/lighting.glsl fireLight
61. Static collision: ground circles in a cell grid - removed; still things are bodies in ludic.physics (Jolt)
62. Cut-out and emissive actors (a flame's cards) — actor.ludic, shaders/model.frag u_emissive
63. Per-instance skin clones (a herd posed individually), per-part tints and hidden parts on actors, uniform-location caching with frustum and distance culling — skin.ludic skin_clone, actor.ludic
64. A second point / cone light (a torch or flashlight in the hand) with a reach in metres and a gentle falloff to it — daylight.ludic daylight_hand, shaders/lighting.glsl handLight
64a. Tree foliage dithered out inside arm's length of the camera, in the depth prepass — shaders/model.vert v_near, shaders/depth.frag, #define NEAR_FADE
65. Auto-exposure adapted on the GPU: a 1x1 pass eases last frame's value toward key / mean and the tonemapper samples it, so nothing is read back (a pixel-buffer read still synchronised on Apple's GL) — post.ludic post_measure, shaders/adapt.frag
66. A PPM reader with box-filtered downscale (photo thumbnails) and word-wrapped overlay text — texture.ludic tex_load_ppm, overlay.ludic ov_text_wrap
67. The overlay batches a whole frame into one upload and draws per texture range with optional scissor clips (per-change uploads stalled the driver: 40 ms -> 25 ms), plus nine-slice, rotated, line, disc and arc primitives — overlay.ludic ov_flush, ov_clip, ov_nine, ov_sub_rot, ov_line, ov_disc, ov_arc
68. Weather on the daylight: an overcast factor greys and dims the sun and the sky's light, a fog multiplier thickens the air, a lightning flash — daylight.ludic daylight_weather
69. An outline pass: an actor carries a rim width and colour, drawn as an inverted hull depth-tested against the scene, and anything that is not an actor can queue one at a transform (a scatter instance, a tile) — actor.ludic actor_draw_outlines, outline_model, shaders/outline.frag
70. Two ways to make an island out of a survey: a radius from a centre, or the sea put around the survey's own edge with a noise-wobbled coast, both scaling the existing relief into the water and compressing the last few metres of height either side of the line into beach and shallows — terrain.ludic terrain_island, terrain_coast, shaders/heightgen.frag
Against Unreal Engine 5 and RAGE (RDR2), honestly
| Technique | UE5 / RAGE | Here |
|---|---|---|
| Physically based shading, IBL, split-sum | both | implemented (1–4) |
| Cascaded shadow maps with PCF | both (UE5 also virtual shadow maps) | implemented (18–21); no virtual shadow maps |
| Temporal AA / upsampling | TSR / TAA | TAA implemented (41), no upscaling |
| Screen-space AO / GI | SSAO, SSGI, Lumen | SSGI + AO implemented (14); no Lumen-class GI, no ray tracing (not available on OpenGL 4.1) |
| Volumetric clouds and fog | both | HDRI sky with cloud shadows and analytic fog; no ray-marched clouds |
| Foliage: impostors, wind, translucency | both | implemented (30–34) |
| Nanite / virtualised geometry | UE5 | not applicable; LOD chain (37) instead |
| Terrain: height fields, layered materials, real-world data | both | implemented (22–27) |
| Water with planar reflection | RAGE planar, UE SSR | planar reflection implemented (40); no screen-space reflection |
| Post: bloom, exposure, tonemap, grading, DoF, motion blur | both | bloom/exposure/tonemap/grading implemented (44–49); no depth of field, no motion blur |
| Streaming world | both | chunk streaming of cover (35); terrain is one 8 km tile |
| Compute shaders, indirect draw, bindless | both | not available in OpenGL 4.1 core on macOS |
Where the remaining visual gap sits: photogrammetry cards for several meadow species, ray-traced or probe-based global illumination, virtual shadow maps, and volumetric clouds. Those are the techniques that separate this from the reference.