Skip to main content

RAYMARCH

Constant RAYMARCH 

Source
pub const RAYMARCH: &str = "// The raymarched SDF volume pass: three families of proxy-cube draw over one\n// authored distance field.\n//\n// A volume rasterises the back faces of its world-space bounding box and the\n// fragment sphere-traces the field inside it. The world\'s `SdfVolume` supplies\n// the field itself, spliced at SDF_BODY between the helpers that forward-declare\n// it and the entry points that call it, so this source is only complete once a\n// world is loaded.\n//\n// RAYMARCH_SURFACE, RAYMARCH_VOLUMETRIC and RAYMARCH_SHADOW select the family:\n// an opaque surface writing colour and depth, a participating medium blended\n// over the scene, and a depth-only caster marched from the light side.\n//\n// RAYMARCH_METAL / RAYMARCH_DXIL select the host\'s binding layout. These slots\n// are private between this file and the encoders: an authored shader defines\n// `map`, `shade` or `sampleVolume` and never names a binding, so unlike the main\n// pass there is no published contract pinning them.\n\n{RAYMARCH_TYPES}\n\n#ifdef RAYMARCH_METAL\n\n// Metal takes its slots from declaration order, not from `register()`: the b and\n// t spaces fold onto one buffer index there, so a `register(t0)` beside a\n// `register(b0)` reads as an overlap and the compile fills with E39001. A\n// declaration no entry point reaches is dropped but leaves its slot behind, so\n// every family below agrees on these numbers whether or not it binds them.\nConstantBuffer<RaymarchView> view_cb;\nConstantBuffer<SdfVolumeUniforms> vol_cb;\nConstantBuffer<LightUniforms> lights_cb;\nConstantBuffer<ShadowUniforms> shadow_cb;\nConstantBuffer<RaymarchShadowCascade> cascade_cb;\n\nTexture2DMS<float> main_depth;\nTexture2DArray<float> shadow_map;\nTextureCube<float4> irradiance_cube;\nTextureCube<float4> prefilter_cube;\nTexture2D<float4> scene_color;\n\nSamplerComparisonState shadow_samp;\nSamplerState cube_samp;\nSamplerState scene_samp;\n\nfloat shadow_map_cmp(float3 uv_layer, float ref)\n{\n    return shadow_map.SampleCmpLevelZero(shadow_samp, uv_layer, ref);\n}\nfloat2 shadow_map_size()\n{\n    uint w, h, e;\n    shadow_map.GetDimensions(w, h, e);\n    return float2(float(w), float(h));\n}\nfloat3 irradiance_sample(float3 d) { return irradiance_cube.Sample(cube_samp, d).rgb; }\nfloat3 prefilter_sample_lod(float3 d, float lod)\n{\n    return prefilter_cube.SampleLevel(cube_samp, d, lod).rgb;\n}\nfloat3 scene_sample(float2 uv) { return scene_color.SampleLevel(scene_samp, uv, 0.0).rgb; }\n\n// Metal is the one host that binds the main pass\'s depth, so it is the one that\n// can clip the march to the rasterised surface instead of paying for a march\n// the depth test would discard. The other two return a distance past any\n// `max_distance`, which makes the clip a no-op there.\n#define RAYMARCH_READS_SCENE_DEPTH 1\n\n// Slang lowers `Texture2DMS.Load` to an MSL `read` taking `int2`, which the\n// Metal compiler rejects because it wants `uint2` (upstream bug 1). The\n// mis-lowering only reaches a metallib, so emitting MSL text hides it; this is\n// the one construct in the pass that has to be spelled per target.\nfloat ms_depth_read(Texture2DMS<float> t, uint2 px)\n{\n    __target_switch\n    {\n    case metal: __intrinsic_asm \"($0).read($1, 0).x\";\n    default: return t.Load(int2(px), 0);\n    }\n}\n\nfloat rasterised_distance(float2 px, float3 cam, float4 sv_pos)\n{\n    float depth_ndc = ms_depth_read(main_depth, uint2(px));\n    float2 ndc_xy = (sv_pos.xy / view_cb.viewport) * 2.0 - 1.0;\n    // Metal clip space is y-down after the projection flip the engine applies,\n    // so re-mirror Y to match the inv_vp the CPU built from the unflipped one.\n    ndc_xy.y = -ndc_xy.y;\n    float4 world = mul(view_cb.inv_vp, float4(ndc_xy, depth_ndc, 1.0));\n    world /= max(world.w, 1e-6);\n    return length(world.xyz - cam);\n}\n\n#elif defined(RAYMARCH_DXIL)\n\n// Root parameters 0..3 are CBVs as root descriptors, 4 a four-SRV table, 5 a\n// three-sampler table; the shadow family\'s signature is 0..3 plus b4 as 32-bit\n// root constants. A `register()` without a `[[vk::binding]]` warns (E39029), so\n// every declaration carries both even though only one is read per target.\n[[vk::binding(0, 0)]] ConstantBuffer<RaymarchView> view_cb : register(b0);\n[[vk::binding(0, 1)]] ConstantBuffer<SdfVolumeUniforms> vol_cb : register(b1);\n[[vk::binding(1, 0)]] ConstantBuffer<LightUniforms> lights_cb : register(b2);\n[[vk::binding(2, 0)]] ConstantBuffer<ShadowUniforms> shadow_cb : register(b3);\n[[vk::binding(7, 0)]] ConstantBuffer<RaymarchShadowCascade> cascade_cb : register(b4);\n\n[[vk::binding(3, 0)]] Texture2DArray<float> shadow_map : register(t0);\n[[vk::binding(4, 0)]] TextureCube<float4> irradiance_cube : register(t1);\n[[vk::binding(5, 0)]] TextureCube<float4> prefilter_cube : register(t2);\n[[vk::binding(6, 0)]] Texture2D<float4> scene_color : register(t3);\n\n[[vk::binding(8, 0)]] SamplerComparisonState shadow_samp : register(s0);\n[[vk::binding(9, 0)]] SamplerState cube_samp : register(s1);\n[[vk::binding(10, 0)]] SamplerState scene_samp : register(s2);\n\nfloat shadow_map_cmp(float3 uv_layer, float ref)\n{\n    return shadow_map.SampleCmpLevelZero(shadow_samp, uv_layer, ref);\n}\nfloat2 shadow_map_size()\n{\n    uint w, h, e;\n    shadow_map.GetDimensions(w, h, e);\n    return float2(float(w), float(h));\n}\nfloat3 irradiance_sample(float3 d) { return irradiance_cube.Sample(cube_samp, d).rgb; }\nfloat3 prefilter_sample_lod(float3 d, float lod)\n{\n    return prefilter_cube.SampleLevel(cube_samp, d, lod).rgb;\n}\nfloat3 scene_sample(float2 uv) { return scene_color.SampleLevel(scene_samp, uv, 0.0).rgb; }\n\n// No scene depth is bound here: the hardware depth test against the writable\n// DSV is what composites this pass against rasterised geometry.\nfloat rasterised_distance(float2 px, float3 cam, float4 sv_pos) { return 1e30; }\n\n#else\n\n// Set 0 is the scene-wide raymarch set, set 1 the per-volume block, and the\n// cascade index rides a push constant. The cubes and the shadow array are\n// combined image samplers, which is what this host allocates.\n[[vk::binding(0, 0)]] ConstantBuffer<RaymarchView> view_cb;\n[[vk::binding(0, 1)]] ConstantBuffer<SdfVolumeUniforms> vol_cb;\n[[vk::binding(1, 0)]] ConstantBuffer<LightUniforms> lights_cb;\n[[vk::binding(2, 0)]] ConstantBuffer<ShadowUniforms> shadow_cb;\n[[vk::push_constant]] ConstantBuffer<RaymarchShadowCascade> cascade_cb;\n\n[[vk::binding(3, 0)]] Sampler2DArrayShadow shadow_map;\n[[vk::binding(4, 0)]] SamplerCube<float4> irradiance_cube;\n[[vk::binding(5, 0)]] SamplerCube<float4> prefilter_cube;\n[[vk::binding(6, 0)]] Sampler2D<float4> scene_color;\n\nfloat shadow_map_cmp(float3 uv_layer, float ref)\n{\n    return shadow_map.SampleCmp(uv_layer, ref);\n}\nfloat2 shadow_map_size()\n{\n    uint w, h, e;\n    shadow_map.GetDimensions(w, h, e);\n    return float2(float(w), float(h));\n}\nfloat3 irradiance_sample(float3 d) { return irradiance_cube.SampleLevel(d, 0.0).rgb; }\nfloat3 prefilter_sample_lod(float3 d, float lod) { return prefilter_cube.SampleLevel(d, lod).rgb; }\nfloat3 scene_sample(float2 uv) { return scene_color.SampleLevel(uv, 0.0).rgb; }\n\nfloat rasterised_distance(float2 px, float3 cam, float4 sv_pos) { return 1e30; }\n\n#endif\n\n#define VIEW view_cb\n#define VOL vol_cb\n#define LIGHTS lights_cb\n#define SHADOW_UNI shadow_cb\n\n// A world direction in the environment cubemaps\' own frame, so the ambient fill\n// turns with the sky exactly as the main pass\'s does.\n#define RM_SKY_DIR(d) float3(dot(VIEW.sky_rot[0].xyz, (d)), \\\n                             dot(VIEW.sky_rot[1].xyz, (d)), \\\n                             dot(VIEW.sky_rot[2].xyz, (d)))\n\n{RAYMARCH_COMMON}\n\n// The world\'s own distance field.\n{SDF_BODY}\n\n// The proxy geometry is a unit cube at +/-1 in the engine\'s 56-byte vertex\n// layout; only position is fetched. Scaling by the volume\'s extent and offsetting\n// by its centre lands it on the bounding box, and the encoders cull front faces\n// so each pixel inside the box takes exactly one fragment whether the camera is\n// outside the box or in it.\nstruct RaymarchVertexIn\n{\n    float3 pos : POSITION;\n};\n\nstruct RaymarchVertexOut\n{\n    // A pixel shader that writes SV_DepthLessEqual without running at sample\n    // frequency must declare its position input centroid; DXIL validation\n    // rejects the plain one. `noperspective centroid` is the other legal\n    // spelling and Slang cannot express it (bug 13), but the position is\n    // already non-perspective, so `centroid` alone is what validates.\n    centroid float4 sv_pos : SV_Position;\n    float3 world_pos : WORLDPOS;\n};\n\nfloat3 proxy_world_pos(float3 pos) { return pos * VOL.extent.xyz + VOL.centre.xyz; }\n\n#if defined(RAYMARCH_SURFACE) || defined(RAYMARCH_VOLUMETRIC)\n\nRaymarchVertexOut raymarch_proxy(RaymarchVertexIn v)\n{\n    float3 wp = proxy_world_pos(v.pos);\n    RaymarchVertexOut o;\n    o.sv_pos = mul(VIEW.vp, float4(wp, 1.0));\n    o.world_pos = wp;\n    return o;\n}\n\n#endif\n\n#ifdef RAYMARCH_SURFACE\n\n[shader(\"vertex\")]\nRaymarchVertexOut raymarch_vertex(RaymarchVertexIn v) { return raymarch_proxy(v); }\n\nstruct RaymarchFragOut\n{\n    float4 color : SV_Target;\n    // Writing a nearer depth keeps early-Z while letting the hit composite\n    // against rasterised geometry, and feeds the raymarched surface\'s depth to\n    // the passes downstream that sample it.\n    float depth : SV_DepthLessEqual;\n};\n\n[shader(\"fragment\")]\nRaymarchFragOut raymarch_fragment(RaymarchVertexOut input)\n{\n    float3 cam = VIEW.cam_pos.xyz;\n    float3 ray_dir = normalize(input.world_pos - cam);\n\n    // The proxy\'s back faces rasterised, so `world_pos` is on the far side of\n    // the box. The slab test gives entry and exit both, and handles a camera\n    // inside the box uniformly.\n    float3 box_min = VOL.centre.xyz - VOL.extent.xyz;\n    float3 box_max = VOL.centre.xyz + VOL.extent.xyz;\n    float2 box_t = rayBox(cam, ray_dir, box_min, box_max);\n    if (box_t.y < max(box_t.x, 0.0)) discard;\n    float t_enter = max(box_t.x, 0.001);\n\n    float t_raster = rasterised_distance(input.sv_pos.xy, cam, input.sv_pos);\n    float t_max = min(box_t.y, min(t_raster, VOL.max_distance));\n    if (t_enter >= t_max) discard;\n\n    RayHit hit = coneRaymarch(cam, ray_dir, t_enter, t_max, VIEW.time);\n    if (!hit.hit) discard;\n\n    float3 hit_pos = cam + ray_dir * hit.t;\n    float3 normal = sdfNormal(hit_pos, VOL.params, VIEW.time, 0.001);\n    float2 frag_uv = input.sv_pos.xy / VIEW.viewport;\n    SdfSurface surf = shade(hit_pos, normal, VOL.params, VIEW.time, frag_uv);\n\n    float3 view_dir = -ray_dir;\n    float3 color = shadeAmbientIbl(surf, normal, view_dir);\n    if (LIGHTS.num_dir > 0)\n    {\n        float shadow_factor = 1.0;\n        if (VOL.receive_shadows != 0)\n        {\n            // `hit.t` is distance along the view ray, close enough to view-space\n            // depth for cascade selection without carrying the view matrix.\n            shadow_factor = sampleSunShadow(hit_pos, hit.t, input.sv_pos.xy);\n        }\n        color += shadePbrSun(surf, normal, view_dir, LIGHTS.dir[0], shadow_factor);\n    }\n    // Whatever the authored shader wants to show through, already attenuated.\n    color += surf.transmitted;\n\n    // Reprojecting through the same matrix the proxy rasterised with puts the\n    // hit in the rasterised geometry\'s depth space exactly.\n    float4 hit_clip = mul(VIEW.vp, float4(hit_pos, 1.0));\n    RaymarchFragOut o;\n    o.color = float4(color, 1.0);\n    o.depth = hit_clip.z / max(hit_clip.w, 1e-6);\n    return o;\n}\n\n#endif\n\n#ifdef RAYMARCH_VOLUMETRIC\n\n[shader(\"vertex\")]\nRaymarchVertexOut raymarch_volumetric_vertex(RaymarchVertexIn v) { return raymarch_proxy(v); }\n\n// Linear march from box entry to exit accumulating Beer-Lambert transmittance\n// front to back, adding in-scattered sun light and emission per slab. The result\n// is alpha-blended over the scene and writes no depth.\n//\n// The medium is not self-shadowed and the march is not clamped to scene depth,\n// so a volume is sized not to intersect geometry it should render behind.\n[shader(\"fragment\")]\nfloat4 raymarch_volumetric_fragment(RaymarchVertexOut input) : SV_Target\n{\n    float3 cam = VIEW.cam_pos.xyz;\n    float3 ray_dir = normalize(input.world_pos - cam);\n\n    float3 box_min = VOL.centre.xyz - VOL.extent.xyz;\n    float3 box_max = VOL.centre.xyz + VOL.extent.xyz;\n    float2 box_t = rayBox(cam, ray_dir, box_min, box_max);\n    if (box_t.y < max(box_t.x, 0.0)) discard;\n    float t_enter = max(box_t.x, 0.001);\n    float t_exit = min(box_t.y, VOL.max_distance);\n    if (t_enter >= t_exit) discard;\n\n    uint step_count = uint(max(VOL.max_steps, 1));\n    float step_size = (t_exit - t_enter) / float(step_count);\n\n    float3 sun_radiance = float3(0.0, 0.0, 0.0);\n    if (LIGHTS.num_dir > 0)\n    {\n        sun_radiance = LIGHTS.dir[0].col.xyz * LIGHTS.dir[0].dir_i.w;\n    }\n\n    float transmittance = 1.0;\n    float3 luminance = float3(0.0, 0.0, 0.0);\n    for (uint i = 0u; i < step_count; ++i)\n    {\n        float t = t_enter + (float(i) + 0.5) * step_size;\n        VolumeSample vs = sampleVolume(cam + ray_dir * t, VOL.params, VIEW.time);\n        if (vs.density <= 0.0) continue;\n\n        float step_T = exp(-vs.density * step_size);\n        // Energy in-scattered inside this slab is (1 - step_T) times the\n        // radiance: single-scatter from the sun, plus self-emission.\n        float3 step_radiance = sun_radiance * vs.scattering + vs.emission;\n        luminance += transmittance * step_radiance * (1.0 - step_T);\n        transmittance *= step_T;\n        if (transmittance < 0.005) break;\n    }\n\n    float alpha = 1.0 - transmittance;\n    if (alpha < 0.005) discard;\n    return float4(luminance, alpha);\n}\n\n#endif\n\n#ifdef RAYMARCH_SHADOW\n\n// The caster draws once per cascade into that cascade\'s slice, projected through\n// the light rather than the camera. The slice\'s depth test composites it with\n// the rasterised casters already there: the nearer occluder wins per texel.\n[shader(\"vertex\")]\nRaymarchVertexOut raymarch_shadow_vertex(RaymarchVertexIn v)\n{\n    float3 wp = proxy_world_pos(v.pos);\n    RaymarchVertexOut o;\n    o.sv_pos = mul(SHADOW_UNI.light_vps[cascade_cb.cascade_idx], float4(wp, 1.0));\n    o.world_pos = wp;\n    return o;\n}\n\n[shader(\"fragment\")]\nfloat raymarch_shadow_fragment(RaymarchVertexOut input) : SV_DepthLessEqual\n{\n    // `dir_i.xyz` is L, surface to light, which is what `shadePbrSun` reads from\n    // the same field; incoming light travels along -L, so the shadow ray does.\n    float3 ray_dir = -normalize(LIGHTS.dir[0].dir_i.xyz);\n\n    // `world_pos` is on the box face farthest from the light, the encoder having\n    // culled front faces. Stepping back by the bounding-sphere diameter lets the\n    // slab test pick up the true front-face entry from outside the box.\n    float3 origin = input.world_pos - ray_dir * (length(VOL.extent.xyz) * 2.5);\n    float3 box_min = VOL.centre.xyz - VOL.extent.xyz;\n    float3 box_max = VOL.centre.xyz + VOL.extent.xyz;\n    float2 box_t = rayBox(origin, ray_dir, box_min, box_max);\n    if (box_t.y < max(box_t.x, 0.0)) discard;\n    float t_enter = max(box_t.x, 0.001);\n    float t_max = min(box_t.y, VOL.max_distance);\n    if (t_enter >= t_max) discard;\n\n    RayHit hit = coneRaymarch(origin, ray_dir, t_enter, t_max, VIEW.time);\n    if (!hit.hit) discard;\n\n    // Reprojecting through the same cascade matrix the vertex rasterised with\n    // shares the rasterised casters\' depth space in this slice. The march is\n    // bounded by the box exit, so the hit is never behind the back face and the\n    // SV_DepthLessEqual contract holds.\n    float3 hit_pos = origin + ray_dir * hit.t;\n    float4 hit_clip = mul(SHADOW_UNI.light_vps[cascade_cb.cascade_idx], float4(hit_pos, 1.0));\n    return hit_clip.z / max(hit_clip.w, 1e-6);\n}\n\n#endif\n";
Expand description

raymarch.slang.