Lighting Up the Sky

We’ve always thought of Guns of Icarus as connected with big, dramatic panoramas of sky. Spectacular sunsets and rises, towering thunderstorms, mountainous ranges of puffy white beneath a noon sun, that sort of thing. I’ve been working recently on some things to populate our big empty expanses of 3D sky: clouds.

There are two competing goals here: first, to make our clouds look awesome, which means spending graphical resources, and in particular integrating with our lighting system. At the same time, I had to do this without slowing the display on user machines to a crawl. Simple particle systems fail both of these tests: the options for rendering are limited, and they become extremely fill-rate hungry when you layer them thickly enough to look good.

Some (okay, a lot) of shader experiments and procedural mesh manipulation code later, and I think we’ve got something pretty cool. Here’s the same scene three times, lit by a single directional light that’s been rotated to different angles.

There are a couple of technical tricks going on here that help performance a lot. Though similar to particle systems, in that these are flat meshes oriented towards the camera, the thick central portions of the mesh are fully opaque and participate in depth testing, which cuts down significantly on fill-rate usage. Also, we can do most of our lighting per-vertex, and it looks just fine – for big soft clouds, we’ve found that high-detail lighting actually looks worse, because it destroys the illusion of a mass of vapor.

Unity’s standard lighting model actually gives you quite a lot per-vertex, basically for free. The combination of multiple vertex lights per pass with spherical harmonics for very distant lights allows us to add lights almost freely so long as we’re willing to accept that they are low detail. For fast, flickering lights (explosions, thunderbolts, fires), this seems like a win-win situation!

The same scene with "night" lighting
The same scene, with three point lights added amongst the clouds.

The cloud particles are positioned procedurally (just random distribution), but within volumes that are defined (by boxes, nothing too fancy) within the Unity editor. This allows us to shape the cloud volumes, an important factor if we want to use these as level geometry, with players flying in and out of them for cover. Which is, of course, the plan.

Artists and level designers can lay out cloud volumes with simple boxes.

These static images are well and good, but let me leave you with a better demonstration of just how well this system interacts with real-time lighting. This is the result of a simple script that spawns randomly positioned, short-lived point lights.

Shadow Blues

Shadows are not absolute: light reflecting off of surrounding objects, or just diffusing through the atmosphere, always gives them a little touch of light and of color.  Outdoors, this frequently means blue, taking on the color of the faint reflected light coming from the sky.

Artists, meanwhile, have been cleverly exaggerating this blue-shadows thing for many decades: our eye tends read cool hues (blues, greens, purples) as receding or distant, while warm hues (reds, yellows, oranges) are read as advancing or important.  The contrast created by warm highlights and cool shadows also serves to make the image more vibrant.

Simulated lighting frequently neglects this, so after a bit of discussion with Tim, I set out to make our shadows blue.  I ended up reusing one of my favorite shader tricks, something that actually allows for a lot more than that.

The trick in question is the ramp texture: I calculate a pretty normal half-Lambert lighting term that ranges from 0 to 1, but use that term in a texture lookup rather than multiplying directly.

Standard lighting
A character lit with the base lighting term
Blue-shadow lighting
A character lit with a gradient that fades to dark blue rather than black
Toon lighting
A character lit by a sharply-transitioning gradient to produce a "cartoon" look

The lookup value also includes the effects of cast shadows; you can see the shadow cast by the cylinder on the character’s back in the images above.  Light attenuation, however, happens outside this system: if it didn’t, multiple point lights affecting the same object start to accumulate significant brightness even in the “dim” edges of the light region.