UE 公式ドキュメントの翻訳: グラフィックス プログラミングの概要

元のアドレス: https://docs.unrealengine.com/5.2/en-US/graphics-programming-overview-for-unreal-engine/
(このドキュメントは中国語で正式に入手可能です。ここで再度翻訳する理由は次のとおりです) : 1 つは、元の翻訳の一部の用語に慣れていないため、意味が正確に伝わらない可能性があること、2 つ目は、より注意深く読めるように再翻訳したいことです。)

: このドキュメントはバージョン 5.2 のものですが、DrawingPolicy などの内容の多くは古いようです (詳細については、「Unreal Engine 4.22 のメッシュ描画パイプライン変換ガイド」を参照してください)。そのため、今はアイデアを学ぶことしかできず、コードの詳細をあまり参照できないのが残念です。

初心者向けガイド

Unreal Engine には大量のレンダリング コードがあるため、レンダリング プロセスを高レベルですばやく観察するのは困難です。FDeferredShadingSceneRenderer::Renderコードを読み取るときは、レンダリング スレッドで新しいフレームがレンダリングされる場所から始めるとよいでしょう。また、GPU プロファイリング コマンドを実行して、Draw イベントを確認すると役立ちます。次に、Visual Studio でファイル内でDraw イベントの名前を検索し、対応する C++ 実装を見つけることができます。

レンダリング開発を行うときに役立ついくつかのコンソール コマンド (?パラメーターとして使用され、現在の状態にパラメーターがない場合にヘルプが表示されます)

コンソールコマンド 効果
stat unit このフレームの全体的な継続時間、ゲーム スレッドの継続時間、レンダリング スレッドの継続時間、GPU の継続時間も表示されます。この中で最も長いものがボトルネックです。ただし、GPU(翻訳者の記録:ここでは中国語版では「CPU」と書いてありますが、どちらが間違っていますか?)の持続時間にはアイドル時間が含まれるため、「それが最長であり、それに匹敵するものが他にない」場合にのみボトルネックになります。
Ctrl+ Shift+.またはrecompileshaders changed .usf ファイルの最後の保存以降に変更されたシェーダーを再コンパイルします。これはロード後にも自動的に行われます。
profilegpu 現在のビューのレンダリングに費やした GPU 時間を測定します。結果はポップアップ UI またはエンジン ログで表示できます。
VisまたVisualizeTexture さまざまな RenderTarget の内容を視覚的に表示し、bmp ファイルとして保存します。
showバツ 特定のショーフラグをオンまたはオフにします。showさまざまなショーフラグとその現在のステータスをリストするために使用します。エディターでは、ビューポートの UI を使用できます。
pause ゲームを一時停止しますが、レンダリングは続行します。シミュレーションはすべて停止されます。
slomoバツ ゲーム速度を変更します。このコマンドは、シミュレーションをスキップせずに解析を実行する際の時間を遅くするのに役立ちます。例えばslomo .01
debugcreateplayer 1 分割画面のテストに役立ちます。
r.DumpShaderDebugInfo に設定すると、1コンパイルされたすべてのシェーダーのデバッグ情報が GameName/Saved/ShaderDebugInfo にダンプされます。
r.SetRes 現在のゲームビューの表示解像度を設定します。エディターでは機能しません。

レンダリング開発を行うときに役立ついくつかのコマンド ライン パラメーター:
(翻訳者はここでは詳しくないため、翻訳しません)

モジュール

レンダラー コードは独自のRenderer モジュール内に存在します。つまり、単一の dll ファイルにコンパイルされます (非モノリシック コンパイルを使用する場合)。これにより、コードの変更をレンダリングするときにアプリケーション全体を再リンクする必要がなくなるため、反復処理が高速化されます。

レンダラ モジュールにはエンジンへのコールバックが多数あるため、エンジン モジュール (エンジン モジュール) に依存します。ただし、エンジンがレンダラー内のコードを呼び出す必要がある場合、これはインターフェイス (通常は IRendererModule または FSceneInterface) を通じて行われます。

シーンの内容の表現

UE では、レンダラーによって表示されるシーンは、FScene に保存されているプリミティブ コンポーネントとその他のさまざまな構造のリストによって定義されます。空間クエリを高速化するために、プリミティブ オクツリーが維持されます。

