109 lines
4.5 KiB
WebGPU Shading Language
109 lines
4.5 KiB
WebGPU Shading Language
// WebGPU raygen. Camera state comes from the host every frame via a
|
|
// storage buffer bound at @group(2) @binding(2); main.cpp drives that
|
|
// from WASD + mouse-delta through Crafter::Input.
|
|
//
|
|
// The shading + shadow trace all happens here because WGSL forbids
|
|
// recursive function call graphs — closesthit_main can't call traceRay
|
|
// (that would loop closesthit → traceRay → runClosestHit → closesthit).
|
|
// Raygen is the entry point and not called by anyone, so it can call
|
|
// traceRay twice (once primary, once shadow) without forming a cycle.
|
|
|
|
struct Camera {
|
|
origin: vec3<f32>,
|
|
pad0: f32,
|
|
right: vec3<f32>,
|
|
tanHalf: f32,
|
|
up: vec3<f32>,
|
|
aspect: f32,
|
|
forward: vec3<f32>,
|
|
pad1: f32,
|
|
};
|
|
@group(2) @binding(2) var<storage, read> camera : Camera;
|
|
|
|
// Sun coming through Sponza's open roof. Y is up; this points "down and
|
|
// slightly along +X" so the light grazes the colonnades on one side.
|
|
const SUN_DIR_TO_LIGHT: vec3<f32> = vec3<f32>(-0.35, 1.00, -0.20);
|
|
const SUN_COLOR: vec3<f32> = vec3<f32>( 1.10, 1.00, 0.85);
|
|
const AMBIENT_COLOR: vec3<f32> = vec3<f32>( 0.18, 0.20, 0.28);
|
|
|
|
fn raygen_main(gid: vec3<u32>) {
|
|
if (gid.x >= hdr.surfaceW || gid.y >= hdr.surfaceH) { return; }
|
|
|
|
let pixel = vec2<f32>(f32(gid.x), f32(gid.y));
|
|
let resolution = vec2<f32>(f32(hdr.surfaceW), f32(hdr.surfaceH));
|
|
let uv = (pixel + vec2<f32>(0.5)) / resolution;
|
|
let ndc = uv * 2.0 - vec2<f32>(1.0);
|
|
|
|
// Pinhole camera reconstructed from the host basis. ndc.x runs left-
|
|
// to-right across the screen → +right; ndc.y is top-down so we
|
|
// negate before applying +up.
|
|
let direction = normalize(
|
|
camera.right * (ndc.x * camera.aspect * camera.tanHalf) +
|
|
camera.up * (-ndc.y * camera.tanHalf) +
|
|
camera.forward);
|
|
|
|
// ── Primary ray ────────────────────────────────────────────────────
|
|
var payload: Payload;
|
|
payload.color = vec3<f32>(0.0);
|
|
payload.shadowRay = 0u;
|
|
payload.hit = 0u;
|
|
|
|
traceRay(
|
|
0u, 0u, 0xFFu,
|
|
0u, 0u, 0u,
|
|
camera.origin, 0.001,
|
|
direction, 10000.0,
|
|
&payload);
|
|
|
|
var finalColor: vec3<f32>;
|
|
if (payload.hit == 1u) {
|
|
// Closesthit filled albedo/worldPos/worldNormal. Two-sided
|
|
// shading: flip the normal toward the camera if we hit the back
|
|
// face — Sponza's curtains in particular have inconsistent
|
|
// winding, and without this half the surface would go black.
|
|
let albedo = payload.color;
|
|
let nFacing = select(-payload.worldNormal,
|
|
payload.worldNormal,
|
|
dot(payload.worldNormal, direction) < 0.0);
|
|
let lightDir = normalize(SUN_DIR_TO_LIGHT);
|
|
let nDotL = max(0.0, dot(nFacing, lightDir));
|
|
|
|
// ── Shadow ray ────────────────────────────────────────────────
|
|
// Only worth tracing if the surface faces the sun at all.
|
|
var visibility = 0.0;
|
|
if (nDotL > 0.0) {
|
|
// Normal-offset bias on Sponza's units (~3700 wide atrium)
|
|
// is hefty; 0.5 keeps the shadow ray clear of the originating
|
|
// triangle without producing visible "floating" shadows.
|
|
let shadowOrigin = payload.worldPos + nFacing * 0.5;
|
|
|
|
var shadowPayload: Payload;
|
|
shadowPayload.color = vec3<f32>(0.0); // default: blocked
|
|
shadowPayload.shadowRay = 1u;
|
|
shadowPayload.hit = 0u;
|
|
traceRay(
|
|
0u,
|
|
RT_FLAG_SKIP_CLOSEST_HIT | RT_FLAG_TERMINATE_ON_FIRST_HIT,
|
|
0xFFu,
|
|
0u, 0u, 0u,
|
|
shadowOrigin, 0.001,
|
|
lightDir, 10000.0,
|
|
&shadowPayload);
|
|
visibility = shadowPayload.color.x;
|
|
}
|
|
|
|
let lit = AMBIENT_COLOR + SUN_COLOR * (nDotL * visibility);
|
|
finalColor = albedo * lit;
|
|
} else {
|
|
// Sky color was filled by miss_main.
|
|
finalColor = payload.color;
|
|
}
|
|
|
|
// Reinhard tonemap + gamma 2.2 so sun-lit albedos don't clip and
|
|
// shadow detail stays readable.
|
|
let mapped = finalColor / (finalColor + vec3<f32>(1.0));
|
|
let gamma = pow(mapped, vec3<f32>(1.0 / 2.2));
|
|
textureStore(outImage,
|
|
vec2<i32>(i32(gid.x), i32(gid.y)),
|
|
vec4<f32>(gamma, 1.0));
|
|
}
|