JSFiddle - React, Tailwind, and code Playground
HTML
<canvas id="webgl"></canvas>
<script type="importmap">
{
"imports":
{
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script>
var brushFile = "https://i.imgur.com/ycQeaAb.png";
var bgImage = "https://images.unsplash.com/photo-1437482078695-73f5ca6c96e2?q=80&w=1470&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
</script>
JavaScript
import * as THREE from 'three';
// Canvas and sizes
const canvas = document.getElementById('webgl');
const sizes = { width: window.innerWidth, height: window.innerHeight };
// Track mouse position and movement
const mousePos = { x: 0, y: 0 };
const prevMousePos = { x: 0, y: 0 };
let currentWave = 0;
window.addEventListener('mousemove', (e) => {
mousePos.x = e.clientX - sizes.width / 2;
mousePos.y = sizes.height / 2 - e.clientY;
});
// Three.js scene setup
const scene = new THREE.Scene();
const scene2 = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// Orthographic camera
const aspect = sizes.width / sizes.height;
const frustumSize = sizes.height;
const camera = new THREE.OrthographicCamera(
(frustumSize * aspect) / -2, (frustumSize * aspect) / 2,
frustumSize / 2, frustumSize / -2, -1000, 1000
);
camera.position.set(0, 0, 5);
camera.lookAt(0, 0, 0);
// Load brush texture
const brushTexture = new THREE.TextureLoader().load(brushFile);
// Shader setup
const vertexShader = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
uniform sampler2D uImage;
uniform sampler2D uDisplacement;
varying vec2 vUv;
void main() {
vec2 displacement = texture2D(uDisplacement, vUv).rg * 0.1;
vec2 uv = vUv + displacement;
gl_FragColor = texture2D(uImage, uv);
}
`;
// Wave planes setup
const maxCount = 200;
const meshes = [];
const planeGeometry = new THREE.PlaneGeometry(100, 100, 1, 1);
for (let i = 0; i < maxCount; i++) {
const material = new THREE.MeshBasicMaterial({
map: brushTexture, transparent: true, blending: THREE.AdditiveBlending,
depthTest: false, depthWrite: false
});
const plane =...