自由学习记录(127)

Unity 的 SRP 顶层入口,核心工作

核心工作

  • 遍历需要渲染的 camera

  • 为每个 camera 选择/驱动一个 renderer

  • 把具体渲染细节下放到 renderer.Execute 这一层

SRP 的关键类型就是这几个:

  • RenderPipelineAsset

  • RenderPipeline

  • ScriptableRenderer 或你自己的 camera renderer

  • ScriptableRenderContext

  • CommandBuffer

RenderPipeline.Render(...) 这一层做的事情,天然就是"按 camera 驱动渲染"。从源码阅读角度,把顶层逻辑概括成"foreach camera -> execute"是成立的。

本质上是"当前渲染目标尺寸/缩放后尺寸"相关参数,不是规范化到 [0,1] 的屏幕坐标。你如果把 i.positionCS.xy / _ScaledScreenParams.xy 这样自己拼 UV,在 URP 里很容易在这些情况下出错:

  • Dynamic Resolution / Render Scale

  • XR / 单双眼路径

  • RTHandle / 中间纹理尺寸变化

  • Y 翻转与平台差异

  • 半像素偏移、透视除法链路不完整

一旦屏幕 UV 算错,采到的 depth texture 就不是当前像素对应的场景深度,而是一片错误区域。错误深度再进 rim 判定,结果通常不是"边缘亮",而是"大面积全亮/全白"。

"当前物体视深度"去和"偏移位置采样出来的场景深度"比。这里常见有几种错法:

  1. 一个是 LinearEyeDepth(sceneDepth),另一个是 positionCS.zpositionNDC.z

  2. 一个是以相机 forward 为正的 view-space z,另一个是 clip/NDC/depth buffer 空间

  3. 一个取的是当前像素深度,另一个取的是偏移像素深度,但偏移方向本身又在错误空间里

  4. 反向 Z 项目下比较符号写反

只要这两个量不在同一深度定义下,差值就会在大部分表面上恒大于阈值,于是整个模型都满足 rim 条件,表现就是"整块发白"。

URP 推荐宏:

overflow-visible! 复制代码

SampleSceneDepth(uv)

完整例子:

overflow-visible! 复制代码

hlsl
float rawDepth = SampleSceneDepth(uv);

Shader 中如何访问(官方推荐)

Unity 在 shader 中统一提供:

overflow-visible! 复制代码

_CameraDepthTexture

采样方式:

overflow-visible! 复制代码

hlsl
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, uv);

URP 推荐宏:

overflow-visible! 复制代码

SampleSceneDepth(uv)

完整例子:

overflow-visible! 复制代码

hlsl
float rawDepth = SampleSceneDepth(uv);

注意:

rawDepth 是 非线性深度

The Scene Depth node in Unity Shader Graph samples the camera's depth buffer using screen-space UV coordinates to determine the distance of objects . It outputs a value (typically 0-1) representing proximity, where 0 is the near plane and 1 is the far plane.

  • GetNormalizedScreenSpaceUV(...)

    作用是 算屏幕空间 UV

    输入通常是 positionCS / SV_POSITION 相关数据,输出 0..1 屏幕坐标。

  • Scene Depth node / SampleSceneDepth(uv)

    作用是 用这个屏幕 UV 去采样相机深度纹理

GetNormalizedScreenSpaceUV 是一个常用的 HLSL 工具函数,主要用于将裁剪空间坐标转换为归一化的屏幕空间 UV 坐标。输入 (Input): float4 positionCS float2 positionCS通常传入 裁剪空间坐标 (Clip Space Position) 。顶点着色器中,这通常是 TransformWorldToHClip 的计算结果。

有透明裁剪或 alpha 控制,fragDepth 里还得复用同样的 clip(...) 逻辑,否则深度轮廓会和实际渲染轮廓不一致。

在实际项目里通常不止 fragDepth,凡是"决定像素是否存在"的 pass,都要复用同一套裁剪逻辑,否则轮廓一定会分叉。

