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

Have CGProgram blocks render together in a Surface Shader

Discussion in 'Shaders' started by PsycheMac, Aug 17, 2017.

  1. PsycheMac

    PsycheMac

    Joined:
    Apr 15, 2013
    Posts:
    32
    I have a lot of 2d sprite surface shaders which render textures on top of each other. A few of these shaders represent bodies of water that have water textures rendering on top of land textures. I would like the water textures in these shaders to render together and always have the land textures render underneath.

    This process works correctly when I am using the same body of water shader. The land textures always render underneath the water textures no matter how I layer them in space. Although when I add a different body of water shader with it this does not work. The land textures from one shader render on top of the water in the other shader. I would like to have it so that the water will always render on top of the land between multiple different shaders.

    I understand that surface shader CGProgram blocks break into multiple passes when rendered by Unity. And I understand that it is possible to label passes in fragment shaders and use them in other fragment shaders. Is there a way to tag CGProgram blocks in surface shaders or tag the passes that Unity will end up using and use those within a separate surface shader?

    Here is code Im using for one of the body of water shaders:

    Code (csharp):
    1.  
    2. Shader "Custom/Ocean"
    3. {
    4.     Properties
    5.     {
    6.         [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
    7.         _AlphaTex ("Alpha Texture", 2D) = "white" {}
    8.         _BeachTex ("Beach Texture", 2D) = "white" {}
    9.  
    10.         _WaterUVOffset ("Water UV Offset", Range(0,1)) = 0
    11.         [Toggle(_ALPHAXVARIATION)] _AlphaXVariation ("Alpha X Variation", Float) = 0
    12.     }
    13.  
    14.     CGINCLUDE
    15.     #pragma surface surf Lambert vertex:vert nofog keepalpha addshadow fullforwardshadows
    16.     #pragma shader_feature _ALPHAXVARIATION
    17.  
    18.     sampler2D _AlphaTex;
    19.  
    20.     struct Input
    21.     {
    22.         float2 uv_MainTex;
    23.         fixed4 color;
    24.     };
    25.  
    26.     void vert (inout appdata_full v, out Input o)
    27.     {
    28.         v.vertex = UnityPixelSnap (v.vertex);
    29.      
    30.         UNITY_INITIALIZE_OUTPUT(Input, o);
    31.         o.color = v.color;
    32.     }
    33.     ENDCG
    34.  
    35.     SubShader
    36.     {
    37.         Tags
    38.         {
    39.             "Queue"="Transparent"
    40.             "IgnoreProjector"="True"
    41.             "RenderType"="Transparent"
    42.             "PreviewType"="Plane"
    43.             "CanUseSpriteAtlas"="True"
    44.         }
    45.  
    46.         Cull Off
    47.         Lighting Off
    48.         ZWrite Off
    49.         Blend One OneMinusSrcAlpha
    50.  
    51.         CGPROGRAM
    52.         sampler2D _BeachTex;
    53.      
    54.         void surf (Input IN, inout SurfaceOutput o)
    55.         {
    56.             fixed2 uv = IN.uv_MainTex;
    57.  
    58.             #if _ALPHAXVARIATION
    59.                 uv.x = 1.0 - uv.x;
    60.             #endif
    61.  
    62.             fixed4 c = tex2D (_BeachTex, uv) * IN.color;
    63.  
    64.             c.a = tex2D (_AlphaTex, uv).g;
    65.  
    66.             o.Albedo = c.rgb * c.a;
    67.             o.Alpha = c.a;
    68.         }
    69.         ENDCG
    70.  
    71.         CGPROGRAM
    72.         sampler2D _MainTex;
    73.  
    74.         fixed _ScrollX,_ScrollY,_WaterUVOffset;
    75.  
    76.         void surf (Input IN, inout SurfaceOutput o)
    77.         {
    78.             fixed2 uv = IN.uv_MainTex;
    79.             fixed xScrollValue = frac(_ScrollX * _Time.y);
    80.             fixed yScrollValue = frac(_ScrollY * _Time.y);
    81.  
    82.             fixed4 c = tex2D (_MainTex, uv + fixed2 (xScrollValue + _WaterUVOffset, yScrollValue - _WaterUVOffset)) * IN.color;
    83.  
    84.             #if _ALPHAXVARIATION
    85.                 uv.x = 1.0 - uv.x;
    86.             #endif
    87.  
    88.             c.a = tex2D (_AlphaTex, uv).r;
    89.  
    90.             o.Albedo = c.rgb * c.a;
    91.             o.Alpha = c.a;
    92.         }
    93.         ENDCG
    94.     }
    95.  
    96.     Fallback "Transparent/VertexLit"
    97. }
    98.  
     
  2. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    Is there a reason these can't be in a single pass?
     
  3. PsycheMac

    PsycheMac

    Joined:
    Apr 15, 2013
    Posts:
    32
    I want one pass to always render on top of another pass. This won't happen if I combine it into one pass. What Im trying to achieve works when using the same shader but Id like it to work between multiple different shaders. This would require having some sort of ability to tag a pass in a surface shader to be able to use that pass in another shader.
     
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    I get you want one thing on top. But... just lerp between texture A and texture B based on a mask. What you're doing is far less complicated than it seems you're making it (or I don't understand your goal???).

    If you need similar parts in multiple places put the code in a cginc and use the functions from there to avoid duplication. You can also set your properties via script as globals and then avoid needing to set them in every material.
     
  5. PsycheMac

    PsycheMac

    Joined:
    Apr 15, 2013
    Posts:
    32
    Ill try out the cginc with the surf function to see if thats what im looking for. Although I may not have explained it well enough. I made a quick picture to illustrate it:



    I want to be able to weave passes between different game objects that have different shaders on them. I think you may be confused because I didnt specify the fact that there would be separate game objects involved. Right now for me the land and water surface shader passes in Shader 2 both render on top of Shader 1's water which is not what I want.
     
  6. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    Do you have separate sprites for water and land? If so have one water shader and one land shader. You can tweak the render queue to ensure your water stays on top if needed.

    If they aren't separate sprites, you just need one shader and to lerp between the two textures.
     
  7. PsycheMac

    PsycheMac

    Joined:
    Apr 15, 2013
    Posts:
    32
    The land and water aren't separate sprites. Im using a layered texture shader within each game object. If I lerp between two textures in the same pass, two separate game objects won't weave together like the illustration above. One will just completely render on top of the other.

    I have multiple shaders with different code that I would like to be able to weave together. These multiple shaders are each applied to separate game objects with some overlapping each other. I would like all their land passes to render first, then I would like all of their water passes to render after so that they appear on top of all the land passes.
     
  8. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    The overhead of extra Passes is non-trivial. And your objects are still going to have to sort for draw order.

    Do your layered textures tile? If so it seems like you should be able to use some projected UVs and avoid seams in a single Pass. If not I think you should have two objects and a shader for each. You're looking at like 3 draws total including the overdraw that way and just about everything will batch if you set the queues sensibly.
     
  9. PsycheMac

    PsycheMac

    Joined:
    Apr 15, 2013
    Posts:
    32
    These game objects are all based on a tile system but they dont need to seam together as far as UV's go. The reason I want these layered textures in a shader solution is so that I only need one game object per tile. Id rather not have to create and destroy extra game objects just because I need extra texture layers on top of the tiles.

    I looked up information on draw calls a while back and it seems to me that it is incredibly specific to get draw calls to batch. For example you cant even have alpha in a texture or scaling in a game object if you want it to batch and everything Im using has alphas and gameobject scaling.

    I suppose what Im trying to do is difficult and underperformant. Ive been curious on how much overhead is required for drawing extra passes. Maybe I should just collapse all of this into one texture lerp pass and try to figure out a way to layer them crudely within the game object system. I just know that it won't entirely be what Im looking for. Or maybe I should try the layered game object solution. It's just that this will create/destroy a couple hundred extra game objects upon tile generation which I was hoping to avoid.

    Edit: I misunderstood the projected UV part you said. These textures are tileable. So I could some how UV project all of the water in one pass across multiple game objects? There are a couple different water textures, for example ocean water and lake water. Could I have all of the lake water drawn in one pass with UV projection and all the lake land underneath drawn in a separate UV projection pass?
     
    Last edited: Aug 20, 2017