<!--
UPD: This example is a part of a series of examples of shaders - https://codepen.io/collection/pgLwJp , and some parts of the code are inherited from the previous examples and they are not necessary for this particular effect.
-->
<div class='canvas-container'></div>
<script src='https://unpkg.com/three@0.99.0/build/three.min.js'></script>
<!-- <script src='https://unpkg.com/three@0.99.0/examples/js/controls/OrbitControls.js'></script> -->
<script src='https://unpkg.com/three@0.99.0/examples/js/postprocessing/EffectComposer.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/postprocessing/RenderPass.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/postprocessing/ShaderPass.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/shaders/CopyShader.js'></script>
<!-- <script src='https://unpkg.com/three@0.99.0/examples/js/shaders/LuminosityHighPassShader.js'></script> -->
<!-- <script src='https://unpkg.com/three@0.99.0/examples/js/postprocessing/UnrealBloomPass.js'></script> -->
<script src='https://unpkg.com/animejs@2.2.0/anime.min.js'></script>
<script id='plane-vertex-shader' type='x-shader/x-vertex'>
uniform sampler2D uImage1;
uniform sampler2D uImage2;
uniform vec2 uImageSize;
uniform vec2 uWindowSize;
uniform vec2 uMousePosition;
uniform float uTime;
uniform float uAnimationTime;
varying vec3 vPosition;
varying vec2 vUv;
void main() {
vec3 delta = vec3(0.0, 0.0, 0.0);
vec3 newPosition = position + delta;
vUv = uv;
gl_Position = vec4(newPosition, 1.0);
}
</script>
<script id='plane-fragment-shader' type='x-shader/x-fragment'>
uniform sampler2D uImage1;
uniform sampler2D uImage2;
uniform vec2 uImageSize;
uniform vec2 uWindowSize;
uniform vec2 uMousePosition;
uniform float uTime;
uniform float uAnimationTime;
varying vec2 vUv;
vec2 rotate(vec2 v, float angle) {
float s = sin(angle);
float c = cos(angle);
mat2 m = mat2(c, -s, s, c);
return m * v;
}
void main() {
vec2 ratio = vec2(
min((uWindowSize.x / uWindowSize.y) / (uImageSize.x / uImageSize.y), 1.0),
min((uWindowSize.y / uWindowSize.x) / (uImageSize.y / uImageSize.x), 1.0)
);
vec2 uv = vec2(
vUv.x * ratio.x + (1.0 - ratio.x) * 0.5,
vUv.y * ratio.y + (1.0 - ratio.y) * 0.5
);
vec2 centeredUV = vec2(uv.x - 0.5, uv.y - 0.5);
float centeredUVLength = length(centeredUV);
vec2 scaledUV = centeredUV / cos(uAnimationTime * 1.2);
vec2 temp1 = rotate(scaledUV, smoothstep(0.1, 1.0, uAnimationTime));
vec2 rotatedUV1 = vec2(temp1.x + 0.5, temp1.y + 0.5);
vec2 temp2 = rotate(centeredUV, (uAnimationTime - 1.0) / 3.0);
vec2 rotatedUV2 = vec2(temp2.x + 0.5, temp2.y + 0.5);
vec2 delta = vec2(-sin(centeredUV.x), -sin(centeredUV.y)) * centeredUVLength * 2.0;
vec2 delta1 = uAnimationTime * delta;
vec2 delta2 = (1.0 - uAnimationTime) * delta;
float mask = smoothstep(
0.6,
1.0,
uAnimationTime + uAnimationTime * centeredUVLength * 2.0);
vec4 color1 = texture2D(uImage1, rotatedUV1 + delta1);
vec4 color2 = texture2D(uImage2, rotatedUV2 + delta2);
gl_FragColor = mix(color1, color2, mask);
}
</script>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
}
.canvas-container {
height: 100vh;
width: 100vw;
background: #000000;
}
View Compiled
let SCENE;
let CAMERA;
let RENDERER;
let CONTROLS;
let COMPOSER;
let TIMES = {
GLOBAL: 10, // Let it be non zero at start
ANIMATION: 0
};
let WINDOW_SIZE = new THREE.Vector2(window.innerWidth, window.innerHeight);
let MOUSE_POSITION = new THREE.Vector2(WINDOW_SIZE.x / 2.0, WINDOW_SIZE.y / 2.0);
main();
function main() {
init();
onWindowResize();
animate();
/* ----- */
let animation = anime({
targets: TIMES,
ANIMATION: 1,
duration: 3000,
delay: 1500,
easing: 'easeInOutQuad'
});
setInterval(() => {
SCENE.traverse(function(child) {
if (child instanceof THREE.Mesh && child.material.type === 'ShaderMaterial') {
const temp = child.material.uniforms.uImage1.value;
child.material.uniforms.uImage1.value = child.material.uniforms.uImage2.value;
child.material.uniforms.uImage2.value = temp;
}
});
animation.restart();
}, 4500);
/* ----- */
}
function init() {
initScene();
initCamera();
initRenderer();
initComposer();
initControls();
initEventListeners();
createObjects();
document.querySelector('.canvas-container').appendChild(RENDERER.domElement);
}
function initScene() {
SCENE = new THREE.Scene();
//initLights();
}
function initLights() {
//const point = new THREE.PointLight(0xffffff, 1, 0);
//point.position.set(0, 100, 50);
//SCENE.add(point);
}
function initCamera() {
CAMERA = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
CAMERA.position.z = 5;
}
function initRenderer() {
RENDERER = new THREE.WebGLRenderer({ alpha: true });
RENDERER.setPixelRatio(window.devicePixelRatio);
RENDERER.setSize(window.innerWidth, window.innerHeight);
RENDERER.shadowMap.enabled = true;
RENDERER.shadowMapSort = true;
RENDERER.setClearColor(0x000000, 0.3);
}
function initComposer() {
COMPOSER = new THREE.EffectComposer(RENDERER);
COMPOSER.setSize(window.innerWidth, window.innerHeight);
const renderPass = new THREE.RenderPass(SCENE, CAMERA);
COMPOSER.addPass(renderPass);
renderPass.renderToScreen = true;
}
function initControls() {
/*
CONTROLS = new THREE.OrbitControls(CAMERA);
CONTROLS.enableZoom = false;
CONTROLS.minPolarAngle = Math.PI * 1 / 4;
CONTROLS.maxPolarAngle = Math.PI * 3 / 4;
CONTROLS.update();
*/
}
function initEventListeners() {
window.addEventListener('resize', onWindowResize);
document.addEventListener('mousemove', onMouseMove);
onWindowResize();
}
function onWindowResize() {
WINDOW_SIZE.set(window.innerWidth, window.innerHeight);
CAMERA.aspect = window.innerWidth / window.innerHeight;
CAMERA.updateProjectionMatrix();
RENDERER.setSize(window.innerWidth, window.innerHeight);
COMPOSER.setSize(window.innerWidth, window.innerHeight);
}
function onMouseMove(e) {
MOUSE_POSITION.set(parseFloat(e.clientX), parseFloat(WINDOW_SIZE.y - e.clientY));
}
function animate() {
requestAnimationFrame(animate);
// CONTROLS.update();
TIMES.GLOBAL += 0.005;
updateUniforms();
render();
}
function updateUniforms() {
SCENE.traverse(function(child) {
if (child instanceof THREE.Mesh && child.material.type === 'ShaderMaterial') {
child.material.uniforms.uTime.value = TIMES.GLOBAL;
child.material.uniforms.uAnimationTime.value = TIMES.ANIMATION;
child.material.uniforms.uWindowSize.value = WINDOW_SIZE;
child.material.uniforms.uMousePosition.value = MOUSE_POSITION;
child.material.needsUpdate = true;
}
});
}
function render() {
CAMERA.lookAt(SCENE.position);
COMPOSER.render(SCENE, CAMERA);
}
function createObjects() {
const loader = new THREE.TextureLoader();
const urls = [
'https://picsum.photos/id/690/1920/1080.jpg',
'https://picsum.photos/id/692/1920/1080.jpg'
];
const promises = [];
urls.forEach((url) => {
promises.push(new Promise((resolve, reject) => {
loader.load(url, (texture) => {
texture.magFilter = THREE.NearestFilter;
texture.minFilter = THREE.NearestFilter;
resolve(texture);
});
}));
});
Promise.all(promises).then((loadedTextures) => {
const geometry = new THREE.PlaneGeometry(2, 2, 100, 100);
const shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
uImage1: {
type: 't',
value: loadedTextures[0]
},
uImage2: {
type: 't',
value: loadedTextures[1]
},
uImageSize: {
type: 'v2',
value: new THREE.Vector2(1920, 1080)
},
uWindowSize: {
type: 'v2',
value: WINDOW_SIZE
},
uMousePosition: {
type: 'v2',
value: MOUSE_POSITION
},
uTime: {
type: 'f',
value: TIMES.GLOBAL
},
uAnimationTime: {
type: 'f',
value: TIMES.ANIMATION
}
},
transparent: true,
side: THREE.DoubleSide,
vertexShader: document.getElementById('plane-vertex-shader').textContent,
fragmentShader: document.getElementById('plane-fragment-shader').textContent
});
const plane = new THREE.Mesh(geometry, shaderMaterial);
SCENE.add(plane);
});
}
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.