GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
feat(vk): SceneDraw::prelit — a fill whose colours already carry their lighting
The scene pass's fragment shader flat-shades every fill from derivative
normals. A host that wants smooth shading bakes it into vertex colours —
the light is world-fixed, so per-vertex lighting from interpolated normals
is exact and the vertex format needs no normal — and marks the draw prelit
so the shader does not shade it a second time. The flag takes one of the
uniform block's two pad floats; the block stays 112 bytes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
src/vk/scene.rs | 14 ++++++++++++--
src/vk/scene3d.wgsl | 6 +++++-
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/src/vk/scene.rs b/src/vk/scene.rs
index 24ff899..f10d525 100644
--- a/src/vk/scene.rs
+++ b/src/vk/scene.rs
@@ -57,6 +57,13 @@ pub struct SceneDraw {
/// contour-following wires) while the fill's slope is exactly the
/// quantity needed.
pub wire_base_width: f32,
+ /// FILL draws only: the vertex colours already carry their lighting, so
+ /// the fragment shader skips its derivative-normal flat shading and
+ /// draws them as they are. A host that wants SMOOTH shading bakes it —
+ /// the light is fixed in world space (see `scene3d.wgsl`), so lighting
+ /// per vertex from interpolated normals is exact for a static light,
+ /// and the vertex format needs no normal. False for an ordinary draw.
+ pub prelit: bool,
}
/// shader_3d.wgsl's uniform block.
@@ -73,7 +80,9 @@ struct SceneUniforms {
/// normal flat shading, whose screen-space derivatives are degenerate on
/// line fragments (along-axis only) and light the wires with noise.
is_wire: f32,
- _pad: [f32; 2],
+ /// 1.0 on `SceneDraw::prelit` draws: the flat shading is skipped too.
+ prelit: f32,
+ _pad: [f32; 1],
}
const UNIFORM_SIZE: vk::DeviceSize = std::mem::size_of::<SceneUniforms>() as vk::DeviceSize;
@@ -736,7 +745,8 @@ impl SceneStage {
wire_tint: draw.wire_tint,
opacity: draw.opacity,
is_wire: if draw.wireframe { 1.0 } else { 0.0 },
- _pad: [0.0; 2],
+ prelit: if draw.prelit { 1.0 } else { 0.0 },
+ _pad: [0.0; 1],
};
let offset = (self.uniform_stride as usize) * i;
mapped[offset..offset + UNIFORM_SIZE as usize]
diff --git a/src/vk/scene3d.wgsl b/src/vk/scene3d.wgsl
index 438295a..14a54ce 100644
--- a/src/vk/scene3d.wgsl
+++ b/src/vk/scene3d.wgsl
@@ -15,6 +15,10 @@ struct Uniforms {
// screen-space derivatives of a line fragment are along-axis only, so
// the "normal" is noise and speckles the wires.
is_wire: f32,
+ // 1 on a draw whose vertex colours are already lit (the host baked
+ // smooth shading from vertex normals against the same world light):
+ // skip the flat shading below so it is not applied twice.
+ prelit: f32,
}
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
@@ -93,7 +97,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
// self-similar surface (a UV sphere's lattice, especially under a
// wireframe overlay whose fill occludes the back wires) reads as glued to
// the camera without it. Two-sided so unculled back faces stay sane.
- if (in.lit > 0.5 && uniforms.is_wire < 0.5) {
+ if (in.lit > 0.5 && uniforms.is_wire < 0.5 && uniforms.prelit < 0.5) {
let n = normalize(cross(dpdx(in.world), dpdy(in.world)));
// A strongly AZIMUTHAL light, wrap-shaded. A near-vertical light (or a
// two-sided |dot|) yields a latitude-dominated / 180-degree-symmetric