I created a simple effect for images using three.js and shaders. Here is a link
Now I have a question how would I use it on a website, for example with this layout where blue blocks are the images which should have this effect.
I googled around and can’t really find anything about that.
Because this whole scene is a canvas and I would need multiple images I would imagine that the canvas should stay as it is right now but how do you position images at the right spots?
The code from the link above:
<script id="vertex" type="x-shader/x-vertex">
varying float wave;
varying vec2 vUv;
uniform float mouseIntersects;
void main() {
vUv = uv;
vec3 pos = position;
pos.z += -mouseIntersects / 20.0;
wave = pos.z;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0 );
}
</script>
<script id="fragment" type="x-shader/x-vertex">
uniform vec2 u_resolution;
uniform float u_time;
varying vec2 vUv;
varying float wave;
uniform sampler2D uTexture;
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
float r = texture2D(uTexture, vUv + wave).r;
float g = texture2D(uTexture, vUv).g;
float b = texture2D(uTexture, vUv).b;
vec4 image = vec4(r, g, b, 1.0);
gl_FragColor = image;
}
</script>
<div id="container"></div>
</body>
and three.js part:
import { OrbitControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r110/examples/jsm/controls/OrbitControls.js";
let container, camera, scene, renderer, controls, material, uniforms, geometry, mesh, raycaster;
const setup = () => {
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerWidth);
renderer.setClearColor(0xffffff);
container = document.getElementById('container');
container.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.001, 100);
camera.position.set(0, 0, 0.5);
controls = new OrbitControls(camera, renderer.domElement);
uniforms = {
u_time: { type: 'f', value: 0 },
u_mouse: { type: 'v2', value: new THREE.Vector2() },
u_resolution: { type: 'v2', value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
uTexture: { type: 't', value: new THREE.TextureLoader().load('https://images.unsplash.com/photo-1512505965932-0dbfa7e7b8c8?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=701&q=80') },
mouseIntersects: { type: 'f', value: 0 },
};
material = new THREE.ShaderMaterial({
side: THREE.DoubleSide,
uniforms: uniforms,
// wireframe: true,
vertexShader: document.querySelector("#vertex").textContent,
fragmentShader: document.querySelector("#fragment").textContent
});
geometry = new THREE.PlaneBufferGeometry(0.4, 0.6, 16, 16);
mesh = new THREE.Mesh(geometry, material);
raycaster = new THREE.Raycaster();
scene.add(mesh);
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
document.addEventListener('mousemove', (e) => updateMousePositions(e), false);
};
const updateMousePositions = (e) => {
uniforms.u_mouse.value.x = (event.clientX / window.innerWidth) * 2 - 1;
uniforms.u_mouse.value.y = -(event.clientY / window.innerHeight) * 2 + 1;
};
const onWindowResize = (event) => {
renderer.setSize(window.innerWidth, window.innerHeight);
uniforms.u_resolution.value.x = renderer.domElement.width;
uniforms.u_resolution.value.y = renderer.domElement.height;
};
const animate = () => {
requestAnimationFrame(animate);
render();
};
let targetValue = 0;
const render = () => {
uniforms.u_time.value += 0.05;
raycaster.setFromCamera(uniforms.u_mouse.value, camera);
const intersects = raycaster.intersectObject(mesh);
if (intersects.length === 1) {
targetValue = 1;
} else {
targetValue = 0;
}
uniforms.mouseIntersects.value = THREE.MathUtils.lerp(
uniforms.mouseIntersects.value,
targetValue,
0.1,
);
renderer.render(scene, camera);
};
setup();
animate();