如果你的主渲染 pass 里有这类逻辑:

overflow-visible! 复制代码

hlsl
half alpha = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv).a * _BaseColor.a;
clip(alpha - _Cutoff);

那么下面这些 pass 也应当保持同样的 coverage 判定:

  • DepthOnly

  • ShadowCaster

  • 任何你自己写的 prepass / custom depth pass

  • 任何输出 SV_Depth 的 fragment 路径

原因很直接:GPU 的深度缓冲记录的是"哪些像素被认为存在"。

主 pass 里被 clip 掉的像素,如果 depth pass 没有同步 clip,那 depth buffer 里就会留下本不该存在的深度,结果就是:

  • 深度轮廓比真实可见轮廓更大

  • 头发卡片、树叶、镂空网格边缘出现"实心深度"

  • SSAO / SSR / soft particles / intersection / rim / post effect 采样深度时出现脏边

  • 自遮挡、排序、透明叠加、部分后处理表现异常

  • 阴影轮廓和实际可见轮廓不一致(若 ShadowCaster 也没同步)

你截图里那句"有透明裁剪或 alpha 控制,fragDepth 里还得复用同样的 clip(...) 逻辑"本质是在说:
深度写入的 coverage 必须与颜色 pass 的 coverage 一致。

要区分"alpha clip"和"半透明 alpha blend"。

  1. Alpha clip / cutout

    这是最典型必须同步的情况。

    因为像素是"有 / 无"二值覆盖,depth 和 shadow 都必须按同样规则裁掉。

Alpha blend 透明

这类通常不写常规深度,或者只做特殊 prepass。

如果你在透明物体上又额外写了 depth(比如头发、dither transparency、custom depth prepass),那这个 depth pass 的 coverage 规则也必须和你最终显示出来的可见区域一致。

否则一样会出现"看起来是透明边缘,深度却是实心"的问题。

如果你的头发还有顶点位移,比如 halo shift、风摆、卡片偏移,也必须在这些 pass 的 vertex 阶段保持一致,否则也会出现:

  • 颜色轮廓在这里

  • 深度 / 阴影轮廓在另一个位置

这比单纯漏掉 clip 更常见。

只要主渲染轮廓受 alpha clip 或其他 coverage 逻辑控制,那么 depth pass(包括 fragDepth 路径)必须复用完全相同的 coverage 判定,否则深度轮廓、阴影轮廓和实际渲染轮廓就会不一致。

Name "DepthOnly" / Tags { "LightMode"="DepthOnly" } 的作用

这两个字段服务于 SRP Pass 选择机制

在 URP 中,Renderer 在执行某个 RenderPass(例如 Depth Prepass)时,会通过 ShaderTagId 查找 shader pass:

overflow-visible! 复制代码

C#
new ShaderTagId("DepthOnly")

也就是说:

overflow-visible! 复制代码

Tags { "LightMode"="DepthOnly" }

用于 告诉 URP:这个 pass 可以用于 DepthOnly 阶段

Name "DepthOnly" 只是调试友好的名字:

  • FrameDebugger

  • RenderDoc

  • SRP Debug

遍历 pass 时显示的名称。

所以关系是:

字段 作用
Name 调试可读性
LightMode 真正决定 SRP 是否使用这个 pass

如果缺少 LightMode="DepthOnly"

  • URP Depth Prepass 不会使用这个 pass

  • 会 fallback 或直接跳过

