DEV Community

Cover image for Making a rain animation with WebGL shaders in Three.js.
NordicBeaver
NordicBeaver

Posted on

5 2 1

Making a rain animation with WebGL shaders in Three.js.

I made a small shader that resembles rain on a window. You can try it here.

Check out the full code here:




The most interesting part is the fragment shader, where the magic happens. I tried to add some comments, so it's easier to read.

in vec2 uvInterpolator;
uniform float u_time;
uniform sampler2D u_texture;

// Generate a random float from a single input and seed.
float Random11(float inputValue, float seed) {
  return fract(sin(inputValue * 345.456) * seed);
}

// Generate a random float from a 2d input and seed.
float Random21(vec2 inputValue, float seed) {
  return fract(sin(dot(inputValue, vec2(123.456, 43.12))) * seed);
}

// Generate drops as distortions, that can be applied to UV coordinates
vec2 Drops(vec2 uv, float seed) {
  // Randmply move everything
  float shiftY = Random11(0.5, seed);
  uv.y += shiftY;

  // Split UV spac into cells. Each cell will contain a drop.
  float cellsResolution = 10.0;
  uv *= cellsResolution;

  // Move each row randomly.
  float rowIndex = floor(uv.y);
  float shiftX = Random11(rowIndex, seed);
  uv.x += shiftX;

  vec2 cellIndex = floor(uv);
  vec2 cellUv = fract(uv);

  vec2 cellCenter = vec2(0.5);
  float distanceFromCenter = distance(cellUv, cellCenter);

  // We don't want to show every drop. So randomly remove some of them.
  float isDropShown = step(0.8, Random21(cellIndex, seed + 14244.324));

  // Decrease each drop intensity with time. Then make it appear again.
  float dropIntensity = 1.0 - fract(u_time * 0.1 + Random21(cellIndex, seed + 32132.432) * 2.0) * 2.0;
  dropIntensity = sign(dropIntensity) * abs(dropIntensity * dropIntensity * dropIntensity * dropIntensity);
  dropIntensity = clamp(dropIntensity, 0.0, 1.0);

  // We only need results from inside a specefec radius of a drop.
  float isInsideDrop = 1.0 - step(0.1, distanceFromCenter);

  vec2 vecToCenter = normalize(cellCenter - cellUv);

  // Drop value is a vector to the center that increases with distance form it.
  vec2 dropValue = vecToCenter * distanceFromCenter * distanceFromCenter * 40.0;

  vec2 drop = dropValue * isInsideDrop * isDropShown * dropIntensity;
  return drop;
}

void main() {
  vec2 uv = uvInterpolator;

  // Run the Drop function 10 times to create seemingly random pattern.
  vec2 drops = vec2(0.0);
  for(int i = 0; i < 10; i++) {
    drops += Drops(uv, 42424.43 + float(i) * 12313.432);
  }

  // Distort UV.
  uv += drops;

  // Sample the texture after distorting the UV space.
  vec4 color = texture2D(u_texture, uv);

  gl_FragColor = color;
}
Enter fullscreen mode Exit fullscreen mode

Basically, all I do is generate drops at random positions. For every pixel around the drop I then calculate how much I need to distort the background. And then I apply the distortion to the UV space and sample the texture with the updated coordinates.

It's way easier to explain visually, so I made a video of the whole process of making this.

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay