Skip to main content

WATER

Constant WATER 

Source
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.