Shader "Custom/TiltShift" { SubShader { Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" } ZTest Always ZWrite Off Cull Off Blend Off HLSLINCLUDE #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" // DeclareDepthTexture provides TEXTURE2D_X_FLOAT(_CameraDepthTexture) and SampleSceneDepth(). // Include unconditionally; it is only sampled in DEPTH_MODE branches. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl" // ── Mode selection ────────────────────────────────────────────────────────── // DEPTH_MODE: Scheimpflug-based (uses depth buffer). Default: screen-space. #pragma multi_compile_local _ DEPTH_MODE // ── Shared parameters ─────────────────────────────────────────────────────── float _TiltShift_MaxBlurRadius; // max blur in pixels int _TiltShift_SampleCount; // samples per side per pass // ── Screen-space mode ─────────────────────────────────────────────────────── float _TiltShift_CenterOffset; // vertical offset of sharp band (UV units, +up) float _TiltShift_FocusWidth; // full height of sharp band as fraction of screen float _TiltShift_FalloffRange; // blur fade-in distance in UV units // ── Depth mode (Scheimpflug / tilt plane) ─────────────────────────────────── // _CameraDepthTexture and _ZBufferParams are provided by URP / DeclareDepthTexture.hlsl. float _TiltShift_FocusDistance; // world-space depth of the focal plane centre float _TiltShift_TiltFactor; // world-space depth shift per UV unit of screen height // positive → top focuses farther (typical bird's-eye) float _TiltShift_FocusBand; // half-width of the sharp zone in world units float _TiltShift_DepthFalloff; // blur ramp width in world units // ── Blur radius computation ───────────────────────────────────────────────── float ComputeBlurRadius(float2 uv) { #if defined(DEPTH_MODE) // ---------------------------------------------------------------- // Scheimpflug tilt-plane CoC // SampleSceneDepth uses TEXTURE2D_X_FLOAT + point-clamp (from DeclareDepthTexture.hlsl). // ---------------------------------------------------------------- float rawDepth = SampleSceneDepth(uv); float eyeDepth = LinearEyeDepth(rawDepth, _ZBufferParams); // Depth of the focus plane at this screen row: // uv.y = 0 → bottom, uv.y = 1 → top (OpenGL-style; URP handles flip internally) float focusAtY = _TiltShift_FocusDistance + _TiltShift_TiltFactor * (uv.y - 0.5); float distToPlane = abs(eyeDepth - focusAtY); float band = max(_TiltShift_FocusBand, 0.001); float falloff = max(_TiltShift_DepthFalloff, 0.001); return smoothstep(band, band + falloff, distToPlane) * _TiltShift_MaxBlurRadius; #else // ---------------------------------------------------------------- // Screen-space approximation (no depth buffer needed) // ---------------------------------------------------------------- float dist = abs(uv.y - 0.5 + _TiltShift_CenterOffset); float halfFocus = _TiltShift_FocusWidth * 0.5; float range = max(_TiltShift_FalloffRange, 0.001); return smoothstep(halfFocus, halfFocus + range, dist) * _TiltShift_MaxBlurRadius; #endif } // ── Separable Gaussian blur ───────────────────────────────────────────────── // stepUV = one sample offset in UV space (direction × pixelSize) float4 GaussianBlur(float2 uv, float2 stepUV) { int n = max(1, _TiltShift_SampleCount); float4 color = 0; float totalWeight = 0; [loop] for (int i = -n; i <= n; i++) { // t ∈ [−1, 1]; gaussian shaped so weight → ~0 at the extremes float t = float(i) / float(n); float weight = exp(-4.5 * t * t); color += SAMPLE_TEXTURE2D_X_LOD(_BlitTexture, sampler_LinearClamp, uv + stepUV * float(i), 0) * weight; totalWeight += weight; } return color / totalWeight; } // ── Fragment shaders ──────────────────────────────────────────────────────── float4 HorizontalBlur(Varyings input) : SV_Target { UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); float2 uv = input.texcoord; float radius = ComputeBlurRadius(uv); if (radius < 0.5) return SAMPLE_TEXTURE2D_X_LOD(_BlitTexture, sampler_LinearClamp, uv, 0); int n = max(1, _TiltShift_SampleCount); float2 stepUV = float2((radius / float(n)) * _BlitTexture_TexelSize.x, 0.0); return GaussianBlur(uv, stepUV); } float4 VerticalBlur(Varyings input) : SV_Target { UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); float2 uv = input.texcoord; float radius = ComputeBlurRadius(uv); if (radius < 0.5) return SAMPLE_TEXTURE2D_X_LOD(_BlitTexture, sampler_LinearClamp, uv, 0); int n = max(1, _TiltShift_SampleCount); float2 stepUV = float2(0.0, (radius / float(n)) * _BlitTexture_TexelSize.y); return GaussianBlur(uv, stepUV); } ENDHLSL // Pass 0 — Horizontal blur Pass { Name "TiltShift_H" HLSLPROGRAM #pragma vertex Vert #pragma fragment HorizontalBlur ENDHLSL } // Pass 1 — Vertical blur Pass { Name "TiltShift_V" HLSLPROGRAM #pragma vertex Vert #pragma fragment VerticalBlur ENDHLSL } } }