Unity Shader 使用裁减空间结合Reversed Z 实现天空盒在NDC远裁面

第1步:理解裁剪空间(Clip Space)

TransformObjectToHClip(v.positionOS) 输出一个 float4

positionCS = (x, y, z, w)

这里的 w 不是常数,它和相机距离成正比——离相机越远,w 越大。

第2步:透视除法

GPU 自动做:

NDC = (x/w, y/w, z/w)

深度缓冲区最终存储的就是 z/w 这个值。


第3步:Reversed Z 的含义

近平面(相机附近)远平面(最远处)
非 Reversed Zz/w = 0z/w = 1
Reversed Zz/w = 1z/w = 0

Unity 在 DX12/Vulkan/Metal 平台上默认开启 Reversed Z,所以两个平台的「最远」对应的数值是反的。


第4步:为什么是 z = w × factor

因为 z/w = factor,所以:

  • 非 Reversed:想要 z/w ≈ 1 → factor = 0.999999
  • Reversed Z:想要 z/w ≈ 0 → factor = 0.000001

为什么不直接写 z = 0 或 z = w

因为 w 是动态变化的(每个顶点的 w 不一样),写固定值 z=0 会导致 z/w = 0,在非 Reversed 模式下反而是最近处,完全反了。必须用 w × factor 才能正确映射到 NDC 深度。


还有哪个环节想深入追问?比如「为什么 Reversed Z 能提高深度精度」或者「透视投影矩阵的 w 分量到底是怎么算出来的」?

裁剪空间到深度缓冲区的变换管线

 

关键:Clip Space 的 z 和 w 的关系

经过 MVP 变换后,裁剪空间中:
z = w × (某个与深度相关的值)

透视除法后 z/w 就是 NDC 深度,直接写入深度缓冲区。
所以我们控制 positionCS.z,就是在控制最终的深度值。

为什么用 0.000001 和 0.999999,而不是 0 和 1?

① 精度问题:z=0 或 z=w 时,浮点精度在最远处最差,容易产生 z-fighting。
② 裁剪剔除:z 恰好等于 0 或 w 可能被视锥剔除误判,导致物体消失。
③ 0.000001 和 0.999999 足够接近极值,同时避开了边界精度陷阱。

Shader "zmx/Water_1_SKY"
{
    Properties
    {
        [MainTexture] _BaseMap("Base Map", 2D) = "white" {}
    }

    SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float2 uv : TEXCOORD0;
            };

            TEXTURE2D(_BaseMap);
            SAMPLER(sampler_BaseMap);

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
            CBUFFER_END

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
            #if UNITY_REVERSED_Z
                OUT.positionHCS.z = OUT.positionHCS.w * 0.000001f;
            #else
                OUT.positionHCS.z = OUT.positionHCS.w * 0.999999f;
            #endif
                OUT.uv = TRANSFORM_TEX(IN.uv, _BaseMap);
                return OUT;
            }

            half4 frag(Varyings IN) : SV_Target
            {
                float4 mainColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
                return mainColor;
            }
            ENDHLSL
        }
    }
}

NDC指的是什么

NDC = Normalized Device Coordinates(归一化设备坐标)

一句话定义

NDC 是顶点在经过透视除法之后、真正变成屏幕上像素之前的坐标空间。

裁剪空间 (Clip Space) → 透视除法 → NDC → 视口变换 → 屏幕像素

(x,y,z,w)                             ÷w (x/w, y/w, z/w)                    → (px, py)

NDC 的坐标范围

NDC 范围含义
x[-1, +1]屏幕左右
y[-1, +1]屏幕上下
z[0, 1]深度!直接写入深度缓冲区

为什么叫"归一化"

所有顶点,无论离相机多远、视野多宽,最终都会被压缩到 [-1, +1] × [-1, +1] × [0, 1] 这个立方体里。这就是"归一化"——统一到同一个标准 cube。

那段代码的本质就是:

// 在裁剪空间里动手脚
o.positionCS.z = o.positionCS.w * 0.999999f;
//                                ^^^^^^^^^^
//                      这个比值就是 NDC 的 z 值!
//                      因为 NDC.z = positionCS.z / positionCS.w

所以:

  • z/w = 0.999999 → NDC.z ≈ 1 → 深度缓冲区写入 ~1 → 最远处
  • z/w = 0.000001 → NDC.z ≈ 0 → 深度缓冲区写入 ~0 → 最远处(Reversed Z)

假设某个顶点的 positionCS = (x, y, z, w) = (2.4, -0.8, 8.5, 10.0)

步骤计算结果
裁剪空间(2.4, -0.8, 8.5, 10.0)
透视除法 x2.4 / 10.00.24 → NDC.x
透视除法 y-0.8 / 10.0-0.08 → NDC.y
透视除法 z8.5 / 10.00.85 → NDC.z → 深度值

代码把 8.5 改成了 10.0 × 0.999999 = 9.99999,所以 NDC.z = 0.999999,深度缓冲写入 0.999999——最大值,最远处。

其中 DecodeHDREnvironment指的是什么意思?

DecodeHDREnvironment 是 Unity 内置的一个解码函数,专门用来从 HDR 格式的环境贴图(Cubemap)里采样并解码颜色值**。

它解决什么问题

环境贴图(Skybox / IBL Cubemap)在 HDR 格式下,存储的不是普通 RGB 颜色,而是经过编码的高动态范围值(因为 8-bit 存不下超亮的光)。

DecodeHDREnvironment 做的事情就是:采样 Cubemap → 解码回真实的 HDR 颜色值

函数签名

// UnityGI_Base 或 UnityGI_Indirect 里常用
real3 DecodeHDREnvironment(float4 raw, float4 hdrDecodeParams);
参数说明
rawtexCUBE() 采样出来的原始 float4 值
hdrDecodeParamsUnity 自动传给你的解码参数(在 unity_SpecCube0_HDR 里)

unity_SpecCube0_HDR 是什么

这是 Unity 自动定义的 float4,里面存着 HDR 解码参数:

x = RGBM 的 M 系数
y = 指数
z = 保留
w = 保留

DecodeHDREnvironment 内部就是根据这个 float4 把 raw.rgb × (M ^ exp) 之类的方式解码回线性 HDR 值。

在 ASE 里

ASE 的 GI / Refletion 节点内部已经帮你调用了 DecodeHDREnvironment,你一般不直接接触它。

但如果你手写 Custom Expression 做自定义 IBL 采样,就需要手动调用它,否则采样出来的颜色会是错的(太暗或完全不正常)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值