<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 id='sphere-vertex-shader' type='x-shader/x-vertex'>
    uniform float uTime;

    varying vec2 vUv;

    void main() {
        vUv = uv;

        vec3 delta = normal * sin(position.x * position.y * uTime / 10.0);
        vec3 newPosition = position + delta;

        gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
    }
</script>


<script id='sphere-fragment-shader' type='x-shader/x-fragment'>
    uniform float uTime;

    varying vec2 vUv;

    float rand(vec2 seed);
    float noise(vec2 position);

    void main() {
        vec2 position1 = vec2(vUv.x * 4.0, vUv.y - uTime);
        vec2 position2 = vec2(vUv.x * 4.0, vUv.y - uTime * 2.0);
        vec2 position3 = vec2(vUv.x * 4.0, vUv.y - uTime * 3.0);

        float color = (
            noise(position1 * 5.0)
            + noise(position2 * 10.0)
            + noise(position3 * 15.0)) / 3.0;

        gl_FragColor = vec4(0.0, 0.0, 0.0, color - smoothstep(0.1, 1.3, vUv.y));
    }

    float rand(vec2 seed) {
        return fract(sin(dot(seed, vec2(12.9898,78.233))) * 43758.5453123);
    }

    float noise(vec2 position) {
        vec2 blockPosition = floor(position);

        float topLeftValue     = rand(blockPosition);
        float topRightValue    = rand(blockPosition + vec2(1.0, 0.0));
        float bottomLeftValue  = rand(blockPosition + vec2(0.0, 1.0));
        float bottomRightValue = rand(blockPosition + vec2(1.0, 1.0));

        vec2 computedValue = smoothstep(0.0, 1.0, fract(position));

        return mix(topLeftValue, topRightValue, computedValue.x)
            + (bottomLeftValue  - topLeftValue)  * computedValue.y * (1.0 - computedValue.x)
            + (bottomRightValue - topRightValue) * computedValue.x * computedValue.y;
    }
</script>
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    overflow: hidden;
}

.canvas-container {
    height: 100%;
    width: 100%;
}
let SCENE;
let CAMERA;
let RENDERER;
let CONTROLS;
let COMPOSER;

let TIME = 0;


main();


function main() {
    init();
    animate();
}


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.y = 100;
    CAMERA.position.z = 100;
}


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(0x0e050f, 1);
}


function initComposer() {
    COMPOSER = new THREE.EffectComposer(RENDERER);
    COMPOSER.setSize(window.innerWidth, window.innerHeight);

    const renderPass = new THREE.RenderPass(SCENE, CAMERA);
    COMPOSER.addPass(renderPass);

    const bloomPass = new THREE.UnrealBloomPass(
        new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 1, 0.1);
    bloomPass.renderToScreen = true;
    COMPOSER.addPass(bloomPass);
}


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);

    onWindowResize();
}


function onWindowResize() {
    CAMERA.aspect = window.innerWidth / window.innerHeight;
    CAMERA.updateProjectionMatrix();

    RENDERER.setSize(window.innerWidth, window.innerHeight);
    COMPOSER.setSize(window.innerWidth, window.innerHeight);
}


function animate() {
    requestAnimationFrame(animate);
    CONTROLS.update();
    TIME += 0.005;
    updateUniforms();
    render();
}


function updateUniforms() {
    SCENE.traverse(function(child) {
        if (child instanceof THREE.Mesh
            && child.material.type === 'ShaderMaterial') {
            child.material.uniforms.uTime.value = TIME;
            child.material.needsUpdate = true;
        }
    });
}


function render() {
    CAMERA.lookAt(SCENE.position);
    COMPOSER.render(SCENE, CAMERA);
}


function createObjects() {
    const geometry1 = new THREE.SphereBufferGeometry(25, 64, 64);
    const geometry2 = new THREE.SphereBufferGeometry(30, 64, 64);

    const phongMaterial = new THREE.MeshPhongMaterial({
        color: 0x782b7f,
        emissive: 0x180819,
        transparent: true,
        opacity: .5
    });

    const shaderMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTime: { value: TIME }
        },
        transparent: true,
        side: THREE.DoubleSide,
        vertexShader:   document.getElementById('sphere-vertex-shader').textContent,
        fragmentShader: document.getElementById('sphere-fragment-shader').textContent
    });

    const sphere1 = new THREE.Mesh(geometry1, phongMaterial);
    const sphere2 = new THREE.Mesh(geometry2, shaderMaterial);

    SCENE.add(sphere1);
    SCENE.add(sphere2);
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.