Unreal Engine 3自定义Post Process Effect
本文成于学习独行剑侠的一篇文章《Unreal Engine Shader编程基础》的过程中遇到的问题以及自己尝试的结果。
1: // TestShader.usf
2:
3: float4 MainPS(float2 InUV : TEXCOORD0) : COLOR0
4: {
5: return float4(InUV, 0, 1.0);
6: }
1: IMPLEMENT_CLASS(UTestEffect);
2:
3: class FTestPixelShader : public FGlobalShader
4: {
5: DECLARE_SHADER_TYPE(FTestPixelShader, Global);
6: public:
7: static UBOOL ShouldCache(EShaderPlatform Platform)
8: {
9: return TRUE;
10: }
11:
12: static void ModifyCompilationEnvironment(EShaderPlatform Platform,
13: FShaderCompilerEnvironment& OutEnvironment)
14: {
15: }
16:
17: FTestPixelShader()
18: {
19: }
20:
21: FTestPixelShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
22: : FGlobalShader(Initializer)
23: {
24: }
25: };
26:
27: IMPLEMENT_SHADER_TYPE(, FTestPixelShader, TEXT("TestPixelShader"), TEXT("MainPS"), SF_Pixel, 0, 0);
DECLARE_SHADER_TYPE和IMPLEMENT_SHADER_TYPE宏分别用来声明和定义Shader。这是Shader不带参数的最简单的情况,如果Shader带参数还要在类中声明相应的FShaderParameter/FShaderResourceParameter参数。FGlobalShader还有一个重载函数virtual UBOOL Serialize(FArchive& Ar)用于序列化,我们这里没有用到所以不需要重写它。
在原文这里就要添加相应的绘制代码到引擎源文件中,出于简单测试目的我不是很想改动底层文件,于是我想能不能利用UE3的PostProcess来达到同样的目的。经过一番探索有了理想的结果。
要在UE3中使用Post Process首先要创建PostProcessChain,然后将相应的Post Process Effect连接到PostProcessChain。这里就要涉及到自定义Post Process Effect如何实现了。一个Post Process Effect的实现由三部分组成,.cpp文件, .uc文件以及.usf文件,其中usf和cpp文件我们在文章开头已经准备好。经过对UE3自带Post Process Effect的参考,需要创建TestEffect.uc文件以及修改UnTestPixelShaderEffect.cpp文件。
在Engine/Src/Classes/PostProcess/项目目录创建TestEffect.uc。
1: class TestEffect extends PostProcessEffect
2: native;
3:
4: cpptext
5: {
6: // UPostProcessEffect interface
7: /**
8: * Creates a proxy to represent the render info for a post process effect
9: * @param WorldSettings - The world's post process settings for the view.
10: * @return The proxy object.
11: */
12: virtual class FPostProcessSceneProxy* CreateSceneProxy(const FPostProcessSettings* WorldSettings);
13:
14: /**
15: * @param View - current view
16: * @return TRUE if the effect should be rendered
17: */
18: virtual UBOOL IsShown(const FSceneView* View) const;
19:
20: /**
21: * Called after this instance has been serialized. UberPostProcessEffect should only
22: * ever exists in the SDPG_PostProcess scene
23: */
24: virtual void PostLoad();
25: }
然后修改UnTestPixelShader.cpp,在开头的代码之后添加如下代码:
1: class FTestEffectPostProcessSceneProxy : public FPostProcessSceneProxy
2: {
3: public:
4: /**
5: * Initialization constructor.
6: * @param InEffect - DOF post process effect to mirror in this proxy
7: */
8: FTestEffectPostProcessSceneProxy(const UTestEffect* InEffect,
9: const FPostProcessSettings* WorldSettings);
10:
11: /**
12: * Render the post process effect
13: * Called by the rendering thread during scene rendering
14: * @param InDepthPriorityGroup - scene DPG currently being rendered
15: * @param View - current view
16: * @param CanvasTransform - same canvas transform used to render the scene
17: * @param LDRInfo - helper information about SceneColorLDR
18: * @return TRUE if anything was rendered
19: */
20: UBOOL Render(const FScene* Scene, UINT InDepthPriorityGroup,FViewInfo& View,
21: const FMatrix& CanvasTransform,FSceneColorLDRInfo& LDRInfo);
22: };
23:
24: FTestEffectPostProcessSceneProxy
25: ::FTestEffectPostProcessSceneProxy(const UTestEffect* InEffect,const FPostProcessSettings* WorldSettings)
26: : FPostProcessSceneProxy(InEffect)
27: {
28: }
29:
30: UBOOL FTestEffectPostProcessSceneProxy::Render(const FScene* Scene, UINT InDepthPriorityGroup,FViewInfo& View,
31: const FMatrix& CanvasTransform,FSceneColorLDRInfo& LDRInfo)
32: {
33: RHISetRenderTarget(GSceneRenderTargets.GetSceneColorSurface(), FSurfaceRHIRef());
34:
35: GSceneRenderTargets.BeginRenderingSceneColor();
36:
37: TShaderMapRef<FScreenVertexShader> TestVertexShader( GetGlobalShaderMap() );
38: TShaderMapRef<FTestPixelShader> TestPixelShader( GetGlobalShaderMap() );
39:
40: RHISetViewport( 0, 0, 0.0f, View.RenderTargetX + View.RenderTargetSizeX,
41: View.RenderTargetY + View.RenderTargetSizeY, 1.0f );
42: RHISetViewParameters(View);
43:
44: static FGlobalBoundShaderState TestBoundState;
45: SetGlobalBoundShaderState(
46: TestBoundState,
47: GFilterVertexDeclaration.VertexDeclarationRHI,
48: *TestVertexShader,
49: *TestPixelShader,
50: sizeof(sizeof(FFilterVertex))
51: );
52:
53: DrawDenormalizedQuad(
54: View.RenderTargetX, View.RenderTargetY,
55: View.RenderTargetSizeX, View.RenderTargetSizeY,
56: View.RenderTargetX, View.RenderTargetY,
57: View.RenderTargetSizeX, View.RenderTargetSizeY,
58: View.RenderTargetSizeX, View.RenderTargetSizeY,
59: GSceneRenderTargets.GetBufferSizeX(), GSceneRenderTargets.GetBufferSizeY()
60: );
61:
62: GSceneRenderTargets.FinishRenderingSceneColor();
63:
64: return TRUE;
65: }
66:
67: FPostProcessSceneProxy* UTestEffect::CreateSceneProxy(const FPostProcessSettings* WorldSettings)
68: {
69: return new FTestEffectPostProcessSceneProxy(this, WorldSettings);
70: }
71:
72: UBOOL UTestEffect::IsShown(const FSceneView* View) const
73: {
74: return Super::IsShown(View);
75: }
76:
77: void UTestEffect::PostLoad()
78: {
79: Super::PostLoad();
80: }
完成之后先运行一遍Editor,引擎会解析TestEffect.uc然后在EngineClasses.h头文件中添加对应的native类定义。之后完整编译整个项目。重新打开Editor,新建一个PostProcessChain,命名为TestEffect,双击打开,在Post Process编辑器中右键可以看到菜单底部有我们自定义的TestEffect,将它连接到默认的SceneRenderTarget上,保存PostProcessChain,然后选中它。
打开任意一张地图,进入View->World Properties菜单项,点击World Post Process Chain栏旁的箭头按钮赋予我们选中的TestEffect,如果一切正常,结果应该类似这样:
至此一个简单的自定义Post Process Effect就完成了^_^。
更新:带参数的Shader
昨天试验过不带参数的简单Shader之后,今天尝试了添加Shader参数的支持。
首先我们在TestPixelShader.usf文件中添加一个变量:
1: // TestShader.usf
2: float4 ShadingColor;
3:
4: float4 MainPS(float2 InUV : TEXCOORD0) : COLOR0
5: {
6: return float4(InUV, 0, 1.0) + ShadingColor;
7: }
简单的进行颜色叠加。
然后我们在TestEffect.uc文件中添加一个编辑器变量以便我们在运行时更改Shader参数进行测试。
1: var() Vector ShadingColor;
在defaultproperties块中给一个默认值:
1: defaultproperties
2: {
3: ShadingColor = (X=0.0f, Y=0.0f, Z=0.0f)
4: }
保存文件,在这时运行一下Editor,让EngineClass中UTestShaderEffect的声明得到更新。现在来修改UnTestPixelShader.cpp文件。
先在FTestPixelShader类中声明一个FShaderParameter变量:
1: FShaderParameter ShadingColorParameters;
因为这一次我们用到了Shader参数,所以要重写FGlobalShader基类的Serialize(FArchive& Ar)方法:
1: virtual UBOOL Serialize(FArchive& Ar)
2: {
3: UBOOL bShaderHasOutdatedParameters = FShader::Serialize(Ar);
4: Ar << ShadingColorParameters;
5:
6: return bShaderHasOutdatedParameters;
7: }
然后在构造函数中绑定我们的Shader参数:
1: FTestPixelShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
2: : FGlobalShader(Initializer)
3: {
4: ShadingColorParameters.Bind(Initializer.ParameterMap, TEXT("ShadingColor"), TRUE);
5: }
之后,在FTestEffectPostProcessSceneProxy类中定义一个用来传入Shader的变量FVector ShadingColor,在FTestEffectPostProcessSceneProxy构造函数中接收来自Post Process编辑器中用户指定的ShadingColor值。
1: FTestEffectPostProcessSceneProx
2: y::FTestEffectPostProcessSceneProxy(const UTestEffect* InEffect,const FPostProcessSettings* WorldSettings)
3: : FPostProcessSceneProxy(InEffect)
4: {
5: ShadingColor = InEffect->ShadingColor;
6: }
最后,我们需要将接收到的Shader参数实际应用到Shader中,在FTestEffectPostProcessSceneProxy::Render函数的SetGlobalBoundShaderState后面添加以下代码:
1: SetPixelShaderValues(
2: TestPixelShader->GetPixelShader(),
3: TestPixelShader->ShadingColorParameters,
4: &ShadingColor,
5: 1);
这样,编辑器中的ShadingColor参数就和Shader文件中的ShadingColor变量建立了联系,现在启动Editor来进行测试。
加载任意一张地图,打开内容浏览器找到上次创建TestEffect PostProcessChain,双击打开,在Post Process编辑器中选中它,我们可以发现编辑中已经可以看到我们定义的Shading Color变量了。
随便改一下试试,比如把X改成1.0,然后将将PostProcessChain赋予场景观察结果:
B
Bingo!运行正常~