版权声明:本文为CSDN博主「YakSue」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
简介
新建全局着色器为插件 | 虚幻引擎文档展示了如何创建一个 GlobalShader 。然而文档的内容基本上是指导你如何将现有的 “Lens Distortion” 插件(位于 Engine\Plugins\Compositing\LensDistortion
)中的代码拷贝到一个新的插件中并改名,并没有详细介绍更深的知识。因此,想要深入了解这方面的知识,还需要自己研究代码。
我决定先尝试使用这个插件的功能,然后再观察它的代码,希望对 UE4 的渲染有更多的了解。不过首先,先了解下 GlobalShader 这个概念。
GlobalShader 概念
在着色器开发 | 虚幻引擎文档中对 GlobalShader 有如下描述:
Global shaders are shaders which operate on fixed geometry (like a full screen quad) and do not need to interface with materials. Examples would be shadow filtering, or post processing. Only one shader of any given global shader type exists in memory. 全局着色器是对固定几何体(例如全屏幕四边形)执行操作并且不需要与材质进行交互的着色器。例如阴影过滤或者后处理。在内存中,对于任何给定的全局着色器类型,只有一个着色器。
FGlobalShader
继承自 FShader
,他们继承的关系如下:
FMaterialShader
是 FShader
的另一个子类,看来他和 FGlobalShader
的区别就是需要和 Material
进行交互。
GlobalShader
不需要 Material
的参与,看来相对简单。可能这也是为什么官方文档:在插件里写 Shaders | 虚幻引擎文档中目前还只有 GlobalShader
的范例,希望未来会有较为复杂的 MaterialShader
的范例。
试用插件功能
官方文档新建全局着色器为插件 | 虚幻引擎文档里的第二部分手把手指导了如何使用插件功能。 这个插件的功能,从表现上来看,他就是提供了一个蓝图函数:
这个函数输入了一些参数(包括一个自己定义的 Camera Model
对象),然后输出到一个 RenderTarget
中。
使用它时,将它挂到 Tick
中。此外,在调用 DrawUVDisplacementToRenderTarget
之前需要调用ClearRenderTarget2D
。另外,它需要一个 Camera Model
对象,因此需要先构造一个。
除此之外,为了调试,也加了键盘的事件,可以按 "W" 和 "S" 键来对 K1
参数进行调整,以此来改变渲染的效果。
把这些节点放入一个蓝图 Actor 中,再将 Actor 放入场景中,就可以不断地渲染一个 RenderTarget
。把这个 RenderTarget
放入一个材质中连接到 BaseColor
,可以直观地观察它的颜色:
将这个材质赋给场景中的一个面片,运行试试:
功能上是成功了,官方范例的展示也是类似这个情况。不过渲染出来的内容,说真的并没有看出来具体意义是什么。。。
出于好奇,我想将结果给 UV
试一试(毕竟这个 shader 文件的名字叫 UVGeneration),于是我随便找了一个图片:
将材质中采样节点的 UV
根据 RenderTarget
进行变形:
结果如下:
结合这个插件的名字:LensDistortion(透镜扭曲)。我想将它用在 UV
上应该是正确的。但是具体的用法应该会更有讲究,想要搞清楚的话就要看 shader 代码了。这一篇文章不想对 shader 的具体算法进行观察,而是想观察流程以及渲染的 API 调用。
文件内容概括分析
LensDistortion 插件的文件很少:
其中 .usf
是 shader 文件,.Build.cs
就是常规的模块定义文件,.uplugin
是常规的定义插件的文件。下面对 C++ 文件进行逐个观察,从最简单的到最复杂的:
ILensDistortion.h
这个文件非常空,它的工作就是定义了一个模块:
class ILensDistortion : public IModuleInterface
LensDistortion.cpp
这个文件也非常空,它做的最主要的工作是在启动模块时加入一个 Shader 目录:
void FLensDistortion::StartupModule()
{
FString PluginShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("LensDistortion"))->GetBaseDir(), TEXT("Shaders"));
AddShaderSourceDirectoryMapping(TEXT("/Plugin/LensDistortion"), PluginShaderDir);
}
关于 AddShaderSourceDirectoryMapping
函数,它的定义:
/**
* Maps a real shader directory existing on disk to a virtual shader directory.
* @param VirtualShaderDirectory Unique absolute path of the virtual shader directory (ex: /Project).
* @param RealShaderDirectory FPlatformProcess::BaseDir() relative path of the directory map.
*/
extern RENDERCORE_API void AddShaderSourceDirectoryMapping(const FString& VirtualShaderDirectory, const FString& RealShaderDirectory);
这个函数最终会将一组虚拟目录和实际硬盘目录的配对插入 GShaderSourceDirectoryMappings
中
GShaderSourceDirectoryMappings
是一个全局变量:
/** Global map of virtual file path to physical file paths */
static TMap<FString, FString> GShaderSourceDirectoryMappings;
而 GetShaderSourceFilePath
函数会根据 GShaderSourceDirectoryMappings
从一个虚拟目录获得一个实际的文件目录,如:
LensDistortionBlueprintLibrary.h
它定义了一个类:
UCLASS(MinimalAPI, meta=(ScriptName="LensDistortionLibrary"))
class ULensDistortionBlueprintLibrary : public UBlueprintFunctionLibrary
这个类不重要,关键是它有 static
蓝图可调用的函数:
UFUNCTION(BlueprintCallable, Category = "Lens Distortion", meta = (WorldContext = "WorldContextObject"))
static void DrawUVDisplacementToRenderTarget(
const UObject* WorldContextObject,
const FLensDistortionCameraModel& CameraModel,
float DistortedHorizontalFOV,
float DistortedAspectRatio,
float UndistortOverscanFactor,
class UTextureRenderTarget2D* OutputRenderTarget,
float OutputMultiply = 0.5,
float OutputAdd = 0.5
);
LensDistortionBlueprintLibrary.cpp
这个文件包含了蓝图函数的实现。实际上蓝图函数 DrawUVDisplacementToRenderTarget
只是将参数传递给了 CameraModel
,并调用 CameraModel
的 DrawUVDisplacementToRenderTarget
函数:
CameraModel.DrawUVDisplacementToRenderTarget(
WorldContextObject->GetWorld(),
DistortedHorizontalFOV, DistortedAspectRatio,
UndistortOverscanFactor, OutputRenderTarget,
OutputMultiply, OutputAdd);
LensDistortionAPI.h
这个文件定义了 FLensDistortionCameraModel
对象:
/** Mathematic camera model for lens distortion/undistortion.
*
* Camera matrix =
* | F.X 0 C.x |
* | 0 F.Y C.Y |
* | 0 0 1 |
*/
USTRUCT(BlueprintType)
struct LENSDISTORTION_API FLensDistortionCameraModel
LensDistortionRendering.cpp
这个文件的内容相当多,概括来讲:
它定义了一个 GlobalShader
:
class FLensDistortionUVGenerationShader : public FGlobalShader
之后顶点着色器和像素着色器都继承它:
class FLensDistortionUVGenerationVS : public FLensDistortionUVGenerationShader
class FLensDistortionUVGenerationPS : public FLensDistortionUVGenerationShader
他们会在渲染时调用。 这个文件还定义了一个新的结构体:
/**
* Internal intermediary structure derived from FLensDistortionCameraModel by the game thread
* to hand to the render thread.
*/
struct FCompiledCameraModel
它在 CameraModel
的 DrawUVDisplacementToRenderTarget
函数中使用,被构造出来然后传递给 DrawUVDisplacementToRenderTarget_RenderThread
函数。
这一部分内容很多,下面专门分析这部分:
观察渲染方面的代码
Shader 创建
首先看 FLensDistortionUVGenerationShader
,它继承自 FGlobalShader
class FLensDistortionUVGenerationShader : public FGlobalShader
它的成员变量是一些参数:
private:
FShaderParameter PixelUVSize;
FShaderParameter RadialDistortionCoefs;
FShaderParameter TangentialDistortionCoefs;
FShaderParameter DistortedCameraMatrix;
FShaderParameter UndistortedCameraMatrix;
FShaderParameter OutputMultiplyAndAdd;
在它的构造函数中,让这些参数和 shader 文件中的变量(类似于 D3D 的 shader 文件的 Constant Buffer[^1 )进行绑定:
FLensDistortionUVGenerationShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FGlobalShader(Initializer)
{
PixelUVSize.Bind(Initializer.ParameterMap, TEXT("PixelUVSize"));
RadialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT("RadialDistortionCoefs"));
TangentialDistortionCoefs.Bind(Initializer.ParameterMap, TEXT("TangentialDistortionCoefs"));
DistortedCameraMatrix.Bind(Initializer.ParameterMap, TEXT("DistortedCameraMatrix"));
UndistortedCameraMatrix.Bind(Initializer.ParameterMap, TEXT("UndistortedCameraMatrix"));
OutputMultiplyAndAdd.Bind(Initializer.ParameterMap, TEXT("OutputMultiplyAndAdd"));
}
shader 文件中有有这些变量的声明:
// Size of the pixels in the viewport UV coordinates.
float2 PixelUVSize;
// K1, K2, K3
float3 RadialDistortionCoefs;
// P1, P2
float2 TangentialDistortionCoefs;
// Camera matrix of the undistorted viewport.
float4 UndistortedCameraMatrix;
// Camera matrix of the distorted viewport.
float4 DistortedCameraMatrix;
// Output multiply and add to the render target.
float2 OutputMultiplyAndAdd;
FLensDistortionUVGenerationShader
拥有 SetParameters
函数,用来给参数赋值。
template<typename TShaderRHIParamRef>
void SetParameters(
FRHICommandListImmediate& RHICmdList,
const TShaderRHIParamRef ShaderRHI,
const FCompiledCameraModel& CompiledCameraModel,
const FIntPoint& DisplacementMapResolution)
{
FVector2D PixelUVSizeValue(
1.f / float(DisplacementMapResolution.X), 1.f / float(DisplacementMapResolution.Y));
FVector RadialDistortionCoefsValue(
CompiledCameraModel.OriginalCameraModel.K1,
CompiledCameraModel.OriginalCameraModel.K2,
CompiledCameraModel.OriginalCameraModel.K3);
FVector2D TangentialDistortionCoefsValue(
CompiledCameraModel.OriginalCameraModel.P1,
CompiledCameraModel.OriginalCameraModel.P2);
SetShaderValue(RHICmdList, ShaderRHI, PixelUVSize, PixelUVSizeValue);
SetShaderValue(RHICmdList, ShaderRHI, DistortedCameraMatrix, CompiledCameraModel.DistortedCameraMatrix);
SetShaderValue(RHICmdList, ShaderRHI, UndistortedCameraMatrix, CompiledCameraModel.UndistortedCameraMatrix);
SetShaderValue(RHICmdList, ShaderRHI, RadialDistortionCoefs, RadialDistortionCoefsValue);
SetShaderValue(RHICmdList, ShaderRHI, TangentialDistortionCoefs, TangentialDistortionCoefsValue);
SetShaderValue(RHICmdList, ShaderRHI, OutputMultiplyAndAdd, CompiledCameraModel.OutputMultiplyAndAdd);
}
总结一下一个 C++ 中的变量怎么传入 shader,以 PixelUVSize
为例:
顶点着色器和像素着色器
顶点着色器 和 像素着色器 继承自 FLensDistortionUVGenerationShader
,看起来他们并不包含额外的内容,只是遵从一种固定的格式:
class FLensDistortionUVGenerationVS : public FLensDistortionUVGenerationShader
{
DECLARE_SHADER_TYPE(FLensDistortionUVGenerationVS, Global);
public:
/** Default constructor. */
FLensDistortionUVGenerationVS() {}
/** Initialization constructor. */
FLensDistortionUVGenerationVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FLensDistortionUVGenerationShader(Initializer)
{
}
};
class FLensDistortionUVGenerationPS : public FLensDistortionUVGenerationShader
{
DECLARE_SHADER_TYPE(FLensDistortionUVGenerationPS, Global);
public:
/** Default constructor. */
FLensDistortionUVGenerationPS() {}
/** Initialization constructor. */
FLensDistortionUVGenerationPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
: FLensDistortionUVGenerationShader(Initializer)
{ }
};
然后,需要宏来实现他们:
IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationVS, TEXT("/Plugin/LensDistortion/Private/UVGeneration.usf"), TEXT("MainVS"), SF_Vertex)
IMPLEMENT_SHADER_TYPE(, FLensDistortionUVGenerationPS, TEXT("/Plugin/LensDistortion/Private/UVGeneration.usf"), TEXT("MainPS"), SF_Pixel)
主线程(GameThread)的 DrawUVDisplacementToRenderTarget
主线程的 DrawUVDisplacementToRenderTarget
被蓝图调用。他的任务是最终想要入列一个渲染命令:
ENQUEUE_RENDER_COMMAND(CaptureCommand)(
[CompiledCameraModel, TextureRenderTargetResource, TextureRenderTargetName, FeatureLevel](FRHICommandListImmediate& RHICmdList)
{
DrawUVDisplacementToRenderTarget_RenderThread(
RHICmdList,
CompiledCameraModel,
TextureRenderTargetName,
TextureRenderTargetResource,
FeatureLevel);
}
);
为此,需要四个变量:
1.CompiledCameraModel
他的类型是 FCompiledCameraModel
,定义如下:
/**
* Internal intermediary structure derived from FLensDistortionCameraModel by the game thread
* to hand to the render thread.
*/
struct FCompiledCameraModel
{
/** Orignal camera model that has generated this compiled model. */
FLensDistortionCameraModel OriginalCameraModel;
/** Camera matrices of the lens distortion for the undistorted and distorted render.
* XY holds the scales factors, ZW holds the translates.
*/
FVector4 DistortedCameraMatrix;
FVector4 UndistortedCameraMatrix;
/** Output multiply and add of the channel to the render target. */
FVector2D OutputMultiplyAndAdd;
};
其中 FLensDistortionCameraModel OriginalCameraModel;
包含了一些参数,而剩下的三个变量都是通过参数计算出来的:
// Compiles the camera model to know the overscan scale factor.
float TanHalfUndistortedHorizontalFOV = FMath::Tan(DistortedHorizontalFOV * 0.5f) * UndistortOverscanFactor;
float TanHalfUndistortedVerticalFOV = TanHalfUndistortedHorizontalFOV / DistortedAspectRatio;
// Output.
FCompiledCameraModel CompiledCameraModel;
CompiledCameraModel.OriginalCameraModel = *this;
CompiledCameraModel.DistortedCameraMatrix.X = 1.0f / TanHalfUndistortedHorizontalFOV;
CompiledCameraModel.DistortedCameraMatrix.Y = 1.0f / TanHalfUndistortedVerticalFOV;
CompiledCameraModel.DistortedCameraMatrix.Z = 0.5f;
CompiledCameraModel.DistortedCameraMatrix.W = 0.5f;
CompiledCameraModel.UndistortedCameraMatrix.X = F.X;
CompiledCameraModel.UndistortedCameraMatrix.Y = F.Y * DistortedAspectRatio;
CompiledCameraModel.UndistortedCameraMatrix.Z = C.X;
CompiledCameraModel.UndistortedCameraMatrix.W = C.Y;
CompiledCameraModel.OutputMultiplyAndAdd.X = OutputMultiply;
CompiledCameraModel.OutputMultiplyAndAdd.Y = OutputAdd;
2.TextureRenderTargetResource
它通过参数 OutputRenderTarget
调用 GameThread_GetRenderTargetResource
获得:
FTextureRenderTargetResource* TextureRenderTargetResource = OutputRenderTarget->GameThread_GetRenderTargetResource();
3.TextureRenderTargetName
它直接就是 OutputRenderTarget
的名字
const FName TextureRenderTargetName = OutputRenderTarget->GetFName();
4.FeatureLevel
ERHIFeatureLevel::Type FeatureLevel = World->Scene->GetFeatureLevel();
渲染线程的 DrawUVDisplacementToRenderTarget
DrawUVDisplacementToRenderTarget_RenderThread
会在渲染线程中调用:
首先,它检查了是否在渲染线程中:
check(IsInRenderingThread());
然后,它获得了 RenderTargetTexture
对象:
FRHITexture2D* RenderTargetTexture = OutTextureRenderTargetResource->GetRenderTargetTexture();
将 RenderTargetTexture
对象设为 “可写”
RHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, RenderTargetTexture);
创建一个 FRHIRenderPassInfo
对象,并将 RenderTargetTexture
给他
FRHIRenderPassInfo RPInfo(RenderTargetTexture, ERenderTargetActions::DontLoad_Store, OutTextureRenderTargetResource->TextureRHI);
接下俩, 就开始 RenderPass
了:
RHICmdList.BeginRenderPass(RPInfo, TEXT("DrawUVDisplacement"));
RenderPass
中,首先设置了 viewport:
FIntPoint DisplacementMapResolution(OutTextureRenderTargetResource->GetSizeX(), OutTextureRenderTargetResource->GetSizeY());
RHICmdList.SetViewport(
0, 0, 0.f,
DisplacementMapResolution.X, DisplacementMapResolution.Y, 1.f);
然后,获得 shader:
TShaderMap<FGlobalShaderType>* GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef< FLensDistortionUVGenerationVS > VertexShader(GlobalShaderMap);
TShaderMapRef< FLensDistortionUVGenerationPS > PixelShader(GlobalShaderMap);
然后,设置 pipeline state:
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
之后,更新顶点着色器和像素着色器中的参数:
VertexShader->SetParameters(RHICmdList, VertexShader->GetVertexShader(), CompiledCameraModel, DisplacementMapResolution);
PixelShader->SetParameters(RHICmdList, PixelShader->GetPixelShader(), CompiledCameraModel, DisplacementMapResolution);
最后,执行 Draw Call
uint32 PrimitiveCount = kGridSubdivisionX * kGridSubdivisionY * 2;
RHICmdList.DrawPrimitive(0, PrimitiveCount, 1);
其中 kGridSubdivisionX
和 kGridSubdivisionY
都是定义在 cpp 文件中的常量:
static const uint32 kGridSubdivisionX = 32;
static const uint32 kGridSubdivisionY = 16;
等 RenderPass
所有的操作都执行完,最终执行 EndRenderPass
:
RHICmdList.EndRenderPass();
最后,整体看下这个函数吧:
static void DrawUVDisplacementToRenderTarget_RenderThread(
FRHICommandListImmediate& RHICmdList,
const FCompiledCameraModel& CompiledCameraModel,
const FName& TextureRenderTargetName,
FTextureRenderTargetResource* OutTextureRenderTargetResource,
ERHIFeatureLevel::Type FeatureLevel)
{
check(IsInRenderingThread());
FRHITexture2D* RenderTargetTexture = OutTextureRenderTargetResource->GetRenderTargetTexture();
RHICmdList.TransitionResource(EResourceTransitionAccess::EWritable, RenderTargetTexture);
FRHIRenderPassInfo RPInfo(RenderTargetTexture, ERenderTargetActions::DontLoad_Store, OutTextureRenderTargetResource->TextureRHI);
RHICmdList.BeginRenderPass(RPInfo, TEXT("DrawUVDisplacement"));
{
FIntPoint DisplacementMapResolution(OutTextureRenderTargetResource->GetSizeX(), OutTextureRenderTargetResource->GetSizeY());
// Update viewport.
RHICmdList.SetViewport(
0, 0, 0.f,
DisplacementMapResolution.X, DisplacementMapResolution.Y, 1.f);
// Get shaders.
TShaderMap<FGlobalShaderType>* GlobalShaderMap = GetGlobalShaderMap(FeatureLevel);
TShaderMapRef< FLensDistortionUVGenerationVS > VertexShader(GlobalShaderMap);
TShaderMapRef< FLensDistortionUVGenerationPS > PixelShader(GlobalShaderMap);
// Set the graphic pipeline state.
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit);
// Update shader uniform parameters.
VertexShader->SetParameters(RHICmdList, VertexShader->GetVertexShader(), CompiledCameraModel, DisplacementMapResolution);
PixelShader->SetParameters(RHICmdList, PixelShader->GetPixelShader(), CompiledCameraModel, DisplacementMapResolution);
// Draw grid.
uint32 PrimitiveCount = kGridSubdivisionX * kGridSubdivisionY * 2;
RHICmdList.DrawPrimitive(0, PrimitiveCount, 1);
}
RHICmdList.EndRenderPass();
}