I like games.

You like games.

Let's be friends.



Alex Swaim

@phoenixashes

@vgfoliage

Experiment: Firewatch Day/Night Cycle

I saw the art from the Firewatch announcement website and thought it would be cool if I could get that kind of color scheme and have it change time of day.

So I made it. You can take a look here.

The whole scene uses only 2 textures and 3 shaders. There’s about a dozen materials in use, but that could be cut down with more time.

More about what’s going on behind the scenes after the fold.

There’s a few neat things here, but most of it comes down to two parts: the shaders and the gradient texture. (The art is also great, but every last ounce of credit for that goes to Campo Santo.)

Here’s what the scene view looks like:

Scene View

Shaders and Materials

The shaders all operate off a similar principal: they have a parameter that tells them the time of day, and the use that to find what part of the gradient to sample. The final output color comes directly from that read, ignoring all contributions from light colors.

So what are the 3 shaders? I have one that’s unlit, one that lerps between two gradients based on the difference between a per-material normal direction and the light direction, and one that does the lerping-from-light-direction but doesn’t use the texture atlas.

The code for all 3 are similar. Here’s the code for the unlit shader:

Shader "Cid/TimeOfDay Flat" {
	Properties {
		_MainTex ("Mask (A)", 2D) = "white" {}
		_RampTex ("Ramp (RGB)", 2D) = "white" {}
		_TimeOfDay ("TOD", Range(0,1)) = 0.0
		_ReadChannel("Palette Channel (0-9)", Float) = 0.0
	}
	SubShader {
		Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf Unlit alpha
		
		half4 LightingUnlit (SurfaceOutput s, half3 lightDir, half atten) {
			half4 c;
			float palette = (_ReadChannel + 0.5) / 10.0; // 10 total channels in the texture
			half4 t = tex2D (_RampTex, float2(palette, _TimeOfDay));
			c.rgb = t.rgb;
			c.a = s.Alpha;
			return c;
		}

		sampler2D _MainTex;
		sampler2D _RampTex;
		float _TimeOfDay;
		float _ReadChannel;

		struct Input {
			float2 uv_MainTex;
		};

		void surf (Input IN, inout SurfaceOutput o) {
			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			o.Albedo = half3(1.0, 1.0, 1.0);
			o.Alpha = c.a;
		}
		ENDCG
	} 
	FallBack "Diffuse"
}

The other shaders change the lighting function, with some additional differences in the Properties section.

Here’s the lighting function for the lit shaders:

half4 LightingRamp (SurfaceOutput s, half3 lightDir, half atten) {
	half NdotL = dot (s.Normal, lightDir);
	half diff = smoothstep(0, 1.0, NdotL);
	float palette = (_ReadChannel + 0.5) / 10.0; // 10 total channels in the texture
	float palette2 = (_HighChannel + 0.5) / 10.0;
	half3 ramp = tex2D (_RampTex, float2(palette, _TimeOfDay)).rgb;
	half3 hlight = tex2D (_RampTex, float2(palette2, _TimeOfDay)).rgb;
	half4 c;
	c.rgb = lerp(ramp, hlight, diff);
	c.a = s.Alpha;
	return c;
}

Using these shaders I set up materials for each unlit layer (the source use 6-ish layers), two for the central mountain which use the per-material normals, one for the watchtower, and one for the watchtower windows.

Example material

The time of day parameter gets animated for all the materials in the scene by a script.

Gradient Ramp

The other part this relies on is the gradient ramp texture. My early attempts at this were done in photoshop, but that proved to be VERY cumbersome to itterate with.

So I wrote my own tool for generating gradient textures.

Gradient editor

That made things much easier to tweak, even letting me re-bake the ramp texture in-editor to see some nearly instant feedback.

Time of Day ramp

The tool is a bit single-purpose: the controls are made to do 24 hour cycles with 10 color palette channels. But I have a feeling I can get some more use out of these shaders and the gradient editor in other projects. The nice part about writing your own tools is that you can easily change them to suit the project.

Published on in Experiments

Header Photo: Images sampled from promotional art for Firewatch by Campo Santo