git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commitbefb275c37f67a6180d6fa5cff40645b79f0e775
parent6c6bcabb8e
authorLucas Galante <lsgalante12@gmail.com>
date2026-09-29 17:16
feat: the traced image is unlit, as the raster pass draws it

A ray that lands on the image takes the image's colour as it is and the
path ends there, where it was a lit surface with the image as its albedo
and a white page traced grey. Its pixels are marked for the denoiser as
the sky's are: a colour that is the image's own has no noise to take out,
and smoothing took its fine print first.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

 src/vk/rt.rs          | 13 ++++++++-----
 src/vk/rt_common.wgsl | 23 ++++++++++++++++++-----
 2 files changed, 26 insertions(+), 10 deletions(-)

diff --git a/src/vk/rt.rs b/src/vk/rt.rs
index bdb163c..a6dd2af 100644
--- a/src/vk/rt.rs
+++ b/src/vk/rt.rs
@@ -39,9 +39,11 @@ pub struct RtMaterial {
     pub emission: [f32; 3],
 }
 
-/// An image standing in the traced scene: a quad whose surface is the
-/// image's colour, lit as any other surface is, and which lets a ray through
-/// where the image is clear. The tracer adds the quad to the scene itself.
+/// An image standing in the traced scene: a quad that shows the image's
+/// colour as it is — unlit, as the raster pass's `SceneImage` draws it — and
+/// lets a ray through where the image is clear. A path ends on it, so to
+/// the rest of the scene it is a light of its own colour. The tracer adds
+/// the quad to the scene itself.
 ///
 /// The image is one the 2D pass already holds (an id from `upload_rgba`),
 /// so a picture shown in the raster viewport costs the tracer nothing more.
@@ -2685,8 +2687,9 @@ mod tests {
         };
         // The quad spans x in -1..1 of a view about 2.9 wide at z = 0: the
         // pane's columns 10 to 54, and rows 21 to 43.
-        let (r, g, _) = at(16, 32);
-        assert!(r > g + 40, "the image's red half is not red: r={r} g={g}");
+        // Unlit: the texel's own colour, whatever the sky is doing.
+        let (r, g, b) = at(16, 32);
+        assert!(r >= 250 && g <= 5 && b <= 5, "the image's red half is not its red: {r} {g} {b}");
         let (r, g, _) = at(48, 32);
         assert!(g > r + 40, "the image's clear half hides the wall: r={r} g={g}");
         let (r, g, _) = at(32, 6);
diff --git a/src/vk/rt_common.wgsl b/src/vk/rt_common.wgsl
index 2b153da..57f9431 100644
--- a/src/vk/rt_common.wgsl
+++ b/src/vk/rt_common.wgsl
@@ -162,11 +162,14 @@ fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
             let tri = tris[hit.tri];
             let mat = materials[bitcast<u32>(tri.p0.w)];
             let at = ro + rd * hit.t;
-            var albedo = mat.albedo.rgb;
+            let albedo = mat.albedo.rgb;
             if mat.albedo.w > 0.5 {
-                // The image: its colour is the surface's, and where it is
-                // clear the ray goes on as if nothing were there — by
-                // chance, in proportion, which over the samples is the
+                // The image: a picture carries its own light. What a ray
+                // finds there is the image's colour as it is, neither lit
+                // by the sky nor shadowed by the scene — the colour the
+                // raster pass draws — and the path ends on it. Where the
+                // image is clear the ray goes on as if nothing were there:
+                // by chance, in proportion, which over the samples is the
                 // image's own alpha.
                 let rel = at - params.img_origin.xyz;
                 let u = params.img_u.xyz;
@@ -188,7 +191,17 @@ fn cs_main(@builtin(global_invocation_id) gid: vec3<u32>) {
                     ro = at + rd * (1e-4 * max(1.0, hit.t));
                     continue;
                 }
-                albedo = texel.rgb;
+                if primary {
+                    // Marked as the sky is, with no depth: the colour is
+                    // the image's own and has no noise to take out, so the
+                    // denoiser passes it through and mixes it into nothing
+                    // — smoothed, the image's fine print is the first
+                    // thing to go.
+                    features[2u * idx] = vec4<f32>(0.0, 0.0, 0.0, 1e30);
+                    features[2u * idx + 1u] = vec4<f32>(texel.rgb, 0.0);
+                }
+                radiance = radiance + throughput * texel.rgb;
+                break;
             }
             radiance = radiance + throughput * mat.emission.rgb;
             var n = normalize(cross(tri.p1.xyz - tri.p0.xyz, tri.p2.xyz - tri.p0.xyz));