canvas{
  width: 100%;
  height: 100%;
}
View Compiled


if (!Detector.webgl) Detector.addGetWebGLMessage();

			let scene       = null;
			let normal      = null;
			let outline     = null;
			let outScene    = null;
			let maskScene   = null;
			let light       = null;
			let renderer    = null;
			let composer    = null;
			let camera1     = null;
			let camera2     = null;
			let camera3     = null;
			let mesh1       = null;
			let mesh2       = null;
			let mesh3       = null;
			let bg_mesh = null;
			let renderTarget = null;

			let screenWidth  = window.innerWidth;
			let screenHeight = window.innerHeight;

			const clock = new THREE.Clock;

			let elapsedTime = 0;
			let frameCount  = 0;

			const init = function() {

			    // SCENE

			    scene     = new THREE.Scene;
			    maskScene = new THREE.Scene;
			    outScene  = new THREE.Scene;
			    setModel();

			    // SCENE CAMERA

			    camera1 = new THREE.PerspectiveCamera(40, screenWidth/screenHeight, 1, 1000);
			    camera1.position.set(0, 0, 10);
			    scene.add(camera1);

			    camera2 = new THREE.PerspectiveCamera(40, screenWidth/screenHeight, 1, 1000);
			    camera2.position.set(0, 0, 10);
			    outScene.add(camera2);

			    camera3 = new THREE.PerspectiveCamera(40, screenWidth/screenHeight, 1, 1000);
			    camera3.position.set(0, 0, 10);
			    maskScene.add(camera3);

					scene.background = new THREE.Color( 0xffffff );

			    // RENDERER

			    renderer = new THREE.WebGLRenderer({
			        width: screenWidth,
			        height: screenHeight,
			        antialias: true
			    });

			    renderer.setSize(screenWidth, screenHeight);
			    renderer.setClearColor(0x000000);
			    renderer.autoClear = false;
			    renderer.gammaInput = true;
			    renderer.gammaOutput = true;

			    document.body.appendChild(renderer.domElement);

			    // POSTPROCESSING

			    const renderTargetParameters = {
			        minFilter:      THREE.LinearFilter,
			        magFilter:      THREE.LinearFilter,
			        format:         THREE.RGBAFormat,
			        stencilBuffer:  true
			    };

			    renderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, renderTargetParameters);

			    composer    = new THREE.EffectComposer(renderer);
			    composer.renderTarget1.stencilBuffer = true;
			    composer.renderTarget2.stencilBuffer = true;

			    normal      = new THREE.RenderPass(scene, camera1);
			    outline     = new THREE.RenderPass(outScene, camera2);
			    outline.clear = false;

			    const mask        = new THREE.MaskPass(maskScene, camera3);
			    mask.inverse = false;
			    const clearMask   = new THREE.ClearMaskPass;
			    const copyPass    = new THREE.ShaderPass(THREE.CopyShader);
			    copyPass.renderToScreen = true;

			    composer.addPass(normal);
			   	composer.addPass(mask);
			    composer.addPass(outline);
			    composer.addPass(clearMask);
			    composer.addPass(copyPass);

			    // EVENTS

			    return window.addEventListener('resize', onWindowResize, false);
			};

			var setModel = function() {

					var box_mask_1 = new THREE.CircleGeometry( 2, 4 );
					var box_mask_2 = new THREE.CircleGeometry( 2, 4 );

					var singleGeometry_mask = new THREE.Geometry();

					var boxMesh_mask_1 = new THREE.Mesh(box_mask_1);
					boxMesh_mask_1.position.z = 1;
					var boxMesh_mask_2 = new THREE.Mesh(box_mask_2);
					boxMesh_mask_2.rotation.y = Math.PI;
					boxMesh_mask_2.position.z = -1;

					boxMesh_mask_1.updateMatrix(); // as needed
					singleGeometry_mask.merge(boxMesh_mask_1.geometry, boxMesh_mask_1.matrix);
					boxMesh_mask_2.updateMatrix(); // as needed
					singleGeometry_mask.merge(boxMesh_mask_2.geometry, boxMesh_mask_2.matrix);


					var box_1_1 = new THREE.CircleGeometry( 2, 4 );
					var box_1_2 = new THREE.CircleGeometry( 2, 4 );

					var singleGeometry_1 = new THREE.Geometry();

					var boxMesh_1_1 = new THREE.Mesh(box_1_1);
					boxMesh_1_1.position.z = -1;
					var boxMesh_1_2 = new THREE.Mesh(box_1_2);
					boxMesh_1_2.rotation.y = Math.PI;
					boxMesh_1_2.position.z = 1;

					boxMesh_1_1.updateMatrix();
					singleGeometry_1.merge(boxMesh_1_1.geometry, boxMesh_1_1.matrix);
					boxMesh_1_2.updateMatrix();
					singleGeometry_1.merge(boxMesh_1_2.geometry, boxMesh_1_2.matrix);


					var box_2_1 = new THREE.CircleGeometry( 2, 4 );
					var box_2_2 = new THREE.CircleGeometry( 2, 4 );

					var singleGeometry_2 = new THREE.Geometry();

					var boxMesh_2_1 = new THREE.Mesh(box_2_1);
					boxMesh_2_1.position.z = -1;
					var boxMesh_2_2 = new THREE.Mesh(box_2_2);
					boxMesh_2_2.rotation.y = Math.PI;
					boxMesh_2_2.position.z = 1;

					boxMesh_2_1.updateMatrix();
					singleGeometry_2.merge(boxMesh_2_1.geometry, boxMesh_2_1.matrix);

					boxMesh_2_2.updateMatrix();
					singleGeometry_2.merge(boxMesh_2_2.geometry, boxMesh_2_2.matrix);

					const matColor = new THREE.MeshBasicMaterial({
			        color: 0x000000});
			    mesh1 = new THREE.Mesh(singleGeometry_1, matColor); // geometry, matColor);
			    scene.add(mesh1);

			    // flat mask
			    const matFlat = new THREE.MeshBasicMaterial({
			        color: 0x000000});
			    mesh2 = new THREE.Mesh(singleGeometry_mask, matFlat);
			    maskScene.add(mesh2);

					const matColor2 = new THREE.MeshBasicMaterial({
			        color: 0xffffff});
			    mesh3 = new THREE.Mesh(singleGeometry_2, matColor2);
			    outScene.add(mesh3);

					const matColor3 = new THREE.MeshBasicMaterial({
			        color: 0x000000});

					bg_mesh = new THREE.Mesh(new THREE.CircleGeometry( 100, 4 ), matColor3);
					outScene.add(bg_mesh);
					bg_mesh.position.set(0, 0, -10);

			};

			var onWindowResize = function() {

			    screenWidth  = window.innerWidth;
			    screenHeight = window.innerHeight;

			    camera1.aspect = screenWidth / screenHeight;
			    camera2.aspect = camera1.aspect;
			    camera3.aspect = camera1.aspect;

			    camera1.updateProjectionMatrix();
			    camera2.updateProjectionMatrix();
			    camera3.updateProjectionMatrix();

			    return renderer.setSize(screenWidth, screenHeight);
			};

			var animate = function() {

			    updateFps();
			    requestAnimationFrame(animate);
			    return render();
			};

			var render = function() {

			    const now = Date.now();
			    const delta = clock.getDelta();

			    if (mesh1) {
			        mesh1.rotation.y += 0.015;
			        mesh2.rotation.y = mesh1.rotation.y;
			        mesh3.rotation.y = mesh1.rotation.y;
			    }

			    return composer.render();
			};

			var updateFps = function() {

			    elapsedTime += clock.getDelta();
			    frameCount++;

			    if (elapsedTime >= 1) {
			        $('#fps').html(frameCount);
			        frameCount  = 0;
			        return elapsedTime = 0;
			    }
			};


			init();
			animate();
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js
  2. https://threejs.org/examples/js/shaders/CopyShader.js
  3. https://threejs.org/examples/js/postprocessing/EffectComposer.js
  4. https://threejs.org/examples/js/postprocessing/ClearPass.js
  5. https://threejs.org/examples/js/postprocessing/RenderPass.js
  6. https://threejs.org/examples/js/postprocessing/MaskPass.js
  7. https://threejs.org/examples/js/postprocessing/ShaderPass.js
  8. https://threejs.org/examples/js/Detector.js
  9. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js