Advanced Cloud Rendering Using Perlin Noise

Hello,
I’m looking into adding cooler looking and faster clouds/rain in v1.2 of StackIt, and I thought I might share my prototype to get feedback:
Edit: Now on CC.


--# Main
displayMode(OVERLAY)
    
function setup()
    rainMesh = genFadedNoise(
                            color(0,0,255,0),
                            color(0,0,255,255),
                            vec2(300,5),
                            vec2(WIDTH,HEIGHT-200),
                            vec2(WIDTH/2,(HEIGHT-200)/2),
                            300,
                            true)
    cloudMesh = genFadedNoise(
                            color(255, 255, 255, 0),
                            color(255, 255, 255, 255),
                            vec2(3,6),
                            vec2(WIDTH,300),
                            vec2(WIDTH/2,HEIGHT-150),
                            100,
                            false)
    
    inSetup = true
    parameter.number("cloudiness",0,1,.5,configWeather)
    parameter.number("storminess",0,1,0,configWeather)
    parameter.number("windiness",-1,1,0,configWeather)
    parameter.number("raininess",0,1,0,configWeather)
    configWeather()
end

function draw()
    inSetup = nil
    
    local bgColor = color(0,108,255)
    bgColor = bgColor:mix(color(50,50,50,255),1-storminess)
    background(bgColor)
    
    rainMesh.shader.time = vec2(0,ElapsedTime*2*raininess)
    rainMesh:draw()
    
    cloudMesh.shader.time = vec2(ElapsedTime/2*-windiness,0)
    cloudMesh:draw()
end

function configWeather()
    if not inSetup then
        setNoiseParameters(rainMesh,
                            {
                                col2 = color(0,0,255,raininess*255)
                            })
        
        setNoiseParameters(cloudMesh,
                            {
                                col1 = color(255-storminess*175,cloudiness*200+100*storminess),
                                col2 = color(255-storminess*200,255),
                                stretch = vec2(
                                            2+(1-math.abs(windiness))*2,
                                            6-(1-math.abs(windiness))*3)
                            })
    end
end

function genFadedNoise(col1,col2,stretch,size,pos,fadeSize,fadeTop)
    local maskMesh = mesh()
    maskMesh.shader = shader(linearGradientShader())
    maskMesh.shader.angle = 0
    if fadeTop then
        maskMesh.shader.col2 = vec4(0,0,0,0)
        maskMesh.shader.col1 = vec4(1,1,1,1)
        maskMesh:addRect(size.x/2,size.y-fadeSize/2,size.x,fadeSize)
    else
        maskMesh.shader.col2 = vec4(1,1,1,1)
        maskMesh.shader.col1 = vec4(0,0,0,0)
        maskMesh:addRect(size.x/2,fadeSize/2,size.x,fadeSize)
    end
    
    local maskImg = image(size.x,size.y)
    setContext(maskImg)
    pushStyle()
    noStroke()
    fill(0, 0, 0, 255)
    if fadeTop then
        rect(0,0,size.x,size.y-fadeSize+1)
    else
        rect(0,fadeSize-1,size.x,size.y-fadeSize+1)
    end
    popStyle()
    maskMesh:draw()
    setContext()
    
    local m = mesh()
    m.shader = shader(noiseShader())
    m.fadeTop = fadeTop
    m.shader.maskTexture = maskImg
    m.mSize = size
    m.mPos = pos
    m:addRect(pos.x,pos.y,size.x,size.y)
    
    setNoiseParameters(m,
                    {
                        col1 = col1,
                        col2 = col2,
                        stretch = stretch,
                        size = size,
                        pos = pos
                    })
    
    return m
end

function setNoiseParameters(m,param) -- col1, col2, stretch, size, pos
    if param.col1 then
        m.shader.col1 = vec4(param.col1.r/255,param.col1.g/255,param.col1.b/255,param.col1.a/255)
    end
    if param.col2 then
        m.shader.col2 = vec4(param.col2.r/255,param.col2.g/255,param.col2.b/255,param.col2.a/255)
    end
    if param.stretch then
        m.shader.scale = param.stretch
    end
    if param.size or param.pos then
        print(param.size)
        m.mSize = (param.size or m.mSize)
        m.mPos = (param.pos or m.mPos)
        m:setRect(1,m.mPos.x,m.mPos.y,m.mSize.x,m.mSize.y)
    end
