做引擎开发的时候经常需要在UE中添加全局(Global)的Shader来提供新的渲染功能。
全局Shader与一般写在Custom节点中的Shader相比,可以直接被引擎中的其他Shader调用,同时可以从C++里调用,一般做新的底层渲染功能,添加后处理还有一些不需要依赖Material和Mesh运行的Shader的时候我们需要直接修改底层的全局Shader。
.usf(Unreal Shader Files) 和.usf的使用
顾名思义,Unreal Shader Files就是主要负责Shading部分的着色器文件。
UE实际编译的时候,会从Engine/Shaders文件夹读取.usf 并且编译Shader文件。所有新Shader都可以放在这里来和引擎一起编译。
一般我们会在 ConsoleVariables.ini 配置文件中启用 r.ShaderDevelopmentMode=1 来便于开发。
关于Shader Debug的部分,可以看官网的文档:Shader Debugging Workflows
.usf文件编写
举个例子,我们可以在 Engine/Shaders 文件夹中添加一个新的名为MyTest.usf
的 .usf 文件,。然后添加一个简单的直接输出的顶点着色器VS和一个返回自定义颜色的像素着色器PS:
// MyTest.usf
// Simple pass-through vertex shader
void MainVS(
in float4 InPosition : ATTRIBUTE0,
out float4 Output : SV_POSITION
)
{
Output = InPosition;
}
// Simple solid color pixel shader
float4 MyColor;
float4 MainPS() : SV_Target0
{
return MyColor;
}
代码中的ATTRIBUTE0
,SV_POSITION
和 SV_Target0
都算是着色器语义,关于这个部分可以直接去DX的shader官网看:Direct3D中的语义。
.usf文件绑定.cpp和.h文件
现在我们手上有了MyTest.usf
,但是它是一个光杆司令,没有办法编译和被其他C++调用和识别,为了让这个文件能够顺利被编译和使用,我们还需要声明一个对应的C++类,这个类的声明可以放在.cpp文件里,也可以放在.h文件里:
// This can go on a header or cpp file
class FMyTestVS : public FGlobalShader
{
DECLARE_EXPORTED_SHADER_TYPE(FMyTestVS, Global, MY_TEST_API);
FMyTestVS() { } //构造函数1: 默认构造函数
FMyTestVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer) //构造函数2: 序列化构造函数
{
}
static bool ShouldCache(EShaderPlatform Platform)
{
return true;
}
};
这个类写的时候有以下几个要求:
-
所有Shader的基类是FShader, Unreal有两个主要shader类别:
- FGlobalShader, FGlobalShader只有一个实例存在,也就是它无法为每个实例设置参数。
- FMaterialShader, FMaterialShader与materail绑定, 大量Shader派生于这个类,因为它是所有需要material和vertex factory参数Shader的基类。
FMaterialShader一般分为FMaterialShader和FMeshMaterialShader两种。这两个Class都允许多个instance,每一个关联自己GPU资源拷贝。FMaterialShader添加一个SetParameter函数,允许你自己shader中C++代码来修改HLSL中参数。参数绑定通过FShaderParameter/FShaderResourceParameter完成,能在Shader构造函数中完成。SetParameters函数在使用Shader渲染前被调用,并传递一些信息下去,包括material,这为你想修改的参数提供一部分用于计算的信息。
FShader与FShaderResource成对出现, FShaderResource追踪一个shader在GPU上相关资源。FShaderResource可以被多个FShaders共享。
这里我们选择的基类是
FGlobalShader
,因为FGlobalShader能让Shader编译为一个全局的Shader来直接被引擎调用。 -
使用
DECLARE_EXPORTED_SHADER_TYPE()
宏来 生成 Shader类型序列化等所需要的必要的导出信息。这个宏的第一个参数就是类名, 第二个参数一般固定为Global, 第三个参数是着色器模块所在的代码模块的外部链接类型。 -
需要有两个构造函数 : 默认构造函数和序列化构造函数。
-
ShouldCache()
函数用来决定应不应该在编译这个Shader, 一般就调用IsFeatureLevelSupported
看平台和编译环境支不支持这个Shader, 支持的话就编译, 这里我们直接return true也是可以的.(如果RHI不支持计算着色器的话, 就应该return false 来取消CS的编译)。
有了上面这个类的声明之后,我们就可以把Shader类型注册到 UE 的列表中了:
// This needs to go on a cpp file
IMPLEMENT_SHADER_TYPE(, FMyTestVS, TEXT("MyTest"), TEXT("MainVS"), SF_Vertex);
这个宏的意思是它会把类FMyTestVS
映射到 后面的三个参数对应的三个不同的属性 :
- Shader所绑定的 usf文件
MyTest.usf
- 着色器入口点( 可以理解为主函数 )
MainVS
- 着色器类型
SF_Vertex
。
上述宏IMPLEMENT_SHADER_TYPE
的三个参数中 :
- 第一个
TEXT("")
内的参数可以写.usf的全路径, 因为这里使用的是引擎本身的Shader文件夹,所以可以直接写MyTest.usf的名称MyTest就可以; - 第二个参数入口点EnterPoint (可以理解为主函数)
MainVS
的函数名并不固定, 和你usf文件中的主函数对应上就好; - 第三个参数
SF_Vertex
界定了绑定的Shader实现的类型, 同理如果绑定的是PS的话就是SF_Pixel
, 计算着色器的话就是SF_Compute
.
只要函数 ShouldCache()
方法返回 true,它就会把 Shader 添加进UE的编译列表中。
上面提到的像素着色器还是比较简单的, 它只返回了一个自定义的Color, 下面我们来写一个比较复杂的FMyTestPS :
class FMyTestPS : public FGlobalShader
{
DECLARE_EXPORTED_SHADER_TYPE(FMyTestPS, Global, /*MYMODULE_API*/);
FShaderParameter MyColorParameter;
FMyTestPS() { } //构造函数1: 默认构造函数
FMyTestPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer) //构造函数2: 序列化构造函数
{
MyColorParameter.Bind(Initializer.ParameterMap, TEXT("MyColor"), SPF_Mandatory);
}
static void ModifyCompilationEnvironment(EShaderPlatform Platform, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Platform, OutEnvironment);
// Add your own defines for the shader code
OutEnvironment.SetDefine(TEXT("MY_DEFINE"), 1);
}
static bool ShouldCache(EShaderPlatform Platform)
{
// Could skip compiling for Platform == SP_METAL for example
return true;
}
// FShader interface.
virtual bool Serialize(FArchive& Ar) override
{
bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar);
Ar << MyColorParameter;
return bShaderHasOutdatedParameters;
}
void SetColor(FRHICommandList& RHICmdList, const FLinearColor& Color)
{
SetShaderValue(RHICmdList, GetPixelShader(), MyColorParameter, Color);
}
};
可以看到, 这个类里的序列化构造函数多了一些操作:
- 类内多了一个
FShaderParameter
类型的局部变量MyColorParameter; - 序列化构造函数里面我们调用了MyColorParameter的
.Bind
函数,
MyColorParameter.Bind(Initializer.ParameterMap, TEXT(“MyColor”), SPF_Mandatory);
这个函数里面的MyColor
这个名字必须和.usf里面的声明一样, .usf里面我们叫 float4 MyColor;那么在Bind函数里面就必须是TEXT(“MyColor”). -
Serialize()
, 这个函数是添加新参数所必须的. 这是运行时Shader绑定(在序列化构造函数期间匹配)的编译/cook时间信息被加载和存储的地方 -
ModifyCompilationEnvironment()
,因为我们在同一个 C++ 类定义了不同的行为, 并能够在着色器中设置#define 值,所以需要设定编译环境. - 最后,我们有一个自定义的 SetColor() 方法用来在运行时 把 Color 赋值给 MyColorParameter。
有了上面这些操作, 我们就能够在运行时修改PS中的MyColor的值了.
添加控制台变量
让我们编写一个简单的函数来使用上面的着色器类型绘制 全屏的四边形:
void RenderMyTest(FRHICommandList& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, const FLinearColor& Color)
{
// Get the collection of Global Shaders
auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
// Get the actual shader instances off the ShaderMap
TShaderMapRef MyVS(ShaderMap);
TShaderMapRef MyPS(ShaderMap);
// Declare a bound shader state using those shaders and apply it to the command list
static FGlobalBoundShaderState MyTestBoundShaderState;
SetGlobalBoundShaderState(RHICmdList, FeatureLevel, MyTestBoundShaderState, GetVertexDeclarationFVector4(), *MyVS, *MyPS);
// Call our function to set up parameters
MyPS->SetColor(RHICmdList, Color);
// Setup the GPU in prep for drawing a solid quad
RHICmdList.SetRasterizerState(TStaticRasterizerState::GetRHI());
RHICmdList.SetBlendState(TStaticBlendState<>::GetRHI());
RHICmdList.SetDepthStencilState(TStaticDepthStencilState::GetRHI(), 0);
// Setup the vertices
FVector4 Vertices[4];
Vertices[0].Set(-1.0f, 1.0f, 0, 1.0f);
Vertices[1].Set(1.0f, 1.0f, 0, 1.0f);
Vertices[2].Set(-1.0f, -1.0f, 0, 1.0f);
Vertices[3].Set(1.0f, -1.0f, 0, 1.0f);
// Draw the quad
DrawPrimitiveUP(RHICmdList, PT_TriangleStrip, 2, Vertices, sizeof(Vertices[0]));
}
如果想要在你的代码库中测试上面这个函数,我们可以尝试声明一个控制台变量,这样它就可以在运行时切换,如下所示:
static TAutoConsoleVariable CVarMyTest(
TEXT("r.MyTest"),
0,
TEXT("Test My Global Shader, set it to 0 to disable, or to 1, 2 or 3 for fun!"),
ECVF_RenderThreadSafe
);
void FDeferredShadingSceneRenderer::RenderFinish(FRHICommandListImmediate& RHICmdList)
{
[...]
// ***
// Inserted code, just before finishing rendering, so we can overwrite the screen’s contents!
int32 MyTestValue = CVarMyTest.GetValueOnAnyThread();
if (MyTestValue != 0)
{
FLinearColor Color(MyTestValue == 1, MyTestValue == 2, MyTestValue == 3, 1);
RenderMyTest(RHICmdList, FeatureLevel, Color);
}
// End Inserted code
// ***
FSceneRenderer::RenderFinish(RHICmdList);
[...]
}
关于添加控制台变量的方法, 可以看UE官方的文档: C++中的控制台变量
TAutoConsoleVariable的定义如下:
template <class T>
class TAutoConsoleVariable : public FAutoConsoleObject
{
public:
//一共四个参数:
TAutoConsoleVariable(const TCHAR* Name, const T& DefaultValue, const TCHAR* Help, uint32 Flags = ECVF_Default);
}
定义的时候需要注意, TAutoConsoleVariable 中的类型T只能是int32
、float
、FString
三种, 构造函数的四个参数如下:
- Name:变量名
- DefaultValue:变量的默认值
- Help:如何输入“<变量名> ?”,将要显示的帮助信息
- Flags:可选值是从
ECVF_Default
到ECVF_ScalabilityGroup
的值
其中关于第四个参数Flags的说明如下:
enum EConsoleVariableFlags
{
/**
* 默认,没有设置标志,值由构造函数设置。
* Default, no flags are set, the value is set by the constructor
*/
ECVF_Default = 0x0,
/**
* 正式版会隐藏在控制台中,用户无法更改。
* Console variables marked with this flag behave differently in a final release build.
* Then they are are hidden in the console and cannot be changed by the user.
*/
ECVF_Cheat = 0x1,
/**
* 用户只能从从 C++ 或 ini 更改, 不能在控制台中更改这个变量的数值, 因为是只读, 没什么可说的。
* Console variables cannot be changed by the user (from console).
* Changing from C++ or ini is still possible.
*/
ECVF_ReadOnly = 0x4,
/**
* 如果变量再次注册为同一类型,则该对象将重新激活。这有利于 DLL 卸载。
* UnregisterConsoleObject() was called on this one.
* If the variable is registered again with the same type this object is reactivated. This is good for DLL unloading.
*/
ECVF_Unregistered = 0x8,
/**
* 在这个变量没有被注册初始化的时候,使用的是ini中的变量,
* 注册之后这个变量的数值就会立马被拷贝到别的地方并且变量本身被销毁。
* This flag is set by the ini loading code when the variable wasn't registered yet.
* Once the variable is registered later the value is copied over and the variable is destructed.
*/
ECVF_CreatedFromIni = 0x10,
/**
* 这个变量使用的时候基本上主要是通过维护另一个copy的变量,
* 使用的时候首先通过渲染线程命令来更新copy的变量。
* 并且需要注意:它假定引用仅在渲染线程上访问, 所以不要在任何其他线程中使用,
* 或者最好不要使用引用以避免潜在的问题。
* Maintains another shadow copy and updates the copy with render thread commands to maintain proper ordering.
* Could be extended for more/other thread.
* Note: On console variable references it assumes the reference is accessed on the render thread only
* (Don't use in any other thread or better don't use references to avoid the potential pitfall).
*/
ECVF_RenderThreadSafe = 0x20,
/**
* 如果未设置 ApplyCVarSettingsGroupFromIni 将警告,不能与 ECVF_Cheat 放在一起使用
* ApplyCVarSettingsGroupFromIni will complain if this wasn't set, should not be combined with ECVF_Cheat */
ECVF_Scalability = 0x40,
/**
* 这些 cvar 控制其他带有标志 ECVF_Scalability 的 cvar,名称应以“sg”开头。
* those cvars control other cvars with the flag ECVF_Scalability, names should start with "sg." */
ECVF_ScalabilityGroup = 0x80,
// ------------------------------------------------
/* to get some history of where the last value was set by ( useful for track down why a cvar is in a specific state */
ECVF_SetByMask = 0xff000000,
// 剩下的这些主要表示 优先级 (从低到高)
//the ECVF_SetBy are sorted in override order (weak to strong), the value is not serialized, it only affects it's override behavior when calling Set()
// lowest priority (default after console variable creation)
ECVF_SetByConstructor = 0x00000000,
// from Scalability.ini (lower priority than game settings so it's easier to override partially)
ECVF_SetByScalability = 0x01000000,
// (in game UI or from file)
ECVF_SetByGameSetting = 0x02000000,
// project settings (editor UI or from file, higher priority than game setting to allow to enforce some setting fro this project)
ECVF_SetByProjectSetting = 0x03000000,
// per device setting (e.g. specific iOS device, higher priority than per project to do device specific settings)
ECVF_SetByDeviceProfile = 0x04000000,
// per project setting (ini file e.g. Engine.ini or Game.ini)
ECVF_SetBySystemSettingsIni = 0x05000000,
// consolevariables.ini (for multiple projects)
ECVF_SetByConsoleVariablesIni = 0x06000000,
// a minus command e.g. -VSync (very high priority to enforce the setting for the application)
ECVF_SetByCommandline = 0x07000000,
// least useful, likely a hack, maybe better to find the correct SetBy...
ECVF_SetByCode = 0x08000000,
// editor UI or console in game or editor
ECVF_SetByConsole = 0x09000000,
// ------------------------------------------------
};
一般用的比较多的就是 ECVF_Scalability | ECVF_RenderThreadSafe
.ECVF_Cheat
表示此变量会在正式版本中隐藏。
从ECVF_SetByConstructor
到ECVF_SetByConstructor
是表示优先级。
也可以注册一个对现有变量的引用FAutoConsoleVariableRef
, 引用方便快速但会绕过多项功能(如线程安全、回调、sink、cheat), 调用的函数 类似如下:
FAutoConsoleVariableRef CVarVisualizeGPUSimulation(
TEXT("FX.VisualizeGPUSimulation"),
VisualizeGPUSimulation,
TEXT("Visualize the current state of GPU simulation.\n")
TEXT("0 = off\n")
TEXT("1 = visualize particle state\n")
TEXT("2 = visualize curve texture"),
ECVF_Cheat
);
这样在运行项目的时候, 用波浪号 (~) 调出控制台之后就可以
- 输入
r.MyTest 1
启用SetColor功能,r.MyTest 0
禁用SetColor功能。 - 输入
r.MyTest 2
或r.MyTest 3
来更改颜色。
Debug生成的源码
如果您希望能够调试 .usf 文件的编译和/或查看处理后的文件,请查看博客文章Debugging the Shader Compiling Process。文章来源:https://www.toymoban.com/news/detail-796016.html
快速编译
在编辑器运行时修改 .usf 文件,然后按 Ctrl+Shift+. (句点)或在控制台中输入recompileshaders changed
来rebuild 改动过的 Shader .文章来源地址https://www.toymoban.com/news/detail-796016.html
到了这里,关于UE | Shader | 在UE中添加全局Shader的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!