Post Processing

Hi Guys,

I have been trying to get post processing (Image Effects) to work in my 2D game for a while.

Sure its easy enough to add a “GreyScale” image effect to my camera, which then in the editor correctly renders the world in geyscale but as soon as you try to play like that on a mobile device (any mobile device) then the FPS tanks from 60 to 1-3.

I just read this post:

In it Alexy from Unity mentions you need to use Render Textures else a read-back will happen. Does anyone have any tips on how to do this?

Do I need two cameras, one to render into the render texture and another to render the render texture to the screen to get optimal performance?

Cheers,
Mike

Sorry to bump but does no one have a clue on this?

It shouldn’t be a problem on modern devices (2012+), what is your target mobile device?
Greyscale effect is a very simple effect, maybe the one you are using is doing fancy stuff.

No its the simplest of the simple, one of the standard assets from Unity. I agree it shouldnt be tanking the FPS like this which makes me think im missing something.

I think its something to do with not setting a render target correctly. Do I have to set render targets to prevent read-back when using image effects?

@Mikeysee

I did the RenderTarget trick you talked about and it did give me a 30% increase in performance on some device for my mobile SSAO!

I quickly did your grayscale effect with it, you can download it here (add the MobileGrayscale script to your camera) :
https://dl.dropboxusercontent.com/u/24044878/Unity/MobileGrayscale.unitypackage

This should be the only post process if you want it to work, if you add an another post process it will cancels out the optimization.

If this doesn’t work, it’s probably more of a hardware issue.

Hmm… Could you share the project that you tested this in as it doesnt work for me, I get a goggleygook output.

Well if you control the shaders all the sprites use, then simply add a lerp to one channel in the shader and no post will be required - you’d just set a single global shader variable from script and it by large should have 0 impact on framerate.

Hmm not sure I fully understand. Are you saying all sprites in the game should have a single material / shader? I am using different materials for some of my sprites…

Also how would that work for effects such as bloom that require down-sampling and full-screen blur?

Well, each material has it’s own shader, if your unaware of this it’s likely they all already use the same shader!
This would fix the greyscale issue without it having to be a post process! (Cheaper!)

It wouldn’t.

Yep, I could do this but what im saying is effects like glow and other effects wont work as they are full-screen-post-processing effects.

I only used the greyscale as a simple example of an Image Effect that doesnt work for me. I want to do full screen effects and the greyscale was the simplest (processing wise) I could think of.

So the problem still isnt solved.

How do you use Image Effects on mobile without tanking the FPS?! (readback?)

From what I read on the thread you linked me you need to set up a renderTexture as the cameras target, for some stupid reason unity doesn’t seem to detect that you have a post process and do it automatically, then on your last post process you do a blit to null instead of the target you get as input.
If this doesn’t make sense feel free to add me on skype, I’ll pm you my skype name.

@Mikeysee
I used OnRenderImage() before and “read back from framebuffer”'s time cost is too large that even greyscale postfx is slow on my mobile(iPhone4S,10FPS).

I end up not using OnRenderImage(),
but Awake(), OnPreRender() & OnPostRender().

OnAwake()
-create a new renderTexture
renderTexture

myTargetTexture = new RenderTexture((int)(Screen.width*RTTScale),(int)(Screen.height*RTTScale),24,RenderTextureFormat.ARGB32);

-call Shader.SetGlobalTexture (“_ScreenTexture”, myTargetTexture);
any shader with Sampler2D_ScreenTexture can get the current screen rendered by this camera

-create material for final render (e.g.bloom/blur/distort…anything that need the screen render texture you created)

OnPreRender()
-set camera’s target texture to that renderTexture that you created

after OnPreRender(),the camera will start render anything that included in the camera’s culling mask, to that renderTexture, not the framebuffer.
after all rendering is done, unity will call onPostRender()

OnPostRender()
-set camera’s targrt texture to null, which means any new rendering will actually write to the frameBuffer
-call Graphics.blit to do the final post fx render
graphics.blit will draw a new quad which fit your camera, and write to the “null”,which means the framebuffer.

OnPostRender()

void OnPostRender ()
    {
        _camera.targetTexture = null;
        //postFX_material.SetPass (0);
        Graphics.Blit(myTargetTexture, null, postFX_material, 0);
    }

this method run 60FPS on iPhone4S and the actual performance cost is only fillrate(fragment shader complexity)

Please correct me if I am wrong. I wish to know the best performance method to do postFX in Unity also!

1 Like

Hi Colin,