シーンコンテンツのメインクラス

UE には、ゲーム スレッドと並行して実行されるレンダリング スレッドがあります。ゲーム スレッドとレンダリング スレッドにまたがるほとんどのクラスは、どのスレッドが対応するデータの所有権を持っているかに応じて 2 つの部分に分割されます。

主なクラスは次のとおりです。

親切 説明
ユーワールド 相互に作用する複数のアクターとコンポーネントを含むワールド。レベルはワールドの内外にストリーミングできます。プログラム内で複数のワールドを同時にアクティブにすることができます。
Uレベル 一緒にロード/アンロードされ、同じマップ ファイルに保存されるアクタとコンポーネントのコレクション。
USシーンコンポーネント FScene に追加する必要があるオブジェクト (光源、メッシュ、フォグなど)。
UPrimitiveコンポーネント レンダリングまたは物理的に操作できるオブジェクト。可視性のカリングやレンダリングのプロパティ設定 (影を付けるかどうかなど) の単位としても使用されます。すべての UObject と同様、ゲーム スレッドはすべての変数とデータを所有しており、レンダリング スレッドから直接アクセスすべきではありません。
ULightコンポーネント 光源を表します。レンダラーは、その影響を計算してシーンに追加する責任があります。
Fシーン UWorld のレンダラー バージョン。オブジェクトは、FScene (コンポーネントの登録時に呼び出されます) に追加されるまで、レンダラーには存在しません。レンダリング スレッドは FScene のすべてのデータを所有しており、ゲーム スレッドはそれを直接変更できません。
FPrimitiveSceneProxy UPrimitiveComponent のレンダラ バージョン。これは、レンダリング スレッドの UPrimitiveComponent のデータをマップします。このクラスはエンジン モジュールで定義され、さまざまなタイプのプリミティブ (ボーン、剛体、BSP など) をサポートするためにサブクラスに分割されます。GetViewRelevance、DrawDynamicElements などの非常に重要な関数を実装する必要があります。
FPrimitiveSceneInfo UPrimitiveComponent および FPrimitiveSceneProxy に対応する内部レンダラー状態 (FRendererModule の実装用)。レンダラー モジュール内に存在するため、エンジンはそれを認識しません。
FSceneView エンジンによって表される FScene 内のビュー。シーンは、異なる FSceneRenderer::Render を呼び出して異なるビューをレンダリングしたり (マルチビュー エディター)、1 つの FSceneRenderer::Render で複数のビューをレンダリングしたり (分割画面ゲーム) できます。フレームごとに新しいビューが構築されます。
FViewInfo レンダラーによって内部的に表されるビューは、レンダラー モジュール内に存在します。
FSceneViewState ViewState は、複数のフレームにわたって必要なビューのレンダラーの内部情報を保存します。ゲームでは、ULocalPlayer ごとに ViewState が 1 つだけ存在します。
FSceneRenderer フレーム全体で一時オブジェクトをカプセル化するためにフレームごとに作成されるクラス。

これらのクラスが定義されているモジュールを以下に示します。この情報は、リンカーの問題のトラブルシューティングを行う場合に非常に重要です。

エンジンモジュール レンダラーモジュール
ユーワールド Fシーン
UPrimitiveComponent / FPrimitiveSceneProxy FPrimitiveSceneInfo
FSceneView FViewInfo
ULocalPlayer FSceneViewState
ULightComponent / FLightSceneProxy フライトシーン情報

以下に、どのスレッドがこれらのクラスのデータを維持するかを示します。競合状態を避けるために、作成しているコードのデータがどのスレッドに属しているかを必ず理解してください

ゲームスレッド レンダリングスレッド
ユーワールド Fシーン
UPrimitiveコンポーネント FPrimitiveSceneProxy / FPrimitiveSceneInfo
- FSceneView / FViewInfo
ULocalPlayer FSceneViewState
ULightコンポーネント FLightSceneProxy / FLightSceneInfo

マテリアルクラス

