- Home /
Chain multiple custom camera image effects?
UPDATE: I've added the shader code to the post. Sorry that it makes the post so long!
I wrote my own custom camera image effects. Individually, they work great. Unfortunately, if I add more than two to the camera, only the last effect is applied. I pored over the documentation for OnRenderImage
(link), which says this:
When there are multiple image filters attached to the camera, they process image sequentially, by passing first filter's destination as the source to the next filter.
But for some reason, this isn't working for me. Can you think of any reasons why I can't chain my custom effects together? I've posted the source for the C# scripts below. (NOTE: I also tried inheriting my scripts from the ImageEffectBase
class in the UnityStandardAssets.ImageEffects
namespace, but it didn't fix anything.) Thanks!
JoshImageEffect.cs (base class)
using UnityEngine;
using System.Collections;
public class JoshImageEffect : MonoBehaviour {
public Shader shader;
protected Material _material;
protected Material material{
get {
if (!_material) _material = new Material(shader);
return _material;
}
}
void OnDisable(){
Destroy(material);
}
public virtual void OnRenderImage(RenderTexture src, RenderTexture dst){
Graphics.Blit(src, dst, material);
}
}
TintImageEffect.cs (inherits from JoshImageEffect
)
using UnityEngine;
using System.Collections;
public class TintImageEffect : JoshImageEffect {
public Color tint;
public float intensity;
void Awake(){
material.SetColor("_Tint", tint);
material.SetFloat("_Intensity", intensity);
}
}
PixellatorImageEffect.cs (inherits from JoshImageEffect
)
using UnityEngine;
using System.Collections;
public class PixellatorImageEffect : JoshImageEffect {
public Vector2 grid;
void Awake(){
material.SetVector("_Grid", new Vector4(grid.x, grid.y, 0, 0));
}
}
GhostImageEffect.cs (inherits from JoshImageEffect
)
using UnityEngine;
using System.Collections;
public class GhostImageEffect : JoshImageEffect {
public int iterations;
public float radius;
public float timeScale;
public float shadowBrightness;
public float overallBrightness;
void Awake(){
material.SetFloat("_Iterations", iterations);
material.SetFloat("_Radius", radius);
material.SetFloat("_TimeScale", timeScale);
material.SetFloat("_ShadowBrightness", shadowBrightness);
material.SetFloat("_Brightness", overallBrightness);
}
}
TintShader.shader
Shader "Custom/TintShader" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_Tint ("Tint", Color) = (1, 1, 1, 1)
_Intensity ("Intensity", Float) = 0.1
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform float4 _Tint;
uniform float _Intensity;
float4 frag(v2f_img i) : COLOR {
float4 col = tex2D(_MainTex, i.uv);
return lerp(col, _Tint, _Intensity);
}
ENDCG
}
}
FallBack Off
}
PixellatorShader.shader
Shader "Custom/PixellatorShader" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_Grid ("Grid", Vector) = (0.02, 0.02, 0, 0)
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform float4 _Grid;
float4 frag(v2f_img i) : COLOR{
float u = ((i.uv.x / _Grid.x) - fmod(i.uv.x, _Grid.x)/(_Grid.x)) * _Grid.x;
float v = ((i.uv.y / _Grid.y) - fmod(i.uv.y, _Grid.y)/(_Grid.y)) * _Grid.y;
float4 col = tex2D(_MainTex, float2(u, v));
return col;
}
ENDCG
}
}
}
GhostShader.shader
Shader "Custom/GhostShader" {
Properties {
_MainTex ("Main Texture", 2D) = "white" {}
_Iterations ("Iterations", Float) = 10
_Radius ("Radius", Float) = 0.01
_TimeScale ("Time Scale", Float) = 0.1
_ShadowBrightness ("Shadow Brightness", Float) = 500
_Brightness ("Overall Brightness", Float) = 0.5
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
#include "UnityCG.cginc"
uniform sampler2D _MainTex;
uniform float _Iterations;
uniform float _Radius;
uniform float _TimeScale;
uniform float _ShadowBrightness;
uniform float _Brightness;
float4 frag(v2f_img i) : COLOR {
float4 col = tex2D(_MainTex, i.uv);
for (int c=0; c<_Iterations; c++){
float x = _Time.y * _TimeScale + c;
float xOffset = sin(x*x*x + x*x + x) * _Radius;
float yOffset = sin(x*x*x - x*x - x) * _Radius;
float2 off = float2(xOffset, yOffset);
col += mul(tex2D(_MainTex, i.uv + off), _ShadowBrightness * pow(length(off), 2));
}
return mul(col, _Brightness);
}
ENDCG
}
}
FallBack Off
}
Since you have your shader as public variable, are you sure you assigned the correct shader for each image effect? Tried Debug.Log() inside your OnRenderImage callback? You could print the classname to see which one are actually called:
Debug.Log("ImageEffect: " + GetType().Name);
I don't see a reason why it shouldn't work. If all your images effects are executed the problem is more likely in your shader code which you haven't posted.
I have now double-checked that I have the right shaders in the right scripts. That does not seem to be the source of the problem.
Answer by Bunny83 · Oct 20, 2015 at 08:00 PM
As said in my comment there has to be something wrong with your shaders. I just created two simple shaders, one that inverts the colors (the unaltered image-effect- template shader) and one that simply adds a bit red to the whole color. I also created a script which basically is identical to your "JoshImageEffect" script.
I attached this script two times to my camera and set my two shaders in the public variables. The resulting image has inverted colors and a red touch.
If i swap the two instances (I moved the second one up in the context menu) I get inverted colors and a blue-green touch instead since the red touch is added first and then the colors are inverted. So on my side it works as it should.
edit
So after a long try and error session it turns out that the shader were missing those important settings in the subshader:
// the culling isn't that important.
Cull Off
ZWrite Off
ZTest Always
More details in the comments below ^^.
As I said in my question, the scripts work for two effects, but not for more. Did you try more than two? Also, I've posted the shader code; maybe you can help me spot errors there.
Well, i just added 4 scripts (though just with the two test shaders) and all 4 are working as expected. I can even enable / disable them and rearrange the order at runtime and get the expected result.
However i exchanged 2 of my shaders with your pixellator and the ghost shader. So i have those effects:
color invert
add 0.5 red
pixellator
ghost
This works fine. However If i disable the first one strange things happens. Basically all effects have no effect except the last one. But additionally the image is flipped upside down. It get's even stranger ^^. If i also disable the second effect the remaining two start working again and the image is up right. If i enable the first one again only the last one works and the image is flipped again.
If the first two are enabled i can properly disable the last two and get the correct result. This almost "feels" like some sort if internal bitmask madness which goes horrible wrong at some point. Everything seems to depend on the 3rd image effect if i disable it i can set any combination on the others without problems.
It get's totally messed up with 5 effects:
color invert
add 0.5 red
second add 0.5 red
ghost
pixellator
If all are enabled, only the color invert, one of the "add red" and the pixellator works. The second red and the ghost shader do not work. Now it get's funny: If i disable the ghost shader all effects are working (except the ghost of course). Same when i disable the pixellator. So with all i mean the second "add red" is now working as well....
I think i will setup some simple, easy to follow example project and file a bug report. This feature seems to be completely broken ^^.
I've found those issues which might be related:
If the first link applies to our problem, it might be already fixed in the next release. However since that's the only issues i've found it looks like they don't have enough data to reproduce the problem. So sending in more bug reports certainly will help. If you send a bug report you might want to include a link to your question here.
Just added a Debug.Log to the "OnRenderImage" callback and it looks like all 5 are called in my last test. So even they are called and execute the Blit some don't work in some cases...
Apart from all the bugs in Unity, there are some improvements in your shaders. In the pixellator you can simply do:
float u = (i.uv.x - fmod(i.uv.x, _Grid.x) );
float v = (i.uv.y - fmod(i.uv.y, _Grid.y) );
All the dividing and multiplying cancels out.
In your ghost shader i'm not sure why you use the "mul" operator which is a matrix multiplication. You treat your color vector as a 4x1 matrix and multiply it with a 1x1 matrix to get back a 1x4 matrix. I'm not sure if that has an impact on performance but i simply haven't ever seen someone using it for vector scaling. You can ins$$anonymous$$d simply multiply them together. Also "pow" is one of the most expensive function. It might have a shortcut for the power of 2 but since you just want to square the length or a vector you can simply do a "dot" with itself that gives you the squared length:
col += tex2D(_$$anonymous$$ainTex, i.uv + off) * _ShadowBrightness * dot(off, off);
// ...
return col * _Brightness;
Dang, @Bunny83! Thanks for going to so much trouble! I'll have some free time later today and I'll try out your stuff. Thanks!
Yes, it was the culling and depth-testing and -writing that were the problem! Thanks!
Hi! I have the same problems. I write 2 image effects script, use OnPreRender & OnPostRender, attached to the main camera. Which one has a higher order in inspector won't be triggered? How could you solve the problems? Thanks!
Have you actually read the last bit in my answer?
Your answer
Follow this Question
Related Questions
Blacksmith Atmospheric Scattering failing in Unity 5.5 2 Answers
How to get picked worldposition to clipspace, in fragment shader? (Image effect shader) 0 Answers
Is it possible to create a scrolling texture as an image effect (shader)? 2 Answers
Image Effects in URP (Shadergraph) 1 Answer
How to Partially Blur an Object 0 Answers