AI教我UE渲染-学习笔记(6):启用深度缓冲

0. 画有遮挡关系的三角形

根据之前AI建议的短期内的学习路线,上一篇已经画了一个3D三角形了。下一篇就是要引入深度缓冲了,同时需要画两个有遮挡关系的三角形来验证深度缓冲是有效的。

和AI说过后,Claude Opus 4.6 再次一遍就完成了代码的编写,效果没问题:

下面根据代码的修改来问AI一些问题。

1. 顶点数据变为两个三角形

首先是顶点数据变成了两个有遮挡关系的三角形,没啥好问的。

只需要注意之后Draw的时候是画俩三角形即可:

2. 创建深度纹理

渲染的时候,在Render_Learning函数中首先创建了一张深度纹理。然后Clear掉:

其实我注意到在SceneTextures应该已经有深度纹理了,于是问AI它为什么要再创建一次,它的回答:(仅供参考)

SceneTextures 里确实有 Depth

77:77:Engine/Source/Runtime/Renderer/Internal/SceneTextures.h 复制代码
FRDGTextureMSAA Depth{};

Color 一样是 FRDGTextureMSAA(你屏蔽了 MSAA 后 Target == Resolve,等于一张普通纹理)。
我没用它,是因为 SceneTextures.Depth 需要在 InitializeViewFamily 之后额外调用特定的 setup 流程才会被真正创建和分配 。在你当前的 Render_Learning 里,只做了 InitializeViewFamily + 取 SceneTextures.Color,如果直接用 SceneTextures.Depth.Target 很可能是 nullptr(未被创建),因为深度纹理的创建取决于 SetupMode 里是否包含了 SceneDepth 等标志。

不过,如果你希望和原版更一致地使用 SceneTextures.Depth,也是可以的------需要在 Render_Learning 里确保 SceneTextures 的 depth 被正确初始化。我选择自己 CreateTexture 是为了最小化依赖 :不需要了解 SceneTextures 的深度初始化流程,只要知道"创建一张深度纹理、清除、绑到 pass"就能学到深度缓冲的概念。

等你后续学习阶段需要用到 SceneTextures.Depth(比如实际的 base pass),再切换过去会更自然。

看起来是这样更有利于学习,那我就想正好学习下这里创建纹理的接口了。

总的来说,就是FRDGTextureDesc用来描述纹理,接下来用 GraphBuilder.CreateTexture 实际创建。SceneColor的Color也是如此,只是描述参数不一样:

让AI为我介绍下并对比下:(仅供参考)

