pub const RAYMARCH_COMMON: &str = "// The body of the raymarched SDF volume pass, spliced at a shader\'s\n// RAYMARCH_COMMON marker: the distance-field primitive library an authored\n// `map` composes, the cone-stepping marcher, and the PBR / IBL / shadow\n// helpers an authored `shade` and the templates call.\n//\n// Nothing here spells a binding. Every resource is reached through an accessor\n// the including file defines ahead of the splice, which is what lets the three\n// hosts\' binding models differ without the shading differing.\n\n// Distance-field primitives, after https://iquilezles.org/articles/distfunctions/\n// Kept small and well known; an authored `map` composes them.\n\nfloat sdSphere(float3 p, float r) { return length(p) - r; }\n\nfloat sdBox(float3 p, float3 b)\n{\n float3 q = abs(p) - b;\n return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);\n}\n\nfloat sdRoundBox(float3 p, float3 b, float r)\n{\n float3 q = abs(p) - b + r;\n return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0) - r;\n}\n\nfloat sdTorus(float3 p, float2 t)\n{\n float2 q = float2(length(p.xz) - t.x, p.y);\n return length(q) - t.y;\n}\n\nfloat sdCapsule(float3 p, float3 a, float3 b, float r)\n{\n float3 pa = p - a;\n float3 ba = b - a;\n float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-6), 0.0, 1.0);\n return length(pa - ba * h) - r;\n}\n\nfloat sdPlane(float3 p, float3 n, float h) { return dot(p, n) + h; }\n\nfloat opSmoothUnion(float a, float b, float k)\n{\n float h = clamp(0.5 + 0.5 * (b - a) / max(k, 1e-6), 0.0, 1.0);\n return lerp(b, a, h) - k * h * (1.0 - h);\n}\n\nfloat opSmoothSubtraction(float d1, float d2, float k)\n{\n float h = clamp(0.5 - 0.5 * (d2 + d1) / max(k, 1e-6), 0.0, 1.0);\n return lerp(d2, -d1, h) + k * h * (1.0 - h);\n}\n\nfloat opSmoothIntersection(float a, float b, float k)\n{\n float h = clamp(0.5 - 0.5 * (b - a) / max(k, 1e-6), 0.0, 1.0);\n return lerp(b, a, h) + k * h * (1.0 - h);\n}\n\n// Slab ray-box intersection, returning (t_enter, t_exit). A miss leaves\n// t_exit < max(0, t_enter).\nfloat2 rayBox(float3 ro, float3 rd, float3 box_min, float3 box_max)\n{\n float3 inv = 1.0 / rd;\n float3 t0 = (box_min - ro) * inv;\n float3 t1 = (box_max - ro) * inv;\n float3 tmin = min(t0, t1);\n float3 tmax = max(t0, t1);\n return float2(max(max(tmin.x, tmin.y), tmin.z), min(min(tmax.x, tmax.y), tmax.z));\n}\n\n// Four-tap central-difference gradient, normalised. `eps` has to be small\n// enough for the linearisation to hold and large enough that the field does not\n// return zero on both sides; 0.001 world units suits the library above.\nfloat3 sdfNormal(float3 p, SdfParams params, float time, float eps)\n{\n float3 ex = float3(eps, 0.0, 0.0);\n float3 ey = float3(0.0, eps, 0.0);\n float3 ez = float3(0.0, 0.0, eps);\n return normalize(float3(\n map(p + ex, params, time) - map(p - ex, params, time),\n map(p + ey, params, time) - map(p - ey, params, time),\n map(p + ez, params, time) - map(p - ez, params, time)));\n}\n\n// Cone-stepping sphere trace. Marches from `t_start` along `dir` until the\n// field returns under the surface epsilon, `t` passes `t_max`, or the volume\'s\n// step cap fires. `cone_ratio` is the reciprocal Lipschitz constant, 1 for the\n// primitives above.\nRayHit coneRaymarch(float3 origin, float3 dir, float t_start, float t_max, float time)\n{\n RayHit r;\n r.t = t_start;\n r.hit = false;\n r.steps = 0;\n float t = t_start;\n int cap = min(VOL.max_steps, 256);\n float ratio = max(VOL.cone_ratio, 0.01);\n const float surface_eps = 0.001;\n for (int i = 0; i < cap; ++i)\n {\n if (t >= t_max) break;\n float3 p = origin + dir * t;\n float d = map(p, VOL.params, time);\n r.steps = i + 1;\n if (abs(d) < surface_eps)\n {\n r.t = t;\n r.hit = true;\n return r;\n }\n t += max(abs(d) * ratio, 0.001);\n }\n return r;\n}\n\n// Sample the pre-raymarch scene through a normal-perturbed screen UV. The\n// perturbation is the world normal\'s XZ tilt scaled by `strength` (0.02 to 0.10\n// suits a water surface). The result is linear-light RGB; attenuate it in the\n// authored shader before writing it into `SdfSurface.transmitted`.\nfloat3 sampleSceneRefracted(float2 frag_uv, float3 normal, float strength)\n{\n return scene_sample(clamp(frag_uv + normal.xz * strength, 0.0, 1.0));\n}\n\n// Cook-Torrance GGX with Smith G and Schlick F, the same math the forward main\n// pass runs, so a raymarched surface and a rasterised one agree under one sun.\n// `shadow` is 1 for fully lit.\nfloat3 shadePbrSun(SdfSurface s, float3 normal, float3 viewDir, DirLight sun, float shadow)\n{\n float3 L = normalize(sun.dir_i.xyz);\n float3 H = normalize(viewDir + L);\n float NdotL = max(0.0, dot(normal, L));\n float NdotV = max(1e-3, dot(normal, viewDir));\n float NdotH = max(0.0, dot(normal, H));\n float VdotH = max(0.0, dot(viewDir, H));\n\n float a = max(s.roughness * s.roughness, 1e-3);\n float a2 = a * a;\n float denom = NdotH * NdotH * (a2 - 1.0) + 1.0;\n float D = a2 / (3.14159265 * denom * denom);\n\n float k = (s.roughness + 1.0) * (s.roughness + 1.0) / 8.0;\n float G = (NdotL / (NdotL * (1.0 - k) + k)) * (NdotV / (NdotV * (1.0 - k) + k));\n\n float3 F0 = lerp(float3(0.04), s.albedo, s.metallic);\n float3 F = F0 + (1.0 - F0) * pow(1.0 - VdotH, 5.0);\n\n float3 spec = (D * G * F) / max(4.0 * NdotL * NdotV, 1e-3);\n float3 diff = (1.0 - F) * (1.0 - s.metallic) * s.albedo / 3.14159265;\n float3 light = sun.col.xyz * sun.dir_i.w * shadow;\n return (diff + spec) * light * NdotL;\n}\n\n// Hemispheric ambient fallback, used when no EnvironmentMap is bound.\nfloat3 shadeAmbient(SdfSurface s, float3 normal)\n{\n float3 sky = float3(0.45, 0.52, 0.62);\n float3 ground = float3(0.07, 0.06, 0.05);\n float t = clamp(0.5 + 0.5 * normal.y, 0.0, 1.0);\n return s.albedo * lerp(ground, sky, t) * 0.35 + s.emissive;\n}\n\n{SHADOW_BIAS}\n\nfloat raymarchHashRotation(float2 p)\n{\n return frac(sin(dot(p, float2(12.9898, 78.233))) * 43758.5453) * 6.2831853;\n}\n\n// Cascade-shadow PCF, mirroring `shadow_factor_cascaded` in\n// `main_shading.slang` so a raymarched surface takes the shadow a rasterised\n// one at the same point would.\nfloat sampleSunShadow(float3 world_pos, float view_depth, float2 screen_xy)\n{\n int cascade = 4;\n if (view_depth < SHADOW_UNI.cascade_splits[0]) cascade = 0;\n else if (view_depth < SHADOW_UNI.cascade_splits[1]) cascade = 1;\n else if (view_depth < SHADOW_UNI.cascade_splits[2]) cascade = 2;\n else if (view_depth < SHADOW_UNI.cascade_splits[3]) cascade = 3;\n if (cascade >= int(SHADOW_UNI.active_cascades)) return 1.0;\n\n float4 lc = mul(SHADOW_UNI.light_vps[cascade], float4(world_pos, 1.0));\n float3 ndc = lc.xyz / max(lc.w, 1e-6);\n float2 uv = float2(ndc.x * 0.5 + 0.5, -ndc.y * 0.5 + 0.5);\n if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 || ndc.z < 0.0 || ndc.z > 1.0)\n {\n return 1.0;\n }\n\n float ref = ndc.z - cascade_depth_bias(cascade);\n\n // A per-pixel rotation breaks the 5x5 kernel\'s banding.\n float angle = raymarchHashRotation(screen_xy);\n float ca = cos(angle);\n float sa = sin(angle);\n float2 tex_size = 1.0 / shadow_map_size();\n\n float sum = 0.0;\n const int RADIUS = 2;\n const float SAMPLES = float((2 * RADIUS + 1) * (2 * RADIUS + 1));\n for (int dy = -RADIUS; dy <= RADIUS; dy++)\n {\n for (int dx = -RADIUS; dx <= RADIUS; dx++)\n {\n float2 off = float2(float(dx), float(dy));\n float2 rot = float2(off.x * ca - off.y * sa, off.x * sa + off.y * ca);\n sum += shadow_map_cmp(float3(uv + rot * tex_size, float(cascade)), ref);\n }\n }\n return sum / SAMPLES;\n}\n\nfloat2 raymarchEnvBrdfApprox(float NdV, float rough)\n{\n const float4 c0 = float4(-1.0, -0.0275, -0.572, 0.022);\n const float4 c1 = float4(1.0, 0.0425, 1.040, -0.040);\n float4 r = rough * c0 + c1;\n float a004 = min(r.x * r.x, exp2(-9.28 * NdV)) * r.x + r.y;\n return float2(-1.04, 1.04) * a004 + r.zw;\n}\n\nfloat3 raymarchFresnelSchlick(float cosTheta, float3 F0)\n{\n return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);\n}\n\n// Image-based ambient, mirroring the main pass\'s ambient term: the irradiance\n// cube for diffuse and the prefilter cube at a roughness-selected mip for\n// specular, combined through the Karis split-sum. Falls back to the hemispheric\n// fill when no EnvironmentMap is bound.\nfloat3 shadeAmbientIbl(SdfSurface s, float3 normal, float3 view_dir)\n{\n if (VIEW.prefilter_mip_count <= 0.5)\n {\n return shadeAmbient(s, normal);\n }\n float NdV = max(dot(normal, view_dir), 0.0);\n float3 F0 = lerp(float3(0.04), s.albedo, s.metallic);\n float3 F_ibl = raymarchFresnelSchlick(NdV, F0);\n float3 kd_ibl = (1.0 - F_ibl) * (1.0 - s.metallic);\n\n float3 irradiance = irradiance_sample(RM_SKY_DIR(normal));\n float3 diffuse_ibl = kd_ibl * s.albedo * irradiance / 3.14159265;\n\n float3 R = reflect(-view_dir, normal);\n float lod = s.roughness * (VIEW.prefilter_mip_count - 1.0);\n float3 prefiltered = prefilter_sample_lod(RM_SKY_DIR(R), lod);\n float2 ab = raymarchEnvBrdfApprox(NdV, s.roughness);\n\n return diffuse_ibl + prefiltered * (F0 * ab.x + ab.y) + s.emissive;\n}\n";Expand description
raymarch_common.slang.