Unreal Engine 3自定义Post Process Effect

         本文成于学习独行剑侠的一篇文章《Unreal Engine Shader编程基础》的过程中遇到的问题以及自己尝试的结果。

          首先,要新建一个usf格式的文件,放到引擎根目录/Engine/Shaders目录中。这个是UE3中使用的Shader文件格式(大概就是unreal shader file的意思: ),其实也就是文本文件。我们写个最简单的Pixel Shader:
   1: // TestShader.usf
   2:  
   3: float4 MainPS(float2 InUV : TEXCOORD0) : COLOR0
   4: {
   5:     return float4(InUV, 0, 1.0);
   6: }
      
       Shader代码写好后就要求我们按照Unreal的规则来创建对应的.cpp和/或.uc源文件,我们一步步来。
        在Engine\Src项目目录下新建一个UnTestPixelShaderEffect.cpp文件,用来映射我们的TestPixelShader.usf。
   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!运行正常~

 

posted @ 2012-04-24 23:26  林公子  阅读(1882)  评论(0编辑  收藏  举报