File: ascii-animations-guide.md | Updated: 11/15/2025
a shader-based ascii effect for three.js using post-processing. converts rendered 3d scenes into ascii art in real-time.
npm install three postprocessing
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();
{
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 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: ' āāāā'
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();
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();
});
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);
// 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
});
the effect uses a glsl shader that:
cellSizecharacters are rendered once to a canvas texture, then sampled by the shader for better performance than rendering actual text elements.
project/
āāā ascii.js # effect implementation
āāā index.html # entry point
āāā examples/
ā āāā main.js # initialization
ā āāā app.js # scene setup
ā āāā style.css # styling
āāā package.json
<!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>
cellSize creates finer detail but requires more charactersfontSize produces clearer characters in the textureinvert: true for light backgrounds