我的专栏目录:
小IVan:专题概述及目录本文使用的是UE4.19的源码,介绍的也是4.19之前的管线,DrawingPolicy和DrawList会在4.22后被干掉。但是我后面会持续更新,我后面有写一篇MeshDrawPipline的文章。
小IVan:虚幻4渲染编程(Shader篇)【第十二卷:MeshDrawPipline】这篇介绍的是4.21之前的管线,可以看完这篇再看MeshDrawPipline。如有错误,还请各路巨佬斧正。
4.21之前的虚幻渲染管线(使用Policy和DrawList)
4.22重构后的虚幻渲染管线(使用MeshDraw)
虚幻4中,在渲染线程层真正负责绘制操作的是DrawingPolicy和DrawingPolicy Factory。如果我们要定制自己的渲染器并且想从底层优化我们的渲染的话,就要从这两个东西下手。不过在开始研究这两个东西之前我们需要先搞清楚虚幻的整个渲染框架,我这里大概说一下我自己的总结。之前的文章我也有描述过,但是都没有描述完整。这里分几个部分:
【1】Shader的生成
【2】DrawingPolicy绘制数据的管理
【3】整个绘制过程
在开始之前,一定要明确的几个概念。
(1)Shader一定是在使用之前就编译好的(未来不知道会怎样),从某种角度上说shader可以认为是预编译好的资源,不同的程序不同的环境平台我们需要不同的Shader,也就是说我们一个效果会有很多个shader,要用的时候需要根据不同条件去取对应的出来。
(2)Shader C++类和Shader一定要区分开。Shader C++类是在CPU端管理控制shader的,shader是在GPU上跑的。留言区有位朋友质疑我都文章,就是因为他没有把这个概念明晰导致的。
(3)Shader的定义,编译,使用是三个分开的过程,不能混在一起看作是一套连贯的线性过程。
【1】Shader的生成
首先是shader的生成。虚幻4的shader C++类主要分了三个类型,Material Shader Mesh Material Shader,Global Shader。总得来说MaterialSHader C++类和MeshMaterialShader C++类允许多份实例,GlobalShader C++类的实例只允许存在一份。
我们的材质编辑器负责填充一个叫Material Template的函数模板,当我们点击Compile的时候,材质编辑器的材质节点便会填充这个MaterialTemplate.usf生成一个函数库。这还没完compile操作还会根据材质编辑器里的各种宏,各种绘制状态,游戏的高中低配分级等条件编译出很多份Shader,放在FMaterial的ShaderMap中。有一个宏材,质编辑器就会编译出两份shader,有两个宏,就会编译出4个,呈指数型上涨(先这么简单理解)。
(1)GlobalShader
感觉这样直接说Material Shader太抽象了,我们先自己实际动手敲一个Golobal Shader绘制过程出来吧,自己调用Draw。敲完这个例子之后再进行下一步就会豁然开朗了。首先在引擎里新建三个文件(红圈圈起来的这三个。分别是SkyRender.usf;SkyRender.h;SkyRender.cpp)
我们先打开SkyRender.usf
敲入如下代码:
#include "Common.ush"
uniform float4x4 UnityVP;
uniform float3 TestColor;
void MainVS(
in float3 InPosition : ATTRIBUTE0,
out float4 Position : SV_POSITION
)
{
Position = mul(float4(InPosition, 1.0f), UnityVP);
}
void MainPS(
out float4 OutColor : SV_Target0
)
{
#if SAMPLELEVEL == 0
OutColor = float4(1, 0, 0, 1);
#endif
#if SAMPLELEVEL == 1
OutColor = float4(0, 1, 0, 1);
#endif
#if SAMPLELEVEL == 2
OutColor = float4(0, 0, 1, 1);
#endif
}
我们包含了Common.usf。在Common.usf中会帮我们包含大量需要的辅助设置,从C++传上来的宏,一些函数等等。然后我们声明了一个叫UnityVP的矩阵和一个TestColor的颜色。然后就是我们的顶点着色器了,这里直接把物体从世界空间变换到投影空间。最后是我们的像素着色器,我们这里根据宏输出不同的颜色。
然后打开SkyRender.h文件,敲入如下代码:
#pragma once
#include "CoreMinimal.h"
#include "StaticMeshVertexDataInterface.h"
#include "StaticMeshVertexData.h"
#include "RenderResource.h"
struct FDebugPane
{
FDebugPane();
~FDebugPane();
void FillRawData();
void EmptyRawData();
void Init();
TArray<FVector> VerBuffer;
TArray<uint16> InBuffer;
uint32 Stride;
bool Initialized;
uint32 VertexCount;
uint32 PrimitiveCount;
FVertexBufferRHIRef VertexBufferRHI;
FIndexBufferRHIRef IndexBufferRHI;
};
void FDebugPane::FillRawData()
{
VerBuffer = {
FVector(0.0f, 0.0f, 0.0f),
FVector(100.0f, 0.0f, 0.0f),
FVector(100.0f, 100.0f, 0.0f),
FVector(0.0f, 100.0f, 0.0f)
};
InBuffer = {
0, 1, 2,
0, 2, 3
};
}
我们这里只是做了一个FDebugPlane的数据封装,封装了初始化操作和顶点缓冲数组和索引缓冲数组。
然后打开SkyRender.cpp,敲入如下代码:
#include "SkyRender.h"
#include "CoreMinimal.h"
#include "SceneRendering.h"
#include "RHICommandList.h"
#include "Shader.h"
#include "RHIStaticStates.h"
#include "ScenePrivate.h"
template<uint32 SampleLevel>
class TSkyRenderVS : public FGlobalShader
{
DECLARE_SHADER_TYPE(TSkyRenderVS, Global, /*MYMODULE_API*/);
private:
FShaderParameter Unity_VP;
public:
TSkyRenderVS(){}
TSkyRenderVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{
Unity_VP.Bind(Initializer.ParameterMap, TEXT("UnityVP"));
//VertexOffset.Bind(Initializer.ParameterMap, TEXT("VertexOffset"));
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("SAMPLELEVEL"), SampleLevel);
}
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
static bool ShouldCache(EShaderPlatform Platform)
{
return true;
}
virtual bool Serialize(FArchive& Ar) overr