end

--# Shaders
function noiseShader()
    return [[
uniform mat4 modelViewProjection;

attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
{
    vColor = color;
    vTexCoord = texCoord;
    
    gl_Position = modelViewProjection * position;
}

    ]],[[
precision highp float;

uniform lowp sampler2D maskTexture;

varying lowp vec4 vColor;

varying highp vec2 vTexCoord;

uniform vec2 time;
uniform vec2 scale;
uniform vec4 col1;
uniform vec4 col2;

vec4 mod289(vec4 x)
{
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 permute(vec4 x)
{
  return mod289(((x*34.0)+1.0)*x);
}

vec4 taylorInvSqrt(vec4 r)
{
  return 1.79284291400159 - 0.85373472095314 * r;
}

vec2 fade(vec2 t) {
  return t*t*t*(t*(t*6.0-15.0)+10.0);
}

float cnoise(vec2 P)
{
  vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
  vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
  Pi = mod289(Pi); // To avoid truncation effects in permutation
  vec4 ix = Pi.xzxz;
  vec4 iy = Pi.yyww;
  vec4 fx = Pf.xzxz;
  vec4 fy = Pf.yyww;

  vec4 i = permute(permute(ix) + iy);

  vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;
  vec4 gy = abs(gx) - 0.5 ;
  vec4 tx = floor(gx + 0.5);
  gx = gx - tx;

  vec2 g00 = vec2(gx.x,gy.x);
  vec2 g10 = vec2(gx.y,gy.y);
  vec2 g01 = vec2(gx.z,gy.z);
  vec2 g11 = vec2(gx.w,gy.w);

  vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));
  g00 *= norm.x;  
  g01 *= norm.y;  
  g10 *= norm.z;  
  g11 *= norm.w;  

  float n00 = dot(g00, vec2(fx.x, fy.x));
  float n10 = dot(g10, vec2(fx.y, fy.y));
  float n01 = dot(g01, vec2(fx.z, fy.z));
  float n11 = dot(g11, vec2(fx.w, fy.w));

  vec2 fade_xy = fade(Pf.xy);
  vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
  float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
  return 2.3 * n_xy;
}

void main(void) {
    float noiseCol = cnoise(vec2(vTexCoord.x+time.x,vTexCoord.y+time.y)*scale);
    vec4 col = (col2-col1)*noiseCol+col1;
    col.a = texture2D(maskTexture,vTexCoord).a*col.a;
    gl_FragColor = col;
}
    ]]
end

function linearGradientShader()
    return [[
uniform mat4 modelViewProjection;

attribute vec4 position;
attribute vec4 color;
attribute vec2 texCoord;

varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

uniform float angle;

void main()
{
    vColor = color;
    vTexCoord = texCoord;
    
    vec2 pos = vec2(.5,.5);
    vec2 tpos = texCoord - pos;
    float ct = cos(angle/360.0*6.28);
    float st = sin(angle/360.0*6.28);
    vTexCoord = vec2(ct*tpos.x - st*tpos.y,st*tpos.x + ct*tpos.y) + pos;
    
    gl_Position = modelViewProjection * position;
}

    ]],[[

precision highp float;

//uniform lowp sampler2D texture;

uniform vec4 col1;
uniform vec4 col2;

varying lowp vec4 vColor;

varying highp vec2 vTexCoord;

void main()
{
    lowp vec4 col = vec4(0,0,0,0);
    
    col = (col2-col1)*vTexCoord.y+col1;

    gl_FragColor = col;
}

    ]]
end

LMK what you guys think and how the performance is.
Thanks!

@zoyt very nice job. I’m getting a pretty steady 60fps with a few drops to 54fps. Nice job. I’d wrap it all up in a class.

Very nice clouds. The rain pbly needs some more work to be more reallistic…

@Briarfox - Thanks. I already have a class for my weather, so I was just testing an aspect of it.
@Jmv38 - I agree. If you have any suggestions, I’m open to them.
Thanks!

@Zoyt interesting weather, can if use the code in my own project?

@kirorp - Yes you may, as long as you do me a favor and don’t use live backgrounds (like live weather) and put me in the credits.
Thanks!

@Zoyt what are live backgrounds

@kirorp - Like in my game where it fetches the weather and displays it in the app.