top of page

2D Procedural Clouds Shader Breakdown


In the vast landscape of DARUMA, you can see some clouds in the background. These clouds are generated using a custom shader. There are other approaches to create clouds, but for simplicity, I decided to handle all the cloud effects in a single shader. Basically, the clouds are drawn on a large sprite that covers the entire sky, using the following shader.


First, I'm using a parameter to determine the number of noise layers the clouds will be generated with. I've found that using between 2 and 4 layers yields good results:

_LayerCount ("Layer Count", Range(2, 5)) = 3

I have a function that generates noise layers with scrolling speed:

float NoisesAndScroll(float2 uv)
{
    float noiseValue = 0.0;
    float amplitude = 1.0;
    float angle = DegToRad(_ScrollDirection);
    float2 scroll = float2(cos(angle), sin(angle)) * _ScrollSpeed * _Time.y;
    
    for (int i = 0; i < _LayerCount; i++)
    {
        noiseValue += GradientNoise((uv + scroll), _Scale) * amplitude;
        uv *= 2.0;
        amplitude *= 0.5;
        scroll *= 0.5;
    }
    
    return noiseValue;
}

I call this function at the beginning of the fragment shader. I add a random seed value and scale to the UV coordinates to stretch the clouds if needed:

half4 Fragment(Varyings i) : SV_Target
{
    float2 uv = i.positionWS.xy * _GlobalScale.xy;
    float noise = NoisesAndScroll(uv + _Seed);
    [...]
}

Now we have a scrolling texture like this:


To control the cloud shape, we can remap this texture using two parameters:

_Threshold ("_Threshold", Range(0,1)) = .8
_Blur ("_Blur",  Range(0 , 1)) = .5

Apply it in the fragment shader:

noise = Remap(noise, float2(_Threshold, _Threshold + _Blur) ,float2(0,1));
noise = saturate(noise);

Here is the result with tweaked values:


To match the DARUMA art style, I add blue noise dithering to the texture (similar to the lights):


This texture represents our alpha value, and we can use a color to draw the cloud:


It's good, but not enough. We can try adding a fake shadow to the cloud for a more realistic appearance. To do this, we will draw another set of noise layers but with an offset. We will use this for the alpha and then for the color.

With these two parameters, we can get the desired offset and pass it into the noise function:

_ShadowDistance ("Shadow Distance", float) = 1
_ShadowAngle ("Shadow Angle", Range(0,360)) = 1
float angle = DegToRad(_ShadowAngle);
float2 shadowAngle = float2(cos(angle), sin(angle));
float shadow = NoisesAndScroll(uv + shadowAngle * _ShadowDistance + _Seed);
shadow = Remap(shadow, float2(_Threshold, _Threshold + _Blur) ,float2(0,1));
shadow = saturate(shadow);

We just need to control the color of the shadow using a lerp. As you can see, the shadow smoothly blends into the cloud color, but in the DARUMA style, we need to add blue noise to it.

float shadowMap = ApplyBlueNoise(i.ScreenPosition, _DarumaNoise, sampler_DarumaNoise, noise);
float4 cloudColor = lerp(_CloudShadowColor, _CloudColor, shadowMap);

Finally, with multiple layers of clouds, fine-tuning, and post-processing, here is the final result in-game (speed x3):


Comments


bottom of page