Shader Programming, Volume 9

Physically Based Rendering and Realistic Lighting

Now that we’ve completed a few recipes and written a few Specular lighting models, perhaps its time we examined some of the techniques we can use in Unity 5 to simulate realistic lighting. PBR is relatively new in Unity — in the form of the Standard Shader model — and it can look fantastic.

Chapter 4

Physically Based Rendering in Unity 5

I’m going to be a little less formal with this chapter because it doesn’t really have “recipes”, really. It just walks you through some of Unity’s PBR features. I wanted to skip over it and get back to custom shaders, but this really is good knowledge to have in your backpocket if you want to incorporate realistic lighting and materials in your games.

The philosophy behind PBR is to simulate as accurately as possible the physics behind the processes that give each material a unique appearance. Unity 5 implements PBR by introducing two changes: a completely new Standard Shader lighting model and Global Illumination.

The Standard Shader allows developers to specify physical properties of a material without imposing actual physical constraints on them. In order to calculate the Standard lighting model, PBR enforces physical principles such as the conservation of energy (an object can’t reflect more light than it receives), microsurface scattering (rough surfaces reflect light more erratically than smooth ones), Fresnel reflectance (specular reflections appear at grazing angles), and surface occlusion (corners and other geometries that are hard to light appear darker), among other things.

Here’s a quick run-down of how to initialize the Standard Shader parameters for common materials, and a good overview of what the Standard Shader is capable of:

You can even use the Standard Shader to create mirrors that work in realtime! Set the shader’s metallic and smoothness properties to 1, create a reflection probe and place it in front of your mirror, change its size until it encompasses all objects you want the mirror to reflect, then change the shader’s type to Realtime.

Global Illumination simulates physically-based light transport. All objects in the scene contribute to the final rendering because light can reflect on them before hitting something else. Accurately simulating how light rays actually bounce off of surfaces in realtime is beyond what modern GPUs are capable of. But you can simulate GI for static objects with a process called “baking”.

To bake lights in your scene, select all of the objects in your scene that do not change position, size, or material and check the Static box from the Inspector tab in the editor. If a light qualifies as static geometry but illuminates non-static geometry set its Baking property to Mixed. If it will only affect static objects, set it to Baked. To finally bake the lights, open the Lighting window, select its Lightmaps tab, and click Build.

Light baking is very expensive when it comes to memory, though. If you have a forest of trees sharing the same texture and you set them to be Static objects, each tree will instead have its own texture. That is, every static surface is re-textured to include its lighting condition. But it looks amazing.

See for yourself. Amaaaazinggggg.

If there are objects in your scene that will enter a static region that is illuminated, you might want to enclose the static region with light probes. Make sure the moving objects have Use Light Probes checked in their Mesh Renderers. This is important because non-static objects in static regions will often look detached from the environment — their naive lighting will make them stick out like a sore thumb. To resolve this, every light probe you place samples the global illumination at a specific point in space. Each light probe group can interpolate global illumination within a specific volume. This allows us to approximate casting more accurate lighting on moving objects, despite the fact that GI has only been calculated for a small set of points. Light probes should be placed wisely, though; they consume memory just like lightmaps do.

Alright, I think that’s sufficient introduction to those concepts. I really want to get back to making custom shaders. I’m pretty excited about the next chapter — we’re going to learn how to manipulate the geometry of a 3D object via shaders using Vertex Functions. Yaaasssss.