Search Unity

Transparent shader looks additive when triangles are stacked

Discussion in 'Shaders' started by Alesk, Jun 26, 2014.

  1. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Hi,

    I'm working on a billboard shader and some mesh tweaks, to make them look like spheres.
    For now it's pretty basic and I already have a problem with it : when the billboards are distant from each others, they are looking right.
    But if they are many in a small space, the shader adds up, like on the column of billboards in this picture :
    shader_wrong_add.jpg
    Note : to check the correctness of lighting, real spheres and billboards are mixed in this picture, billboards have fuzzy edges.

    Here is my current shader :
    Code (csharp):
    1.  
    2. Shader "Simple Bumped Diffuse" {
    3. Properties {
    4.    _Color ("Main Color", Color) = (1,1,1,1)
    5.    _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
    6.    _BumpMap ("Normalmap", 2D) = "bump" {}
    7. }
    8.  
    9. SubShader {
    10.    Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
    11.    LOD 300
    12.    Cull Off ZWrite Off
    13.    Blend SrcAlpha OneMinusSrcAlpha
    14.  
    15.    CGPROGRAM
    16.    #pragma surface surf Lambert alpha
    17.  
    18.    sampler2D _MainTex;
    19.    sampler2D _BumpMap;
    20.    sampler2D _DetailTex;
    21.    fixed4 _Color;
    22.  
    23.    struct Input {
    24.      float2 uv_MainTex;
    25.      float2 uv_BumpMap;
    26.    };
    27.  
    28.    void surf (Input IN, inout SurfaceOutput o) {
    29.      fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    30.      o.Albedo = c.rgb;
    31.      o.Alpha = c.a;
    32.      o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
    33.    }
    34.    ENDCG
    35. }
    36.  
    37. FallBack "Transparent/Diffuse"
    38. }
    39.  
    What should I do to fix this additive effect ?
    Thanks for you help !
     
    rakkarage likes this.
  2. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    415
    If I'm not mistaken your custom Blend does not work if you use the "alpha" keyword in your pragma sentence
     
  3. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    This is true, although the first pass should be alpha blended anyway due to the alpha keyword. Subsequent passes (for extra pixel lights) will use SrcAlpha One.
     
    kebrus likes this.
  4. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Yes, if "alpha" is specified in the pragma, the Blend has no noticeable effect.
    If I remove the alpha pragma and keep the Blend, then the transparency is broken, like in this picture :
    shader_wrong_add3.jpg

    But in both cases, the annoying additive effect is still present... So what should I do ? :/
     
  5. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    415
    I'm not sure if it has something to do with the shader alone, I cannot reproduce your problem using your shader, everything looks fine and i can even remove the alpha keyword and everything still looks the same, no errors, i tried to compare with the built in shaders and it looks exactly the same aside from being able to see from both sides

    That said, why exactly are you disabling the culling? didn't you said you were billboarding them? if they are always looking at the camera you don't need to draw the back faces
     
  6. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Hi,

    I think I've found the cause of this problem : in the previous pictures, the column of billboards is a single mesh, so drawn in a single draw call, all other single billboards are created in a different way and have a mesh each.

    Since each light is renderer in a separated pass and the z buffer is not affected by this transparent shader, the extra passes are fully rendered and added over the first one, that's why I get this additive effect :(

    After some google search, I've found, and tweaked a bit, a fragment shader to replace this surface shader, and I have the exact same problem.
    Here it is :
    Code (csharp):
    1.  
    2.  
    3. Shader "Cg normal mapping" {
    4.   Properties {
    5.       _MainTex ("Shape Map", 2D) = "white" {}
    6.   _BumpMap ("Normal Map", 2D) = "bump" {}
    7.   _Color ("Diffuse Material Color", Color) = (1,1,1,1)
    8.   }
    9.   SubShader {
    10.        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
    11.  
    12.      Blend SrcAlpha OneMinusSrcAlpha
    13.  
    14.      ZWrite Off
    15.  
    16.   Pass {
    17.   Tags { "LightMode" = "ForwardBase" }
    18.   // pass for ambient light and first light source
    19.   CGPROGRAM
    20.   #pragma vertex vert
    21.   #pragma fragment frag
    22.       #pragma multi_compile_fwdbase
    23.       #pragma fragmentoption ARB_precision_hint_fastest
    24.       #pragma glsl_no_auto_normalization
    25.   #include "UnityCG.cginc"
    26.   #include "AutoLight.cginc"
    27.   uniform float4 _LightColor0;
    28.   // color of light source (from "Lighting.cginc")
    29.   // User-specified properties
    30.   uniform sampler2D _MainTex;
    31.   uniform float4 _MainTex_ST;
    32.   uniform sampler2D _BumpMap;
    33.   uniform float4 _BumpMap_ST;
    34.   uniform float4 _Color;
    35.  
    36.   struct vertexInput {
    37.   float4 vertex : POSITION;
    38.   float4 texcoord : TEXCOORD0;
    39.   float4 texcoord1 : TEXCOORD1;
    40.   float3 normal : NORMAL;
    41.   float4 tangent : TANGENT;
    42.   };
    43.   struct vertexOutput {
    44.   float4 pos : SV_POSITION;
    45.  
    46.   float4 tex : TEXCOORD0;
    47.   float4 tex1 : TEXCOORD1;
    48.  
    49.   float4 posWorld : TEXCOORD2;// position of the vertex (and fragment) in world space
    50.  
    51.   float3 tangentWorld : TEXCOORD3;
    52.   float3 normalWorld : TEXCOORD4;
    53.   float3 binormalWorld : TEXCOORD5;
    54.   };
    55.   vertexOutput vert(vertexInput input)
    56.   {
    57.   vertexOutput output;
    58.   float4x4 modelMatrix = _Object2World;
    59.   float4x4 modelMatrixInverse = _World2Object;
    60.   // unity_Scale.w is unnecessary
    61.   output.tangentWorld = normalize(float3(mul(modelMatrix, float4(float3(input.tangent), 0.0))));
    62.   output.normalWorld = normalize(float3(mul(float4(input.normal, 0.0), modelMatrixInverse)));
    63.   output.binormalWorld = normalize( cross(output.normalWorld, output.tangentWorld) * input.tangent.w); // tangent.w is specific to Unity
    64.   output.posWorld = mul(modelMatrix, input.vertex);
    65.   output.tex  = input.texcoord;
    66.   output.tex1 = input.texcoord1;
    67.   output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
    68.   return output;
    69.   }
    70.   float4 frag(vertexOutput input) : COLOR
    71.   {
    72.   // in principle we have to normalize tangentWorld,
    73.   // binormalWorld, and normalWorld again; however, the
    74.   // potential problems are small since we use this
    75.   // matrix only to compute "normalDirection",
    76.   // which we normalize anyways
    77.        float4 shapeTex =  tex2D(_MainTex, _MainTex_ST.xy * input.tex.xy + _MainTex_ST.zw);
    78.   float4 encodedNormal = tex2D(_BumpMap, _BumpMap_ST.xy * input.tex.xy + _BumpMap_ST.zw);
    79.   float3 localCoords = float3(2.0 * encodedNormal.ag - float2(1.0), 0.0);
    80.   localCoords.z = sqrt(1.0 - dot(localCoords, localCoords));
    81.   // approximation without sqrt:  localCoords.z =
    82.   // 1.0 - 0.5 * dot(localCoords, localCoords);
    83.   float3x3 local2WorldTranspose = float3x3(
    84.   input.tangentWorld,
    85.   input.binormalWorld,
    86.   input.normalWorld);
    87.  
    88.   float3 normalDirection = normalize(mul(localCoords, local2WorldTranspose));
    89.   float3 viewDirection = normalize(_WorldSpaceCameraPos - float3(input.posWorld));
    90.   float3 lightDirection;
    91.   float attenuation;
    92.   if (0.0 == _WorldSpaceLightPos0.w) // directional light?
    93.   {
    94.   attenuation = 1.0; // no attenuation
    95.   lightDirection = normalize(float3(_WorldSpaceLightPos0));
    96.   }
    97.   else // point or spot light
    98.   {
    99.   float3 vertexToLightSource = float3(_WorldSpaceLightPos0 - input.posWorld);
    100.   float distance = length(vertexToLightSource);
    101.   attenuation = 1.0 / distance; // linear attenuation
    102.   lightDirection = normalize(vertexToLightSource);
    103.   }
    104.   float3 ambientLighting = float3(UNITY_LIGHTMODEL_AMBIENT) * 2;
    105.   float3 diffuseReflection = attenuation * float3(_LightColor0 * 2) * float3(_Color) * max(0.0, dot(normalDirection, lightDirection));
    106.   return float4(ambientLighting + diffuseReflection, shapeTex.w * _Color.a);
    107.   }
    108.   ENDCG
    109.   }
    110.   Pass {
    111.   Tags { "LightMode" = "ForwardAdd" }
    112.   // pass for additional light sources
    113.   Blend One One // additive blending
    114.  
    115.   CGPROGRAM
    116.   #pragma vertex vert
    117.   #pragma fragment frag
    118.       #pragma multi_compile_fwdadd
    119.       #pragma fragmentoption ARB_precision_hint_fastest
    120.       #pragma glsl_no_auto_normalization
    121.   #include "UnityCG.cginc"
    122.   #include "AutoLight.cginc"
    123.   uniform float4 _LightColor0;
    124.   // color of light source (from "Lighting.cginc")
    125.   // User-specified properties
    126.   uniform sampler2D _MainTex;
    127.   uniform float4 _MainTex_ST;
    128.   uniform sampler2D _BumpMap;
    129.   uniform float4 _BumpMap_ST;
    130.   uniform float4 _Color;
    131.  
    132.   struct vertexInput {
    133.   float4 vertex : POSITION;
    134.   float4 texcoord : TEXCOORD0;
    135.   float4 texcoord1 : TEXCOORD1;
    136.   float3 normal : NORMAL;
    137.   float4 tangent : TANGENT;
    138.   };
    139.   struct vertexOutput {
    140.   float4 pos : SV_POSITION;
    141.  
    142.   float4 tex : TEXCOORD0;
    143.   float4 tex1 : TEXCOORD1;
    144.  
    145.   float4 posWorld : TEXCOORD2;// position of the vertex (and fragment) in world space
    146.  
    147.   float3 tangentWorld : TEXCOORD3;
    148.   float3 normalWorld : TEXCOORD4;
    149.   float3 binormalWorld : TEXCOORD5;
    150.   };
    151.   vertexOutput vert(vertexInput input)
    152.   {
    153.   vertexOutput output;
    154.   float4x4 modelMatrix = _Object2World;
    155.   float4x4 modelMatrixInverse = _World2Object;
    156.   // unity_Scale.w is unnecessary
    157.   output.tangentWorld = normalize(float3(mul(modelMatrix, float4(float3(input.tangent), 0.0))));
    158.   output.normalWorld = normalize(float3(mul(float4(input.normal, 0.0), modelMatrixInverse)));
    159.   output.binormalWorld = normalize(cross(output.normalWorld, output.tangentWorld) * input.tangent.w); // tangent.w is specific to Unity
    160.   output.posWorld = mul(modelMatrix, input.vertex);
    161.   output.tex  = input.texcoord;
    162.   output.tex1 = input.texcoord1;
    163.   output.pos  = mul(UNITY_MATRIX_MVP, input.vertex);
    164.   return output;
    165.   }
    166.   float4 frag(vertexOutput input) : COLOR
    167.   {
    168.   // in principle we have to normalize tangentWorld,
    169.   // binormalWorld, and normalWorld again; however, the
    170.   // potential problems are small since we use this
    171.   // matrix only to compute "normalDirection",
    172.   // which we normalize anyways
    173.        float4 shapeTex =  tex2D(_MainTex, _MainTex_ST.xy * input.tex.xy + _MainTex_ST.zw);
    174.   float4 encodedNormal = tex2D(_BumpMap, _BumpMap_ST.xy * input.tex.xy + _BumpMap_ST.zw);
    175.   float3 localCoords = float3(2.0 * encodedNormal.ag - float2(1.0), 0.0);
    176.   localCoords.z = sqrt(1.0 - dot(localCoords, localCoords));
    177.   // approximation without sqrt:  localCoords.z =
    178.   // 1.0 - 0.5 * dot(localCoords, localCoords);
    179.   float3x3 local2WorldTranspose = float3x3(
    180.   input.tangentWorld,
    181.   input.binormalWorld,
    182.   input.normalWorld);
    183.  
    184.   float3 normalDirection = normalize(mul(localCoords, local2WorldTranspose));
    185.   float3 viewDirection = normalize(_WorldSpaceCameraPos - float3(input.posWorld));
    186.   float3 lightDirection;
    187.   float attenuation;
    188.   if (0.0 == _WorldSpaceLightPos0.w) // directional light?
    189.   {
    190.   attenuation = 1.0; // no attenuation
    191.   lightDirection = normalize(float3(_WorldSpaceLightPos0));
    192.   }
    193.   else // point or spot light
    194.   {
    195.   float3 vertexToLightSource = float3(_WorldSpaceLightPos0 - input.posWorld);
    196.   float distance = length(vertexToLightSource);
    197.   attenuation = 1.0 / distance; // linear attenuation
    198.   lightDirection = normalize(vertexToLightSource);
    199.   }
    200.   float3 diffuseReflection = shapeTex.w * attenuation * float3(_LightColor0 * 2) * float3(_Color) * max(0.0, dot(normalDirection, lightDirection));
    201.   return float4(diffuseReflection * _Color.a, 0);
    202.   }
    203.   ENDCG
    204.   }
    205.   }
    206.  
    207.   Fallback "Mobile/VertexLit"
    208. }
    209.  
    210.  
    (source http://en.wikibooks.org/wiki/Cg_Programming/Unity/Lighting_of_Bumpy_Surfaces)

    As I need to keep one drawcall, is there a way to work around this additive problem ?
    Can deffered rendering be a solution ?

    About the culling, that's a mistake after a copy/paste from another shader ;)
     
    Last edited: Jun 27, 2014
  7. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    415
    I tried and tried and tried and got nothing... :|

    The problem is the second pass for the point lights, even after manually aligning the vertices so they show up in the correct order the effect breaks where alpha is not 0 or 1, I thought i could do it by writing in the z buffer, tried using blendops and multiple passes as well as colormask. Maybe i'm missing something and someone might help you. Till then i'm out of ideas.

    The best way would be to remove the second pass entirely...

    This is what i got (ideal on the right, problem on the left, my best try on the middle):
    transp.png

    But considering your problem i think there's a better solution. You only want one draw call right? you can guarantee one draw call if all sprites have the same scale and the same material, the draw call increases as soon as an object passes through them, meaning:
    • if you have a stack of 100 spritesA sharing the scale and material thats 1 draw call
    • if you the put a spriteB right in the middle of the stack it becomes 3 draw calls, 1 draw call for the further 50 stack of spritesA, 1 for spriteB and 1 for the closest 50 stack of spritesA
    • if you now put another spriteB between the other spriteB and the closest 50 spritesA that still counts as 3 draw calls since the 2 spritesB are getting batched
    • but if you instead move that second spriteB into the middle the closest 50 stack of sprite A (lets say right in the middle) it then becomes 5 draw calls, 1 for stackA of 50, 1 for spriteB, 1 for stackA of 25, 1 for spriteB and 1 for stackA of 25
    Maybe now you can adapt to your case and keep that one draw call you want

    I hope that helps

    PS: Deffered wont to anything for you because it doesn't support transparency, Unity actually uses forward to render transparency when in deferred mode.
     
    rakkarage likes this.
  8. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Hi,

    Thanks a lot for your efforts ! :)

    I had exactly the same results while testing on my side ...

    My original goal was to allow more than one light to interact on a smoke effect I'm working on since some time. (link in my signature)
    So removing the extra pass will bring me back to one light, which is my starting point :p

    For now, as I said earlier, all my billboards are combined in a single mesh (and I create extra meshes every 16k).
    Splitting them in individual gameObjects/meshes may solve the shader problem... But in this case, filling my scene with thousands of gameObjects completely ruins the performances :/

    So for now, I'm screwed :(

    Thanks again for you help and explanations !
     
  9. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    415
  10. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Thanks, I tried to tweak the shader as described, but didn't managed to make it work :(
    EDIT : It needs Zwrite to be on to get proper extra lights rendering, but this break the alpha effect, and changing the blend mode as described always display the corners of the billboards, ignoring the alpha :/
    I tried to put everything to zero, so the croners are not shown, but the Zwrite "on" still break it somehow ...
     
    Last edited: Jun 28, 2014
  11. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    415
    yeah, just tried it, didn't work... looks better though, and its great to create particle that are bright and then turn to dark, like flames turning to smoke, thats how unity does it btw

    how about the stencil buffer? i never used it before but maybe you can mask the alpha or light passes so it doesn't get overbright

    i know i'm just throwing bones now, but i feel like this should be something feasible to do, illuminated particle system to produce effects like lights on clouds is something i've seen before and i don't quite believe they were all different objects
     
  12. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Not sure on how to properly use it... but with what I've read about it, I foresee that it may also be a dead end >_<
     
  13. sschaem

    sschaem

    Joined:
    Feb 14, 2014
    Posts:
    148
    I do this all the time (transparent surface shader) without any of those side effect.

    .. Deleted irrelevant stuff ...
     

    Attached Files:

    Last edited: Jun 30, 2014
  14. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Thanks, but I think you you missed something : my problems are starting when I have more than one light interacting with the shader ;)
     
  15. sschaem

    sschaem

    Joined:
    Feb 14, 2014
    Posts:
    148
    I think I can reproduce now ! Something doesn't make sense.. need more testing :)

    ok so, It worked for me because I didn't see that adding a point light stopped the objects from batching. If I collapse the string (all char over each other), I clearly see the additive lighting problem.

    The multiple pass lighting is not compatible with transparent mesh objects where you have overlaps.
    But I guess you guys already figured it out.

    (in the process I'm surprised Unity need multiple pass to render a simple point light...)

    Knowing all this, it seem that the only correct way to render this is to break up the mesh into individual draw call. Its sound horrible, but there is no other way to have a correct result, short of having Unity perform the lighting in a single pass ?

    Maybe the best solution is to support a limited subset of Unity lighting so you can render the mesh in a simple pass... (by that I mean, do your own custom lighting in a single pass based on Unity lighting source code?)
     
    Last edited: Jun 30, 2014
  16. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Hi !

    The multiple passes are not needed to render specificaly a point light, but to render more than one light, of any kind ;)

    I've already tried to break the mesh, the rendering is ok, but the performances are drastically lower, so this is not an option.

    I've also thought about doing a custom lighting...but this may take some time to figure out how to do it properly, so before that I'd like to be sure that there is clearly no simpler solution ;)
     
  17. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Hi,

    I've found this : http://en.wikibooks.org/wiki/GLSL_Programming/Unity/Multiple_Lights

    Using vertex lighting is not as precise, but is not too bad in my specific case...
    But here, all lights present in the scene are checked to be included in the array of 4 lights.
    Would it be possible to exclude directional lights from this ?

    For now it looks like this :
    fumfum.png

    If I enable a fifth light, a directional one, one of these point lights is replaced by it in the array... so my goal is to get my shader only affected by point lights for the vertex lighting.
     
    Last edited: Jul 6, 2014
  18. kebrus

    kebrus

    Joined:
    Oct 10, 2011
    Posts:
    415
    you can mark lights as "important" in the inspector, as far as shader goes, just like that page says there's no way of telling what kind of light each iteration is

    PS: i don't know if you are aware but wikibooks is written in both glsl and cg (for example: http://en.wikibooks.org/wiki/Cg_Programming/Unity/Multiple_Lights)
     
  19. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Ok thanks, playing with the light importance parameter did the trick!
    I didn't noticed about the CG version, thanks also for that ;)
     
  20. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    What is your pixel light limit in Quality Settings? Reducing it to 0 or 1 should make the light handling more stable.
     
  21. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    I've set it to 4 ;)
     
  22. Daniel_Brauer

    Daniel_Brauer

    Unity Technologies

    Joined:
    Aug 11, 2006
    Posts:
    3,355
    In forward rendering, each additional pixel light requires another additive pass. A limiting your pixel lights to 0 or 1 will avoid the additional passes that cause the original brightening problem.
     
  23. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,054
    What about using noforwardadd in the surface shader?
    That will disable additional additive passes and provide a single directional light with all other lights as vertex/SphericalHarmonics.

    You may also find this video of Unite 2012 talk useful, at approx 19 mins in they talk about creating a single shader that supports 1 directional light + shadows, 4 pixel point lights and all other lights as SphericalHarmonics. The point being its done in a single pass shader and therefore not using forward add.
     
    Last edited: Jul 8, 2014
    rakkarage, Alesk and kebrus like this.
  24. Alesk

    Alesk

    Joined:
    Jul 15, 2010
    Posts:
    340
    Hey Noisecrime !

    Thanks a lot for this useful information, I'll investigate, it looks promising :)