親切 説明
F素材 用于渲染的材质的接口。可用于访问材质属性(如混合模式)。包含一个 Shader Map,渲染器将使用这个Map来得到特定的Shader。
FMaterialResource 针对于 UMaterial 的 FMaterial 接口实现。
FMaterialRenderProxy 材质在渲染线程上的表示。可用于访问 FMaterial 接口和各个标量、向量和纹理参数。
UMaterialInterface 这是个抽象类,是游戏线程上的材质功能的接口。用于得到渲染用的 FMaterialRenderProxy 和作为数据源的 UMaterial。
UMaterial 材质的数据源。通过节点网络进行编辑制作。计算出用于着色、设置混合模式、等等的材质属性。
UMaterialInstance 这是个抽象类,表示 UMaterial 的实例。实例们使用同一套 UMaterial 节点网络,但提供不同的参数(标量、向量、纹理、静态开关)。每个实例都有一个父项 UMaterialInterface。因此,材质实例的父项可能是 UMaterial,但也可能是另一个 UMaterialInstance。所以这会形成一个链,但链的最终端是 UMaterial。
UMaterialInstanceConstant 只能在编辑器中修改的 UMaterialInstance。可以提供标量、向量、纹理和静态开关参数。
UMaterialInstanceDynamic 可以在运行时修改的 UMaterialInstance。可提供标量、向量和纹理参数。无法提供静态开关参数,且无法成为另一 UMaterialInstance 的父项。

Primitive Component

Primitive组件是 “确定可视性和相关性” 的基本单位。举个例子,“occlusion” 和 “视锥剔除” 都是以Primitive为单位进行的。因此在设计系统时,考虑组件的大小十分重要。每个组件都有一个边界,用于多种操作如:剔除、阴影投射、确定光照影响、等等。

组件只有在注册之后才会对场景(以及渲染器)可见。如果游戏线程代码更改了组件属性,那么必须调用组件上的 MarkRenderStateDirty(),才能将更改传递给渲染线程。

FPrimitiveSceneProxy 和 FPrimitiveSceneInfo

FPrimitiveSceneProxy 是 UPrimitiveComponent 的渲染线程版本,根据Component的类型划分子类。它定义在引擎模块中,并在渲染时有函数调用。FPrimitiveSceneInfo 是PrimitiveComponent的状态,定义在渲染器模块内部,对外不可见。

重要的 FPrimitiveSceneProxy 的函数

函数 描述
GetViewRelevance 在帧的开始从 InitViews 调用,填充一个 FPrimitiveViewRelevance 结构并返回。
DrawDynamicElements (假如Proxy表示自己拥有动态的相关性)任何此Proxy相关的pass中都会调用此函数来绘制此Proxy。
DrawStaticElements (假如Proxy表示自己拥有静态的相关性)当Primitive在游戏线程中被添加时,将会调用Proxy的此函数来提交StaticMesh元素。

场景渲染顺序

渲染器按照 “期望将数据合并到RenderTarget上的顺序” 处理场景。例如,“仅深度” 的Pass会比 BasePass先渲染,这样就可以得到 Heirarchical Z (HiZ) 数据,从而降低BasePass中的着色消耗。此顺序是由Pass函数在 C++ 中调用的顺序静态决定的。

相关性

FPrimitiveViewRelevance 是说明了哪些 Pass 与 Primitive 相关。Primitive 可能有多个元素,且元素们有不同的相关性,因此 FPrimitiveViewRelevance 相当于所有元素的相关性的逻辑 OR (译者注:元素中只要有任意一个是相关的,那么就是真,只有都不相关是才是假)。这表示一个 Primitive 可以同时具有不透明和透明的相关性,有动态和静态的相关性,这并不互斥。

FPrimitiveViewRelevance 还会表明 Primitive 是否需要使用动态 (bDynamicRelevance) 和/或静态 (bStaticRelevance) 渲染路径。

Drawing Policy

Drawing Policy 包括了 “通过特定的着色器” 来渲染mesh的逻辑。它使用 FVertexFactory 接口来抽象出mesh的类型,并使用 FMaterial 接口来抽象材质的数据。

在最底层,一个 Drawing Policy 会负责持有一组 “mesh材质着色器” 以及一个 “顶点工厂(vertex factory)”,它会将顶点工厂的 buffer 与 RHI(渲染硬件接口) 绑定,将mesh材质着色器与 RHI 绑定,设置适当的着色器参数,然后执行 RHI 的 DrawCall。

