Skip to main content

DECAL

Constant DECAL 

Source
pub const DECAL: &str = "// Projected (deferred) decal pass: single source for every backend.\n//\n// Runs after the main HDR pass. The vertex stage draws a unit cube (positions\n// in [-0.5, 0.5]^3 in local space) under the decal\'s model matrix and the\n// camera VP, jittered when TAA is on so the rasterised pixel grid matches the\n// main pass exactly. The fragment stage reconstructs the world-space sample\n// point of each rasterised pixel from the main pass\'s depth attachment,\n// transforms it back into decal-local space, and discards where the point\n// falls outside the unit box. Pixels near the top / bottom face along local +Y\n// fade out so the stamp shows no hard edge where it meets a tilted or curved\n// surface.\n//\n// The composited colour is alpha-blended into the resolved HDR target by the\n// pipeline\'s blend state (Src.A * Src + (1 - Src.A) * Dst). The pass binds no\n// depth attachment: the unit-box test is the depth test.\n//\n// USE_MSAA is a HOST difference rather than a target one: Vulkan reads the\n// multisampled main depth while Metal reads the resolved copy.\n\n#ifndef USE_MSAA\n#define USE_MSAA 0\n#endif\n\n// Per-frame view inputs, 144 B. Mirrors `DecalView` in each backend\'s uniforms\n// module.\nstruct DecalView\n{\n    // The main pass\'s view-projection (jittered when TAA is on).\n    float4x4 vp;\n    // Inverse of the same, for the world-space reconstruction.\n    float4x4 inv_vp;\n    // Attachment width, height in pixels.\n    float2 viewport;\n    float2 _pad;\n};\n\n// Per-decal uniforms, 160 B. Mirrors `DecalParams`.\nstruct DecalParams\n{\n    float4x4 model;     // local -> world\n    float4x4 inv_model; // world -> local\n    float4 tint;        // linear RGB x alpha\n    float fade_pow;     // exponent on the soft fade along local +Y\n    float _pad0;\n    float _pad1;\n    float _pad2;\n};\n\n// Vulkan binds both as UBOs in the per-frame set (the params through a\n// dynamic offset into one MAX_DECALS-slot ring); Metal writes both with\n// setBytes at the buffer indices the register numbers name.\n[[vk::binding(0, 0)]] ConstantBuffer<DecalView> view : register(b0);\n[[vk::binding(1, 0)]] ConstantBuffer<DecalParams> params : register(b1);\n\n// Declaration order is the Metal texture index: the scene depth at 0 and this\n// decal\'s albedo at 1, which is what that host binds. The albedo lives in its\n// own Vulkan set so swapping decals rebinds set 1 rather than the per-frame set.\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(0, 1)]] Sampler2D<float4> decal_tex;\n\nstruct DecalVertexIn\n{\n    // Unit cube vertex; the bound buffer carries 8 corners in [-0.5, 0.5]^3.\n    [[vk::location(0)]] float3 pos : POSITION;\n};\n\n// The cube is only a rasterisation proxy, so the fragment derives everything it\n// needs from the pixel position and carries no varying.\nstruct DecalVertexOut\n{\n    float4 position : SV_Position;\n};\n\n[shader(\"vertex\")]\nDecalVertexOut decal_vertex(DecalVertexIn v)\n{\n    DecalVertexOut o;\n    float4 world = mul(params.model, float4(v.pos, 1.0));\n    o.position = mul(view.vp, world);\n    return o;\n}\n\nfloat decal_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[shader(\"fragment\")]\nfloat4 decal_fragment(DecalVertexOut i) : SV_Target\n{\n    // The single-sample depth resolve shares the rasterised pixel grid with the\n    // composite target, so integer texel coordinates address it directly.\n    int2 pixel = int2(i.position.xy);\n    if (pixel.x < 0 || pixel.y < 0 || pixel.x >= int(view.viewport.x)\n        || pixel.y >= int(view.viewport.y))\n    {\n        discard;\n    }\n    // 1.0 is the cleared / \"no geometry\" sentinel: the main pass left this\n    // pixel empty (the sky writes near-far-plane depth instead). Nothing to\n    // project onto.\n    float depth = decal_scene_depth(pixel);\n    if (depth >= 1.0)\n    {\n        discard;\n    }\n\n    // Reconstruct world space at this pixel through the inverse VP. Every\n    // backend rasterises this pass with the framebuffer\'s Y running downwards\n    // (Vulkan through the same negative-height viewport as the main pass), so\n    // the NDC flip is shared rather than target-specific.\n    float2 ndc_xy = (i.position.xy / view.viewport) * 2.0 - 1.0;\n    ndc_xy.y = -ndc_xy.y;\n    float4 clip = float4(ndc_xy, depth, 1.0);\n    float4 world = mul(view.inv_vp, clip);\n    world /= world.w;\n\n    // Decal-local clip against the unit box.\n    float4 local = mul(params.inv_model, world);\n    float3 ab = abs(local.xyz);\n    if (ab.x > 0.5 || ab.y > 0.5 || ab.z > 0.5)\n    {\n        discard;\n    }\n\n    // Soft fade along the projection axis (local +Y) so the stamp shows no hard\n    // band where the surface tilts away from the projection plane: alpha rolls\n    // off as |local.y| approaches 0.5.\n    float fade = saturate(1.0 - (ab.y * 2.0));\n    fade = pow(fade, max(params.fade_pow, 1.0));\n\n    // Sample on local X-Z; UV in [0, 1] with V = 0 at the top, matching the\n    // rest of the engine\'s textures.\n    float2 uv = local.xz + 0.5;\n    uv.y = 1.0 - uv.y;\n    float4 tex = decal_tex.Sample(uv);\n\n    return float4(tex.rgb * params.tint.rgb, tex.a * params.tint.a * fade);\n}\n";
Expand description

decal.slang.