Wow, firstly massive thanks for that helpful post. It looks like thats definately the right way to do it, im just struggling a little with the details. This is my component:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Assets.Scripts.Effects
{
    public class PostProcessEffect : MonoBehaviour
    {
        public float renderTextureScale = 1;
        public Material postProcessEffect;

        private RenderTexture renderTexture;
        private UnityEngine.Camera camera;

        void Awake()
        {
            renderTexture = new RenderTexture((int)(Screen.width * renderTextureScale), (int)(Screen.height * renderTextureScale), 24, RenderTextureFormat.ARGB32);
            Shader.SetGlobalTexture("_ScreenTexture", renderTexture);
            camera = GetComponent<UnityEngine.Camera>();
        }

        void OnPreRender()
        {
            camera.targetTexture = renderTexture;
        }

        void OnPostRender()
        {
            camera.targetTexture = null;
            Graphics.Blit(renderTexture, null, postProcessEffect, 0);
        }
    }
}

This is my shader:

Shader "PostProcess/Invert Effect" {
Properties {
    //_ScreenTexture("Base (RGB)", 2D) = "white" {}
}

SubShader {
    Pass {
        ZTest Always Cull Off ZWrite Off
        Fog { Mode off }
              
        CGPROGRAM
        #pragma vertex vert_img
        #pragma fragment frag
        #pragma fragmentoption ARB_precision_hint_fastest
        #include "UnityCG.cginc"

        uniform sampler2D _ScreenTexture;

        fixed4 frag (v2f_img i) : SV_Target
        {
            fixed4 original = tex2D(_ScreenTexture, i.uv);
            return fixed4(1- original.r, 1- original.g, 1- original.b, original.a);
        }
        ENDCG

    }
}

Fallback off

}

For now im just trying to do a simple invert effect, very simple.

When I run it in game I get the following:

Which looks like its taking my main sprite sheet then inverting it.

So it looks like I am on the right track but im not sure why it isnt rendering the game instead of rendering the sprite sheet.

It should look like this :

But inverted of course.

Any ideas whats going on?

Mike

P.S. This is my camera object:

The best way to do a grayscale without loosing any Framerate on mobile isn’t to use a post process effect, but to change the shader from the material or the SpriteRenderer.

So, if you don’t want to lose any FPS, don’t use post processing, change the shaders.

Anyway, If you’re looking for some fast process effect, check out Camera Filter Pack from the Level 11 :slight_smile:

As I have already mentioned, they greyscale is just an example. I want to do effects that cant just be applied to the object being rendered and must be applied to the screen as a whole.

I understand that Mikeysee, I believe that your are talking about mostly color manipulation.

The way that I mentioned it to remplace the whole object shaders, with the personalised shader.

For exemple, if your using the Unity SpriteRenderer, with 50 sprites, the default material used is “Sprites-Default”, if you remplace this shader by a personalised shader, in this case all the 50 sprites will be modified.

( Here is a small shader with grayscale, of course, as an exemple )

Shader "Sprites/DefaultGray"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "IgnoreProjector"="True"
            "RenderType"="Transparent"
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Fog { Mode Off }
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile DUMMY PIXELSNAP_ON
            #include "UnityCG.cginc"
          
            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                half2 texcoord  : TEXCOORD0;
            };
          
            fixed4 _Color;

            v2f vert(appdata_t IN)
            {
                v2f OUT;
                OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
                OUT.texcoord = IN.texcoord;
                OUT.color = IN.color * _Color;
                #ifdef PIXELSNAP_ON
                OUT.vertex = UnityPixelSnap (OUT.vertex);
                #endif

                return OUT;
            }

            sampler2D _MainTex;

            fixed4 frag(v2f IN) : COLOR
            {
                fixed4 r=tex2D(_MainTex, IN.texcoord) * IN.color;
                r.g=r.r;
                r.b=r.r;
                return r;
            }
        ENDCG
        }
    }
}

if you want the negative effect, remplace “r.g=r.r; r.b=r.r;” by “r.rgb=1-r.rgb;”

That’s a “trick” for mobile users to have a fullscreen color manipulation without any fps reduction from post processing.

2 Likes

@vetasoft yes thankyou, if you look up then you can see that this has already been suggested to me and I have replied saying that that is not what I need as some of my objects already have custom shaders, also I wont be able to do custom effects such as bloom using that method.

Thanks for taking the time to think about my problem tho. Im pretty sure that @colin299 is on the right track, im just having an issue with the setup.

hi @Mikeysee

try to convert your RenderTexture from private to public,
double click that renderTexture in inspector,
did you see the original screen or the sprite sheet?

1 Like

@colin299 I tried it, its just a black texture…

do OnPreRender and OnPostRender need to be public?