UE4 RHI与Render模块简解

  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::RHIDrawPrimitiveIndirect

  前面说过,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
};
ELightMapPolicyType

  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的源码都联系起来了。

相关文章
相关标签/搜索