top of page

2D Procedural Clouds Shader Breakdown

  • Photo du rédacteur: Lilian Millers
    Lilian Millers
  • 27 mars 2023
  • 2 min de lecture

ree

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:

ree

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:

ree

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

ree

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

ree

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);
ree

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);
ree

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

ree

Commentaires


bottom of page