shadimage
awwwardedui
blockclouds-shader-backgroundsource: ok · refreshed

Clouds Shader Background

A self-contained WebGL cloudscape built from the supplied Shadertoy-style noise shader.

npx shadcn add "https://awwwardedui.vercel.app/r/clouds-shader-background.json"
free

Visual style

particle

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Files2 · source cached 10/7/2026, 11:29:20 PM

registry/components/clouds-shader-background-source.ts
export const cloudsFragmentShader = String.raw`#version 300 es
precision highp float;

layout(location = 0) out vec4 outColor;

uniform vec3 iResolution;
uniform float iTime;
uniform float iTimeDelta;
uniform float iFrameRate;
uniform int iFrame;
uniform float iChannelTime[4];
uniform vec3 iChannelResolution[4];
uniform vec4 iMouse;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;
uniform vec4 iDate;
uniform float iSampleRate;

const float cloudscale = 1.1;
const float speed = 0.03;
const float clouddark = 0.5;
const float cloudlight = 0.3;
const float cloudcover = 0.2;
const float cloudalpha = 8.0;
const float skytint = 0.5;
const vec3 skycolour1 = vec3(0.2, 0.4, 0.6);
const vec3 skycolour2 = vec3(0.4, 0.7, 1.0);

const mat2 m = mat2(1.6, 1.2, -1.2, 1.6);

vec2 hash(vec2 p) {
  p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
  return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

float noise(in vec2 p) {
  const float K1 = 0.366025404;
  const float K2 = 0.211324865;
  vec2 i = floor(p + (p.x + p.y) * K1);
  vec2 a = p - i + (i.x + i.y) * K2;
  vec2 o = (a.x > a.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
  vec2 b = a - o + K2;
  vec2 c = a - 1.0 + 2.0 * K2;
  vec3 h = max(0.5 - vec3(dot(a, a), dot(b, b), dot(c, c)), 0.0);
  vec3 n = h * h * h * h * vec3(dot(a, hash(i)), dot(b, hash(i + o)), dot(c, hash(i + 1.0)));
  return dot(n, vec3(70.0));
}

float fbm(vec2 n) {
  float total = 0.0;
  float amplitude = 0.1;
  for (int i = 0; i < 7; i++) {
    total += noise(n) * amplitude;
    n = m * n;
    amplitude *= 0.4;
  }
  return total;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
  vec2 p = fragCoord.xy / iResolution.xy;
  vec2 uv = p * vec2(iResolution.x / iResolution.y, 1.0);
  float time = iTime * speed;
  float q = fbm(uv * cloudscale * 0.5);

  float r = 0.0;
  uv *= cloudscale;
  uv -= q - time;
  float weight = 0.8;
  for (int i = 0; i < 8; i++) {
    r += abs(weight * noise(uv));
    uv = m * uv + time;
    weight *= 0.7;
  }

  float f = 0.0;
  uv = p * vec2(iResolution.x / iResolution.y, 1.0);
  uv *= cloudscale;
  uv -= q - time;
  weight = 0.7;
  for (int i = 0; i < 8; i++) {
    f += weight * noise(uv);
    uv = m * uv + time;
    weight *= 0.6;
  }

  f *= r + f;

  float c = 0.0;
  time = iTime * speed * 2.0;
  uv = p * vec2(iResolution.x / iResolution.y, 1.0);
  uv *= cloudscale * 2.0;
  uv -= q - time;
  weight = 0.4;
  for (int i = 0; i < 7; i++) {
    c += weight * noise(uv);
    uv = m * uv + time;
    weight *= 0.6;
  }

  float c1 = 0.0;
  time = iTime * speed * 3.0;
  uv = p * vec2(iResolution.x / iResolution.y, 1.0);
  uv *= cloudscale * 3.0;
  uv -= q - time;
  weight = 0.4;
  for (int i = 0; i < 7; i++) {
    c1 += abs(weight * noise(uv));
    uv = m * uv + time;
    weight *= 0.6;
  }

  c += c1;

  vec3 skycolour = mix(skycolour2, skycolour1, p.y);
  vec3 cloudcolour = vec3(1.1, 1.1, 0.9) * clamp(clouddark + cloudlight * c, 0.0, 1.0);
  f = cloudcover + cloudalpha * f * r;

  vec3 result = mix(skycolour, clamp(skytint * skycolour + cloudcolour, 0.0, 1.0), clamp(f + c, 0.0, 1.0));
  fragColor = vec4(result, 1.0);
}

void main() {
  vec4 color;
  mainImage(color, gl_FragCoord.xy);
  outColor = color;
}`
registry/components/clouds-shader-background.tsx
"use client"

import { useEffect, useRef } from "react"

import { cn } from "@/lib/utils"

import { cloudsFragmentShader } from "./clouds-shader-background-source"

const vertexShader = `#version 300 es
in vec2 a_position;
void main() { gl_Position = vec4(a_position, 0.0, 1.0); }`

export type CloudsShaderBackgroundProps = {
  className?: string
  animate?: boolean
}

