PostFx Workaround (Unity Plugin)

From PopcornFX
Jump to: navigation, search

/!\ This workaround is deprecated as of v2.8 and will hinder you perfs. Don't use it. /!\

Issue

When using DirectX 11, you might encounter some issues caused by post effects in Unity. Some of the default image effects proposed in the standard Unity assets break your depth buffer and it is then impossible to draw particles on top of this effect.

You know that you are facing this issue if:

  1. You have one or more post-effect(s) applied to your camera (image effect opaque)
  2. The "Insert Native Rendering" option (in "Edit" -> "PopcornFX Preferences") is set to "Before Image Effect" (this actually means that the particles are rendered after the image effect opaque, which causes the rendering to be broken)
  3. The particles are not z-tested correctly (generally, they appear on top of all your geometry)

/!\ This issue should be fixed in the next version of the plugin! (2.8)

Solutions

Switch to DirectX 9

If you don't especially need DirectX 11, you can switch to DirectX 9 and this issue should be fixed.

For soft particles only

If you are only using soft particles in your particle effect, enabling the "Soft Particle" option in your camera should solve this problem. This only works for billboards.

Generic solution

This is a generic solution that works for all kind of particles in any configuration. The trick is to create 2 cameras, one to render the geometry on which the post-effects will be applied, and another one to render only the particles, or the particles and some other geometry on which you don't want post-effects. Here is the step-by-step workaround:

  1. Create a first camera (let's call it "Main Camera")
  2. Create a second camera (let's call it "Particle Camera"), make sure it is exactly similar to the "Main Camera" (same transformation, same projection options)
  3. Make sure the "Depth" option of the "Main Camera" is set to a smaller number than the one on the "Particle Camera" so that it is rendered first
  4. Set the "Particle Camera" "Clear Flags" to "Don't Clear"
  5. Set the "Particle Camera" "Culling Mask" to "Nothing"
  6. Add the PkFxRenderingPlugin component to the "Particle Camera"

You will then need a C# script and two shaders that will copy the depth from the "Main Camera" to the "Particle Camera" so that it can be used to z-test the particles.

The first shader just copies the depth of a camera to a texture:

Shader "Hidden/PKFx Depth Grab"
{
	SubShader
	{
		// No culling or depth
		ZTest Always
		ZWrite On

		// Writes to a single-component texture (TextureFormat.Depth)
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				fixed2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			v2f vert(appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;
				return o;
			}

			sampler2D _CameraDepthTexture;

#if !defined(SHADER_API_D3D9) && !defined(SHADER_API_D3D11_9X)
			fixed frag(v2f i) : SV_Depth
			{
				fixed4 col = tex2D(_CameraDepthTexture, i.uv);
				return col.r;
			}
#else
			void frag(v2f i, out float4 dummycol:COLOR, out float depth : DEPTH)
			{
				fixed4 col = tex2D(_CameraDepthTexture, i.uv);
				dummycol = col;

				depth = col.r;
			}
#endif
			ENDCG
		}
	}
}

The second shader writes a texture in the depth:

Shader "Hidden/PKFx Depth Write"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		// No culling or depth
		ZTest Always
		ZWrite On

		// Writes to a single-component texture (TextureFormat.Depth)
		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
				fixed2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D	_MainTex;

			v2f vert(appdata v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = v.uv;
				return o;
			}
			
#if !defined(SHADER_API_D3D9) && !defined(SHADER_API_D3D11_9X)
			fixed frag(v2f i) : SV_Depth
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				return col.r;
			}
#else
			void frag(v2f i, out float4 dummycol:COLOR, out float depth : DEPTH)
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				dummycol = col;

				depth = col.r;
			}
#endif
			ENDCG
		}
	}
}

And the script takes two cameras as input and copies the depth of the first one in the second one:

using UnityEngine;
using System.Collections;
using UnityEngine.Rendering;

public class CopyMainCameraDepth : MonoBehaviour {

    public Camera   m_Camera1;
    public Camera   m_Camera2;

    private RenderTexture   m_CamDepth = null;
    private CommandBuffer   m_CmdDepthWrite = null;
    private CommandBuffer   m_CmdDepthGrab = null;
    private Material        m_DepthWriteMat = null;
    private Material        m_DepthGrabMat = null;

    private int             m_pixelWidth = 0;
    private int             m_pixelHeight = 0;

    // Use this for initialization
    void Start ()
    {

        m_DepthGrabMat = new Material(Shader.Find("Hidden/PKFx Depth Grab"));
        m_DepthWriteMat = new Material(Shader.Find("Hidden/PKFx Depth Write"));
        m_CmdDepthGrab = new CommandBuffer();
        m_CmdDepthWrite = new CommandBuffer();

        m_Camera1.AddCommandBuffer(CameraEvent.AfterForwardOpaque, m_CmdDepthGrab);
        m_Camera2.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, m_CmdDepthWrite);
    }
	
    // Update is called once per frame
    void Update ()
    {
        if (m_CamDepth == null ||
            m_CamDepth.IsCreated() == false ||
            m_Camera1.pixelWidth != m_pixelWidth ||
            m_Camera1.pixelHeight != m_pixelHeight)
        {
            m_pixelWidth = m_Camera1.pixelWidth;
            m_pixelHeight = m_Camera1.pixelHeight;
            m_CamDepth = new RenderTexture( m_pixelWidth,
                                            m_pixelHeight, 16, RenderTextureFormat.Depth);
            m_CamDepth.Create();
        }

        m_CmdDepthGrab.Clear();
        m_CmdDepthGrab.name = "Grab depth";
        m_CmdDepthGrab.Blit((Texture)m_CamDepth, m_CamDepth, m_DepthGrabMat);

        m_CmdDepthWrite.Clear();
        m_CmdDepthWrite.name = "Write depth";
        m_CmdDepthWrite.Blit((Texture)m_CamDepth, BuiltinRenderTextureType.CurrentActive, m_DepthWriteMat);
    }
}
  1. Add those three files to your project
  2. Add the CopyMainCameraDepth component in your scene
  3. Set the Camera1 input to your "Main Camera" and the Camera2 to your "Particle Camera"

This trick should work, but it costs a little bit more performances than the standard rendering pipeline.

/!\ You might encounter some problems with the soft particles using this technique.