执行某个 RenderPass(例如 Depth Prepass)时ShaderTagId 查找shader pass:new ShaderTagId("DepthOnly")告诉 URP:这个 pass 可以用于 DepthOnly 阶段Name "DepthOnly" 只是调试友好

  • FrameDebugger

  • RenderDoc

  • SRP Debug

  • 遍历 pass 时显示的名称。

    为什么 Attributes 里有 normal / tangent / uv?

    你截图里的代码:

    overflow-visible! 复制代码

    hlsl
    struct Attributes
    {
    float4 vertex : POSITION;
    float2 uv : TEXCOORD;
    float3 normal : NORMAL;
    float4 tangent : TANGENT;
    };

    但在 vertDepth 中只用了:

    overflow-visible! 复制代码

    TransformObjectToHClip(v.vertex.xyz)

    确实看起来 多余

    实际原因通常有三个:

    (1) 与主 Pass 共享 Vertex Layout

    Unity Mesh Vertex Buffer layout 是固定的:

    overflow-visible! 复制代码

    POSITION
    NORMAL
    TANGENT
    UV0
    UV1
    ...

    Shader Attributes 一般写完整,以避免:

    • 不同 pass vertex layout 不一致

    • GPU vertex fetch mismatch

    虽然 DX12 实际只会读取用到的 attribute
    与主 Pass 共享 Vertex LayoutUnity Mesh Vertex Buffer layout 是固定的:

    overflow-visible! 复制代码

    struct Attributes

    在多个 pass 共用:

    • Forward

    • ShadowCaster

    • DepthOnly

    因此直接 copy。

    很多 shader 会:

    overflow-visible! 复制代码

    struct Attributes

    在多个 pass 共用:

    • Forward

    • ShadowCaster

    • DepthOnly

    因此直接 copy。

    SRP Batcher / GPU Layout 兼容

    SRP Batcher 要求:

    overflow-visible! 复制代码

    same vertex declaration across passes

    否则可能触发:

    overflow-visible! 复制代码

    SRP Batcher fallback
    SRP Batcher / GPU Layout 兼容same vertex declaration across passesSRP Batcher / GPU Layout 兼容GPU Layout 兼容SRP Batcher fallback

    理论上可以:

    overflow-visible! 复制代码

    struct Attributes
    {
    float4 vertex : POSITION;
    };
    DepthOnly pass 完全能运行

    在项目中通常不会这么做,因为:

    • 与其他 pass layout 不一致

    • 容易引入维护问题

    fragDepth() 为什么 return 0

    你的代码:

    overflow-visible! 复制代码

    hlsl
    half4 fragDepth(VaryingsDepth i) : SV_Target
    {
    return 0;
    }

    关键点:

    overflow-visible! 复制代码

    ColorMask 0

    这意味着:

    不会写 color buffer

    所以 fragment shader 的 return 完全不会被写入 render target
    Depth 写入不是来自 return,而是:SV_Position.z

    GPU pipeline:

    overflow-visible! 复制代码

    VS → Rasterizer → Depth Test → Depth Write

    TransformObjectToHClip() 生成:

    overflow-visible! 复制代码

    SV_POSITION

    其中:

    overflow-visible! 复制代码

    z/w → depth

    GPU自动写入 Depth Buffer。

    仍然需要 Fragment Shader?

    因为 pipeline 需要完整 stage:

    overflow-visible! 复制代码

    VS -> PS

    但 PS 不做任何事。
    但 PS 不做任何事。pipeline 需要完整 stage:仍然需要 Fragment Shader?

    很多 shader 甚至写:

    overflow-visible! 复制代码

    hlsl
    float4 fragDepth(...) : SV_Target
    {
    discard;
    }

    overflow-visible! 复制代码

    return 0;

    效果相同。
    没有 ColorMask 0 会发生什么?

    如果没有 ColorMask 0 会发生什么?

    如果写:

    overflow-visible! 复制代码

    return 0;

    ColorMask 默认 RGBA

    就会写入一个:

    overflow-visible! 复制代码

    (0,0,0,0)

    到 Color RT。

    Depth 仍然来自 SV_Position.z

    这个 DepthOnly pass 的完整 pipeline

    实际执行流程:

    overflow-visible! 复制代码

    Depth Prepass

    Draw objects with "LightMode=DepthOnly"

    VS:
    TransformObjectToHClip

    Rasterizer

    Depth Test

    Depth Write

    Fragment shader 只是占位

    URP 要单独 DepthOnly Pass?

    只要任意一个方向出现明显深度跳变,就把这个像素当成边缘。

    看起来像 "max 三次",本质只是把 4 个值取最大值。

    如果不用 max,常见替代是:

    • average:边缘会更软,但容易被稀释
    • sum:更亮,但容易过强
    • length(float4(...)):更像梯度幅值

    你截图里的写法本质是在做一个 离散梯度幅值估计

    overflow-visible! 复制代码

    max(
    max(abs(left-center), abs(right-center)),
    max(abs(down-center), abs(up-center))
    )

    这里实际上是:

    overflow-visible! 复制代码

    max(|Δx_left|, |Δx_right|, |Δy_up|, |Δy_down|)

    4 个方向差值的最大值

    这和真正的经典边缘算子(Sobel / Prewitt / Scharr)不同,但在实时渲染里非常常见,因为:

    • ALU 少

    • 不需要平方/开根

    • 对任意方向的边缘都有响应

    • branchless,warp 友好

    所以很多实时 shader(outline / depth edge / normal edge / SSAO edge detect)都会用这种 max-gradient heuristic

    但严格来说:

    max 不是"最常用的边缘检测算法",而是最便宜的实时近似。
    max 不是"最常用的边缘检测算法",而是最便宜的实时近似。标准的梯度幅值是:|∇I| = sqrt( (dI/dx)^2 + (dI/dy)^2 )

    • 单方向强边缘非常敏感

    • 不会被平均稀释

    • 计算非常便宜

    • 边缘更"硬"

    实时 outline shader 非常爱用这个。

    length

    overflow-visible! 复制代码

    edge = sqrt(dx1² + dx2² + dy1² + dy2²)

    相当于:

    overflow-visible! 复制代码

    ||g||₂

    L2 norm

    特性:

    • 更接近真实梯度幅值

    • 多方向变化会叠加

    • 更平滑

    • 对噪声更稳定

    • 计算稍贵(sqrt)

    如果不用 max,常见替代是:

    • average:边缘会更软,但容易被稀释
    • sum:更亮,但容易过强
    • length(float4(...)):更像梯度幅值

    当前用 max 是最直接的"任一方向命中即算边缘"。

    把"能被严格证明的部分"和"不能被单一文档严格证明的部分"分开。

    不能被任何单一官方文档直接证明的命题是这个:

    "在所有现代 GPU 上,100 万次 sqrt 相比 100 万次 add / mul,FPS 只差 0.x 或 <2%。"

    这个命题本身就不成立为"普适真理",因为帧率差取决于架构、shader stage、wave occupancy、寄存器压力、是否被编译器重写、是否 ALU bound、是否 texture/memory bound。AMD 自己就明确强调,occupancy 只有在 GPU 能拿它去隐藏 latency、并且与 cache hierarchy 平衡时才会改善性能;最大 occupancy 也不等于所有 memory latency 都被隐藏。(GPUOpen)

    所以我前面的结论,若要改成可被强引用支撑的版本,应写成下面这样:

    结论 A:length / L2 范数在语义上确实包含平方根,而 L1 不包含。

    Microsoft 的 HLSL 文档把 length(x)定义为"返回指定浮点向量的长度",而 sqrt(x)定义为"返回指定浮点值的平方根"。也就是说,L2 范数语义上必然包含 sqrt,而 L1 范数只需要 abs + add。(Microsoft Learn)

    结论 B:在现代 GPU 上,单看某个算术指令的 latency 并不能直接推出帧率差,因为 GPU 依赖大量并行 wave 去隐藏 latency。

    AMD RDNA 的官方 occupancy 说明写得很直接:occupancy 可以理解为 SIMD 隐藏 latency 的能力;occupancy 很低时,等待无法被隐藏;occupancy 高时,更有条件隐藏 latency。AMD 的 RDNA3 ISA 文档也明确写到,GPU 通过同时跟踪大量 work-items,并将计算与内存访问重叠来隐藏 memory latency。(GPUOpen)

    结论 C:在典型屏幕空间边缘检测这类 shader 里,texture fetch 往往是非常重要的成本来源,不能只盯着 add / mul / sqrt。

    NVIDIA Nsight 的官方文档说明,texture fetch 是 memory request;texture memory 设计为具有恒定 latency,texture cache hit 会减少带宽消耗,但不会降低 fetch latency。AMD 也强调,现代 GPU 的性能高度受 cache hierarchy 和 external memory access 影响,occupancy 的实际意义主要体现在对 latency 尤其是 memory latency 的隐藏能力上。(NVIDIA Docs)

    结论 D:sqrt 在现代 GPU ISA 里不是"神秘的超重型宏操作";它就是明确存在的硬件/ISA 级浮点指令类别之一。

    AMD RDNA3 ISA 里明确列出了 V_SQRT_F32V_RSQ_F32 指令:前者计算单精度平方根,后者计算单精度倒数平方根,均按 IEEE 规则执行。它们与 V_ADD_F32V_MUL_F32 同属于向量 ALU 指令体系,而不是某种"语言层虚构操作"。这并不意味着它和 add/mul 完全同价,但至少说明"sqrt 贵到不可用"这种老经验,不能直接套到现代 GPU 上。(AMD Documentation)

    基于这四段证据链,前面那句更严谨的改写应当是:

    对于现代 GPU 上的实时 outline / Sobel / 屏幕空间边缘检测,L2 相比 L1 的额外代价是真实存在的,但不能脱离具体 shader 说成会显著拉低 FPS;在包含多次 texture fetch 的 pass 里,这点额外 ALU 很多时候不是主导瓶颈。 这不是"拍脑袋经验",而是由 HLSL 语义、GPU 的 latency-hiding 模型,以及 texture fetch 的 memory-latency 特性共同推出的工程结论。(Microsoft Learn)

    再进一步,把你最关心的"百万 add / mul / sqrt 会差多少"换成能被强支撑的表达

    "没有厂商官方文档会给出一个跨 GPU 的统一 FPS 数字;正确的方法是对目标架构做 microbenchmark,并结合 ISA、寄存器数、occupancy、memory 指标来解释结果。"

    这点 AMD 和 Microsoft/NVIDIA 的工具链文档其实都在暗示同一件事:AMD 推荐用性能分析来判断 occupancy 与 cache/memory 的平衡;Microsoft PIX 提供 occupancy 视图;NVIDIA 则通过 Nsight 把 texture fetch 单独作为 memory request 来分析。也就是说,厂商官方推荐的方法本来就不是靠经验估一个"sqrt 很贵"。(GPUOpen)

    如果你需要"更硬"的支持,我建议证据分两层来写:

    第一层放官方语义与架构文档 ,也就是上面这些引用。

    第二层放你自己可复现实验,比如:

    • 全屏 compute / pixel microbench

    • 固定 wave size、固定寄存器占用

    • 三组:纯 add、纯 mul、纯 sqrt

    • 另做一组 9-tap Sobel,分别用 L1 / L2 / squared magnitude

    • 用 PIX / RGP / Nsight 记录 GPU duration、occupancy、ISA 指令数、寄存器数、cache/memory 指标

    这样你得到的是"官方原理 + 自己机器上的可复现实测",比引用任何博客都硬。

    如果你要,我下一条可以直接给你一版可放进技术文档/评审材料里的引用式论证,按"命题---证据---推论"格式整理好,并附一个 DX12/Unity 的 microbenchmark 方案。

    ShiftTangent 既不是 HLSL 语言自带函数,也不是 GPU / DX12 的 intrinsic 。它只是 某个 shader 库里定义的普通函数,通常来自 Unity 的 shader 代码或 Shader Graph 自动生成代码。

    区分三个层级:

    HLSL / GPU intrinsic(语言或硬件提供)

    例如:

    overflow-visible! 复制代码

    dot()
    normalize()
    cross()
    saturate()
    mul()
    ddx()
    ddy()

    这些是 编译器内建函数

    Unity Shader Library(Unity 提供的工具函数)

    例如:

    overflow-visible! 复制代码

    TransformObjectToWorld()
    GetWorldSpaceViewDir()
    SafeNormalize()

    这些定义在:

    overflow-visible! 复制代码

    Packages/com.unity.render-pipelines.core/ShaderLibrary/

    用户 / Graph / Shader 文件里的普通函数

    例如:

    overflow-visible! 复制代码

    ShiftTangent()
    XorShift()
    AnisotropyBRDF()

    这些只是:

    overflow-visible! 复制代码

    real3 ShiftTangent(real3 T, real3 N, real shift)
    {
    return normalize(T + shift * N);
    }

    编译器只是把它当作普通函数 inline。

    raw depth 或线性深度做邻域比较。这是最便宜、接入最简单的方案,也是很多项目最先上的版本。但它的本质问题非常明确:阈值和采样半径都是屏幕空间定义,距离一致性最差。近处容易过粗、过强,远处容易消失。比 raw depth 稳定得多,因为 raw depth 在透视投影下分布高度非线性,远处精度压缩很严重;换成 linear eye depth 后,至少"深度差的语义"更接近真实距离,参数更可控。但它仍然是屏幕采样,所以只能说"减轻距离漂移",不能彻底消除。距离补偿,也就是把采样半径、阈值或最终强度,按 view depth / clip w / 像素世界尺寸做归一化。这一类是实际项目里最常见的增强手段。因为你不只是改"比较的深度值",而是在改"屏幕 1 像素在世界里代表多少距离"这个根因。所以如果你的目标是"近远处表现尽量一致",单看控制效果,这一类通常最好。

    linearEyeDepth 你现在看到"近处接近 0,远处更大/更亮",这是正常的可视化结果。

    因为它本质上是把深度纹理还原成"视空间距离",离相机越远数值越大。

    _ZBufferParams 这组值不是你手算的,是 Unity 根据当前相机和平台深度约定准备给 shader 的参数

    它的作用就是把 GPU 深度缓冲里的 raw depth 正确还原/线性化。

    更准确说:

    1. raw depth
      • 是 GPU 深度缓冲写出来的值
      • 非线性
      • 受平台、投影矩阵、reversed-Z 影响
    2. _ZBufferParams
    • 是 Unity/SRP 根据当前深度缓冲规则传给 shader 的辅助参数
    • 让 LinearEyeDepth(rawDepth, _ZBufferParams) 能还原出线性眼空间深度

    所以结论是:

    • raw zbuffer 值本体是 GPU 产出的
    • _ZBufferParams 是引擎为了正确解释这个 GPU 深度值而提供的参数

    "Used to linearize Z buffer values. x is (1-far/near), y is (far/near), z is (x/far) and w is (y/far)."

    说的就是 _ZBufferParams 四个分量的构造方式。它的目的不是给你直接拿来做业务逻辑,而是喂给 LinearEyeDepth / Linear01Depth 这类函数,把投影后的非线性深度还原成更有物理意义的量。

    _ZBufferParams 不是 shader 自己生成的数据,它来自 Unity 引擎在每帧设置的全局常量(camera projection 相关) 。Unity 内部,_ZBufferParams 会被填进 UnityPerCamera CBuffer ,然后 shader 通过自动绑定读取。相机 near / far plane 以及 是否使用 reversed Z 计算四个参数:_ZBufferParams.x
    _ZBufferParams.y
    _ZBufferParams.z
    _ZBufferParams.w

    Unity 官方定义:
    x = 1 - far / near
    y = far / near
    z = x / far
    w = y / far

    所以本质来源只有两个:
    nearClipPlane
    farClipPlane

    Unity 在 C++ 渲染层根据这两个值生成 _ZBufferParams

    它为什么需要 4 个值。

    GPU 深度缓冲不是线性的。
    深度缓冲不是线性的。

    透视投影下 depth buffer 存的是:
    z_buffer = a + b / viewZ
    hyperbolic 分布导致远处精度极低。

    Unity 为了让 shader 能恢复 view space depth ,提供 _ZBufferParams,配合公式:
    LinearEyeDepth(z) = 1 / (z * _ZBufferParams.x + _ZBufferParams.y)

    或者在 UnityCG / Core.hlsl 里:
    hlsl
    float LinearEyeDepth(float z, float4 zBufferParams)
    {
    return 1.0 / (zBufferParams.z * z + zBufferParams.w);
    }

    不同版本实现形式略有差别,但本质一样。

    Reversed Z

    现代 Unity(DX11+ / DX12 / Vulkan / Metal)默认是 reversed depth buffer
    near -> 1
    far -> 0

    这样远处精度更好。

    当 reversed Z 打开时,Unity 会重新构造 _ZBufferParams,保证:
    LinearEyeDepth() 公式不需要改

    所以 shader 永远不要自己写 near/far 公式,而是直接用:
    LinearEyeDepth(depth, _ZBufferParams)
    Linear01Depth(depth, _ZBufferParams)

    Unity 已经帮你处理了:

    • reversed Z

    • API 差异

    • clip space 差异

    • projection 细节

    想在 CPU 侧验证 _ZBufferParams,可以直接打印:
    C#
    Shader.GetGlobalVector("_ZBufferParams")

    或在 FrameDebugger / RenderDoc constant buffer 里看到:
    UnityPerCamera
    _ZBufferParams

    它会随 Camera near/far 改变。
    CPU 侧验证 _ZBufferParams直接打印:Shader.GetGlobalVector("_ZBufferParams")FrameDebugger / RenderDoc constant buffer 里看到:UnityPerCamera
    _ZBufferParams随 Camera near/far 改变。_ZBufferParams 只和 projection 有关 ,和 depth texture 本身无关。SampleSceneDepth()

    • _ZBufferParams= 可恢复 view space depth可恢复可恢复可恢复可恢复可恢复view space depth=SampleSceneDepth()
    • _ZBufferParams

    custom projection(例如:

    • oblique projection

    • reversed infinite far

    • logarithmic depth

    • custom clip control

    _ZBufferParams 就不再正确,需要你自己提供。

    _ZBufferParams 的数据来源:
    Camera.nearClipPlane
    Camera.farClipPlane

    • Unity projection mode (reversed Z)
      → Unity 渲染管线计算
      → UnityPerCamera CBuffer
      → shader 全局变量 _ZBufferParams

    用途:
    把 GPU depth buffer → 线性深度 / view space depth

    做的是 深度边缘 / depth rim / SSAO / SSR 这一类屏幕空间算法,其实还有一个更底层的问题很多项目会踩坑:LinearEyeDepth 并不是 world distance。

    它是:view space Z

    也就是 camera forward 方向的深度,而不是length(viewPos)

    这在 大 FOV 或斜角观察时 会导致 rim 厚度变化。

相关推荐
庭前云落2 小时前
从零开始的OpenZeppelin学习 2| ERC20-permit、erc20pausable
学习·区块链
zyb11475824332 小时前
Redis的学习
数据库·redis·学习
小白自救计划2 小时前
【计算机视觉】学习历程
人工智能·学习·计算机视觉
怪侠_岭南一只猿3 小时前
爬虫阶段一实战练习题:爬取豆瓣电影 Top250 复盘
css·经验分享·爬虫·python·学习·正则表达式
ADHD多动联盟3 小时前
ADHD注意力缺陷是什么?主要有儿童ADHD和多动症运动干预吗?
学习·学习方法·玩游戏
weixin_443478513 小时前
flutter组件学习之Stack 组件详解
学习·flutter
梦里1米83 小时前
大模型的使用和Prompt-Tuning学习笔记
笔记·学习·prompt
小雨凉如水4 小时前
flutter 基础组件学习
学习·flutter
云边散步4 小时前
godot2D游戏教程系列二(11)
笔记·学习·游戏·游戏开发