For the rage you have accumulated: Now, until the moment you die~!

For the past year I’ve had a little side project I always go back to work on when bored (not to be confused with the project I work on to relax) that involves light-based visibility. I went to the Unity irc once or twice looking for help, but long story short I couldn’t get any solid help though as usual being able to talk with someone helped a lot in sorting things out.

/*
* I’ll put cliff notes at the bottom since I tend to ramble
* /

The idea behind this project is that you can only see certain objects by enabling certain lights, I’ve been cheating with occlusion masks and switching renders on/off, but since I had a long weekend and some time off from my other projects I thought : Just do it.

So what I wanted this shader to do was make something more visible when light hits it and less visible when light doesn’t hit it (or invisible when there isn’t any). A simple thing to do I thought, it’s just a matter of a little multiplication. Well, I was right that it was simple, but I was wrong in thinking I could simply pull something so simple off.

I started with something like this :

CGPROGRAM

      #pragma surface surf Phased alpha

      #pragma debug

      half4 LightingPhased (SurfaceOutput s, half3 lightDir, half atten) {

          half NdotL = dot (s.Normal, lightDir);

          half4 c;

          c.rgb = s.Albedo * (NdotL * atten * 2) * _LightColor0.rgb;

          c.a = 0.8279;

          return c;

      }

      struct Input {

          float2 uv_MainTex;

      };

      sampler2D _MainTex;

      void surf (Input IN, inout SurfaceOutput o) {

          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;

          o.Alpha = 0.1337;

      }

      ENDCG

But that would make an only 13% visible cube instead of a 80% visible one. Unity never really explained what was calling the functions or when, so I used the #pragma debug tag in order to get a better peek at the way the functions were being used. When Unity turns your *cough* “shader” code into actual shader code, it has an in-between step where it expands your code to add things that are needed (such as actually calling the lighting function you wrote for example). What #pragma debug does is keep that source code around (source code for the source code, generated using your source code… keeping up?) in a commented out block to let you know how it raped your code before giving birth to a child that isn’t really yours anymore. No, seriously.

So I found this :

fixed4 frag_surf (v2f_surf IN) : COLOR {
  Input surfIN;
  surfIN.uv_MainTex = IN.pack0.xy;
  SurfaceOutput o; <-- Creates SurfaceOutput
  o.Albedo = 0.0;  //And sets default values
  o.Emission = 0.0;
  o.Specular = 0.0;
  o.Alpha = 0.0;
  o.Gloss = 0.0;
  #ifdef LIGHTMAP_OFF
  o.Normal = IN.normal;
  #endif
  surf (surfIN, o); <-- Passes SurfaceOutput as inout
  fixed atten = LIGHT_ATTENUATION(IN);
  fixed4 c = 0;
  #ifdef LIGHTMAP_OFF //THEN calls lighting function
  c = LightingPhased (o, _WorldSpaceLightPos0.xyz, atten);
  #endif // LIGHTMAP_OFF
  #ifdef LIGHTMAP_OFF
  c.rgb += o.Albedo * IN.vlight;
  #endif // LIGHTMAP_OFF
  #ifndef LIGHTMAP_OFF
  fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
  fixed3 lm = DecodeLightmap (lmtex);
  #ifdef SHADOWS_SCREEN
  #if defined(SHADER_API_GLES) && defined(SHADER_API_MOBILE)
  c.rgb += o.Albedo * min(lm, atten*2);
  #else
  c.rgb += o.Albedo * max(min(lm,(atten*2)*lmtex.rgb), lm*atten);
  #endif
  #else // SHADOWS_SCREEN
  c.rgb += o.Albedo * lm;
  #endif // SHADOWS_SCREEN //But what have we down here?
  c.a = o.Alpha;           <-- It's setting alpha back to...
#endif // LIGHTMAP_OFF
  c.a = o.Alpha;           <-- ...whatever it was changed to.
  return c;
}

Now, someone on the forums had a similar problem, and what one person suggested was commenting out those lines. Well, first off that whole code block is already commented out, second the CG program does not ‘transplant’ out very well. It made a nice little shit ton of errors, and the line numbers Unity reports in the case of poorly compiled shaders aren’t always dependable (Unexpected ‘;’ on line 135…. wait a minute there are only 88 lines of code?! for example) .

Anyway, it eventually hit me. (And it may have been more obvious to someone with more programming experience) what’s good for the gander, is good for the geese, so why not so the same thing the surf program does? If the program wants to change it back to what the SurfaceOutput has, then just change the SurfaceOutput, right?

So now I have this :

Shader "FoxFritters" {

    Properties {

      _MainTex ("Pretty Picture", 2D) = "white" {}

    }

    SubShader {

      Tags { 

      	"RenderType" = "Transparent"

      	"Queue" = "Transparent"

      }

      CGPROGRAM

      #pragma surface surf Phased alpha

      #pragma debug
      //Notice I'm taking SurfaceOutput as an inout this time
      half4 LightingPhased (inout SurfaceOutput s, half3 lightDir, half atten) {

          half NdotL = dot (s.Normal, lightDir);

          half4 c;
          //The light color needs to be included or you get black back faces
          c.rgb = s.Albedo * (NdotL * atten * 2) * _LightColor0.rgb;

          c.a = NdotL * atten * _LightColor0.rgb;
          //Finally the new alpha is being stored in the SurfaceOutput
          s.Alpha = c.a;

          return c;

      }

      struct Input {

          float2 uv_MainTex;

      };

      sampler2D _MainTex;

      void surf (Input IN, inout SurfaceOutput o) {

          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;

          o.Alpha = 0.1337; //Apply texture/material alpha here later

      }

      ENDCG

    }

    Fallback "Diffuse"

  }

Which yields this shmancy effect

Showing off my new lighting shader

This is almost exactly what I was hoping for. Now, I can’t give these objects shadows per-say because Unity uses forward lighting for semi-transparent objects and only supports shadows from point/spot lights using deferred lighting. But I think I can live with that. Next on my list is to add some “wrap around” lighting to make it more diffuse, then I can go back and start making this game the way it was meant to be.

Cliff’s Notes:

  • In a compiled “surface” shader the surf function is called before the lighting function
  • The same SurfaceOutput is passed into both
  • The alpha applied is set to whatever is in the SurfaceOutput
  • Putting “inout” in front of the passed in SurfaceOutput let me modify it (could a more experienced programmer explain this?)
  • atten stands for attenuation, or how much light intensity is lost (I didn’t know that)
  • All of that “cgprogram” in the compiled shader #pragma debug is commented out, and further commenting out stuff doesn’t do anything! (facepalm)
  • _LightColor0.rgb is more than basic color information, I don’t know exactly what is going into calculating it yet but point lights in particular look horrible without it.
Advertisement

One thought on “For the rage you have accumulated: Now, until the moment you die~!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s