Search Unity

Blending 2 surface shaders in deferred path

Discussion in 'Shaders' started by Cubic_Creo, Apr 24, 2015.

  1. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    I've written a shader to allow me to use more than 16 texture samplers, and the way I've achieved that is to stack surface shaders, treating them as separate passes, like so:

    Code (csharp):
    1. Shader "Cubic/Terrain" {
    2.     Properties {
    3.         // lots of textures here
    4.         ...
    5.     }
    6.     SubShader {
    7.         Tags { "RenderType"="Opaque" }
    8.         LOD 200
    9.      
    10.         CGPROGRAM
    11.  
    12.         #pragma surface surf StandardSpecular fullforwardshadows vertex:vert exclude_path:deferred
    13.         #pragma target 3.0
    14.  
    15.         void surf(Input IN, inout SurfaceOutputStandardSpecular o) {
    16.             // calculate values and write to o
    17.             ...
    18.         }
    19.  
    20.         ENDCG
    21.  
    22.         CGPROGRAM
    23.  
    24.         #pragma surface surf StandardSpecular fullforwardshadows vertex:vert decal:add exclude_path:deferred
    25.         #pragma target 3.0
    26.  
    27.         void surf(Input IN, inout SurfaceOutputStandardSpecular o) {
    28.             // calculate values and write to o
    29.             ...
    30.         }
    31.  
    32.         ENDCG
    33.     }
    34.     FallBack "Diffuse"
    35. }
    36.  
    The problem I've got is I'm having a hard time getting them to blend properly in deferred. Here's my end goal, but using exclude_path:deferred

    Screenshot at 14-53-57.png

    I thought I would be able to get them to work properly by using decal:add on the second surface shader, but here's what I get if I don't have exclude_path:deferred

    Screenshot at 14-55-13.png

    Everything but the stone block is rendered by the first shader. Then the stone block is rendered by the second shader. Unfortunately, using decal:add just makes it seem to completely ignore the second shader. The above screenshot with the block being black is exactly what it looks like if I comment out the second shader.

    I can get them both to draw in deferred if I add decal:blend to the first shader, but then my reflection probe doesn't pick up anything but the skybox, which messes up the spec & gloss (note how inside the building is too bright):

    Screenshot at 14-54-40.png

    I also tried removing decal:add and decal:blend and adding Blend One One between the shaders, and that renders the albedo correctly, but it screws up my normals:

    Screenshot at 14-55-45.png

    So for now, the only solution I've been able to find is to use exclude_path:deferred, as shown in the first screenshot. Any thoughts on how this can be done without excluding deferred?
     
  2. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    There is a simpler way of doing more then 16 texture samples, as what limits you is NOT the texture count, it's the sampler count...
    And unity is "kind" enough to hide this from us >.<...
    http://docs.unity3d.com/Manual/SL-BuiltinMacros.html
    Almost at the bottom under the heading :
    Texture / Sampler declaration macros
    The idea is to use as few samplers as possible, or at least keep under the 16 limit...
    Now how often do you actually use DIFFERENT settings for this???
     
  3. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    Who knew?! Thanks!
     
  4. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    I figured it out by re-reading alot of the graphics documentation now that unity 5 is out. But even so not every awesome massively time saving feature is easy to know about. (Such as the ability to set Blend Mode, Stencil, ZBuffer ect per material, and possibly even without a custom editor... (At least some of these have been possible since 4.3 !)
     
  5. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    Just tried it out, and UNITY_SAMPLE_TEX2D_SAMPLER simplifies things so much. Unfortunately, while it's working wonders on my Windows build, I also need to target Mac, and apparently textures and samplers can't be separated on OpenGL. So, back to the drawing board... :-/
     
  6. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    Is anyone up for a challenge? Here's a basic shader that illustrates the problem. Put it on a sphere at position 0,0,0 (be sure to add a normal map) and you'll see red on one side and black (should be green!) on the other. I can't get this to render correctly in deferred mode, while keeping the normals intact. The second CGPROGRAM has decal:add set, which I thought would do the trick. But that only seems to work if you add exclude_path:deferred to the first CGPROGRAM.

    Anyone able to solve this? The goal is to see red on one side and green on the other, with correct normals, in deferred.

    EDIT: Updated shader code to render one half of normals in each pass, instead of all normals in both passes.

    Code (CSharp):
    1.  
    2. Shader "Custom/DecalAddTest" {
    3.     Properties {
    4.         _Color1 ("Color1", Color) = (1,0,0,1)
    5.         _Color2 ("Color2", Color) = (0,1,0,1)
    6.         _NormalMap ("Normal Map", 2D) = "bump" {}
    7.     }
    8.     SubShader {
    9.         Tags { "RenderType"="Opaque" }
    10.         LOD 200
    11.        
    12.         CGPROGRAM
    13.         #pragma surface surf Standard fullforwardshadows
    14.         #pragma target 3.0
    15.  
    16.         struct Input {
    17.             float2 uv_NormalMap;
    18.             float3 worldPos;
    19.         };
    20.  
    21.         fixed4 _Color1;
    22.         fixed4 _Color2;
    23.         sampler2D _NormalMap;
    24.  
    25.         void surf (Input IN, inout SurfaceOutputStandard o) {
    26.             fixed3 colorToUse;
    27.             fixed3 normalToUse;
    28.             if (IN.worldPos.x < 0.0) {
    29.                 colorToUse = _Color1.rgb;
    30.                 normalToUse = tex2D(_NormalMap, IN.uv_NormalMap).rgb;
    31.             } else {
    32.                 colorToUse = fixed3(0,0,0);
    33.                 normalToUse = fixed3(0,0,0);
    34.             }
    35.  
    36.             o.Albedo = colorToUse;
    37.             o.Normal = normalToUse;
    38.         }
    39.         ENDCG
    40.  
    41.         CGPROGRAM
    42.         #pragma surface surf Standard fullforwardshadows decal:add
    43.         #pragma target 3.0
    44.  
    45.         struct Input {
    46.             float2 uv_NormalMap;
    47.             float3 worldPos;
    48.         };
    49.  
    50.         fixed4 _Color1;
    51.         fixed4 _Color2;
    52.         sampler2D _NormalMap;
    53.  
    54.         void surf (Input IN, inout SurfaceOutputStandard o) {
    55.             fixed3 colorToUse;
    56.             fixed3 normalToUse;
    57.             if (IN.worldPos.x >= 0.0) {
    58.                 colorToUse = _Color2.rgb;
    59.                 normalToUse = tex2D(_NormalMap, IN.uv_NormalMap).rgb;
    60.             } else {
    61.                 colorToUse = fixed3(0,0,0);
    62.                 normalToUse = fixed3(0,0,0);
    63.             }
    64.  
    65.             o.Albedo = colorToUse;
    66.             o.Normal = normalToUse;
    67.         }
    68.         ENDCG
    69.     }
    70.     FallBack "Diffuse"
    71. }
    72.  
    73.  
     
    Last edited: Apr 27, 2015
  7. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
  8. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    UNITY_SAMPLE_TEX2D_SAMPLER fixed the problem nicely on Windows, but when I try to run the shader on Mac, it doesn't render anything, and it spits out this error:

    I'm thinking I might have to go deferred on Windows and forward on Mac, but I would really like to get this solved so I can use deferred on both.
     
  9. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Have you #pragma glsl to make sure you tell it your targeting OpenGL and NOT OpenGL ES?
     
  10. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    Good idea, and I just tried it, but it still gives the same error. Also tried #pragma only_renderers opengl
     
  11. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    What openGL versions are macs at?
    As I think the #pragma target also sets open GL target?

    Also, the warning you were getting about trying to use to many uv-sets, perhaps that might be related? (Try using global textures, it might bypass the vertex->Fragment uv setupping?)
     
  12. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    My Mac supports OpenGL 4.1, but it looks like Unity doesn't support OpenGL 4 yet: http://forum.unity3d.com/threads/unity-5-is-coming-and-more.234931/page-5#post-1560404

    You mean like this? http://docs.unity3d.com/ScriptReference/Shader.SetGlobalTexture.html
     
  13. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
  14. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
  15. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Actually I think they do based on the #pragma target option, OR that was to come very soon, can't remember the link to the discussion about it sadly.
    And I did look into the texture limit for openGL, there is some extension (that might now be part of the standard, idk) that allows separation of samplers and textures, so you can bypass this limit.
    No idea how well unity can handle such however.
     
  16. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    Thanks. Unfortunately it looks like OpenGL is getting left behind the curve here. According to the docs ( http://docs.unity3d.com/Manual/SL-ShaderPrograms.html ) I can target shader model 4.0 or even 5.0, but OpenGL only gets 3.0 max:
    And the pragma renderer options don't include specific versions of OpenGL, besides OpenGL ES:
     
  17. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Unity is making the target 4.0 and 5.0 also apply to GLSL.
    I just can't remember where I read it, (but it was by unity people).
    You could check the patch notes for the beta?
     
  18. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    Just checked the release notes for the beta and you're right, but it's only for Windows at the moment :-/
     
  19. Cubic_Creo

    Cubic_Creo

    Joined:
    Oct 13, 2014
    Posts:
    47
    Figured out a solution to my problem. Before I was just drawing black in each pass where I didn't want the current pass to draw. Then I was trying to blend the passes together by adding them together. Worked in forward mode, but not deferred. I probably should have thought of this a lot sooner, but the solution was to just clip(-1), and not try to blend at all. So now, instead of drawing the geometry twice, trying to blend the values, I'm essentially drawing half the geometry with the first pass, then drawing the other half with the second pass.

    Code (CSharp):
    1. Shader "Custom/DecalAddTest" {
    2.     Properties {
    3.         _Color1 ("Color1", Color) = (1,0,0,1)
    4.         _Color2 ("Color2", Color) = (0,1,0,1)
    5.         _NormalMap ("Normal Map", 2D) = "bump" {}
    6.     }
    7.     SubShader {
    8.         Tags { "RenderType"="Opaque" }
    9.         LOD 200
    10.      
    11.         CGPROGRAM
    12.         #pragma surface surf StandardSpecular fullforwardshadows
    13.         #pragma target 3.0
    14.         struct Input {
    15.             float2 uv_NormalMap;
    16.             float3 worldPos;
    17.         };
    18.         fixed4 _Color1;
    19.         fixed4 _Color2;
    20.         sampler2D _NormalMap;
    21.         void surf (Input IN, inout SurfaceOutputStandardSpecular o) {
    22.             fixed3 colorToUse;
    23.             fixed3 normalToUse;
    24.             if (IN.worldPos.x < 0.0) {
    25.                 colorToUse = _Color1.rgb;
    26.                 normalToUse = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
    27.             } else {
    28.                 colorToUse = fixed3(0,0,0);
    29.                 normalToUse = fixed3(0,0,0);
    30.                 clip(-1.0);
    31.             }
    32.             o.Albedo = colorToUse;
    33.             o.Normal = normalToUse;
    34.         }
    35.         ENDCG
    36.         CGPROGRAM
    37.         #pragma surface surf StandardSpecular fullforwardshadows
    38.         #pragma target 3.0
    39.         struct Input {
    40.             float2 uv_NormalMap;
    41.             float3 worldPos;
    42.         };
    43.         fixed4 _Color1;
    44.         fixed4 _Color2;
    45.         sampler2D _NormalMap;
    46.         void surf (Input IN, inout SurfaceOutputStandardSpecular o) {
    47.             fixed3 colorToUse;
    48.             fixed3 normalToUse;
    49.             if (IN.worldPos.x >= 0.0) {
    50.                 colorToUse = _Color2.rgb;
    51.                 normalToUse = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
    52.             } else {
    53.                 colorToUse = fixed3(0,0,0);
    54.                 normalToUse = fixed3(0,0,0);
    55.                 clip(-1.0);
    56.             }
    57.             o.Albedo = colorToUse;
    58.             o.Normal = normalToUse;
    59.         }
    60.         ENDCG
    61.     }
    62.     FallBack "Diffuse"
    63. }
     
  20. Plutoman

    Plutoman

    Joined:
    May 24, 2013
    Posts:
    257
    Clever. I had made a few attempts at blending passes that way, and that never occurred to me.