cl_godot: A Custom Godot Fork
A personal fork of Godot 4.8-dev. It adds hardware ray queries to the Metal backend and builds on them: MegaLights-style stochastic lighting, ray-traced GI with a surface cache, OpenColorIO colour management, and the editor tools to go with it. Apple silicon only, not upstream, and every feature below is in the public repo.
What the fork adds
Ray tracing on Metal
Upstream Godot has the ray-tracing API stubbed out on Metal. The fork implements it.
-
Hardware ray queries in the Metal driver
Acceleration structures, ray query bindings and SPIR-V Cross plumbing on Apple silicon. Inline queries only, as Metal offers them.
-
Whole-scene TLAS
Built from every instance, not the frustum cull, so off-screen geometry casts shadows and bounce. Skinned, blend-shaped and multimesh instances included.
-
GPU timestamps on macOS
--gpu-profileprints per-pass GPU time fromMTLCounterSampleBuffer. Upstream reports nothing on Metal.
Direct lighting
Shadow maps replaced by rays, and a light count that no longer sets the frame cost.
-
Ray-traced sun shadows
Replace the directional shadow map. Soft penumbras from the sun's angular size, temporal accumulation, alpha-tested casters.
-
Stochastic direct lighting
MegaLights-style: every omni, spot and area light in one compute pass with reservoir sampling and one shadow ray per sample. Hundreds of shadowed lights at a flat cost.
-
Area lights, end to end
Shadows, stochastic sampling, fog and GI all sample the rectangle. Sampling is uniform over the rect; the biased quadrant warp was measured and dropped.
-
SVGF-style denoiser
Variance-driven a-trous spatial filter with temporal accumulation over demodulated diffuse and specular.
-
Ray-traced volumetric fog shadows
Fog froxels trace their own shadow rays against the TLAS instead of reading shadow maps.
-
Screen-space contact traces
Short screen traces close contact shadows the rays miss at the bias distance.
-
Light size everywhere
A light's size drives the sun lobe, the stochastic pass and the fog, so soft shadows come from the light, not a blur radius.
Global illumination
Traced bounce with a surface cache behind it, in the spirit of Lumen.
-
Ray-traced GI final gather
Replaces the SDFGI resolve and SSIL with a traced cosine gather, depth-validated history, VoxelGI as fallback.
-
SDFGI probes traced by hardware
The probe rays go through the ray query path instead of the SDF march. Interior leak measurably lower.
-
Surface cache
Six orthographic captures per instance into albedo, normal, emission and depth atlases, relit on the GPU. Cards read each other for multi-bounce.
-
Deferred hit shading
Where a hit has no card, the material's own fragment code runs in compute at the hit, one indirect dispatch per material.
-
Traced reflections
Mirror through rough, resolved over a neighbourhood before the temporal pass. Stock SSR is skipped where the traced result covers the roughness.
-
Directional irradiance and specular occlusion
An irradiance moment adds surface detail; a traced occlusion term replaces the bent-normal approximation.
-
Reflection probe re-fit
Captured probe radiance is re-fitted to the frame's irradiance so probes agree with the traced diffuse.
-
Translucency lighting volume
A froxel volume of shadowed direct light and bounce for transparent surfaces, replacing per-fragment light loops.
-
Dynamic lights estimated apart
A light that moves gets its own bounce history, so it neither smears the static cache nor counts twice at the hand-over.
Colour management
OpenColorIO in the renderer, the way a compositing pipeline expects it.
-
OpenColorIO module
OCIO 2.4.2 vendored as
modules/ociowith anOCIOServersingleton and a config chosen in project settings. -
ACEScg working space
Colour textures are converted into the working space at import; data textures are left alone. Authored colours are converted, not just linearised.
-
OCIO tonemapper
TONE_MAPPER_OCIOrenders through a real display, view and look set on the Environment, chosen separately, HDR output included. -
Luminance in the working space
The ray-traced passes measure and clamp luminance in the working colour space rather than assuming sRGB primaries.
Editor and workflow
Small things that make lights easier to place and the passes easier to see.
-
Visible area lights
area_visible_to_camerashows an AreaLight3D's emitting rectangle to the camera and follows size changes. Clicking the rect selects the light. -
Light range gizmos
Omni, spot and area lights show a spherical range shape instead of the selection cube, with an eye toggle per light to hide it.
-
Gizmos out of the screen traces
Editor gizmos no longer appear in SSR or screen-space shadow traces.
-
Ray Tracing settings category
Every setting under Rendering > Ray Tracing is read each frame, so tuning updates the running viewport with no restart. Per-light shadow settings drive the traced passes.
-
Debug draws
Viewport modes for the stochastic light, visibility and analytic terms, the cluster's area lights, and the area-light atlas. A GI tier counter reports where each ray's radiance came from.
-
Editor convergence
The editor keeps repainting until the surface cache reports settled, instead of freezing a half-converged frame.
What is still open
- Diffuse GI restarts about 8% too bright after a mouse flick; the structural fix is unbuilt.
- Particles are not in the TLAS; multimesh sub-instances carry no cards.
- 500-light target missed: 40.0 ms against 24 at 1080p.
- The translucency volume serves one view, holds no area lights, and leaks a froxel of shadow.
- Metal and Apple silicon only. No Vulkan path, nothing proposed upstream, runs not bit-deterministic.
Where the record lives
github.com/cpt-cabbage/godot, branch main. The commit log and MEGALIGHTS_PLAN.md are the full record; this page condenses them.
Written with Claude Code driving the code; I set the architecture, ran the measurements, and chose what to keep. Every commit carries the co-author trailer.
Open to EU and UK supervision roles from October 2026.
Contact Me →