UE4中的RHI指的是Render hardware interface,做用像Ogre裏的RenderSystem,針對Dx11,Dx12,Opengl等等平臺抽象出相同的接口,咱們能方便能使用相同接口對應不一樣渲染平臺.html
和之前同樣,先簡單介紹一些類與文件的做用,咱們有個抽象的瞭解.android
RHI.h :主要定義一些硬件平臺的公共變量.數組
一是 硬件支持項,如是否支持PF_FloatRGBA格式渲染目標,手機平臺是否支持FrameBuffer拾取,支持體紋理,支持硬件合併渲染等等.緩存
二是 硬件變量,如最大Cube紋理數,陰影貼圖長寬最大值等等.app
三是 常見渲染定義,如FSamplerStateInitializerRHI紋理採樣,FRasterizerStateInitializerRHI柵欄化(填充格式,正方向定義,MSAA),FDepthStencilStateInitializerRHI逐片段處理中的模板與深度,FBlendStateInitializerRHI逐片段處理中的混合.FRHIDrawIndirectParameters/FRHIDrawIndexedIndirectParameters DrawCall中相關參數.異步
DynamicRHI.h :包含FDynamicRHI接口定義,渲染所需求全部接口,建立buffer,建立紋理,設置着色器參數,UAV等,簡單來講,對應opengl,dx提供的渲染API,其DynamicRHI.cpp文件會根據平臺(Windows,apple,android等等)來選擇加載合適的渲染平臺(如Opengl,Dx,Vulkan等),在RHI模塊的private文件夾下,可能看到各個系統會如何選擇相應的渲染平臺. ide
FRenderResource:定義接口如InitDynamicRHI /ReleaseDynamicRHI /InitRHI /ReleaseRHI /InitResource /ReleaseResource /UpdateRHI等渲染資源選擇實現函數.函數
RHICommandList相關文件是咱們講RHI主要須要講的,在這咱們先來分析出現的各個類.性能
FRHICommandBase: 主要定義一個函數指針,一個執行方法調用函數指針指向的函數。動畫
函數指針:二個參數(FRHICommandListBase,FRHICommandBase)CallExecuteAndDestruct:傳入本身FRHICommandBase到時函數指針指向的方向.
FRHICommand: FRHICommandBase的一個模板子類,模板須要定義Execute方法,其方法只須要FRHICommandListBase,其會退化上面CallExecuteAndDestruct的FRHICommandBase參數,默認爲本身.
FRHICommand的模板具體化,對應SetRasterizerState/SetDepthStencilState/SetShaderParameter等等,幾乎全部渲染API都有對應的FRHICommand的模板具體化實現.
FRHICommandListBase: 相應FRHICommandBase的鏈表實現,以及定義一些上下文如IRHICommandContext ,IRHIComputeContext ,而且有相關和RHI線程交互的API,RHI自己相應的FRHICommandBase與List都是存放在渲染線程中,RHI線程能夠用於在渲染線程中同步執行異步的複雜操做,如壓入不少FRHICommandBase到渲染線程中執行,有些操做能夠放入RHI線程中與渲染線程一塊兒執行,在某段FRHICommandBase前,調用WaitForTasks等同步渲染線程與RHI線程,你們能夠這麼理解,RHI線程對於渲染線程就至關於渲染線程與遊戲線程的關係,你們能夠看我上篇UE4裏的 渲染線程 ,看到如何在渲染線程裏壓入RHI線程,如何用WaitForTasks與渲染線程同步等.
FRHICommandList: 簡單來講,全部用於渲染API幾乎都有二種方法,一種是插入FRHICommandListBase鏈表,一種是直接調用相應渲染平臺對應FDynamicRHI的實現,在這說下,我看了下OpenGLDrv相應的FDynamicRHI實現,相應API如SetShaderParameter, SetDepthStencilState等等,並無直接調用相應的OpenGL的API,而是把相關改動放入一個FOpenGLRHIState的結構中保存起來,等到DrawCall(如RHIDrawPrimitiveIndirect等)相關命令調用後,才把各個改動對應opengl的API調用起來,如上的glProgramUniform等.
FRHIAsyncComputeCommandList: 多GPU的FRHICommandList實現。
FRHICommandListImmediate: 直接調用相應渲染平臺對應FDynamicRHI的實現,對比FRHICommandList,主要是建立資源這一塊的FDynamicRHI封裝,能夠看到它的一些函數都是以Create開頭的。
FRHICommandListExecutor: 簡單來講,管理FRHICommandListBase的幾個子類單例實現,方便查找到如上的FRHICommandListImmediate 與FRHIAsyncComputeCommandListImmediate 單例實現,通常咱們看到渲染代碼裏常見的如FRHICommandList/RHICmdList就是指的是FRHICommandListExecutor::GetImmediateCommandList().
在這,關於RHI的就先簡單瞭解下,RHI主要調用都在渲染線程中,不過也可使用FRHICommandListBase鏈表與RHI線程來實現一些同步異步操做。其中渲染模塊中FRHICommandList/RHICmdList通常是FRHICommandListExecutor::GetImmediateCommandList(),這個是直接調用相關FDynamicRHI實現,通常並不與RHI線程交互。
介紹RHI模塊後,咱們來看下渲染模塊的相關實現,在說下渲染模塊的實現前,簡單說下,UE4中大量用到C++ 的模版,除開自動生成各個分支代碼,還有二點,一是代替部分接口類,減小如虛函數表的性能,二是減小一些分支判斷,仍是提升性能。可是會形成閱讀代碼比C#等語言驗證,主要在於有些模板你都不知道是那些類能夠用等,還好,UE4裏通常這種模板使用類都有相同的前綴或是後綴,咱們能夠記一些相同的前綴或後綴轉化成本身認爲的接口實現。
咱們先看一段代碼,是OpenGLDrv實現的FDynamicRHI子類FOpenGLDynamicRHI的RHIDrawPrimitiveIndirect,簡接繪製多組圖元集。
void FOpenGLDynamicRHI::RHIDrawPrimitiveIndirect(uint32 PrimitiveType,FVertexBufferRHIParamRef ArgumentBufferRHI,uint32 ArgumentOffset) { if (FOpenGL::SupportsDrawIndirect()) { VERIFY_GL_SCOPE(); check(ArgumentBufferRHI); GPUProfilingData.RegisterGPUWork(0); FOpenGLContextState& ContextState = GetContextStateForCurrentContext(); BindPendingFramebuffer(ContextState); SetPendingBlendStateForActiveRenderTargets(ContextState); UpdateViewportInOpenGLContext(ContextState); UpdateScissorRectInOpenGLContext(ContextState); UpdateRasterizerStateInOpenGLContext(ContextState); UpdateDepthStencilStateInOpenGLContext(ContextState); BindPendingShaderState(ContextState); SetupTexturesForDraw(ContextState); CommitNonComputeShaderConstants(); CachedBindElementArrayBuffer(ContextState,0); // Zero-stride buffer emulation won't work here, need to use VAB with proper zero strides SetupVertexArrays(ContextState, 0, PendingState.Streams, NUM_OPENGL_VERTEX_STREAMS, 1); GLenum DrawMode = GL_TRIANGLES; GLsizei NumElements = 0; GLint PatchSize = 0; FindPrimitiveType(PrimitiveType, ContextState.bUsingTessellation, 0, DrawMode, NumElements, PatchSize); if (FOpenGL::SupportsTessellation() && DrawMode == GL_PATCHES ) { FOpenGL::PatchParameteri(GL_PATCH_VERTICES, PatchSize); } FOpenGLVertexBuffer* ArgumentBuffer = ResourceCast(ArgumentBufferRHI); glBindBuffer( GL_DRAW_INDIRECT_BUFFER, ArgumentBuffer->Resource); { CONDITIONAL_SCOPE_CYCLE_COUNTER(STAT_OpenGLShaderFirstDrawTime, PendingState.BoundShaderState->RequiresDriverInstantiation()); FOpenGL::DrawArraysIndirect( DrawMode, INDEX_TO_VOID(ArgumentOffset)); } glBindBuffer( GL_DRAW_INDIRECT_BUFFER, 0); FShaderCache::LogDraw(0); } else { UE_LOG(LogRHI, Fatal,TEXT("OpenGL RHI does not yet support indirect draw calls.")); } }
前面說過,FOpenGLDynamicRHI是在DrawCall時,才把各個改動對應opengl的API調用起來,因此在這,咱們能夠看到一個渲染的完整過程,固然你們使用過Opengl或是DX直接寫過程序也是同樣,首先設定渲染目標,混合,設定viewport,設定柵欄化,設定逐片段處理(深度,模板),綁定Shader程序,設定shader紋理,設置shader參數,綁定VAO,設定VAO,DrawCall,嗯,就是這麼個過程,不管UE4如何包裝,每次DrawCall就是如上順序處理。
先說一下在渲染模塊裏比較常見的類:
後綴Parameters: 二個主要方法,一是Bind,簡單來講,對應一個或多個參數Parameter與Shader代碼裏參數綁定,對應opengl裏的API就是如glGetUniformLocation。二是Set,簡單來講,上面綁定後,咱們就能夠傳入參數的值到GPU裏,對應opengl裏的API就是如glUniform等等。
模板類裏的模板若是是後綴ParametersType,通常主要是指各個後綴爲Parameters的類。
以下一些類寫了些本身瞭解,後查找資料時發現UE4官方文檔裏 着色器開發 有說,比我說的清楚。
FVertexFactory: 用來表示頂點數據格式,頂點分佈結構,頂點元素Buffer,DeclarationElementList數組,相關opengl的API如glVertexAttribPointer.從opengl3+來講,通常雖然可能有多個buffer,可是應該是在一個glgenbuffer中對應不一樣的區段而已。
方法Set: 只是告訴對應opengl裏各個buffer的起點與終點,相應的如OffsetInstanceStreams/SetPositionStream都是相似。
FVertexFactoryType:表示網格類型,如 Local/Particle(三種sprite/beamtrail,mesh)/Landscape/GPUSkin等,
FMeshBatch: 通常來講,是一組相同頂點格式,相同材質的模型,通常可使用GPU的實例渲染,減小DrawCall.
FShaderType: Global/Material/MeshMaterial (vertex/hull/demain/geomerty/pixel 一種)
FGlobalShader: 全局shader,簡單來講,不和mesh與Material關聯,通常用於後處理,固定畫個方塊啥的,如處理特效這種。
方法SetParameters: 設定Shader裏的FViewUniformShaderParameters /FFrameUniformShaderParameters /FBuiltinSamplersParameters 參數。
FMaterialShader: 特定於過程的着色器,它們須要訪問材質的某些屬性,所以必須針對每一個材質進行編譯,但不須要訪問任何網格屬性。如FLightFunctionVS,FLightFunctionPS等。
對比FGlobalShader,增長一個重載的SetParameters,包含材質對Shader的設置。
FMeshMaterialShader: 着色器是特定於過程的着色器,它們依賴於材質的屬性和網格類型,所以必須針對每一個材質/FVertexFactory組合進行編譯。例如,TBasePassVS / TBasePassPS 須要對前向渲染過程當中的全部材質輸入進行評估。
對比FMaterialShader,增長一個方法SetMesh,添加FMeshBatchElement,FVertexFactory對shader的設置,對應VertexFactory的Parameters針對Mesh填充不一樣的頂點信息。 如GPUSkin,填充骨骼信息到相應的shader參數中, 如MeshParticle,填充動畫加速度 ,時間等。以及填充模型自己的FPrimitiveUniformShaderParameters等共有信息,如FPrimitiveUniformShaderParameters:localToworld ,worldTolocal ,objectBounds, LOD,FadeTimeScaleBias等。
以下這些類表示渲染主要思路,預先一些相同的渲染方式,能夠先緩存起來。
FMeshDrawingPolicy: 整合渲染模型過程,從綁定Shader到調用DrawCall,各個子類對應不一樣的獨立着色器程序。
1 初始化,根據須要生成或綁定各個對應的Shader.
2 SetSharedState,設定和Mesh無關的Shader變量。
3 SetMeshRenderState,設定和Mesh相關的Shader變量。
4 DrawMesh 調用DrawCall.
模板類裏的模板若是是DrawingPolicyType,通常主要是指FMeshDrawingPolicy的各個子類。
FUniformLightMapPolicy: 封裝和光照有關渲染的Shader參數設置。
方法SetMesh:綁定相應光照計算上Shader的參數,如使用GI預計算產生的間接光照圖信息,直接光照圖信息,天空圖AO等。
TUniformLightMapPolicy: FUniformLightMapPolicy的模版子類,模版爲ELightMapPolicyType,表示各類和光照有關,模版預生成多份代碼對應不一樣光照計算表示是否緩存,Shader預編譯指令。
enum ELightMapPolicyType { LMP_NO_LIGHTMAP, LMP_CACHED_VOLUME_INDIRECT_LIGHTING, LMP_CACHED_POINT_INDIRECT_LIGHTING, LMP_SIMPLE_DYNAMIC_LIGHTING, LMP_LQ_LIGHTMAP, LMP_HQ_LIGHTMAP, LMP_DISTANCE_FIELD_SHADOWS_AND_HQ_LIGHTMAP, // Forward shading specific LMP_DISTANCE_FIELD_SHADOWS_AND_LQ_LIGHTMAP, LMP_SIMPLE_DIRECTIONAL_LIGHT_AND_SH_INDIRECT, LMP_SIMPLE_DIRECTIONAL_LIGHT_AND_SH_DIRECTIONAL_INDIRECT, LMP_SIMPLE_DIRECTIONAL_LIGHT_AND_SH_DIRECTIONAL_CSM_INDIRECT, LMP_MOVABLE_DIRECTIONAL_LIGHT, LMP_MOVABLE_DIRECTIONAL_LIGHT_CSM, LMP_MOVABLE_DIRECTIONAL_LIGHT_WITH_LIGHTMAP, LMP_MOVABLE_DIRECTIONAL_LIGHT_CSM_WITH_LIGHTMAP, // LightMapDensity LMP_DUMMY };
TLightMapPolicy: Shader對應預編譯指令,是否緩存,模板爲ELightmapQuality,有二個值,分別是LQ_LIGHTMAP,HQ_LIGHTMAP。
模板類裏的模板若是是LightMapPolicyType,通常主要是指TUniformLightMapPolicy/TLightMapPolicy的各個子類。
如上一些基本比較重要的類就到此,在這咱們重點說下FMeshDrawingPolicy這個類,從上面各個類的說明來看,能夠看到把全部渲染基本類組合在一塊兒,他的子類簡單說幾個,BasePassRendering ,CapsuleShadowing ,DepthRendering ,ForwardBasePassRendering ,VelocityRendering等等,還有別的帶Rendering的渲染,如DistortionRendering ,DeferredShading ,DecalRendering,ShadowRendering等等雖然和FMeshDrawingPolicy不一樣,可是過程其實真差不了多少。 在每一個Rendering中,都有對應的VS,PS,HS等,這些根據須要分別從上面所說的FGlobalShader /FMaterialShader /FMeshMaterialShader繼承,簡單來講,後處理特效針對渲染目標的通常從FGlobalShader繼承,只針對Material不和具體Mesh有關的用FMaterialShader,最後針對模型渲染的從FMeshMaterialShader繼承。
按BasePassRendering說下,只簡單渲染emissive color與light map,對應的FMeshDrawingPolicy子類爲TBasePassDrawingPolicy ,如上所說,針對Mesh產生的都繼承與FMeshMaterialShader生成的VS,PS等,由於光照有影響,咱們看到相應的Shader都對應模版LightMapPolicyType,用於生成正確的Shader對應預編譯指令,若有無光照,光照質量,靜態或動態,陰影類型等。下面還定義一些與BasePassRendering相關的parameters,如天空盒相關參數,如上TBasePassDrawingPolicy在構造函數中獲得或是生成上面的VS,PS,而後在SetSharedState時針對VS,PS設定參數,而後調用SetMeshRenderState針對每一個FMeshBatch設定和Mesh有關的參數,而後提交DrawCall.
每一個DrawingPolicy中,對應VS,PS等對應文件能夠經過宏IMPLEMENT_SHADER_TYPE查看。
本文原本還準備更詳細講述一個基本的Rendering的過程,可是新項目時間緊,只是暫停查看,後面會仔細介紹一個完整流程,從陰影渲染,前向或是後向渲染選擇一部分來詳細介紹,包含大部分參數的含義與做用,不過你們熟悉如上的渲染線程再加個RHI與渲染模塊如上這些基本類,應該就能把UE4的源碼都聯繫起來了。