(整)Unreal渲染模块 框总览
Posted baipao-xd
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(整)Unreal渲染模块 框总览相关的知识,希望对你有一定的参考价值。
@author: 黑袍小道
随缘查看
说明
由于搬山的渲染这部分担心自己理解错误,故而搬移官方下,后面整个完成再反过来更新
(这当且仅当做Unreal的帮助文档)。
图形编程
模块
渲染器代码存在于其自身的模块中。此模块将编译为非单块版本的一个 dll 文件。这可以使迭代更快,因为在渲染代码变更时无需重新链接整个应用程序。渲染器模块取决于引擎,因为其拥有许多向引擎的回调。然而当引擎需要调用渲染器中的某些代码时,这会通过某个接口来完成,通常为 IRendererModule 或 FSceneInterface。
场景代表
在 UE4 中,渲染器所见的场景由基本组件和 FScene 中存储的多种其他结构的列表定义。将维护一个基元的八叉树,用于加速空间查询。
主要场景类
UE4 中拥有和游戏线程并行运行的渲染线程。
主要的类为:
类 | 描述 |
UWorld | 包含多个可交互的 Actor 和组件的世界场景。关卡可以流送进入和退出世界场景,且程序中可以同时有多个世界场景处于激活状态。 |
ULevel | 一同加载/卸载并保存在同一地图文件中的 Actor 和组件合集。 |
USceneComponent | 需要添加到 FScene 中的任意对象的基础类,如光照、网格体、雾等。 |
UPrimitiveComponent | 可渲染或进行物理交互的任意资源的基础类。也可以作为可视性剔除的粒度和渲染属性规范(投射阴影等)。与所有 UObjects 一样,游戏线程拥有所有变量和状态,渲染线程不应直接对其进行访问。 |
ULightComponent | 代表光源。渲染器负责计算和添加其对场景的贡献。 |
FScene | UWorld 的渲染器版本。对象仅在其被添加到 FScene(注册组件时调用)后才会存在于渲染器中。渲染线程拥有 FScene 中的所有状态,游戏线程无法直接对其进行修改。 |
FPrimitiveSceneProxy | UPrimitiveComponent 的渲染器版本,为渲染线程映射 UPrimitiveComponent 状态。存在于引擎模块中,用于划分为子类以支持不同类型的基元(骨架、刚体、BSP 等)。实现某些非常重要的函数,如 GetViewRelevance、DrawDynamicElements 等。 |
FPrimitiveSceneInfo | 内部渲染器状态(FRendererModule 实现专有),对应于 UPrimitiveComponent 和 FPrimitiveSceneProxy。存在于渲染器模块中,因此引擎看不到它。 |
FSceneView | 单个视图到一个 FScene 的引擎代表。视图可以通过对 FSceneRenderer::Render 的不同调用的不同视图来渲染(多编辑器视口)或通过对 FSceneRenderer::Render 的同一调用中的多个视图来渲染(分屏游戏)。为每个帧构建新视图。 |
FViewInfo | 视图的内部渲染器代表,存在于渲染器模块中。 |
FSceneViewState | ViewState 存储有关在多个帧中需要的某个视图的私有渲染器信息。在游戏中,每个 ULocalPlayer 只有一个视图状态。 |
FSceneRenderer | 为每个帧创建的类,用于封装跨帧的临时对象。 |
下面按其所在的模块列出了各种主类。
引擎模块 | 渲染器模块 |
UWorld | FScene |
UPrimitiveComponent / FPrimitiveSceneProxy | FPrimitiveSceneInfo |
FSceneView | FViewInfo |
ULocalPlayer | FSceneViewState |
ULightComponent / FLightSceneProxy | FLightSceneInfo |
以及相同类(按哪个线程对其状态拥有所有权进行排列)。
游戏线程 | 渲染线程 |
UWorld | FScene |
UPrimitiveComponent | FPrimitiveSceneProxy / FPrimitiveSceneInfo |
| FSceneView / FViewInfo |
ULocalPlayer | FSceneViewState |
ULightComponent | FLightSceneProxy / FLightSceneInfo |
材质类
类 | 描述 |
FMaterial | 连接用于渲染的材质的接口。可用于访问材质属性(如混合模式)。包含被渲染器用于检索个体着色器的着色器地图。 |
FMaterialResource | UMaterial 的 FMaterial 接口实现。 |
FMaterialRenderProxy | 材质在渲染线程上的代表。可用于访问 FMaterial 接口和各个标量、向量和纹理参数。 |
UMaterialInterface | [abstract] 用于材质功能的游戏线程接口。用于检索用于渲染的 FMaterialRenderProxy 和用作来源的 UMaterial。 |
UMaterial | 材质资源。授权为节点图形。计算用于着色、设置混合模式等的材质属性。 |
UMaterialInstance | [abstract] UMaterial 的实例。使用 UMaterial 中的节点图形,但提供不同参数(标量、向量、纹理、静态切换)。每个实例都有一个父项 UMaterialInterface。因此,材质实例的父项可能是 UMaterial 或另一个 UMaterialInstance。这会形成一个链,最终通往 UMaterial。 |
UMaterialInstanceConstant | 只能在编辑器中修改的 UMaterialInstance。可以提供标量、向量、纹理和静态开关参数。 |
UMaterialInstanceDynamic | 可以在运行时修改的 UMaterialInstance。可提供标量、向量和纹理参数。无法提供静态开关参数,且无法成为另一 UMaterialInstance 的父项。 |
基元组件和代理
基元(就是Prim)组件是可视性和相关性确定的基本单位。例如,遮蔽和视锥剔除都是按基元进行的。因此在设计系统时,考虑组件的大小十分重要。每个组件都有一个边界,用于多种操作,如剔除、阴影投射和光照影响确定。
组件只有在注册之后才会对场景(以及渲染器)可见。
更改组件属性的游戏线程代码必须调用组件上的 MarkRenderStateDirty(),将更改传播到渲染线程。
FPrimitiveSceneProxy 和 FPrimitiveSceneInfo
FPrimitiveSceneProxy 是 UPrimitiveComponent 的渲染线程版本,用于根据组件类型划分子类。
它存在于引擎模块中,并在渲染通道中调用函数。
FPrimitiveSceneInfo 是基元组件状态,为渲染器模块私有。
重要的 FPrimitiveSceneProxy 方法
函数 | 描述 |
GetViewRelevance | 在帧的开始从 InitViews 调用,并返回填充的 FPrimitiveViewRelevance。 |
DrawDynamicElements | 调用,以便在某代理相关的任何通道中绘制该代理。仅在代理表示自己拥有动态相关性时调用。 |
DrawStaticElements | 调用以在基元与游戏线程相连时提交代理的 StaticMesh 元素。仅在代理表示自己拥有静态相关性时调用。 |
场景渲染顺序
渲染器按照其希望将数据整合给渲染目标的顺序处理场景。例如,仅 Depth 的通道会比 Base 通道先渲染,先填充 Heirarchical Z (HiZ),从而降低基础通道中的着色消耗。此顺序是按通道函数在 C++ 中调用的顺序静态决定的。
相关性
FPrimitiveViewRelevance 是说明通道与基元相关的信息。基元可能有存在不同相关性的多个元素,因此 FPrimitiveViewRelevance 相当于一个所有元素的相关性的逻辑 OR。这表示基元可以同时存在不透明和透明相关性,或动态和静态相关性;它们并非相互排斥。
FPrimitiveViewRelevance 还会显示基元是否需要使用动态 (bDynamicRelevance) 和/或静态 (bStaticRelevance) 渲染路径。
绘制规则
绘制规则包括通过通道特定的着色器渲染网格体的逻辑。它们使用 FVertexFactory 接口来抽取网格体类型,并使用 FMaterial 接口来抽取材质详情。在最底层,一条绘制规则会负责一组网格体材质着色器以及一个顶点工厂,将顶点工厂的缓冲区与渲染硬件接口 (RHI) 绑定,将网格体材质着色器与 RHI 绑定,设置适当的着色器参数,然后执行 RHI 绘制调用。
绘制规则方法
函数 | 描述 |
Constructor | 从给定的顶点工厂和材质着色器地图,并存储这些引用。 |
CreateBoundShaderState | 为绘制规则创建 RHI 边界着色器状态。 |
Matches/Compare | 提供排列绘制规则与静态绘制列表中的其他项目的方法。Matches 必须比较 DrawShared 依赖的所有因素。 |
DrawShared | 设置在从 Matches 返回 True 的绘制规则之间一致的 RHI 状态。例如,大多数绘制规则会为材质和顶点工厂排序,因此着色器参数只依赖可以设置的材质,并且可以绑定特定于该顶点工厂的顶点缓冲区。应尽可能在此处设置状态,而非在 SetMeshRenderState 设置,因为 DrawShared 在静态渲染路径中调用较少。 |
SetMeshRenderState | 设置特定于此网格体的 RHI 状态,或 DrawShared 中未设置的任何项目。这比 DrawShared 调用的次数多得多,因此此处性能非常重要。 |
DrawMesh | 实际发出 RHI 绘制调用。 |
渲染路径
UE4 拥有动态路径(能够提供更多的控制,但转换较慢)和静态渲染路径(缓存尽可能靠近 RHI 级别的场景转换)。差异基本上是整体上的,因为它们都在最底层使用绘制规则。应确保各个渲染通道(绘制规则)在需要时能够同时处理两个渲染路径。
动态渲染路径
动态渲染路径使用 TDynamicPrimitiveDrawer 并对每个要渲染的基元场景代理调用 DrawDynamicElements。需要使用动态路径来渲染的一组基元通过 FViewInfo::VisibleDynamicPrimitives 来跟踪。每个渲染通道都需要在此阵列上迭代,并调用各个基元上的 DrawDynamicElements。随后,代理的 DrawDynamicElements 需按需要组合出多个 FMeshElements,并将其随 DrawRichMesh 或 TDynamicPrimitiveDrawer::DrawMesh 提交。这样最终会创建一个新的临时绘制规则,调用 CreateBoundShaderState、DrawShared、SetMeshRenderState 以及 DrawMesh。
静态渲染路径
静态渲染路径通过静态绘制列表实现。网格体在连接到场景时会插入到绘制列表中。在插入过程中,将调用代理上的 DrawStaticElements,以收取 FStaticMeshElements。随后将随 CreateBoundShaderState 的结果创建并存储一个绘制规则实例。新的绘制示例将根据其 Compare 和 Matches 函数排序,并插入到绘制列表中的适当位置(参见 TStaticMeshDrawList::AddMesh)。在 InitViews 中,包含静态绘制列表中的可见性数据的 bitarray 会初始化并传递到 TStaticMeshDrawList::DrawVisible(实际绘制绘制列表的地方)。DrawShared 只会对所有相互匹配的绘制规则调用一次,而 SetMeshRenderState 和 DrawMesh 会对每个 FStaticMeshElement(参见 TStaticMeshDrawList::DrawElement)调用。
静态渲染路径会将许多工作移动连接时间,这会大大加快渲染时的场景转换。在渲染线程上针对静态网格体时,静态绘制列表的渲染会快 3 倍,从而允许场景中出现多得多的静态网格体。由于静态绘制列表会在连接时间缓存数据,因此它们仅能缓存与视图无关的状态。很少重新连接但经常需要渲染的基元非常适合静态绘制列表。
静态渲染路径可能会暴露出 bug,因为它每个状态桶只调用 DrawShared 一次。这些 bug 可能会很难发现,因为它们取决于场景中网格体的渲染顺序和连接顺序。特别的视图模式(如仅光照、无光照等)会强制所有基元使用动态路径,因此如果在强制动态渲染路径时 bug 消失,则其很可能是由于某绘制规则的 DrawShared 和/或 Matches 函数的错误实现而出现的。
总体渲染顺序
下面将说明从 FDeferredShadingSceneRenderer::Render 开始渲染一个帧时的控制流程:
操作 | 描述 |
GSceneRenderTargets.Allocate | 按需要重新分配全局场景渲染目标,使其对当前视图足够大。 |
InitViews | 通过多种剔除方法为视图初始化基元可见性,设立此帧可见的动态阴影、按需要交叉阴影视锥与世界场景(对整个场景的阴影或预阴影)。 |
PrePass / Depth only pass | RenderPrePass / FDepthDrawingPolicy。渲染遮挡物,对景深缓冲区仅输出景深。该通道可以在多种模式下工作:禁用、仅遮蔽,或完全景深,具体取决于活动状态的功能的需要。该通道通常的用途是初始化 Hierarchical Z 以降低 Base 通道的着色消耗(Base 通道的像素着色器消耗非常大)。 |
Base pass | RenderBasePass / TBasePassDrawingPolicy。渲染不透明和遮盖的材质,向 GBuffer 输出材质属性。光照图贡献和天空光照也会在此计算并加入场景颜色。 |
Issue Occlusion Queries / BeginOcclusionTests | 提出将用于下一帧的 InitViews 的延迟遮蔽查询。这会通过渲染所查询物体周围的相邻的框、有时还会将相邻的框组合在一起以减少绘制调用来完成。 |
Lighting | 阴影图将对各个光照渲染,光照贡献会累加到场景颜色,并使用标准延迟和平铺延迟着色。光照也会在透明光照体积中累加。 |
Fog | 雾和大气在延迟通道中对不透明表面进行逐个像素计算。 |
Translucency | 透明度累加到屏外渲染目标,在其中它应用了逐个顶点的雾化,因而可以整合到场景中。光照透明度在一个通道中计算最终光照以正确融合。 |
Post Processing | 多种后期处理效果均通过 GBuffers 应用。透明度将合成到场景中。 |
以上是相当简单概略的介绍。如需了解详情,请通读相关代码或输出的"profilegpu"日志。
渲染硬件接口 (RHI)
RHI 是平台特定的图形 API 之上的一个薄层。UE4 中的 RHI 抽象层尽可能低,这样大多数功能都能以与平台无关的代码写成,从而能够在支持所需功能层级的任何平台上运行。
功能集将量化到 ERHIFeatureLevel 中,降低复杂程度。如果平台无法支持某个功能层级所需的全部功能,则其必须降低层级,直至能够全部支持。
功能层级 | 描述 |
SM5 | 通常对应于 D3D11 Shader Model 5,但由于 OpenGL 4.3 限制,仅有 16 种纹理可以使用。支持曲面细分、计算着色器和立方体贴图阵列。支持延迟着色路径。 |
SM4 | 对应 D3D11 Shader Model 4,这与 SM5 基本相同,但没有曲面细分、计算着色器和立方体贴图阵列。支持延迟着色路径。不支持眼适应,因为其使用计算着色器。 |
ES2 | 对应大多数 OpenGL ES2 移动设备支持的功能。使用削减向前着色路径。 |
渲染状态分组
渲染状态根据其影响的流程部分而分组。例如,RHISetDepthState 可设置所有与景深缓冲相关的状态。
渲染状态默认值
由于渲染状态数量众多,要在每次绘制之前对它们全部设置一遍是不现实的。为此,UE4 具有隐性设置的一组状态,它们被认为是设置为了默认值(因此在变更后必须恢复为默认值),另外还有一组少得多的状态需要显性设置。没有隐性默认值的状态有:
- RHISetRenderTargets
- RHISetBoundShaderState
- RHISetDepthState
- RHISetBlendState
- RHISetRasterizerState
- 由 RHISetBoundShaderState 设置的着色器的任何依赖性
其他所有状态均视为已设置为其默认值(即相关 TStaticState 的定义,如默认的蜡纸模板状态由 RHISetStencilState(TStaticStencilState<>::GetRHI()) 设置。
以上是关于(整)Unreal渲染模块 框总览的主要内容,如果未能解决你的问题,请参考以下文章