Traducir documentos oficiales de la UE: descripción general de la programación de gráficos

Dirección original: https://docs.unrealengine.com/5.2/en-US/graphics-programming-overview-for-unreal-engine/
(Este documento está oficialmente disponible en chino y las razones por las que lo traduje aquí nuevamente) : uno es que no estoy acostumbrado a algunos términos en la traducción original, y es posible que algunos significados no me sean transmitidos con precisión; en segundo lugar, quiero traducirlo nuevamente para poder leerlo con más atención).

Nota : aunque este documento proviene de la versión 5.2, muchos de los contenidos parecen estar desactualizados, como DrawingPolicy (consulte la Guía de conversión de canalización de dibujo de malla para Unreal Engine 4.22 para obtener más información ). Así que ahora solo puedo aprender una idea y no puedo referirme a muchos detalles del código, lo cual es una pena.

Guía para principiantes

Hay mucho código de renderizado en Unreal Engine, por lo que es difícil observar rápidamente el proceso de renderizado a un alto nivel. Al leer código, un buen lugar para comenzar es que FDeferredShadingSceneRenderer::Renderaquí es donde se representa un nuevo marco en el subproceso de representación. Además, puede ser útil ejecutar comandos de creación de perfiles de GPU y ver eventos de Draw. Luego, puede buscar en el archivo el nombre del evento Draw en Visual Studio para encontrar la implementación de C++ correspondiente.

Algunos comandos de consola útiles cuando se realiza el desarrollo de renderizado ( ?la ayuda se puede mostrar cuando se usa como parámetros y el estado actual no tiene parámetros)

comando de consola efecto
stat unit Muestra la duración general de este cuadro, así como la duración del subproceso del juego, la duración del subproceso de representación y la duración de la GPU. El más largo de ellos es el cuello de botella. Sin embargo, la duración de la GPU (registro del traductor: aquí la versión china dice "CPU", ¿qué está mal?) incluye el tiempo de inactividad, por lo que solo es un cuello de botella cuando "es el más largo y no hay otro igual".
Ctrl+ Shift+ .orecompileshaders changed Vuelve a compilar los sombreadores que han cambiado desde la última vez que se guardó el archivo .usf. Esto también sucede automáticamente después de la carga.
profilegpu Mide el tiempo de GPU dedicado a renderizar la vista actual. Los resultados se pueden ver en la interfaz de usuario emergente o en el registro del motor.
VisoVisualizeTexture Muestre visualmente el contenido de varios RenderTargets y guárdelos como archivos bmp.
showX Enciende o apaga una bandera específica. Úselo showpara enumerar las diversas banderas de presentación y su estado actual. En el editor, puede usar la interfaz de usuario en la ventana gráfica.
pause Pausa el juego, pero continúa renderizando. Se detendrá cualquier simulación.
slomoX Cambiar la velocidad del juego. Este comando ayuda a ralentizar el tiempo mientras se realiza el análisis sin saltarse la simulación. Por ejemploslomo .01
debugcreateplayer 1 Útil para probar pantalla dividida.
r.DumpShaderDebugInfo Cuando se establece en , 1la información de depuración de todos los sombreadores compilados se volcará en GameName/Saved/ShaderDebugInfo.
r.SetRes Establece la resolución de visualización de la vista del juego actual. No funciona en el editor.

Algunos parámetros útiles de la línea de comandos al realizar el desarrollo de renderizado:
(El traductor no está familiarizado aquí, por lo que no traduciré)

módulo

El código del renderizador vive en su propio módulo Renderer , lo que significa que se compilará en un solo archivo dll (cuando se usa una compilación no monolítica). Esto puede hacer que las iteraciones sean más rápidas porque no es necesario volver a vincular la aplicación completa cuando cambia el código de representación.

El módulo Renderer depende del módulo Engine (módulo Engine), porque tiene muchas devoluciones de llamada al motor. Sin embargo, cuando el motor necesita llamar a algún código en el renderizador, esto se hará a través de una interfaz, generalmente IRendererModule o FSceneInterface.

Representación del contenido de la escena.