export function CloudsShaderBackground({ className, animate = true }: CloudsShaderBackgroundProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null)

  useEffect(() => {
    const canvas = canvasRef.current
    const gl = canvas?.getContext("webgl2", { alpha: false, antialias: false })
    if (!canvas || !gl) return
    const shouldAnimate = animate && !window.matchMedia("(prefers-reduced-motion: reduce)").matches

    const compile = (type: number, source: string) => {
      const shader = gl.createShader(type)
      if (!shader) throw new Error("Unable to create shader.")
      gl.shaderSource(shader, source)
      gl.compileShader(shader)
      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        const message = gl.getShaderInfoLog(shader) ?? "Unknown shader compile error."
        gl.deleteShader(shader)
        throw new Error(message)
      }
      return shader
    }

    let program: WebGLProgram
    try {
      program = gl.createProgram()!
      gl.attachShader(program, compile(gl.VERTEX_SHADER, vertexShader))
      gl.attachShader(program, compile(gl.FRAGMENT_SHADER, cloudsFragmentShader))
      gl.linkProgram(program)
      if (!gl.getProgramParameter(program, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(program) ?? "Unable to link shader program.")
      gl.useProgram(program)
    } catch (error) {
      console.error("Clouds shader failed to compile.", error)
      return
    }

    const buffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW)
    const position = gl.getAttribLocation(program, "a_position")
    gl.enableVertexAttribArray(position)
    gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)

    const uniform = (name: string) => gl.getUniformLocation(program, name)
    const resolution = uniform("iResolution")
    const time = uniform("iTime")
    const timeDelta = uniform("iTimeDelta")
    const frameRate = uniform("iFrameRate")
    const frame = uniform("iFrame")
    const channelTime = uniform("iChannelTime")
    const channelResolution = uniform("iChannelResolution")
    const mouse = uniform("iMouse")
    const date = uniform("iDate")
    const sampleRate = uniform("iSampleRate")
    const pointer = { x: 0, y: 0, clickX: 0, clickY: 0 }
    let pointerDown = false
    let width = 1
    let height = 1
    let frameNumber = 0
    let lastTime = 0
    let animationFrame = 0

    const resize = () => {
      const ratio = Math.min(window.devicePixelRatio || 1, 2)
      width = Math.max(1, Math.floor(canvas.clientWidth * ratio))
      height = Math.max(1, Math.floor(canvas.clientHeight * ratio))
      if (canvas.width !== width || canvas.height !== height) {
        canvas.width = width
        canvas.height = height
        gl.viewport(0, 0, width, height)
      }
    }

    const updatePointer = (event: PointerEvent) => {
      const rect = canvas.getBoundingClientRect()
      pointer.x = (event.clientX - rect.left) / rect.width * width
      pointer.y = (rect.bottom - event.clientY) / rect.height * height
    }
    const onPointerDown = (event: PointerEvent) => {
      updatePointer(event)
      pointer.clickX = pointer.x
      pointer.clickY = pointer.y
      pointerDown = true
    }
    const onPointerMove = (event: PointerEvent) => updatePointer(event)
    const onPointerUp = () => { pointerDown = false }
    const setDate = (now: Date) => gl.uniform4f(date, now.getFullYear(), now.getMonth() + 1, now.getDate(), now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds())

    const render = (now: number) => {
      resize()
      const seconds = now * 0.001
      const delta = lastTime ? seconds - lastTime : 0
      lastTime = seconds
      gl.uniform3f(resolution, width, height, 1)
      gl.uniform1f(time, shouldAnimate ? seconds : 0)
      gl.uniform1f(timeDelta, delta)
      gl.uniform1f(frameRate, delta > 0 ? 1 / delta : 60)
      gl.uniform1i(frame, frameNumber)
      gl.uniform1fv(channelTime, new Float32Array([seconds, seconds, seconds, seconds]))
      gl.uniform3fv(channelResolution, new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]))
      gl.uniform4f(mouse, pointerDown ? pointer.x : 0, pointerDown ? pointer.y : 0, pointer.clickX, pointer.clickY)
      setDate(new Date())
      gl.uniform1f(sampleRate, 44100)
      gl.drawArrays(gl.TRIANGLES, 0, 3)
      frameNumber += 1
      if (shouldAnimate) animationFrame = window.requestAnimationFrame(render)
    }

    canvas.addEventListener("pointerdown", onPointerDown)
    canvas.addEventListener("pointermove", onPointerMove)
    window.addEventListener("pointerup", onPointerUp)
    animationFrame = window.requestAnimationFrame(render)
    return () => {
      window.cancelAnimationFrame(animationFrame)
      canvas.removeEventListener("pointerdown", onPointerDown)
      canvas.removeEventListener("pointermove", onPointerMove)
      window.removeEventListener("pointerup", onPointerUp)
      if (buffer) gl.deleteBuffer(buffer)
      gl.deleteProgram(program)
    }
  }, [animate])

  return <div className={cn("relative aspect-video overflow-hidden rounded-lg bg-sky-400", className)}><canvas ref={canvasRef} aria-label="Animated cloud shader background" className="absolute inset-0 h-full w-full touch-none" /></div>
}

More from awwwardedui

Browse all components in this library →