GameTech/URP 渲染实战
源码 / Ch28/ScreenSpaceCommon.hlsl
Ch28/ScreenSpaceCommon.hlsl
// 第 28 章屏幕空间效果的公共函数:读深度、重建世界坐标、视锥射线。
#ifndef COOKBOOK_CH28_SCREENSPACE_COMMON
#define COOKBOOK_CH28_SCREENSPACE_COMMON

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// Blit.hlsl 提供全屏三角形顶点函数 Vert、Varyings(含 texcoord)和源纹理 _BlitTexture
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

// 读“设备深度”,并统一成 ComputeWorldSpacePosition 需要的 NDC z
float SampleDeviceDepth(float2 uv)
{
    float d = SampleSceneDepth(uv);
#if !UNITY_REVERSED_Z
    // OpenGL 系:深度纹理是 [0,1],而它的裁剪空间 z 是 [-1,1]
    d = lerp(UNITY_NEAR_CLIP_VALUE, 1, d);
#endif
    return d;
}

// 循环里采样深度要用显式 LOD 版本:带隐式导数的 SAMPLE_TEXTURE2D 在动态循环里
// 会逼编译器展开整个循环(Metal/D3D 都会报 unable to unroll loop)
float SampleSceneDepthLod(float2 uv)
{
    return SAMPLE_TEXTURE2D_X_LOD(_CameraDepthTexture, sampler_CameraDepthTexture, UnityStereoTransformScreenSpaceTex(uv), 0).r;
}

// 天空(没有写深度的像素):Reversed-Z 下是 0,否则是 1
bool IsSkyDepth(float deviceDepth)
{
#if UNITY_REVERSED_Z
    return deviceDepth < 1e-6;
#else
    return deviceDepth > 1 - 1e-6;
#endif
}

// 方法一:逆 VP 矩阵。uv + 深度 → NDC → 乘逆矩阵 → 齐次除法
float3 ReconstructPositionWS(float2 uv, float deviceDepth)
{
    return ComputeWorldSpacePosition(uv, deviceDepth, UNITY_MATRIX_I_VP);
}

// 方法二:视锥射线。顶点着色器里算“相机 → 远裁剪面”的向量,光栅化线性插值后,
// 片元里乘上 0~1 的线性深度即可,每像素省掉一次 4x4 矩阵乘法和除法。
struct VaryingsRay
{
    float4 positionCS : SV_POSITION;
    float2 texcoord   : TEXCOORD0;
    float3 farRayWS   : TEXCOORD1;   // 相机位置 → 该像素对应的远裁剪面上的点
    UNITY_VERTEX_OUTPUT_STEREO
};

VaryingsRay VertRay(Attributes input)
{
    VaryingsRay output;
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
#if SHADER_API_GLES
    float4 pos = input.positionOS;
    float2 uv  = input.uv;
#else
    float4 pos = GetFullScreenTriangleVertexPosition(input.vertexID);
    float2 uv  = GetFullScreenTriangleTexCoord(input.vertexID);
#endif
    output.positionCS = pos;
    output.texcoord   = uv * _BlitScaleBias.xy + _BlitScaleBias.zw;
    // 远平面上的点关于屏幕坐标是仿射(线性)的,所以只在 3 个顶点算、再插值,结果是精确的
    float3 farWS = ComputeWorldSpacePosition(output.texcoord, UNITY_RAW_FAR_CLIP_VALUE, UNITY_MATRIX_I_VP);
    output.farRayWS = farWS - _WorldSpaceCameraPos;
    return output;
}

float3 ReconstructPositionWSFromRay(float3 farRayWS, float rawDepth)
{
    // Linear01Depth = 观察空间深度 / 远裁剪面距离,正好是射线的缩放系数
    return _WorldSpaceCameraPos + farRayWS * Linear01Depth(rawDepth, _ZBufferParams);
}

#endif