Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Green Screen Style Effect with Transparent Objects

Discussion in 'Shaders' started by stinkhorse, Feb 2, 2016.

  1. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Hi there! I'm trying to build a particular effect, and I don't know if it's going to require shaders or post processing or what. First a look at what I'm trying to make:


    So my scenes are going to have multiple transparent objects that get their color and sense of lighting from a single stationary background image. The more opaque an object is the more 'Lit' it is, and the more transparent it gets the more 'Unlit' it is.

    The problem I'm trying to solve for is how to obscure a background transparent object behind a foreground transparent object, while still allowing the stationary color tone background image to show through?

    Another example of the breakdown:


    All layers combined:


    Right now I'm trying to pursue a 'Chroma Key' post processed solution. All of my objects would have a transparent image of the object in front, while just behind it would be a bright magenta masking shape. The entire scene would be built like this and the final result should be a scene that's just white and magenta. From there I would tell all the magenta in the scene to be replaced with the background color image.

    Is any of this possible? Based on what I know of modern games and shaders it seems like it should be a relatively easy effect to pull off, but I'm finding precious little information along these lines.

    I'm all ears for advice!
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Chroma key style stuff is a path you can take, but usually there are other methods that are easier and work better.

    So here are a few options:

    1: Apply the background layer color to each layer as they're rendered. Have the background just be a plane in the background and each layer just has an alpha channel for the opacity mask and a separate "background bleed" % that just samples the same texture as used in the background and blends between it and that layer's color in the shader. This is the most straightforward method. It's also nice in that it "just works" if you want to layer other elements on top with out having to worry about the background blending like text or vfx.

    2. Two pass render. Render the scene once normally with out the background and again using replacement shaders to generate the background blend mask in a render texture. Then use a full screen camera effect to merge the background onto the scene using the render texture blend mask. This is probably the most involved path, but it's one you might see other Unity devs do. It's also probably most similar to how you did your mockup in terms of the elements.

    3. Single pass multichannel mask. This is kind of a mix of the two above methods, and only works because your art style is monochromatic. It's also a bit like the chroma key idea but plays to real time rendering's strengths. Render the layers with a shader that only renders the layer's main texture into the red channel of the output and render the "background blend" into the green channel. Then do the same full screen camera effect as option #2 but now both the mask and the layer texture are rendered at the same time removing the need for the replacement shader pass and render texture.

    Option #3 is probably the fastest and most efficient, #2 is the slowest, #1 is the easiest to implement and offers the most control. #2&3 require every single object that gets rendered in the main camera to use special shaders that are aware of the final post process, though text and other UI elements are frequently rendered in a second camera to avoid them being altered by camera effects.

    If I was doing it I would use option #1 because even though it's not the fastest option, it's not going to be significantly slower than option #3. If you're doing this for mobile don't use #2, if PC it doesn't matter which you chose as none of them are going to be an issue for modern GPUs to do.
     
  3. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Holy crap Bgolus thank you so much!

    I have no idea how to implement any of this just yet, but I've been finding out that half the battle is just knowing what terms are used to properly describe a given thing to even find an answer. You just gave me the equivalent of an education in that regard.

    At the moment I'm in the process of just proving out the art style in question, so the best choice for a given final platform isn't a super high priority for me at the moment. Seeing as I don't want the background color to move or zoom at all I'll probably look into trying option 3. The game will be monochromatic, though I was planning on using different background colors to define different locations in the world.

    Thanks again for giving me a clearer picture of where I need to go. I'll post again when I have some results to share.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    There's nothing about option #1 that requires the background texture to zoom or move. The background texture can be sampled using the same screen space relative coordinates as a full screen camera effect in all of the layers.
     
  5. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Ok, good to know. I don't understand a lot of it just yet, but I have all evening after work to do the research. If you think option 1 is going to be the low hanging fruit then that's what I'll reach for.

    I'm using Playmaker to build everything so far, and I'd be surprised it it didn't have the tools to access individual layers or opacity masks.

    In regards to option one though, do you mean that each layer will have a single complete opacity mask, or that it creates one by pulling from the masks of the individual objects?

    Sorry for all the questions but I'll freely admit I'm both new and ignorant where most everything technical is concerned.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    So the layers themselves are less important than the individual objects.

    Each object just uses the opacity mask they have in their texture (the texture's transparency / alpha channel) and don't actually adjust their opacity at all. Instead they just have a second value for how much of the background image to use and how much of that object's texture to use.

    Here's a photoshop mockup of what the shader would be doing.
    fakeTransparencyBackground.png

    Since the objects aren't actually transparent they fully occlude other objects they're on top of, but they can be made to appear at different opacities relative to the background by how much of the background is being applied to their color.
     
  7. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Ok that makes total sense now, I really wasn't understanding how it worked.

    So I could use one of the RGB channels to get the shape of the object, telling unity to turn it into a mask with a white fill. I then adjust it's transparency by increasing or decreasing the amount of bleed through the background color exerts. Then I have it's alpha channel creating the outline around it, and set it's bleed through to 100% at all times.

    I never would have thought to do it that way, but it sounds perfect for what I'm trying to do!
     
  8. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Hooookay. New line of questioning because it turns out I don't know how to shader at all.
    • Is this effect possible outside of shaders?
    • Is this effect possible with the standard shader that comes with Unity?
    • Does this effect require something specialized to produce it?
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Yes, but it'd be much harder and much, much slower to the point of not being playable ... So really no.

    No, the standard shader specifically is a physically based shader that does a lot of stuff, none of which is useful to you. If you mean the shaders that come with Unity as standard the answer is still no.

    Yes, custom shaders!

    Seriously though when it comes to real time rendering you can't get away from shaders, it's how this stuff works. For the most part there's nothing particularly special about what you're trying to do, but every option you have to get the look you want eventually needs some kind of custom shader work. And really what you need is only a few lines of shader code away from the example unlit shader that gets created when you do create new shader > unlit.

    I highly recommend you try working through this tutorial:
    http://www.alanzucconi.com/2015/06/10/a-gentle-introduction-to-shaders-in-unity3d/

    It explains a lot of the basics of how shaders work and their actual syntax for Unity. There are a number of "i"s to dot and "t"s to cross as is the case with any kind of programming language, but ultimately you're writing out the same steps and operations you might do in a graphics program with layers, masks, and blend modes.

    Alternatively you can get Shader Forge if you really don't want to learn to write shaders in text form. It's a node based shader editor that dots those "i"s for you.
     
  10. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Thanks for the link and the breakdown Bgolus. I've read through the tutorials, and I definitely understand the anatomy of a shader better than I did before, but I doubt I'm ever going to be comfortable with the wall of text way of doing things. I'll definitely be gathering my pennies for shader forge. I watched a few tutorials of that and I have a much clearer idea of how the effects you're describing would work.

    In the meantime it's always good to make more assets and get more of the project planned out!
     
  11. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Code (CSharp):
    1. Shader "CompositeBackground"
    2. {
    3.     Properties
    4.     {
    5.         [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    6.         _Opacity ("Layer Opacity", Range(0,1)) = 1
    7.         [NoScaleOffset] _BackgroundTex ("Background", 2D) = "black" {}
    8.     }
    9.     SubShader
    10.     {
    11.         Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
    12.         LOD 100
    13.         Cull Off
    14.         Blend One OneMinusSrcAlpha
    15.  
    16.         Pass
    17.         {
    18.             CGPROGRAM
    19.             #pragma vertex vert
    20.             #pragma fragment frag
    21.            
    22.             #include "UnityCG.cginc"
    23.  
    24.             struct appdata
    25.             {
    26.                 float4 vertex : POSITION;
    27.                 float2 uv : TEXCOORD0;
    28.             };
    29.  
    30.             struct v2f
    31.             {
    32.                 float2 uv : TEXCOORD0;
    33.                 float4 uv_back : TEXCOORD1;
    34.                 float4 vertex : SV_POSITION;
    35.             };
    36.  
    37.             sampler2D _MainTex;
    38.             sampler2D _BackgroundTex;
    39.  
    40.             fixed _Opacity;
    41.            
    42.             v2f vert (appdata v)
    43.             {
    44.                 v2f o;
    45.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    46.                 o.uv = v.uv;
    47.                 o.uv_back = ComputeGrabScreenPos(o.vertex);
    48.                 return o;
    49.             }
    50.            
    51.             fixed4 frag (v2f i) : SV_Target
    52.             {
    53.                 // sample the texture
    54.                 fixed4 col = tex2D(_MainTex, i.uv);
    55.  
    56.                 // sample the background
    57.                 float2 screenuv = i.uv_back.xy / i.uv_back.w;
    58.                 fixed4 back = tex2D(_BackgroundTex, screenuv);
    59.  
    60.                 // blend main color and background together
    61.                 return fixed4(lerp(back.rgb, col.rgb, _Opacity) * col.a, col.a);
    62.             }
    63.             ENDCG
    64.         }
    65.     }
    66. }
    67.  
     
  12. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Holy crap. I don't know what to say Bgolus, aside from an incredulous thank you!

    I'm throwing this into my test environment now.

    Update: OH MY GOD IT'S FULL OF STARS. This is EXACTLY what I was looking for! I can't even begin to describe how much this means to me. I've been trying to create this effect for years now. I had an approximation in photoshop but this is just too wonderful for words!

    THANK YOU.

    The image is upside down but that can be fixed, I'll figure out how. THIS IS HAPPENING!
     
    Last edited: Feb 5, 2016
  13. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Does object shape and scale have an effect on the output? Trying some experiments with this. So far rotation has no effect. It's upside down output of the render texture is consistent. Maybe it has something to do with position? Doesn't seem like it should. More experiments!
     
  14. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Ok, scale of the object itself has no effect, HOWEVER the scale of the render texture camera does. I've scaled the render cam (1.2025 x, 1.002 y) and it now matches the background perfectly!
     
  15. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Do you need some art done Bgolus? I basically owe you at least a first born.
     
  16. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    That should be for option #1 with no render texture needed unless you want the background to be something more exciting than a fixed texture. If you want to use a render texture you should change ComputeGrabScreenPos to just ComputeScreenPos (I think that's the function name). Aspect ratio wise the background texture is always going to stretch to fill the full screen dimensions and ignore the texture's aspect ratio. If you want it to respect it you need to get the texture's resolution and the screen's resolution and inverse scale the screen uv, otherwise the render texture method works too for fixing that.
     
  17. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Ah ok. My background image is projected from a 2nd camera, so I thought it was going to have to be a render texture. Do I drop anything in the lower texture slot? The actual texture I'm using for the background?

    I'll try it when I get home in a few minutes.
     
  18. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Yea, just the background texture itself. Might need something like below otherwise.

    float4 _BackgroundTex_TexelSize;

    ...

    float back_aspect = _BackgroundTex_TexelSize.z / _BackgroundTex_TexelSize.w;
    float screen_aspect = _ScreenParams.x / _ScreenParams.y;
    float aspect_correction = back_aspect / screen_aspect;
    screenuv.y *= aspect_correction;

    Not sure if my math is right there.

    Come to think of it actually using a grab pass might work well here too. Bleh.
     
  19. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Ah, yeah I follow. I can tweek the scale of the render texture all I want, but if the screen is adjusted in anyway it's hosed.

    I don't understand what's happening in that script, but I recognize enough to follow the gist of it. It's getting the background image size, the screen size, then math hammering the two until they're the same and then piping that back out to the render texture. Am I close?
     
  20. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    We're getting the aspect ratio of the background texture, then the aspect ratio of the screen, and adjusts the UVs for reading the background so it as the correct aspect ratio. The code above will always keep the texture stretching fully from the left to right, but scale it vertically. This is probably the wrong way to go though.
     
  21. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Where in the script do I append this? Somewhere under CG Program?
     
  22. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    The first line goes somewhere with the fixed _Opacity; line. It's declaring the use of a variable that Unity provides to shaders automatically, but which shaders still need to say they want / need. The rest is all to modify the screenuv value, so it needs to go between when it gets defined and when it gets used.
     
  23. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    I'll get it implemented when I'm loose from work tomorrow!

    Update: Ok, I've added the lines to the shader. There were no errors, but also no noticeable effect on the warping of the render texture being projected through the objects. I probably have something pasted somewhere incorrectly.

     
    Last edited: Feb 6, 2016
  24. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    I realized there's an easier way using GrabPass.

    Code (CSharp):
    1. Shader "CompositeBackground"
    2. {
    3.     Properties
    4.     {
    5.         [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    6.         _Opacity ("Layer Opacity", Range(0,1)) = 1
    7.     }
    8.     SubShader
    9.     {
    10.         Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
    11.         LOD 100
    12.         Cull Off
    13.         Blend One OneMinusSrcAlpha
    14.         ZWrite Off
    15.  
    16.         GrabPass { "_BackgroundTex" }
    17.         Pass
    18.         {
    19.             CGPROGRAM
    20.             #pragma vertex vert
    21.             #pragma fragment frag
    22.          
    23.             #include "UnityCG.cginc"
    24.             struct appdata
    25.             {
    26.                 float4 vertex : POSITION;
    27.                 float2 uv : TEXCOORD0;
    28.             };
    29.             struct v2f
    30.             {
    31.                 float2 uv : TEXCOORD0;
    32.                 float4 uv_back : TEXCOORD1;
    33.                 float4 vertex : SV_POSITION;
    34.             };
    35.             sampler2D _MainTex;
    36.             sampler2D _BackgroundTex;
    37.             fixed _Opacity;
    38.          
    39.             v2f vert (appdata v)
    40.             {
    41.                 v2f o;
    42.                 o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
    43.                 o.uv = v.uv;
    44.                 o.uv_back = ComputeGrabScreenPos(o.vertex);
    45.                 return o;
    46.             }
    47.          
    48.             fixed4 frag (v2f i) : SV_Target
    49.             {
    50.                 // sample the texture
    51.                 fixed4 col = tex2D(_MainTex, i.uv);
    52.                 // sample the background
    53.                 float2 screenuv = i.uv_back.xy / i.uv_back.w;
    54.                 fixed4 back = tex2D(_BackgroundTex, screenuv);
    55.                 // blend main color and background together
    56.                 return fixed4(lerp(back.rgb, col.rgb, _Opacity) * col.a, col.a);
    57.             }
    58.             ENDCG
    59.         }
    60.     }
    61. }
    62.  
    Just put your background in the scene as a quad with an unlit material, or really anything you want. Then use this shader on the layers. It's using a GrabPass which makes a copy of the screen as it exists before the faux transparent layers are rendered. Now you don't have to deal with anything funny for aspect ratios in the shader.
     
    Manny Calavera likes this.
  25. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Oh that's fantastic! I'll get that installed and tested tonight.

    Here's my progress from the other week:


    What I'm finding is that the semi transparency tends to leave a layer muddy or dull. The shapes need a gradient alpha transparency layer over them to display highlights and help define their forms. That layer itself needs have an adjustable opacity as well, but the unlit transparent option doesn't have a scale for that, and the cutout transparency is full white or nothing at all. I might have to settle for a very carefully employed cut out layer, but I'm hoping you might have a better idea.
     
  26. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    What did you use for the original prototype? Is it a screen or overlay?
     
  27. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    The one way up in the first post? That's a very carefully built Photoshop file with a S*** ton of masking and groups to correctly layer everything. The pulsing light was done with a bunch of opacity adjustments that I keyframed out to make the animated gif.
     
  28. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    Yeah, you might need to do dual mask then to get the look you want. You could pack them into the single texture pretty easily, using the red channel for the main image, green for the internal mask, and alpha for the shape mask like it is.

    return fixed4(lerp(back.rgb, col.rrr, col.g * _Opacity)* col.a, col.a);
     
  29. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Yeah I've read about that concept before, hijacking those channels as data vectors rather than image systems. I tried implementing the grab pass shader but i think I eff'd it up. I have my textured quad and that worked fine, but from there I couldn't get it to propagate on anything. I made a test with an object attached to a layer named 'backgroundtex' and another object on a layer named 'maintex'. I feel like I've missed something.
     
  30. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,329
    The object layer shouldn't matter at all. The background object should use an opaque shader, like Unlit/Textured or something similar, but even then it shouldn't matter it just can't use that CompositeBackground texture.
     
  31. stinkhorse

    stinkhorse

    Joined:
    Feb 9, 2014
    Posts:
    18
    Ah, that's what I did wrong then. Correcting it!