Procedural skybox

This section of the guide describes the procedural skybox and how you can use it in your own Unity programs.

The Ice Cave demo uses a time-of-day system to show the dynamic shadowing effects that you can achieve with local cubemaps.

To achieve a dynamic time of day effect, combine the following elements:

  • A procedurally generated sun
  • A series of fading skybox background cubemaps that represent the day to night cycle
  • A skybox clouds cubemap

The procedural sun and the separated cloud texture also create a computationally cheap subsurface scattering effect.

The following image shows a view of the skybox:

The Ice Cave demo uses the view direction to sample from a cubemap. This enables the demo to avoid rendering a hemisphere for the skybox. Instead, the demo just uses planes near the holes in the cave. Therefore, the performance increases, when compared to rendering a hemisphere that is then mostly occluded by other geometry.

The following image shows the skybox rendered with the use of a plane:

Managing the time of day

This effect uses a C# script to manage the mathematics for the time of day, and the animation of the day-night cycle. A shader then combines the sun and skymaps.

You must specify the following values for the script:

  • The number of fading skybox background phases
  • The maximum duration of the day to night cycle

For each frame, the script selects the skybox cubemaps to blend. The selected skyboxes are set as textures for the shader and the script blends the textures together while the frame is rendering.

To set the textures, the Ice Cave demo uses the Unity Shader global features. This enables you to set a texture in one place that all the shaders in your application can use. This is shown in the following code:

Shader.SetGlobalTexture (ShaderCubemap1, _phasesCubemaps [idx1]);
Shader.SetGlobalTexture (ShaderCubemap2, _phasesCubemaps [idx2]);
Shader.SetGlobalFloat (ShaderAlpha, blendAlpha);
Shader.SetGlobalVector (ShaderSunPosition, normalizedSunPosition);
Shader.SetGlobalVector (ShaderSunParameters, _sunParameters);
Shader.SetGlobalVector (ShaderSunColor, _sunColor);
Shader.SetGlobalTexture (ShaderCloudsCubemap, _CloudsCubemap);

Note: The name of the sampler used must not conflict with the one that is locally defined by the shader.

The following items are set in the script code:

  • The two cubemaps that are interpolated.
  • The values idx1 and idx2 are computed based on the elapsed time.
  • A blendAlpha factor, that is used in the shader to blend the two cubemaps.
  • A normalized sun position used to render the sun sphere.
  • Parameters for the sun
  • The color of the sun
  • The cloud cubemap. Specifically, in the code these are: ShaderCubemap1 and ShaderCubemap2 which are two strings that contain the unique sampler names, in this case _SkyboxCubemap1 and _SkyboxCubemap2.

To access these textures in the shader, you must declare them with the following code:

samplerCUBE _SkyboxCubemap1;
samplerCUBE _SkyboxCubemap2;

The script selects the sun color and an ambient color based on the list that you specify. These colors are interpolated for each phase.

The sun color is passed to the shader for rendering the sun with the correct color.

The ambient color is used to dynamically set the Unity variable RenderSettings.ambientSkyColor. This is shown in the following code:

RenderSettings.ambientSkyColor = _ambientColor;

Setting this variable enables all the materials to receive the correct ambient color. In the Ice Cave demo, this effect causes a gradual variation of the overall color of the scene based on the phase of the day it is.

Rendering the sun

To render the sun, in the fragment shader, for each pixel of the skybox you must check whether it is inside the circumference of the sun.

To check whether a pixel is inside the circumference of the sun, the shader computes the dot product of the normalized sun position vector and the normalized view direction vector in world coordinates of the pixel being rendered.

The following diagram shows the rendering of the sun:

Note: The dot product of the normalized view vector and the sun position vector is used to determine if the fragment is rendered, as sky as P2, or rendered as a sun as P1.

The normalized sun position vector is passed to the shader by the C# script, if either of the following occur:

  • The result is greater than a specific threshold. The pixel is colored with the sun color.
  • The result is less than the threshold. The pixel is colored with sky color.

The dot product also creates a fading effect towards the edges of the sun. This is shown in the following code:

half _sunContribution = dot(viewDir,_SunPosition);

The following image shows a clear view of the sun:

