pub const WATER: &str = "// Water surface pass: single source for every backend.\n//\n// The second producer of the engine\'s transparent pass (`PassId::Transparent`,\n// after the SSR resolve and before TAA), alongside `glass.slang`. Where a glass\n// pane is a flat quad the vertex stage only projects, a water surface is a\n// tessellated XZ grid (see geometry::water_grid) the vertex stage displaces by a\n// sum of Gerstner waves. The fragment stage takes the world-space normal from\n// the analytic derivatives of that same sum at its own rest position, so a\n// coarse grid shades as finely as a dense one, then composites, in order:\n//\n// * Refraction - the pre-transparent scene snapshot at a normal-perturbed\n// screen UV, so the seabed bends under the waves.\n// * Tint - shallow to deep by the water-column thickness the main depth\n// gives, an exponential falloff over `depth_falloff`.\n// * Foam - a soft band where the seabed is just below the surface.\n// * Reflection - see below, plus a GGX specular lobe for the scene\'s sun off\n// the same wave normal.\n// * Fresnel - Schlick with a water-vs-air F0 of 0.02, shaped by\n// `fresnel_power`.\n//\n// One fragment entry per compile, selected by a define. Only the reflection\n// differs between them, exactly as in glass.slang:\n//\n// default - the sharp planar reflection when this surface\n// has a slot (screen UV perturbed by the wave\n// normal, so the mirror ripples), else the\n// box-projected reflection-probe set, else the sky\n// prefilter cube, else a hand-tuned sky gradient.\n// WATER_RT - that same planar reflection where the surface has\n// a slot, else a per-pixel reflection ray against\n// the scene acceleration structure (the shared\n// RT_TRACE fragment) off the wave surface point, so\n// `R` follows the waves; the miss falls back to the\n// same probe / sky chain.\n// WATER_RT with RT_TEXTURED - the same trace, with reflected hits taking their\n// albedo / normal / emissive maps from the\n// bindless pool.\n//\n// The mirror outranks the trace for water because a water surface IS a plane:\n// one mirrored scene render resolves it exactly, where a trace off the\n// per-fragment wave normal is hypersensitive at grazing angles and drops to the\n// probe / sky chain wherever it misses. A glass pane does the opposite (see\n// glass.slang), which is why only this file reads `planar` on the RT path.\n//\n// USE_MSAA is a HOST difference rather than a target one: Vulkan reads the\n// multisampled main depth while Metal and DirectX read the resolved copy.\n//\n// Every binding here is deliberately the one glass.slang declares at the same\n// slot, because the two share a transparent pass: on DirectX that lets one root\n// signature serve both pipelines, on Vulkan one set of descriptor set layouts,\n// and on Metal the encoder binds the shared reflection inputs once for the whole\n// pass. A slot may not move on one side alone.\n\n#ifndef POOL_SIZE\n#define POOL_SIZE 1024\n#endif\n#ifndef MAX_PROBES\n#define MAX_PROBES 8\n#endif\n#ifndef USE_MSAA\n#define USE_MSAA 0\n#endif\n\n// Waves summed per surface. Mirrors `MAX_WATER_WAVES` in concinnity-asset and\n// `WATER_MAX_WAVES` in `core::render`\'s uniforms; the array length is part of\n// the `WaterParams` layout, so it is a constant here rather than a define.\nstatic const uint MAX_WATER_WAVES = 4u;\n\n{PROBE_TYPES}\n\n#ifdef WATER_RT\n{RT_TYPES}\n#endif\n\n// Per-frame view shared by every transparent draw. Layout matches\n// `TransparentView` / the TransparentViewBlock UBO (240 B). The same\n// declaration glass.slang carries -- one host block feeds both.\nstruct TransparentView\n{\n float4x4 vp; // world -> clip (jittered when TAA is on)\n float4x4 inv_vp; // clip -> world\n float4 camera_pos; // xyz: world-space camera\n float2 viewport; // attachment dimensions in pixels\n float time; // seconds since startup, driving the wave phase\n // Mips in the sky prefilter cube; 0 = no EnvironmentMap bound, and the\n // reflection falls through to the sky gradient.\n float prefilter_mip_count;\n // Rows of the rotation from world space into the sky cube\'s baked frame;\n // identity when the sky does not turn.\n float4 sky_rot[3];\n // Direction toward the scene\'s sun (the first directional light) and that\n // light\'s colour times its intensity; both zero when the world declares no\n // directional light, which the water glint reads as no sun.\n float4 sun_dir;\n float4 sun_color;\n};\n\n// A world direction in the sky cube\'s own frame; every sky tap goes through it.\n#define 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// One Gerstner wave, packed into two float4 lanes so the CPU `[f32; 4]` pair is\n// byte-identical on every target. Matches `WaterWaveGpu` (32 B).\nstruct WaterWave\n{\n float4 dir_amp_wave; // [direction.x, direction.y, amplitude, wavelength]\n float4 speed_steep_pad; // [speed, steepness, _, _]\n};\n\n// Per-surface tunables. Layout matches `WaterParams` / the WaterParamsBlock UBO\n// (224 B); the vec3 fields are float4 so MSL\'s 16-byte constant-buffer float3\n// cannot desynchronise them from the CPU struct.\nstruct WaterParams\n{\n float4 centre; // world-space surface centre\n float4 deep_colour; // linear RGB at full column depth\n float4 shallow_colour; // linear RGB at the shore\n float depth_falloff;\n float foam_width;\n float foam_intensity;\n float fresnel_power;\n float roughness;\n float refraction_strength;\n uint wave_count;\n // Aligns `waves` to 16, which its float4 lanes require.\n float _pad;\n WaterWave waves[MAX_WATER_WAVES];\n // [strength, distortion, _, _]. `strength > 0.5` selects the sharp planar\n // reflection over the trace / probe / sky cube; `distortion` scales the\n // wave-normal perturbation of its screen-UV lookup. Set on both paths: the\n // host raises it whenever this surface holds a mirror slot and the planar\n // pass ran, and the RT entry honours it too.\n float4 planar;\n};\n\n// ---- Resource bindings ----\n\n#ifdef DXIL_ABI\n\n// Pinned to the root signatures in `directx/transparent.rs`, which glass shares;\n// b1 is visible to every stage there because the water vertex stage reads the\n// wave table out of it.\nConstantBuffer<TransparentView> view : register(b0);\nConstantBuffer<WaterParams> params : register(b1);\nTexture2D<float4> scene_color : register(t0);\n#if USE_MSAA\nTexture2DMS<float> scene_depth : register(t1);\n#else\nTexture2D<float> scene_depth : register(t1);\n#endif\nTextureCube<float4> prefilter_cube : register(t2);\nTexture2D<float4> planar_reflection : register(t3);\nSamplerState post_samp : register(s0);\nSamplerState cube_sampler : register(s2);\n\nfloat4 scene_sample(float2 uv) { return scene_color.Sample(post_samp, uv); }\nfloat4 planar_sample(float2 uv) { return planar_reflection.Sample(post_samp, uv); }\nfloat3 prefilter_level(float3 dir, float lod)\n{\n return prefilter_cube.SampleLevel(cube_sampler, SKY_DIR(dir), lod).rgb;\n}\n\n#else\n\n// Metal buffer(5) / buffer(6): the shared per-frame view and the per-surface\n// params, both written with setBytes by the transparent encoder.\n[[vk::binding(0, 0)]] ConstantBuffer<TransparentView> view : register(b5);\n[[vk::binding(0, 1)]] ConstantBuffer<WaterParams> params : register(b6);\n\n// Declaration order is the Metal texture index, and these are the transparent\n// pass\'s shared slots: the scene snapshot at 0, the resolved depth at 1, the sky\n// prefilter cube at 2, the probe cubes at 3..2+MAX_PROBES, and this surface\'s\n// planar resolve at 11.\n[[vk::binding(1, 0)]] Sampler2D<float4> scene_color;\n#if USE_MSAA\n[[vk::binding(2, 0)]] Texture2DMS<float> scene_depth;\n#else\n[[vk::binding(2, 0)]] Texture2D<float> scene_depth;\n#endif\n[[vk::binding(5, 2)]] SamplerCube<float4> prefilter_cube;\n\nfloat4 scene_sample(float2 uv) { return scene_color.Sample(uv); }\nfloat3 prefilter_level(float3 dir, float lod) { return prefilter_cube.SampleLevel(SKY_DIR(dir), lod).rgb; }\n\n#endif\n\n// The reflection-probe set + cube array, from the forward global set: a pond\n// reflects the same local scene capture the forward IBL specular and the SSR /\n// RT miss fallback use, rather than only the foreign sky cube.\n#ifdef DXIL_ABI\nConstantBuffer<ProbeSet> probe_set : register(b4);\n// The array spans MAX_PROBES registers from its base, so the RT variant moves it\n// clear of the ray-tracing SRVs at t4..t10 rather than starting at t7.\n#ifdef WATER_RT\nTextureCube<float4> probe_cubes[MAX_PROBES] : register(t20);\n#else\nTextureCube<float4> probe_cubes[MAX_PROBES] : register(t7);\n#endif\n\nfloat3 probe_cube_sample_bias(uint i, float3 dir, float lod)\n{\n return probe_cubes[i].SampleBias(cube_sampler, dir, lod).rgb;\n}\n#else\n[[vk::binding(7, 2)]] ConstantBuffer<ProbeSet> probe_set : register(b7);\n#ifdef METAL_ABI\n// Metal reaches the cubes through an argument buffer: a resource array at\n// global scope emits with no [[texture(n)]], and the compiler then places it\n// at whatever slot happens to be unused.\nstruct ProbeCubes\n{\n TextureCube<float4> probe_cubes[MAX_PROBES];\n};\nParameterBlock<ProbeCubes> probe_cube_set : register(b11);\nSamplerState probe_cube_sampler;\n\nfloat3 probe_cube_sample_bias(uint i, float3 dir, float lod)\n{\n return probe_cube_set.probe_cubes[i].SampleBias(probe_cube_sampler, dir, lod).rgb;\n}\n#else\n[[vk::binding(8, 2)]] SamplerCube<float4> probe_cubes[MAX_PROBES];\n\nfloat3 probe_cube_sample_bias(uint i, float3 dir, float lod)\n{\n return probe_cubes[i].SampleBias(dir, lod).rgb;\n}\n#endif\n#endif\n#define PROBE_SET probe_set\n\n#ifndef DXIL_ABI\n// This surface\'s planar reflection target, bound per surface and sampled at the\n// fragment\'s screen UV when `planar.x > 0.5`. Declared after the probe array so\n// it lands on Metal\'s texture(11); a surface with no planar slot binds a valid\n// stand-in and never samples it. Read by both entries: the RT one takes the\n// mirror over its own trace wherever a slot exists.\n[[vk::binding(1, 1)]] Sampler2D<float4> planar_reflection;\n\nfloat4 planar_sample(float2 uv) { return planar_reflection.Sample(uv); }\n#endif\n\n#ifdef WATER_RT\n// The ray-tracing scene resources, at the slots glass.slang uses so the inputs\n// the transparent encoder binds once are valid for both. On Metal they ride the\n// pass\'s otherwise-free fragment buffers (0..4 and 8..10, since 5/6/7 are the\n// view, the params and the probe set); on Vulkan they are a set of their own,\n// past the view / params / global sets; on DirectX they follow the registers the\n// pass already occupies.\n#ifdef DXIL_ABI\n\nConstantBuffer<RtParams> rt_params : register(b5);\nRaytracingAccelerationStructure scene_tlas : register(t4);\nByteAddressBuffer verts : register(t5);\nByteAddressBuffer indices : register(t6);\nByteAddressBuffer sverts : register(t8);\nByteAddressBuffer sidx : register(t9);\nStructuredBuffer<RtGeomEntry> geom : register(t10);\n\nfloat vert_float(uint i) { return asfloat(verts.Load(i * 4u)); }\nfloat svert_float(uint i) { return asfloat(sverts.Load(i * 4u)); }\nuint index_at(uint o) { return indices.Load(o * 4u); }\nuint skinned_index_word(uint w) { return sidx.Load(w * 4u); }\n\n#else\n\n[[vk::binding(0, 3)]] ConstantBuffer<RtParams> rt_params : register(b0);\n[[vk::binding(1, 3)]] RaytracingAccelerationStructure scene_tlas : register(t4);\n[[vk::binding(2, 3)]] StructuredBuffer<RtGeomEntry> geom : register(t3);\n[[vk::binding(3, 3)]] StructuredBuffer<float> verts : register(t1);\n[[vk::binding(4, 3)]] StructuredBuffer<uint> indices : register(t2);\n[[vk::binding(5, 3)]] StructuredBuffer<float> sverts : register(t8);\n[[vk::binding(6, 3)]] StructuredBuffer<uint> sidx : register(t9);\n\nfloat vert_float(uint i) { return verts[i]; }\nfloat svert_float(uint i) { return sverts[i]; }\nuint index_at(uint o) { return indices[o]; }\nuint skinned_index_word(uint w) { return sidx[w]; }\n\n#endif\n\n#ifdef RT_TEXTURED\n// The bindless pool. Metal keeps it at buffer(10): buffer(7), where the main\n// pass puts it, is the probe set in the transparent pass.\nuint nonuniform_index(uint i)\n{\n __target_switch\n {\n case metal:\n return i;\n default:\n return NonUniformResourceIndex(i);\n }\n}\n\n#if defined(METAL_ABI)\nstruct TexturePool\n{\n Texture2D<float4> tex_pool[POOL_SIZE];\n};\nParameterBlock<TexturePool> pool;\nSamplerState pool_sampler;\n\nfloat3 pool_sample_level0(uint idx, float2 uv)\n{\n return pool.tex_pool[nonuniform_index(idx)].SampleLevel(pool_sampler, uv, 0.0).rgb;\n}\n#elif defined(DXIL_ABI)\nTexture2D<float4> tex_pool[] : register(t0, space1);\nSamplerState pool_sampler : register(s1);\n\nfloat3 pool_sample_level0(uint idx, float2 uv)\n{\n return tex_pool[nonuniform_index(idx)].SampleLevel(pool_sampler, uv, 0.0).rgb;\n}\n#else\n[[vk::binding(1, 4)]] Sampler2D<float4> tex_pool[POOL_SIZE];\n\nfloat3 pool_sample_level0(uint idx, float2 uv)\n{\n return tex_pool[nonuniform_index(idx)].SampleLevel(uv, 0.0).rgb;\n}\n#endif\n#endif\n#endif\n\n{PROBE_COMMON}\n\n#ifdef WATER_RT\n{RT_TRACE}\n#endif\n\n// ---- Stage interface ----\n\nstruct WaterVertexIn\n{\n [[vk::location(0)]] float3 pos : POSITION;\n};\n\nstruct WaterVertexOut\n{\n [[vk::location(0)]] float3 world_pos : TEXCOORD0;\n // The flat-rest XZ the vertex was displaced from; the fragment evaluates\n // the wave normal there itself, so the grid\'s density sets only the\n // silhouette of the displacement, never the shading.\n [[vk::location(1)]] float2 rest_xz : TEXCOORD1;\n float4 position : SV_Position;\n};\n\n// The displaced surface point and its normal for one flat-rest XZ position.\nstruct WaterPoint\n{\n float3 pos;\n float3 normal;\n};\n\n// Sum up to MAX_WATER_WAVES Gerstner waves at a flat-rest XZ position. Each wave\n// is a horizontal pinch plus a vertical sinusoid; the analytic partials against\n// (x, z) give the world-space normal at the displaced point. Reference: NVIDIA,\n// \"Effective Water Simulation from Physical Models\", GPU Gems 1.\nWaterPoint gerstner_displace(float2 rest_xz, float time)\n{\n float3 displaced = float3(rest_xz.x, 0.0, rest_xz.y);\n float3 binormal = float3(1.0, 0.0, 0.0); // dP/dx\n float3 tangent = float3(0.0, 0.0, 1.0); // dP/dz\n\n uint count = min(params.wave_count, MAX_WATER_WAVES);\n for (uint i = 0u; i < count; i++)\n {\n float2 dir = normalize(params.waves[i].dir_amp_wave.xy);\n float amp = params.waves[i].dir_amp_wave.z;\n float wavelen = max(params.waves[i].dir_amp_wave.w, 1e-3);\n float speed = params.waves[i].speed_steep_pad.x;\n float steep = saturate(params.waves[i].speed_steep_pad.y);\n\n float k = 2.0 * 3.14159265358979 / wavelen;\n float phase = k * dot(dir, rest_xz) - speed * k * time;\n float c = cos(phase);\n float s = sin(phase);\n\n // Steepness normalised by wave count so summed crests cannot pinch past\n // self-intersection.\n float q = steep / (k * amp * max(float(count), 1.0));\n\n displaced.x += q * amp * dir.x * c;\n displaced.z += q * amp * dir.y * c;\n displaced.y += amp * s;\n\n float wa = k * amp;\n binormal.x += -q * dir.x * dir.x * wa * s;\n binormal.y += dir.x * wa * c;\n binormal.z += -q * dir.x * dir.y * wa * s;\n\n tangent.x += -q * dir.x * dir.y * wa * s;\n tangent.y += dir.y * wa * c;\n tangent.z += -q * dir.y * dir.y * wa * s;\n }\n\n WaterPoint p;\n p.pos = displaced;\n p.normal = normalize(cross(tangent, binormal));\n return p;\n}\n\n[shader(\"vertex\")]\nWaterVertexOut water_vertex(WaterVertexIn v)\n{\n // The grid is built centred on the origin in the XZ plane, so the surface\n // centre rides in through the params rather than the vertex buffer.\n float2 rest_xz = float2(v.pos.x + params.centre.x, v.pos.z + params.centre.z);\n WaterPoint p = gerstner_displace(rest_xz, view.time);\n p.pos.y += params.centre.y;\n\n WaterVertexOut o;\n o.world_pos = p.pos;\n o.rest_xz = rest_xz;\n o.position = mul(view.vp, float4(p.pos, 1.0));\n return o;\n}\n\n// Depth stored at this pixel by the main pass, for the manual occlusion test and\n// the water-column thickness.\nfloat water_scene_depth(int2 pixel)\n{\n#if USE_MSAA\n return scene_depth.Load(pixel, 0);\n#else\n return scene_depth.Load(int3(pixel, 0));\n#endif\n}\n\n// A screen-space pixel coordinate clamped into the attachment.\nint2 water_clamp_pixel(float2 pixel_xy)\n{\n int2 last = int2(max(view.viewport, float2(1.0))) - int2(1, 1);\n return clamp(int2(pixel_xy), int2(0, 0), last);\n}\n\n// Linear camera distance to the scene point a screen NDC position and its stored\n// non-linear depth describe. Gives the water column thickness when differenced\n// against the distance to the surface itself.\nfloat water_scene_distance(float2 ndc_xy, float depth01)\n{\n float4 world = mul(view.inv_vp, float4(ndc_xy, depth01, 1.0));\n return distance(world.xyz / world.w, view.camera_pos.xyz);\n}\n\n// The surface at this fragment: the wave normal, the fragment\'s screen UV, and\n// everything below the waterline -- the refracted scene, tinted by column depth\n// and brightened to foam where the seabed is close.\nstruct WaterSurfacePoint\n{\n float3 normal;\n float2 frag_uv;\n float3 below;\n};\n\nWaterSurfacePoint water_surface(WaterVertexOut i)\n{\n WaterSurfacePoint s;\n s.normal = gerstner_displace(i.rest_xz, view.time).normal;\n\n float2 vp_dim = max(view.viewport, float2(1.0));\n s.frag_uv = i.position.xy / vp_dim;\n\n // Refraction: perturb the screen lookup by the wave normal\'s XZ so the\n // seabed bends under the waves.\n float2 refract_uv = clamp(s.frag_uv + s.normal.xz * params.refraction_strength,\n float2(0.001), float2(0.999));\n float3 refracted = scene_sample(refract_uv).rgb;\n\n // Read the main depth at the REFRACTED pixel, so the thickness matches the\n // texel just sampled: a refraction that bends into a foreground edge would\n // otherwise be tinted as if that edge were underwater.\n float scene_depth01 = water_scene_depth(water_clamp_pixel(refract_uv * vp_dim));\n float2 ndc_xy = float2(s.frag_uv.x * 2.0 - 1.0, -(s.frag_uv.y * 2.0 - 1.0));\n float scene_dist = water_scene_distance(ndc_xy, scene_depth01);\n float water_dist = distance(i.world_pos, view.camera_pos.xyz);\n float water_depth = max(scene_dist - water_dist, 0.0);\n\n // Tint: exponential shallow to deep blend over `depth_falloff` metres.\n float depth_t = 1.0 - exp(-water_depth / max(params.depth_falloff, 1e-3));\n float3 tinted = lerp(params.shallow_colour.rgb, params.deep_colour.rgb, depth_t);\n float3 below = lerp(refracted * params.shallow_colour.rgb, tinted, depth_t);\n\n // Foam: a soft band where the seabed is just below the surface, which is\n // both the shoreline and any intersection line with standing geometry.\n float foam_t = saturate(1.0 - water_depth / max(params.foam_width, 1e-3));\n s.below = lerp(below, float3(1.0), foam_t * foam_t * params.foam_intensity);\n return s;\n}\n\n// The mirror render for this surface, at the fragment\'s own screen UV perturbed\n// by the wave normal. The planar render mirrors the scene across the surface\'s\n// rest plane, so it lands exactly under the reflector; perturbing that lookup is\n// what turns a flat mirror back into rippling water.\nfloat3 water_planar_reflection(WaterSurfacePoint s)\n{\n float2 uv = clamp(s.frag_uv + s.normal.xz * params.planar.y,\n float2(0.001), float2(0.999));\n return planar_sample(uv).rgb;\n}\n\n// The prefilter mip a surface of this roughness reflects at. Blurrier water\n// reads a coarser cube level. The mip count is per-frame state, so it comes from\n// the pass\'s view block rather than the per-surface params.\nfloat water_reflection_mip()\n{\n return saturate(params.roughness) * max(view.prefilter_mip_count - 1.0, 0.0);\n}\n\n// The reflection a surface with no sharp source falls back to: the local\n// box-projected probe set where a probe actually covers this point, else the sky\n// prefilter cube, else a hand-tuned vertical sky gradient (bluer overhead, paler\n// at the horizon) so an environment-less world still reads as water rather than\n// as flat tinted glass.\n//\n// The coverage test matters more here than anywhere else: a pool routinely\n// stretches past every probe box in the world, and the probe set\'s own\n// out-of-box fallback would hand the whole surface one foreign capture.\nfloat3 water_environment(float3 world_pos, float3 r)\n{\n float mip = water_reflection_mip();\n if (probe_set.count > 0u && probe_set_covers(world_pos))\n {\n return probe_set_specular(world_pos, r, mip);\n }\n if (view.prefilter_mip_count > 0.5)\n {\n return prefilter_level(r, mip);\n }\n float horizon = saturate(r.y * 0.5 + 0.5);\n return lerp(float3(0.55, 0.62, 0.7), float3(0.25, 0.45, 0.7), horizon);\n}\n\n// The sun\'s specular lobe off this fragment\'s wave normal: GGX with a\n// height-correlated Smith visibility term and the water-vs-air F0, driven by the\n// first directional light the view block carries. A high sun draws a compact\n// disc; a low one spreads the same lobe into a path running toward the camera,\n// since the wave slopes that mirror it span more of the surface. Zero when the\n// world declares no directional light, whose `sun_color` is zero.\nfloat3 water_sun_glint(float3 normal, float3 view_dir)\n{\n float n_dot_l = dot(normal, view.sun_dir.xyz);\n if (n_dot_l <= 0.0)\n {\n return float3(0.0);\n }\n float3 h = normalize(view.sun_dir.xyz + view_dir);\n float n_dot_v = max(dot(normal, view_dir), 1e-4);\n float n_dot_h = saturate(dot(normal, h));\n float v_dot_h = saturate(dot(view_dir, h));\n\n float alpha = max(params.roughness, 0.02);\n alpha *= alpha;\n float a2 = alpha * alpha;\n float denom = n_dot_h * n_dot_h * (a2 - 1.0) + 1.0;\n float d = a2 / max(3.14159265358979 * denom * denom, 1e-6);\n\n float lambda_v = n_dot_l * sqrt(n_dot_v * n_dot_v * (1.0 - a2) + a2);\n float lambda_l = n_dot_v * sqrt(n_dot_l * n_dot_l * (1.0 - a2) + a2);\n float vis = 0.5 / max(lambda_v + lambda_l, 1e-6);\n\n float f = 0.02 + 0.98 * pow(1.0 - v_dot_h, 5.0);\n return view.sun_color.rgb * (d * vis * f * n_dot_l);\n}\n\n// Schlick Fresnel mix of the reflection over everything below the waterline.\n// F0 = 0.02 is the water-vs-air value; `fresnel_power` shapes the falloff, so a\n// low power keeps the reflection strong head-on instead of only at grazing.\n// The sun\'s glint is added after that mix: its lobe carries its own Fresnel\n// term, which is what strengthens it toward grazing angles, so weighting it by\n// the surface Fresnel as well would count that twice.\n// Alpha 1: water fully covers what it drew over, and the pipeline\'s straight\n// alpha blend leaves the composited colour as-is.\nfloat4 water_resolve(WaterSurfacePoint s, float3 view_dir, float3 reflection)\n{\n float n_dot_v = saturate(dot(s.normal, view_dir));\n float fresnel = 0.02 + 0.98 * pow(1.0 - n_dot_v, max(params.fresnel_power, 1e-3));\n float3 colour = lerp(s.below, reflection, fresnel) + water_sun_glint(s.normal, view_dir);\n return float4(colour, 1.0);\n}\n\n// True where nearer opaque geometry occludes the surface. The transparent pass\n// binds no depth attachment, so the test is manual; every backend rasterised\n// this depth under the same viewport convention the main pass used, so the\n// fragment position lines up with the stored texel.\nbool water_occluded(WaterVertexOut i)\n{\n return water_scene_depth(water_clamp_pixel(i.position.xy)) < i.position.z;\n}\n\n#ifdef WATER_RT\n\n[shader(\"fragment\")]\nfloat4 water_rt_fragment(WaterVertexOut i) : SV_Target\n{\n if (water_occluded(i))\n {\n discard;\n }\n float3 view_dir = normalize(view.camera_pos.xyz - i.world_pos);\n WaterSurfacePoint s = water_surface(i);\n\n float3 reflection;\n if (params.planar.x > 0.5)\n {\n reflection = water_planar_reflection(s);\n }\n // No mirror plane for this surface, so trace instead. The per-fragment\n // Gerstner normal makes `R` vary across the surface, so the traced\n // reflection follows the waves rather than mirroring one flat plane.\n else\n {\n float3 r = reflect(-view_dir, s.normal);\n if (!rt_trace_reflection(i.world_pos + s.normal * 0.02, r,\n view.prefilter_mip_count > 0.5,\n view.prefilter_mip_count - 1.0, reflection))\n {\n reflection = water_environment(i.world_pos, r);\n }\n }\n return water_resolve(s, view_dir, reflection);\n}\n\n#else\n\n[shader(\"fragment\")]\nfloat4 water_fragment(WaterVertexOut i) : SV_Target\n{\n if (water_occluded(i))\n {\n discard;\n }\n float3 view_dir = normalize(view.camera_pos.xyz - i.world_pos);\n WaterSurfacePoint s = water_surface(i);\n\n float3 reflection;\n if (params.planar.x > 0.5)\n {\n reflection = water_planar_reflection(s);\n }\n else\n {\n reflection = water_environment(i.world_pos, reflect(-view_dir, s.normal));\n }\n return water_resolve(s, view_dir, reflection);\n}\n\n#endif\n";Expand description
water.slang.