Drawing Policy 的函数

函数 描述
构造函数 根据给定的顶点工厂和材质ShaderMap,找到合适的Shader,并存储这些引用。
CreateBoundShaderState 为Drawing Policy 创建 RHI 绑定的 shader state。
Matches/Compare 提供一个函数可以让静态绘制列表(static draw lists)中的 Drawing Policy 可以相互比较。Matches函数 必须比较 DrawShared 依赖的所有因素。
DrawShared 设置 DrawingPolicy之间Matches函数返回True 的 RHI state。例如,most drawing policies sort on material and vertex factory, so shader parameters depending only on the material can be set, and the vertex buffers specific to the vertex factory can be bound。state 应尽可能在此处设置,而非 SetMeshRenderState,因为 DrawShared 在静态渲染路径中调用更少。
SetMeshRenderState 设置 特定于此mesh的(或是任何DrawShared没设置的) RHI state。这比 DrawShared 调用的次数多得多,因此此处性能非常关键。
DrawMesh 实际发出 RHI 的 DrawCall。

渲染路径(Rendering paths)

UE 拥有动态渲染路径(能够提供更多的控制,但遍历较慢)和静态渲染路径(缓存尽可能接近 RHI 级别)。两者差异基本是上层的,因为在最底层它们都使用Drawing Policy。应确保各个渲染Pass(Drawing Policy)在需要时能够同时处理两个渲染路径。

动态渲染路径

动态渲染路径使用 TDynamicPrimitiveDrawer 并对每个要渲染的PrimitiveSceneProxy调用 DrawDynamicElements。

FViewInfo::VisibleDynamicPrimitives会跟踪出需要使用动态路径来渲染的Primitive列表。每个渲染 Pass 都需要遍历此列表,并调用各个Primitive上的 DrawDynamicElements。随后,Proxy的 DrawDynamicElements 按照需要的数目组合出多个 FMeshElements,并将其随 DrawRichMesh 或 TDynamicPrimitiveDrawer::DrawMesh 提交。这样最终会创建一个新的临时 Drawing Policy,调用 CreateBoundShaderState、DrawShared、SetMeshRenderState 、最终是 DrawMesh。

动态渲染路径能够提供很高的灵活性,因为每个Proxy都在 DrawDynamicElements 中有一个回调函数,这样它就可在其中执行该组件特别的逻辑。它的插入消耗极小,但遍历消耗很大,因为不存在 state 排序,且不使用缓存。

静态渲染路径

静态渲染路径通过 “静态绘制列表(static draw lists)” 实现。mesh在加入到场景时会插入到静态绘制列表中。在插入过程中,将调用 Proxy 上的 DrawStaticElements 来收集 FStaticMeshElements。然后随 CreateBoundShaderState 的结果,创建并存储一个DrawingPolicy。新的DrawingPolicy将根据其 Compare 和 Matches 函数排序,并插入到静态绘制列表中的适当位置(参见 TStaticMeshDrawList::AddMesh)。在 InitViews 中,一个包含静态绘制列表中的可见性数据的 位列表(bit array) 会初始化并传递到 TStaticMeshDrawList::DrawVisible 中,也就是实际对列表进行绘制的地方)。DrawShared 对所有相互匹配的DrawingPolicy只会调用一次,而 SetMeshRenderState 和 DrawMesh 会对每个 FStaticMeshElement(参见 TStaticMeshDrawList::DrawElement)调用。

静态渲染路径会将许多工作移动到 “加入场景时”,这会大大加快 “渲染时” 的场景遍历。对于静态mesh,在渲染线程上使用静态绘制列表的渲染会快 3 倍,从而允许场景中出现更多的静态网格体。由于静态绘制列表会在加入场景时缓存数据,因此它们仅能缓存与视图无关的状态。那些很少重新加入场景,但经常需要渲染的Primitive非常适合静态绘制列表。

静态渲染路径可能会出现 bug,因为它对于每个 state bucket 只调用一次 DrawShared。这些 bug 可能会很难调查,因为它们受影响于场景中mesh的渲染顺序和加入顺序。特别的视图模式(如仅光照、无光照等)会强制所有Primitive使用动态路径,因此如果在强制使用动态渲染路径时 bug 消失,则其很可能是由于某DrawingPolicy的 DrawShared 和/或 Matches 函数的错误实现而出现的。

