Keep previous frame without turning it into a Texture2D

In order to create a ghosting effect I need to keep the previously rendered frame and blend it with the current one. I keep the previous frame by turning it into a Texture2D but the performance of ReadPixels is terrible. I’m wondering if there’s a better way to do so.

Here’s the code I have:

// Ghosting.

using UnityEngine;
using System.Collections;

[AddComponentMenu ("OCASM/Image Effects/ImageEffect002")]
public class ImageEffect_002 : MonoBehaviour {

   [Range (0,0.99f)]
   public float intensity;
   private Material ImageEffect_002_Mat;
   private Texture2D pastFrame;

   void Awake(){
     ImageEffect_002_Mat = new Material (Shader.Find("Hidden/OCASM/Image Effects/ImageEffect_002"));
   }

   void OnRenderImage(RenderTexture src, RenderTexture dst){
     if (pastFrame) {
       ImageEffect_002_Mat.SetFloat ("_Intensity", intensity);
       ImageEffect_002_Mat.SetTexture ("_BTex", pastFrame);   
       Graphics.Blit (src, dst, ImageEffect_002_Mat);
     }
   }

   void OnPostRender () {
     if (!pastFrame) {
       pastFrame = new Texture2D (Screen.width, Screen.height);
     }
     RenderTexture.active = Camera.main.targetTexture;
     pastFrame.ReadPixels (new Rect(0,0,Screen.width,Screen.height),0,0);
     pastFrame.Apply ();
   }
}
Shader "Hidden/OCASM/Image Effects/ImageEffect_002"
{
    Properties {
        _MainTex ("Input Render Texture", 2D) = "black" {}
        _BTex ("Output Render Texture", 2D) = "black" {}
        _Intensity ("Intensity", Range (0,1)) = 0.5
    }
    SubShader {
        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"

            uniform sampler2D _MainTex;
            uniform sampler2D _BTex;
            uniform float _Intensity;

            float4 frag(v2f_img i) : COLOR{
                float4 c1 = tex2D(_MainTex, i.uv);
                float4 c2 = tex2D(_BTex, i.uv);
                float4 c3 = lerp(c1, c2, _Intensity);
                return c3;
            }
            ENDCG
        }
    }
}

OK, it turned out to be simpler than I thought BUT now the resulting image flips vertically every frame. Any idea on what’s happening?

// Ghosting.

using UnityEngine;
using System.Collections;

[AddComponentMenu ("OCASM/Image Effects/ImageEffect002")]
public class ImageEffect_002 : MonoBehaviour {

    // VARIABLES
    [Range (0,0.99f)]
    public float intensity;
    private Material ImageEffect_002_Mat;
    private RenderTexture pastFrame;

    void Awake(){
        ImageEffect_002_Mat = new Material (Shader.Find("Hidden/OCASM/Image Effects/ImageEffect_002"));
    }

    void OnRenderImage(RenderTexture src, RenderTexture dst){
        if (!pastFrame) {
            pastFrame = new RenderTexture (Screen.width, Screen.height, 16);
        }

        ImageEffect_002_Mat.SetFloat ("_Intensity", intensity);
        ImageEffect_002_Mat.SetTexture ("_BTex", pastFrame);   
        Graphics.Blit (src, dst, ImageEffect_002_Mat);

        RenderTexture.active = Camera.main.targetTexture;
        Graphics.Blit (RenderTexture.active, pastFrame);
    }
}

This was the problem:

Here’s the final code, at least an order of magnitude faster than with the ReadPixels method:

// Ghosting.

using UnityEngine;
using System.Collections;

[AddComponentMenu ("OCASM/Image Effects/ImageEffect002")]
public class ImageEffect_002 : MonoBehaviour {

    [Range (0,0.99f)]
    public float intensity;
    private Material ImageEffect_002_Mat;
    private RenderTexture pastFrame;

    void Start(){
        ImageEffect_002_Mat = new Material (Shader.Find("Hidden/OCASM/Image Effects/ImageEffect_002"));
        pastFrame = new RenderTexture (Screen.width, Screen.height, 16);
    }

    void OnRenderImage(RenderTexture src, RenderTexture dst){
        ImageEffect_002_Mat.SetFloat ("_Intensity", intensity);
        ImageEffect_002_Mat.SetTexture ("_BTex", pastFrame);
        Graphics.Blit (src, dst, ImageEffect_002_Mat);

        Graphics.Blit (RenderTexture.active, pastFrame);
    }
}
Shader "Hidden/OCASM/Image Effects/ImageEffect_002"
{
    Properties {
        _MainTex ("Input Render Texture", 2D) = "black" {}
        _BTex ("Output Render Texture", 2D) = "black" {}
        _Intensity ("Intensity", Range (0,1)) = 0.5
    }
    SubShader {

    // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass {
            CGPROGRAM
            #pragma vertex vert_img
            #pragma fragment frag

            #include "UnityCG.cginc"

            uniform sampler2D _MainTex;
            uniform sampler2D _BTex;
            uniform float _Intensity;

            float4 frag(v2f_img i) : COLOR{
                float4 c1 = tex2D(_MainTex, i.uv);
                i.uv.y = 1 - i.uv.y;
                float4 c2 = tex2D(_BTex, i.uv);
                float4 c3 = lerp(c1, c2, _Intensity);

                return c3;
            }
            ENDCG
        }
    }
}

On Forward it’s still flipped but I used Deferred so I don’t care.

5 Likes

Thank you so much for this. I’ve been searching for an effect like this everywhere and couldn’t find anything. I’ve used this code for my game and am going to study it to learn more about the shader that you’ve created, and will make sure to credit you if I use it in my game for the future! Great work!

OCASM Thanks so much. I was looking everywhere for an example like this. Thanks!

1 Like