šŸ“„ guides/ascii/ascii-animations-guide

File: ascii-animations-guide.md | Updated: 11/15/2025

ascii animations guide

a shader-based ascii effect for three.js using post-processing. converts rendered 3d scenes into ascii art in real-time.

installation

npm install three postprocessing

basic setup

import { WebGLRenderer, Scene, PerspectiveCamera } from 'three';
import { EffectComposer, RenderPass, EffectPass } from 'postprocessing';
import { ASCII } from './ascii.js';

// initialize renderer
const renderer = new WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// create scene and camera
const scene = new Scene();
const camera = new PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;

// configure ascii effect
const asciiEffect = new ASCII({
  fontSize: 35,
  cellSize: 16,
  invert: false,
  color: '#ffffff',
  characters: ` .:,'-^=*+?!|0#X%WM@`
});

// setup composer
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
composer.addPass(new EffectPass(camera, asciiEffect));

// render loop
function animate() {
  requestAnimationFrame(animate);
  composer.render();
}
animate();

configuration options

{
  font: 'arial',                          // font family for characters
  fontSize: 54,                           // size of rendered characters
  cellSize: 16,                           // grid cell size in pixels
  color: '#ffffff',                       // ascii character color
  invert: false,                          // invert brightness mapping
  characters: ` .:,'-^=*+?!|0#X%WM@`     // character set (light to dark)
}

character sets

character density controls the visual effect. order from light to dark:

// minimal
characters: ' .:-=+*#%@'

// standard
characters: ` .:,'-^=*+?!|0#X%WM@`

// extended
characters: ` .':\`^",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$`

// blocks
characters: ' ā–‘ā–’ā–“ā–ˆ'

animation example

import { Mesh, BoxGeometry, MeshStandardMaterial, DirectionalLight } from 'three';

// add lighting
const light = new DirectionalLight('#fff', 6.5);
light.position.set(0, 0, 7);
scene.add(light);

// create animated object
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshStandardMaterial({ metalness: 0.6, roughness: 0.4 });
const cube = new Mesh(geometry, material);
scene.add(cube);

// animation loop
function animate() {
  requestAnimationFrame(animate);
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  composer.render();
}
animate();

loading 3d models

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

const loader = new GLTFLoader();
loader.load('model.glb', (gltf) => {
  const model = gltf.scene;
  model.position.set(0, 0, 0);
  scene.add(model);
  animate();
});

responsive handling

const resizeObserver = new ResizeObserver((entries) => {
  const { width, height } = entries[0].contentRect;
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  composer.setSize(width, height);
});
resizeObserver.observe(document.body);

performance optimization

// detect touch devices
const isTouchDevice = matchMedia('(pointer: coarse)').matches;
const pixelRatio = window.devicePixelRatio;

const renderer = new WebGLRenderer({
  powerPreference: 'high-performance',
  antialias: !isTouchDevice && pixelRatio <= 2,
  stencil: false
});

how it works

the effect uses a glsl shader that:

  1. pixelates the input image based on cellSize
  2. converts each pixel to greyscale
  3. maps brightness to character index
  4. samples from a pre-rendered character texture
  5. applies the specified color

characters are rendered once to a canvas texture, then sampled by the shader for better performance than rendering actual text elements.

technical details

  • uses nearest-neighbor filtering for sharp character rendering
  • character texture is 1024x1024 with 16x16 grid (256 chars max)
  • brightness calculation uses standard luminance weights (0.299, 0.587, 0.114)
  • supports transparent backgrounds via alpha channel

example project structure

project/
ā”œā”€ā”€ ascii.js              # effect implementation
ā”œā”€ā”€ index.html            # entry point
ā”œā”€ā”€ examples/
│   ā”œā”€ā”€ main.js          # initialization
│   ā”œā”€ā”€ app.js           # scene setup
│   └── style.css        # styling
└── package.json

minimal html template

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ascii effect</title>
  <style>
    body { margin: 0; background: #242424; }
    canvas { display: block; }
  </style>
</head>
<body>
  <script type="module" src="main.js"></script>
</body>
</html>

tips

  • smaller cellSize creates finer detail but requires more characters
  • larger fontSize produces clearer characters in the texture
  • use invert: true for light backgrounds
  • character order matters - arrange from light to dark density
  • performance is better than dom-based ascii renderers
  • works with any three.js scene (meshes, particles, shaders)