Three.js Emoji

by Matt

HTML

<base href="https://rawcdn.githack.com/mrdoob/three.js/r163/examples/" />
<script async src="https://cdn.jsdelivr.net/npm/[email protected]/dist/es-module-shims.js"></script>
<script type="importmap">
	{
		"imports": {
			"three": "https://cdn.jsdelivr.net/npm/three@^0.163.0/build/three.module.js",
			"three/addons/": "https://cdn.jsdelivr.net/npm/three@^0.163.0/examples/jsm/",
			"lil-gui": "https://cdn.jsdelivr.net/npm/[email protected]/dist/lil-gui.esm.min.js"
		}
	}
</script>

CSS

canvas {
	position: fixed;
	inset: 0;
}

JavaScript

import * as THREE from 'three';

import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'lil-gui';

let camera, scene, renderer, stats;

let mesh;
const amount = parseInt( window.location.search.slice( 1 ) ) || 10;
const count = Math.pow( amount, 3 );
const dummy = new THREE.Object3D();

init();
animate();

function characterToDataURL(text, size = 64) {
	const canvas = document.createElement("canvas")
	canvas.width = size
	canvas.height = size

	const ctx = canvas.getContext("2d")
	ctx.font = `${size}px sans-serif`

	const {
		actualBoundingBoxRight,
		actualBoundingBoxLeft,
		actualBoundingBoxDescent,
		actualBoundingBoxAscent,
	} = ctx.measureText(text)

	const centerX = 0.5 * (actualBoundingBoxRight - actualBoundingBoxLeft)
	const centerY = 0.5 * (actualBoundingBoxDescent - actualBoundingBoxAscent)

	ctx.fillText(text, size / 2 - centerX, size / 2 - centerY)

	return canvas.toDataURL()
}


function init() {

  camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
  camera.position.set( amount * 0.9, amount * 0.9, amount * 0.9 );
  camera.lookAt( 0, 0, 0 );

  scene = new THREE.Scene();
  
  const loader = new THREE.TextureLoader();
  const texture = loader.load(characterToDataURL("💩"))
  texture.colorSpace = THREE.SRGBColorSpace

	const geometry = new THREE.PlaneGeometry()
  const material = new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide, alphaTest: 0.5 });

  mesh = new THREE.InstancedMesh( geometry, material, count );
  mesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage ); // will be updated every frame
  scene.add( mesh );

  //

  const gui = new GUI();
  gui.add( mesh, 'count', 0, count );

  //

  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

  //

  stats = new Stats();
 ...