Fake Specular Highlights with Shader Graph

So, the other day at work we were exploring various methods of adding nice lighting effects to a Unity scene without actually using real time lighting (in order to maintain the best possible performance on low end mobile devices). I took this to be the perfect opportunity to learn Unity’s new Shader Graph. If you’re not familiar, Shader Graph is Unity’s new node based visual shader editor. Currently, it only works with one of Unity’s new render pipelines (lightweight or HD), so if you’d like to try it out and haven’t yet, be sure to set up a project appropriately using Unity Hub or the package manager.

Once I got the project set up, I thought I’d look at using shader graph to create fake specular lighting and thought I’d share what I discovered. First, specular lighting is not all that complex or involved which is why I thought it’d be pretty well suited for lightweight fake lighting. If you do a quick google search (as I did), you’ll see specular highlighting basically boils down to the following formulae.

The reflected direction, R, is calculated like this:

R = 2N(N·L)-L

where N is the normalized world normal of the reflecting object and L is the normalized light direction. If you consider V the world view direction (in relation to the viewer) then the specular value can be calculated like:

specular = max(0, R·V)strength

That specular value can then be multiplied by a light color and that product then added to the material’s albedo/diffuse texture to create the final effect.

So, in shader graph, then, the first thing to do is create an unlit graph (remember the whole point is to fake the lighting – that’s why we’re starting with an unlit graph rather than a PBR graph). Within the graph, the first thing I did was add a list of properties I knew I’d need: a Texture2D called Albedo, a Vector3 Light Direction, a Vector1 for Strength (or shininess as you may find it called), and a Color called LightColor.

Rather than try to describe the rest of the setup node by node, here’s a screen shot of the entire graph which essentially follows the math above.

One thing to note (as it may be difficult to see in the image) is that both the Normal Vector and View Direction nodes are set to world space. (EDIT: I just looked at this image and realized I took a screenshot of an old version and don’t feel like generating a screenshot of the latest – Unity really needs to work on a way to easily share graphs/images of graphs. While the graph pictured works well, it doesn’t follow the formula above to a T. I’ll let you have fun figuring out where I went wrong :) )

Once that graph is applied to a material and that material applied to some 3D model or other, you may wind up with something like this (note: this is using some spaceship thing from Popup Asylum’s very nice SciFi Enemies and Vehicles – available on the Unity Asset Store).

The model on the left hand side has a material using Unity’s built in Unlit Texture shader, while the model on the right has a material using the above shader graph. Keep in mind, there are no lights/lighting in the Unity scene – that’s what we were trying to avoid from the start.

This being my first go with Shader Graph, all in all, I’d say it’s a fantastic tool for quickly prototyping shader ideas. It did take awhile to get the hang of the node based interface (I’ve never really used any node based tool before), but once I did, it was all pretty easy going. And I really love the idea of just clicking Save Asset and seeing the results immediately applied to any object using the shader’s material.

There are a few drawbacks though. As mentioned, Shader Graph currently only works in a project using a special render pipeline. Also, I’m not sure just how well optimized the final result of shader graph shaders actually are. Here, for example, is the shader code generated from the graph above:

// this operation can be optimized with multiplication once a suitable _Strength value is determined

spec=pow(spec,_Strength);

fixed3 specColor=spec*_LightColor.xyz;

fixed4 texColor=tex2D(_Albedo,i.uv);

texColor.xyz=texColor.xyz+specColor;

returntexColor;

}

ENDCG

}

}

}

Time will tell just how useful Shader Graph will be in the long run. My recommendation: use it for visually prototyping some cool effect or other then use the generated code (and your own shader writing ability) to re-write the output in a more optimized format.

And speaking of work…

Anyone who has followed this blog for a minute (or just happened to skim over some past postings) may have noticed I have been writing more about Unity3D than Flash/Actionscript over the past year or two. That is because I have switched jobs and am now, officially speaking, a Unity3D Engineer with the fantastic game studio TouchPress (StoryToys) in Dublin, Ireland. As much as I did love Flash and Starling and writing shaders in AGAL (not being sarcastic – I actually enjoyed it), I am really enjoying Unity3D and life at TouchPress has just been awesome so far. In fact we just recently released the first game I got to work on there – Disney Coloring World. Check it out. And have a great New Year. I’m sure things can only go up from here.