上层的渲染顺序

下面将描述从 FDeferredShadingSceneRenderer::Render 开始渲染一帧的流程:

操作 描述
GSceneRenderTargets.Allocate 如果需要,重新分配全局场景的RenderTarget,使其对当前视图足够大。
InitViews 通过多种剔除方法为视图初始化Primitive的可见性,设立此帧可见的动态阴影、 intersects shadow frustums with the world if necessary (for whole scene shadows or preshadows)。
PrePass / Depth only pass RenderPrePass / FDepthDrawingPolicy。渲染遮挡物,对景深buffer仅输出景深。该Pass可以在多种模式下工作:禁用、仅遮蔽,或完全景深,具体取决于激活的功能需要什么。该Pass通常的用途是初始化 Hierarchical Z 以降低 BasePass 的着色消耗,因为BasePass的像素着色器消耗非常大。
Base pass RenderBasePass / TBasePassDrawingPolicy。渲染不透明和 masked 的材质,向 GBuffer 输出材质属性。光照图贡献和天空光照也会在此计算并加入场景颜色。
Issue Occlusion Queries / BeginOcclusionTests 触发将用于下一帧 InitViews 的延迟遮蔽查询。这会通过渲染所查询物体的包围盒(有时还会将相邻的包围盒组合在一起以减少绘制调用)来完成。
Lighting 各个光照将渲染出ShadowMap ,光照贡献会累加到场景颜色。它混合使用了标准的延迟光照,以及Tiled延迟着色。光照也会在透明光照体积中累加。
Fog 雾和大气在延迟Pass中对不透明表面进行逐像素的计算。
Translucency 半透明物体会累加到屏外的RenderTarget,在那里会应用逐顶点的雾,因而可以合并到场景中。半透明物体的光照在一个单独Pass中计算最终结果,以正确blend。
Post Processing 多种后期处理效果均通过 GBuffers 来应用。这里也将半透明物体合并到场景中。

以上是相当简单概略的介绍。如需了解详情,请阅读相关代码,或者GPU剖析日志。

渲染硬件接口 (RHI)

RHI 是平台专用图形 API 之上的一个层简单的封装。UE 中的 RHI 抽象层尽可能得低,这样大多数功能都能以“与平台无关”得代码写成,从而能够在支持所需FeatureLevel的任何平台上运行。

ERHIFeatureLevel 将对功能进行划分,以降低复杂度。如果平台无法支持某个 FeatureLevel 所需的全部功能,则其必须降低层级,直至找到一个能全部支持功能的层级。

FeatureLevel 描述
SM5 大体上对应于 D3D11 Shader Model 5,但由于 OpenGL 4.3 限制,纹理仅可以同时使用 16 个。支持曲面细分、计算着色器和CubeMap数组。支持延迟渲染。
SM4 对应 D3D11 的 Shader Model 4,这与 SM5 基本相同,但没有曲面细分、计算着色器和CubeMap数组。支持延迟渲染。不支持 Eye Adaptation(因为其使用计算着色器)。
ES3_1 对应OpenGL ES3.1、Vulkan和Metal支持的功能。

渲染state分组

渲染state会根据其影响的管线部分而分组。例如,RHISetDepthState 可设置所有与景深缓冲相关的state。

渲染state默认值

由于渲染state数量太多了,因此在每次绘制之前对它们全部设置一遍是不现实的。所以 UE 具有隐性设置的一组 state,它们被认为是设置为了默认值(因此,它们在变更后必须恢复为默认值),和另一组少得多的需要显性设置的state。没有隐性默认值的state有:

  • RHISetRenderTargets
  • RHISetBoundShaderState
  • RHISetDepthState
  • RHISetBlendState
  • RHISetRasterizerState
  • 任何 RHISetBoundShaderState 设置的着色器的依赖项

其他所有state均视为已设置为其默认值(即关联的 TStaticState 的定义,如默认的 stencil state 由 RHISetStencilState(TStaticStencilState<>::GetRHI()) 设置。

おすすめ

転載: blog.csdn.net/u013412391/article/details/131495561