Pages

Monday, September 3, 2012

Unity3d shaders: z-depth controlling alpha

The Situation:


In my game Asteroid Ace, the player faces hordes of asteroids, minerals and other space debris. Sometimes there are so many of them storming towards you at the same time, that it becomes very difficult to navigate. For that reason, i gave the player a projection that shows where an incoming object is going to hit the player's plane.

Since this game is for mobile platforms (at least initially), i have a very limited draw call budget (25 to 30 max). Ideally, i want one single draw call to take care of all projections for each object/size combination. So, i used Unity's dynamic batching mechanism: make sure your projections have the same plane mesh, the same scale, and the same material. Works like a charm.

The Problem:


The projections do not provide any information about how long it takes for the object to reach the players plane. Without projections, a player can make a good judgement of distance and speed, but with projections, these actually occlude vision of incoming stuff.

For that reason, i wanted the projections to convey ETA of the objects. On an earlier prototype i simply manipulated the material's main color transparency as the object gets closer (its Z position changes). This approach has the desired effect, but totally kills unity's dynamic batching. Because, when you start manipulating an object's material in-game, the engine creates a material Instance for that object only. This means, the projections no longer share the same material, and dynamic batching no longer works.

The Solution:


Let the shader control the transparency, depending on the Z-depth.

Here's the shader without Z-depth factored in:

Shader "DapperDodo/Mobile Alpha" {

    Properties {
        _EmisColor ("Emissive Color", Color) = (.2,.2,.2,0)
        _MainTex ("Particle Texture", 2D) = "white" {}
    }

    Category {
        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
       
        Lighting Off

        SubShader {
          CGPROGRAM
          #pragma surface surf None finalcolor:mycolor alpha noambient novertexlights nolightmap nodirlightmap noforwardadd

          half4 LightingNone (SurfaceOutput s, half3 lightDir, half atten) {
          half4 c;
          c.rgb = s.Albedo;
          c.a = s.Alpha;
          return c;
        }
          struct Input {
              float2 uv_MainTex;
          };
          fixed4 _EmisColor;
          void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
          {
              color *= _EmisColor;
          }
          sampler2D _MainTex;
          void surf (Input IN, inout SurfaceOutput o) {
              o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
              o.Alpha = tex2D (_MainTex, IN.uv_MainTex).a;
          }
          ENDCG
        }
    }
}

And here's the shader with Z-depth factored in. The code needed for this is Bold

Shader "DapperDodo/Mobile Z-depth Alpha" {

    Properties {
        _EmisColor ("Emissive Color", Color) = (.2,.2,.2,0)
        _MainTex ("Particle Texture", 2D) = "white" {}
    }

    Category {
        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
       
        Lighting Off

        SubShader {
          CGPROGRAM
          #pragma surface surf None finalcolor:mycolor alpha noambient novertexlights nolightmap nodirlightmap noforwardadd

          half4 LightingNone (SurfaceOutput s, half3 lightDir, half atten) {
          half4 c;
          c.rgb = s.Albedo;
          c.a = s.Alpha;
          return c;
        }
          struct Input {
              float2 uv_MainTex;
              float3 worldPos;
          };
          fixed4 _EmisColor;
          void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
          {
              color *= _EmisColor;
          }
          sampler2D _MainTex;
          void surf (Input IN, inout SurfaceOutput o) {
              o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
              o.Alpha = tex2D (_MainTex, IN.uv_MainTex).a * (1 - saturate((IN.worldPos.z - 1000) / 100));
          }
          ENDCG
        }
    }
}

The key piece of code here is '* (1 - saturate((IN.worldPos.z - 1000) / 100))'

The values of 1000 and 100 depend on the camera setup. Change that according to you own situation. In this case, Z <= 1000 means alpha = 1, and as Z moves to 1100 and beyond, alpha becomes 0.

For Asteroid Ace i needed a double camera setup. The flying objects are rendered using a normal perspective camera. However, the projections are rendered using an Orthographic camera. This means that the projections keep the same size, independent of the Z depth.


No comments:

Post a Comment