Fading the sun behind mountains

There is a problem if the sun is low in the sky, because in real life it would disappear behind the mountains.

To create this effect, the alpha channel of the cubemap stores a value of 0 if the texel represents the sky.  This channel stores a value of 1 if the texel represents the mountain.

While rendering the sun, the texture is sampled, and the alpha is used to make the sun fade behind the mountains. This sampling is effectively free. This is because the texture is already sampled to render the mountains.

You can also gradually fade the alpha near the edges or the areas where there is snow on the mountain. This produces an effect of the sun bouncing off the snow for little computational effort.

A similar technique is used to create a computationally cheap subsurface scattering effect for the clouds.

The original phase cubemaps are split into the following groups:

  • One set of cubemaps contain the sky and the mountains. The alpha value is set to 0 for the sky and set to 1 for the mountains.
  • The other set of cubemaps contains the clouds. The alpha value is set to 0 in the areas without clouds and gradually increases to 1 in areas where the clouds are denser.

The following image shows a mountains texture:

Note: The mountains have an alpha value of one, the sky has a value of zero.

The clouds skybox has alpha 0 for the empty areas and gradually fades to 1 when occupied by a cloud. The alpha channel fades smoothly to ensure that the clouds do not look like they have been artificially put on top of the sky. The following image shows a cloud texture with alpha:

The shader performs the following steps:

  1. Samples the two skyboxes that represent the current time of day phase.
  2. Blends the two colors based on a blending factor that is calculated by the C# script.
  3. Samples the cloud skybox.
  4. Blends the color from point 2 with the color from the clouds using the cloud alpha.
  5. Adds the cloud alpha and the skybox alpha.
  6. Calls a function to compute the sun color contribution for the current pixel.
  7. Adds the result of point 6 with the previously blended skybox and cloud color.

Note: You can optimize this sequence by putting the sky, mountains, and clouds into a single skybox. The sky, mountains, and clouds are separate in the Ice Cave demo, so that the artist can easily modify the skyboxes and the clouds separately.

Subsurface scattering

You can add a subsurface scattering effect that uses the alpha information to increase the radius of the sun.

In real life when the sun is in a clear sky, it looks small. This is because there are no clouds that can deviate, or scatter, the light toward your eyes. What you see are the rays directly from the sun, spread only very slightly by the atmosphere.

When the sun is occluded by clouds, but they do not completely obscure it, part of the light is scattered around the cloud. This light can reach your eyes from directions that are some distance away from the sun, making the sun appear larger than it really is.

The following code is used to achieve this effect in the Ice Cave demo:

half4 sampleSun(half3 viewDir, half alpha);
{
	half _sunContribution = dot(viewDir,_SunPosition);
	half _sunDistanceFade = smoothstep(_SunParameters.z -(0.025*alpha), 1.0, _sunContribution);
	half _sunOcclusionFade = clamp( 0.9-alpha, 0.0, 1.0);
	half3 _sunColorResult = _sunDistanceFade * _SunColor * _sunOcclusionFade;
	return half4( _sunColorResult.xyz, 1.0 );
}

Here is an explanation of the preceding code:

  • The parameters of the function are viewDirection and an alpha value that is computed by the cloud alpha and skybox alpha being added together.
  • The dot products of the sun position and view directions are used to compute a scaling factor. The scaling factor represents the distance of the current pixels from center of the sun.
  • The _sunDistanceFade calculation uses the smoothstep() function to provide a more gradual fade from the center of the sun to the sky near the edges.
  • This function has a variable domain based on the alpha, if there is clear sky, the alpha is 0 and the range is within _SunParameters.z and 1.0. In this case _SunParameters.z is initialized to 0.995 in the C# script, _SunParameters.z corresponds to a sun of diameter of 5 degrees: cos(5 degrees) = 0.995
  • If the pixel that is being processed contains a cloud, the radius of the sun is increased to 13 degrees. This enables an elongated scattering effect when approaching the clouds.
  • The _sunOcclusionFade factor is used to fade down the contribution of the sun based on the occlusion received from the mountains and the clouds.

The following image shows the sun when it is not occluded by the clouds:

The following image shows the sun when it is occluded by clouds:

Previous Next