pub const GLASS_MESH: &str = "// See-through glass mesh pass: single source for every backend.\n//\n// The third producer of the engine\'s transparent pass (`PassId::Transparent`,\n// after the SSR resolve and before TAA), alongside `glass.slang` and\n// `water.slang`. Where a glass pane is a flat pre-baked world-space quad, this\n// draws an IMPORTED mesh whose `Material` is flagged `see_through`: the geometry\n// comes from the shared scene vertex / index buffers in LOCAL space, so the\n// vertex stage applies the per-draw model matrix and the fragment shades off the\n// interpolated per-vertex world normal, which is what lets a curved glass facade\n// reflect correctly across its surface.\n//\n// The pass is ray-traced only, by design: what makes the mesh see-through rather\n// than the opaque low-roughness glass of Layer 1 is a real per-pixel reflection\n// ray, so there is no probe-only variant to fall back to. When RT is off the\n// host leaves these meshes in the opaque pass instead of drawing them here.\n// Two fragment entries, differing only in where a reflected hit\'s surface\n// parameters come from:\n//\n// default - the reflected hit takes its flat per-object material tint.\n// RT_TEXTURED - the same trace, with reflected hits taking their albedo /\n// normal / emissive maps from the bindless pool.\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 and water.slang declare\n// at the same slot, because the three share a transparent pass: on DirectX the\n// RT root signature serves all of them, on Vulkan one set of descriptor set\n// layouts, and on Metal the encoder binds the shared reflection inputs once for\n// the whole 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{PROBE_TYPES}\n{RT_TYPES}\n\n// Per-frame view shared by every transparent draw. Layout matches\n// `TransparentView` (240 B); identical to glass.slang\'s copy.\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\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// Per-mesh tunables. Layout matches `GlassMeshParams` (96 B); `model` is first\n// so its 16-byte alignment is satisfied at offset 0.\nstruct GlassMeshParams\n{\n float4x4 model; // local -> world\n float4 tint; // colour multiplied into the refracted scene\n float opacity;\n float refraction_strength;\n float fresnel_power;\n // The mesh\'s own copy of the sky prefilter mip count, so the ray-miss\n // fallback does not depend on which view block is bound. 0 = no\n // EnvironmentMap, and the reflection keeps the white rim.\n float prefilter_mip_count;\n};\n\n// ---- Resource bindings ----\n\n#ifdef DXIL_ABI\n\n// Pinned to the RT root signature in `directx/transparent.rs`, which the pane\n// and water producers share; b1 is visible to every stage there, and this\n// vertex stage reads the model matrix out of it.\nConstantBuffer<TransparentView> view : register(b0);\nConstantBuffer<GlassMeshParams> 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);\nSamplerState post_samp : register(s0);\nSamplerState cube_sampler : register(s2);\n\nfloat4 scene_sample(float2 uv) { return scene_color.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-mesh\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<GlassMeshParams> 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, and the probe cubes at 3..2+MAX_PROBES.\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 glass\n// mesh falls back to the same local scene capture the forward IBL specular and\n// the SSR / 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 it starts clear of the\n// ray-tracing SRVs at t4..t10. Always the RT layout here: this pass has no\n// probe-only variant.\nTextureCube<float4> probe_cubes[MAX_PROBES] : register(t20);\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// A glass mesh never samples a planar reflection -- it is curved, and it traces\n// a sharper reflection than a mirror render anyway. The declaration is here\n// because slangc numbers Metal\'s texture and sampler slots by declaration order,\n// and the transparent encoder binds one slot map for all three of its producers:\n// dropping this would slide the bindless pool\'s sampler off the index the\n// encoder binds it at. glass.slang declares it at the same point for the same\n// reason.\n[[vk::binding(1, 1)]] Sampler2D<float4> planar_reflection;\n#endif\n\n// The ray-tracing scene resources, at the slots glass.slang uses so the inputs\n// the transparent encoder binds once are valid for all three producers. On Metal\n// they ride the pass\'s otherwise-free fragment buffers (0..4 and 8..10, since\n// 5/6/7 are the view, the params and the probe set); on Vulkan they are a set of\n// their own, past the view / params / global sets; on DirectX they follow the\n// registers the 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\n{PROBE_COMMON}\n\n{RT_TRACE}\n\n// ---- Stage interface ----\n\nstruct GlassMeshVertexIn\n{\n [[vk::location(0)]] float3 pos : POSITION;\n [[vk::location(1)]] float3 normal : NORMAL;\n};\n\nstruct GlassMeshVertexOut\n{\n [[vk::location(0)]] float3 world_pos : TEXCOORD0;\n [[vk::location(1)]] float3 world_normal : TEXCOORD1;\n float4 position : SV_Position;\n};\n\n[shader(\"vertex\")]\nGlassMeshVertexOut glass_mesh_vertex(GlassMeshVertexIn v)\n{\n GlassMeshVertexOut o;\n float4 world = mul(params.model, float4(v.pos, 1.0));\n o.world_pos = world.xyz;\n // Rigid / uniform-scale model, so `M * n` needs no inverse-transpose. The\n // fragment renormalises after interpolation.\n o.world_normal = mul((float3x3)params.model, v.normal);\n o.position = mul(view.vp, world);\n return o;\n}\n\n// Depth stored at this pixel by the main pass, for the manual occlusion test.\nfloat glass_mesh_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// The mesh surface at this fragment: the view-facing interpolated normal\n// (two-sided, so a pane of glass lit from behind still Fresnels correctly), the\n// fragment\'s screen UV and the refracted, tinted background behind it.\nstruct GlassMeshSurface\n{\n float3 normal;\n float2 frag_uv;\n float3 refracted;\n};\n\nGlassMeshSurface glass_mesh_surface(GlassMeshVertexOut i, float3 view_dir)\n{\n GlassMeshSurface s;\n s.normal = normalize(i.world_normal);\n if (dot(s.normal, view_dir) < 0.0)\n {\n s.normal = -s.normal;\n }\n\n float2 vp_dim = max(view.viewport, float2(1.0));\n s.frag_uv = i.position.xy / vp_dim;\n\n float2 refract_uv = clamp(s.frag_uv + s.normal.xy * params.refraction_strength,\n float2(0.001), float2(0.999));\n s.refracted = scene_sample(refract_uv).rgb * params.tint.rgb;\n return s;\n}\n\n// Schlick Fresnel (F0 = 0.04 dielectric) mix of the reflection over the\n// refraction, identical to `glass_resolve` so a mesh and a pane read the same at\n// equal inputs.\nfloat4 glass_mesh_resolve(GlassMeshSurface s, float3 view_dir, float3 reflection)\n{\n float n_dot_v = saturate(dot(s.normal, view_dir));\n float rim = pow(1.0 - n_dot_v, max(params.fresnel_power, 1e-3));\n float refl_weight = saturate(RT_F0 + 0.96 * rim);\n float3 colour = lerp(s.refracted, reflection, refl_weight);\n float alpha = saturate(lerp(params.opacity, 1.0, rim));\n return float4(colour, alpha);\n}\n\n[shader(\"fragment\")]\nfloat4 glass_mesh_rt_fragment(GlassMeshVertexOut i) : SV_Target\n{\n float3 view_dir = normalize(view.camera_pos.xyz - i.world_pos);\n GlassMeshSurface s = glass_mesh_surface(i, view_dir);\n\n int2 pixel = min(int2(i.position.xy), int2(max(view.viewport, float2(1.0))) - int2(1, 1));\n if (glass_mesh_scene_depth(pixel) < i.position.z)\n {\n discard;\n }\n\n // A per-pixel reflection ray off the interpolated world-space surface point,\n // so a curved facade mirrors real off-screen geometry across its whole span.\n // The mesh is excluded from the BLAS (glass does not reflect glass), so the\n // trace never self-hits. Glass is smooth, so the trace is sharp and the miss\n // falls back to the probe / sky chain.\n bool ibl = params.prefilter_mip_count > 0.5;\n float3 r = reflect(-view_dir, s.normal);\n float3 reflection;\n if (!rt_trace_reflection(i.world_pos + s.normal * 0.02, r, ibl,\n params.prefilter_mip_count - 1.0, reflection))\n {\n if (probe_set.count > 0u && probe_set_covers(i.world_pos))\n {\n reflection = probe_set_specular(i.world_pos, r, 0.0);\n }\n else\n {\n reflection = ibl ? prefilter_level(r, 0.0) : float3(1.0);\n }\n }\n return glass_mesh_resolve(s, view_dir, reflection);\n}\n";Expand description
glass_mesh.slang.