En UE, la escena que ve el renderizador está definida por una lista de componentes primitivos y varias otras estructuras almacenadas en FScene. Se mantendrá un octárbol primitivo para agilizar las consultas espaciales.

La clase principal del contenido de la escena.

UE tiene un subproceso de representación que se ejecuta en paralelo con el subproceso del juego . La mayoría de las clases que abarcan el subproceso del juego y el subproceso de representación se dividirán en dos partes, según el subproceso que tenga la propiedad de los datos correspondientes.

Las principales clases son:

amable describir
UMundo Un mundo que contiene múltiples Actores y Componentes que interactúan entre sí. Los niveles pueden entrar y salir de los mundos. Múltiples mundos pueden estar activos en un programa al mismo tiempo.
Nivel U Una colección de Actores y Componentes que se cargan/descargan juntos y se guardan en el mismo archivo de mapa.
USceneComponent Objetos que deben agregarse a FScene, como fuente de luz, malla, niebla, etc.
componente primitivo Objetos que se pueden renderizar o con los que se puede interactuar físicamente. También se utiliza como una unidad para la selección de visibilidad y la configuración de propiedades de renderizado (como si proyectar sombras, etc.). Al igual que con todos los UObjects, el subproceso del juego posee todas las variables y datos, y el subproceso de representación no debe acceder directamente a él.
ULightComponent Representa una fuente de luz. El renderizador es responsable de calcular y agregar su influencia a la escena.
FScena Versión renderizadora de UWorld. Los objetos no existen en el renderizador hasta que se agregan a FScene (que se llama cuando se registra el componente). El subproceso de representación posee todos los datos de FScene y el subproceso del juego no puede modificarlo directamente.
FPrimitiveSceneProxy La versión de renderizador de UPrimitiveComponent, que mapea los datos de UPrmitiveComponent para el subproceso de renderizado. Esta clase se define en el módulo del motor y se divide en subclases para admitir diferentes tipos de primitivas (huesos, cuerpos rígidos, BSP, etc.). Tiene que implementar algunas funciones muy importantes, como GetViewRelevance, DrawDynamicElements, etc.
FPrimitiveSceneInfo Estado del renderizador interno (para la implementación de FRendererModule), correspondiente a UPrimitiveComponent y FPrimitiveSceneProxy. Vive en el módulo del renderizador, por lo que el motor no lo ve.
FSceneView Una vista en la FScene representada por el motor. Una escena puede llamar a diferentes FSceneRenderer::Render para generar diferentes vistas (editor de múltiples vistas) o generar múltiples vistas en un FSceneRenderer::Render (juego de pantalla dividida). Cada fotograma se construye una nueva vista.
FVerInfo La vista representada internamente por el renderizador vive en el módulo del renderizador.
FSceneViewState ViewState almacena información interna del renderizador para una vista que se necesita en varios marcos. En el juego, solo hay un ViewState por ULocalPlayer.
FSceneRenderer Una clase creada para cada marco para encapsular objetos temporales a través de marcos.

El módulo en el que se definen estas clases se enumera a continuación. Esta información es muy importante si está tratando de solucionar problemas relacionados con el enlazador.

módulo de motor módulo renderizador
UMundo FScena
UPrimitiveComponent / FPrimitiveSceneProxy FPrimitiveSceneInfo
FSceneView FVerInfo
ULocalPlayer FSceneViewState
ULightComponent/FLightSceneProxy Información de escena de vuelo

A continuación se enumera qué subproceso mantiene los datos de estas clases. Asegúrese de comprender a qué subproceso pertenecen los datos del código que está escribiendo, para evitar Race Condition .

hilo del juego hilo de renderizado
UMundo FScena
componente primitivo FPrimitiveSceneProxy / FPrimitiveSceneInfo
- FSceneView / FViewInfo
ULocalPlayer FSceneViewState
ULightComponent FLightSceneProxy/FLightSceneInfo

clase de material

amable describir
FMaterial 用于渲染的材质的接口。可用于访问材质属性(如混合模式)。包含一个 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()) 设置。

Supongo que te gusta

Origin blog.csdn.net/u013412391/article/details/131495561
Recomendado
Clasificación