UE4 Global Shaders - 01 结构
UE4版本:4.26.2
前置知识
- 了解图形学中的顶点着色器和像素着色器
- UE4中创建和配置插件的方法,了解插件的文件结构
- UE4C++
Global Shaders 是只应用于特定模型(例如全屏Quad),不需要和材质交互的Shader,每个Global Shader在一个项目中只有一个实例。
创建插件
本案例中将使用一个插件来创建和使用Global Shaders。 在空的UE4C++工程中创建一个空插件,插件命名为GlobalShaderPlug。
修改uplugin文件
打开GlobalShaderPlug.uplugin文件,将其中"GlobalShaderPlug"对象的LoadingPhase字段的值修改为PostConfigInit,因为Shader要在引擎完全初始化之前加载。 关于LoadingPhase,可以在C++工程中搜索ELoadingPhase,源码中对每个phase都进行了注释。
"Modules": [
{
"Name": "GlobalShaderPlug",
"Type": "Runtime",
"LoadingPhase": "PostConfigInit"
}
]
修改Build.cs文件
打开GlobalShaderPlug.Build.cs文件,
- 在PublicDependency中加入RenderCore和RHI
- 在PrivateDependency中加入Projects和AssetRegistry
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"RenderCore",
"RHI"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"Projects",
"AssetRegistry"
// ... add private dependencies that you statically link with here ...
}
);
创建usf shader文件
虚幻引擎中,ush/usf文件就是传统意义上的hlsl shader文件,其中ush是头文件,usf是实现文件。如果写简单的shader,没有涉及到复杂的shader引用,一般只使用usf文件即可。 为称呼方便,我们后文中均以usf文件代之所有的ush和usf类型的文件。
先在插件中(GlobalShaderPlug文件夹中)创建一个Shaders文件夹,用于存放本插件中的Shaders文件。
在刚创建的文件夹中放入一个MyGlobalShader.usf文件,其内容如下
#include "/Engine/Public/Platform.ush"
float4 MainColor;
void MainVS(
in float4 InPosition : ATTRIBUTE0,
out float4 OutPosition : SV_POSITION)
{
OutPosition = InPosition;
}
void MainPS(out float4 OutColor : SV_Target0)
{
OutColor = MainColor;
}
这样就创建了两个基本的Shader,MainVS是顶点着色器,MainPS是像素着色器。其中MainColor是一个float4类型的参数,后面我们将使用C++向Shader中传入这个参数,像素着色器将把它作为一个颜色进行输出。
设置Shader加载路径
在模块类的StartupModule函数中添加一个ShaderSourceDirectory的映射,这样后面使用C++读取shader的时候才能正确识别路径
#include "GlobalShaderPlug.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IPluginManager.h"
#include <ShaderCore.h>
#define LOCTEXT_NAMESPACE "FGlobalShaderPlugModule"
void FGlobalShaderPlugModule::StartupModule()
{
FString ShaderPath = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("GlobalShaderPlug"))->GetBaseDir(), TEXT("Shaders"));
AddShaderSourceDirectoryMapping(TEXT("/GlobalShaderPlug"), ShaderPath);
}
创建C++ Shader文件
虚幻引擎中,除了usf的shader文件,我们还需要在C++中为usf文件中的每个Shader定义一个类,可以理解为hlsl shader在CPU中的化身,其主要作用是在C++中为Shader传参。
接下来我们需要在C++文件中声明Global顶点着色器和Global像素着色器,他们都派生自FGlobalShader类,这是UE抽象出来的一个类型。我们需要这两个C++着色器类对Shader进行设置、传参等工作。 在Plugins/GlobalShaderPlug/Source/GlobalShaderPlug/Public文件夹中手动创建一个MyShaders.h文件,刷新工程。
刷新工程方法: 在uproject文件图标上右键 > Generate Visual Studio project files
打开生成/刷新的vs工程,在MyShaders.h文件中输入以下内容
#pragma once
#include "GlobalShader.h"
#include "Shader.h"
class FMyShaderBase : public FGlobalShader
{
DECLARE_INLINE_TYPE_LAYOUT(FMyShaderBase, NonVirtual);
public:
FMyShaderBase(){}
FMyShaderBase(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{
MainColorVal.Bind(Initializer.ParameterMap, TEXT("MainColor"));
}
static bool ShouldCache(EShaderPlatform Platform)
{
return true;
}
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return true;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
}
void SetParameters(FRHICommandListImmediate& RHICmdList, const FLinearColor& MyColor)
{
SetShaderValue(RHICmdList, RHICmdList.GetBoundPixelShader(), MainColorVal, MyColor);
}
private:
LAYOUT_FIELD(FShaderParameter, MainColorVal);
};
class FShader_VS: public FMyShaderBase
{
DECLARE_GLOBAL_SHADER(FShader_VS);
public:
FShader_VS() {}
FShader_VS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
:FMyShaderBase(Initializer)
{ }
};
IMPLEMENT_SHADER_TYPE(, FShader_VS, TEXT("/GlobalShaderPlug/MyGlobalShader.usf"),TEXT("MainVS"),SF_Vertex)
class FShader_PS : public FMyShaderBase
{
DECLARE_GLOBAL_SHADER(FShader_PS);
public:
FShader_PS() {}
FShader_PS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
:FMyShaderBase(Initializer)
{
}
};
IMPLEMENT_SHADER_TYPE(, FShader_PS, TEXT("/GlobalShaderPlug/MyGlobalShader.usf"), TEXT("MainPS"), SF_Pixel)
这里定义了三个类,其中FShaderBase是派生自FGlobalShader自定义的Shader基类,FShader_VS和FShader_PS派生自FShaderBase,分别是顶点着色器和像素着色器。
注意自定义的Shader基类开头的宏, DECLARE_INLINE_TYPE_LAYOUT,这是自定义Shader基类必须的。
IMPLEMENT_SHADER_TYPE
每定义完一个Shader,要使用 IMPLEMENT_SHADER_TYPE 宏 (如果是Global Shader,也可以使用 IMPLEMENT_GLOBAL_SHADER)来关联C++ Shader和usf文件。例如:
class FShader_PS : public FMyShaderBase
{
DECLARE_GLOBAL_SHADER(FShader_PS);
public:
FShader_PS() {}
FShader_PS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
:FMyShaderBase(Initializer)
{
}
};
IMPLEMENT_SHADER_TYPE(, FShader_PS, TEXT("/GlobalShaderPlug/MyGlobalShader.usf"), TEXT("MainPS"), SF_Pixel)
// 上面的宏等价于下面这行
// IMPLEMENT_GLOBAL_SHADER(FShader_PS, TEXT("/GlobalShaderPlug/MyGlobalShader.usf"), TEXT("MainPS"), SF_Pixel)
上面例子中定义了一个Pixel Shader,参数释义:
- FShader_PS是C++的Shader类名称
- TEXT("/GlobalShaderPlug/MyGlobalShader.usf") 是usf shader文件的名称
- TEXT(“MainPS”) 是 usf shader 文件中的Pixel Shader的函数名称
- SF_Pixel 说明这是一个Pixel Shader。这个参数也被称为 Shader的Frequency,即决定这是一个Vertex Shader,Pixel Shader 还是 Compute Shader等。
定义和使用一个属性的步骤
- 声明 在FMyShaderBase的private区域使用LAYOUT_FIELD宏声明了一个参数,名称为MainColorVal
LAYOUT_FIELD(FShaderParameter, MainColorVal);
- 绑定shader文件中相应的属性 在FMyShaderBase的构造函数中使用Bind函数将上一步声明的属性(MainColorVal)和usf shader文件中的MainColor属性进行绑定
MainColorVal.Bind(Initializer.ParameterMap, TEXT("MainColor"));
- 传值 在SetParameters函数中,我们使用了SetShaderValue函数,将属性值传入shader。
void SetParameters(FRHICommandListImmediate& RHICmdList, const FLinearColor& MyColor)
{
SetShaderValue(RHICmdList, RHICmdList.GetBoundPixelShader(), MainColorVal, MyColor);
}
SetParameter的函数原型:
template<typename ShaderRHIParamRef, class ParameterType, typename TRHICmdList>
void SetShaderValue(
TRHICmdList& RHICmdList,
const ShaderRHIParamRef& Shader,
const FShaderParameter& Parameter,
const ParameterType& Value,
uint32 ElementIndex = 0
)
- 第一个参数是RHICmdList
- 第二个参数是Shader,这里获取PixelShader的方式是RHICmdList.GetBoundPixelShader(),也可以通过类似的一组方法获取VertexShader等其他Shader
- 第三个参数是属性,就是第一步中声明的属性
- 第四个参数是值,我们自己给SetParameters函数增加了一个FLinearColor类型的参数,并传递给这个形参。由于FLinearColor是一个包含了四个float类型元素的结构体,因此和usf Shader中的float4类型是匹配的。
因为我们在基类中已经绑定好了参数,所以在后面的FShader_VS和FShader_PS中只做了很少的事情,基本就是使用DECLARE_GLOBAL_SHADER宏声明了Shader。
实际上也可以分别单独定义vertex shader和pixel shader,而不采用共同派生自同一个基类的做法。
DECLARE_GLOBAL_SHADER(FShader_VS)
DECLARE_GLOBAL_SHADER(FShader_PS)
最后,使用IMPLEMENT_SHADER_TYPE宏(61-62行),定义了像素shader的内容和顶点shader的内容,注意第一个参数之前有个逗号,第二个参数是前面的C++类名,第三个参数是usf文件的路径,第四个参数是从usf文件中找出shader函数的名字,最后一个参数是要定义的shader类型。
- 在usf shader文件中使用属性 C++属性中定义和绑定的大多数属性(Uniform buffer除外),在usf shader中都需要在开头有一个同名的变量。 例如第2步中绑定的是“MainColor”,因此usf shader开头要有一个同类型的变量:
float4 MainColor;
因为这个参数只在pixel shader中使用,所以在C++中传参的时候只需要给Pixel Shader传即可,绑定参数也只需要给Pixel shader绑定。但是这里我们采用了继承的结构使得C++中Vertex Shader和Pixel Shader都具有了这个参数。
至此,Global Shader的创建已经完成,剩下的就是如何使用他们了。
编译,确保没有报错即可,目前还看不到任何内容。