成员 类型 基础含义 SceneColor 的值 你的 Depth 的值
Dimension ETextureDimension 纹理维度(2D / 3D / Cube 等) Texture2D Texture2D
Extent FIntPoint 纹理宽高(像素) Config.Extent(视口分辨率,如 1920×1080) SceneTextures.Color.Target->Desc.Extent(和 Color 相同)
Format EPixelFormat 像素格式 Config.ColorFormat(通常 PF_FloatRGBA,每像素 4 个 16 位浮点通道) PF_DepthStencil(深度 24 位 + 模板 8 位,或平台等价格式)
ClearValue FClearValueBinding GPU 快速清除时的默认值 Config.ColorClearValue(通常是黑色透明) FClearValueBinding::DepthFar(深度最远值,Reversed-Z 下为 0)
Flags ETextureCreateFlags 纹理用途标志 包含 TexCreate_RenderTargetable(可作为颜色 RT) TexCreate_DepthStencilTargetable(可作为深度 RT)`
NumSamples uint8 MSAA 采样数,1 = 无 MSAA Config.NumSamples(你强制设为 1) 1(默认值,未指定)
NumMips uint8 Mip 链层数 1(默认值) 1(默认值)
Depth uint16 3D 纹理的深度,2D 时固定为 1 1 1
ArraySize uint16 纹理数组的层数,非数组时为 1 1 1

3. 把深度纹理绑定RT

这段代码是把深度纹理绑定为当前 pass 的深度渲染目标 ,和颜色 RT 的绑定(RenderTargets[0] = FRenderTargetBinding(...))是对应关系。逐参数看让AI解释下参数:

参数 含义
DepthTexture 你创建的那张 PF_DepthStencil 纹理 绑哪张纹理作为深度目标
DepthLoadAction ERenderTargetLoadAction::ELoad pass 开始时加载这张纹理里已有的深度值(你之前清除到了最远值,所以加载的就是"全部最远")
StencilLoadAction ERenderTargetLoadAction::ENoAction 模板通道不关心,不加载也不清除
DepthStencilAccess FExclusiveDepthStencil::DepthWrite_StencilNop 这个 pass 对深度是可写 的,对模板不操作

这里我多嘴问了下,为啥 "深度(Depth)" 和 "模板(Stencil)" 总是一起出现?

AI的回答:(仅供参考)

因为GPU 硬件上,深度和模板就是存在同一张纹理里的

典型的深度/模板格式(比如你用的 PF_DepthStencil)在显存里的布局是:每个像素 32 位,其中 24 位存深度,8 位存模板 。它们共享一块显存、共享一个 DSV(Depth Stencil View),是硬件层面的绑定关系。

所以在 API 层面(D3D、Vulkan 等),绑定深度目标时必然同时绑上了模板------因为它们物理上就是同一张纹理的不同通道。UE 的 FDepthStencilBinding 需要你分别指定两者的 LoadAction 和访问权限,就是因为虽然共享一张纹理,但逻辑上是两个独立功能,你可能只用深度不用模板(你当前的情况),也可能两个都用。

4. PSO:启用深度写入和深度测试

PSO的DepthStencilState发生如下变化:

让AI解释一下:(仅供参考)

第一个参数 truebEnableDepthWrite

控制是否往深度缓冲写入新的深度值

效果
true 像素通过深度测试后,把它的深度值写进深度缓冲,后续像素会和这个新值比较
false 只做深度测试(比较),但不更新深度缓冲

你之前画全屏四边形时用的是 false------因为不需要深度,只是拷贝纹理。现在画 3D 三角形需要 true,这样先画的三角形会把深度写进去,后画的三角形再和它比较,才能实现遮挡。

第二个参数 CF_DepthNearOrEqual:深度测试的比较函数

决定**"新像素的深度值和深度缓冲里已有的值怎么比,才算通过测试"**。

UE 使用 Reversed-Z (近处 z=1,远处 z=0),所以"离相机更近"意味着"z 值更大"。CF_DepthNearOrEqual 会自动适配这个约定:

比较函数 含义 通过条件(Reversed-Z 下实际操作)
CF_DepthNearOrEqual 比已有的更近或等距才通过 新z >= 旧z
CF_DepthNear 比已有的严格更近才通过 新z > 旧z
CF_DepthFartherOrEqual 比已有的更远或等距才通过 新z <= 旧z
CF_DepthFarther 比已有的严格更远才通过 新z < 旧z
CF_Always 永远通过,不做比较 无条件通过
CF_Never 永远不通过 无条件拒绝

你用 CF_DepthNearOrEqual 就是标准的"近处覆盖远处"------这是绝大多数 3D 渲染的默认选择,原版 base pass 用的也是这个。

和之前的对比

旧(全屏四边形) 新(3D 三角形)
写深度 false true
比较函数 CF_Always(不做测试) CF_DepthNearOrEqual(近处通过)

5. 让AI解释下GPU相关的行为

问AI::

目前我已经看完了新加的所有的C++代码了,其实只是增加了深度缓冲纹理并且在PSO上指定了。

那实际的深度比较逻辑,应该是GPU去做的吧。请帮我详细解释下这里GPU的行为,如果能结合目前的代码改动那最好了。

AI回答:(仅供参考)

是的:深度比较和(在你配置下)深度写入,都是 GPU 固定功能管线里做的,不是你在 C++ 里手写循环。你这边做的是:准备好深度纹理、告诉 RDG「这个 pass 要绑深度」、在 PSO 里打开「测深度 + 写深度」。下面按数据流把 GPU 在干什么说清楚,并和你现在的代码对上。


1) 深度值从哪来(和你 shader 的关系)

顶点着色器里:

16:16:Engine/Shaders/Private/Tools/SceneLearning.usf 复制代码
OutPosition = mul(float4(InPosition, 1.0f), ViewProjectionMatrix);

输出的是 裁剪空间float4(x, y, z, w)SV_POSITION)。

之后 GPU 自动做:

  1. 透视除法:得到 NDC 里的 ((x', y', z'))(以及用于插值的 (w) 等)。
  2. 视口变换 :把 NDC 映射到当前 SetViewport 里的像素坐标。

用于深度测试的"片元深度" 来自这一步之后管线里的深度值(在 UE 的 Reversed-Z 投影下,通常离相机越近,这个值越大 )。你没有在 PS 里改深度,所以用的就是光栅化阶段插值后的这个深度。

也就是说:深度比较用的数,间接来自你的 ViewProjectionMatrix × 世界坐标,但比较本身不在 shader 里写逻辑。


2) 你 C++ 里做的事,在 GPU 上对应什么

(1)FDepthStencilBinding + DepthWrite_StencilNop

含义是:这个 pass 把 DepthTexture 绑成当前渲染目标的深度/模板表面 ,并且声明「要写深度、不动模板」。

GPU 侧会:在光栅化/输出合并阶段,读写这张纹理里每个像素对应的深度存储

(2)DepthLoadAction::ELoad

Pass 开始时,深度缓冲里已经是你在 AddClearDepthStencilPass 里清成的「最远」值。

GPU 不会 在深度测试前把整张图清成别的值(除非你用的是 Clear);每个像素会和「当前缓冲里已有的深度」比较。

(3)TStaticDepthStencilState<true, CF_DepthNearOrEqual>

这是 深度测试 + 深度写入 的总开关:

  • true :深度测试通过 的片元,把新深度写回深度缓冲。
  • CF_DepthNearOrEqual :在 UE 的 Reversed-Z 下,等价于「新片元不比已有深度更远」才通过(更近或相等都可以通过)。

未通过 :该片元被丢弃,通常不会执行 PS(也不会写颜色、不写深度)。


3) 对你这次 DrawPrimitive(0, 2, 1) 的具体行为

一次 draw 会画 两个三角形、共 6 个顶点(两个在空间里交叉)。

每一个 最终被光栅化覆盖到的子像素/片元(可理解成「一个像素里可能被三角形盖住的一个采样点」),GPU 大致做:

  1. 用插值得到该片元的 深度 (znewz_{new}znew)
  2. 读当前绑定的深度纹理在该像素处的 (zoldz_{old}zold)(最初是清除后的最远值)。
  3. 用 PSO 里的比较函数判断:在 Reversed-Z + CF_DepthNearOrEqual 下,相当于「(znewz_{new}znew) 是否 ≥ (zoldz_{old}zold)」(语义是「更近或一样近」)。
  4. 若失败:丢弃,不写颜色、不写深度。
  5. 若成功 :执行 PS(你的 MainPS 只输出颜色),然后把 颜色 写到 RenderTargets[0],把 (z_{new}) 写到深度缓冲(因为开启了深度写入)。

绘制顺序 :两个三角形在同一个 draw 里按顶点顺序光栅化,本质上是按图元顺序处理。交叉区域里,后光栅化、且深度更近的那一侧会覆盖先画的那一侧的颜色和深度------这就是你在画面上看到的遮挡。


4) 和「只有颜色 RT、CF_Always」时对比

项目 之前(无深度) 现在
深度纹理 未绑定 绑定 DepthTexture
深度测试 CF_Always(永远过) CF_DepthNearOrEqual
深度写入
交叉三角形 后画的像素会直接盖颜色(画家算法式) 由深度决定谁可见,更符合 3D 遮挡

5) 一句话总结

CPU/RDG/PSO 只负责「绑哪张深度图、清成什么初值、用什么比较、过不过就写不写」;每个片元拿自己的插值深度和缓冲里已有的深度比一比,是 GPU 固定管线在光栅化之后、像素着色器前后(实现因硬件略异,但语义如此)自动完成的。 你的 SceneLearning.usf 只负责给出裁剪空间位置(从而间接决定深度),没有写 SV_Depth 之类的覆盖,所以全程用的就是这套默认深度管线。

相关推荐
YakSue16 小时前
AI教我UE渲染-学习笔记(7):PS输出到多个RT
unreal engine
YakSue5 天前
AI教我UE渲染-学习笔记(5):测试画一个3D三角形
unreal engine
暗光之痕8 天前
Unreal5 研究笔记 蓝图自定义节点
笔记·unreal engine
YakSue11 天前
AI教我UE渲染-学习笔记(4):测试新写个全屏复制贴图的shader与pass
unreal engine
JCHwa15 天前
UE5 GAS 源码深度解析 | 第2篇:AttributeSet 源码导读
游戏开发·unreal engine
侑虎科技1 个月前
在UE5中,预测脚步IK实现-PredictFootIK
性能优化·unreal engine
暗光之痕2 个月前
Unreal5研究笔记 Actor的生命周期函数
笔记·unreal engine
哎呦哥哥和巨炮叔叔2 个月前
Unreal Engine 是否支持光线追踪?UE5 光线追踪原理与性能解析
ue5·unreal engine·光线追踪·lumen·实时渲染·渲染101云渲染·ue云渲染
DoomGT2 个月前
Physics Simulation - Hit Event的触发机制
ue5·游戏引擎·虚幻·虚幻引擎·unreal engine