Skip to content

Native Three.js

Terminal window
pnpm add crt-shader three@^0.186.0

Live example · Click to inspect

Save as crt.js in your browser app.

crt.js
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import { CRTPass } from 'crt-shader/three';
export function mount(host) {
const renderer = new THREE.WebGLRenderer({ antialias: false, preserveDrawingBuffer: true });
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.NoToneMapping;
renderer.setPixelRatio(1);
renderer.setSize(640, 400);
renderer.domElement.style.cssText = 'display:block;width:100%;height:auto';
renderer.domElement.setAttribute('aria-label', 'CRT torus knot');
host.append(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color('black');
const camera = new THREE.PerspectiveCamera(40, 640 / 400, 0.1, 100);
camera.position.z = 5;
const geometry = new THREE.TorusKnotGeometry(0.8, 0.25, 128, 16);
const material = new THREE.MeshNormalMaterial();
scene.add(new THREE.Mesh(geometry, material));
const composer = new EffectComposer(renderer);
const render = new RenderPass(scene, camera);
const output = new OutputPass();
const crt = new CRTPass({ preset: 'reference', inputResolution: 160 });
composer.addPass(render);
composer.addPass(output); // Tone map and encode before native CRT.
composer.addPass(crt); // Last: do not encode again.
composer.render(); // For animation, call this in your render loop.
return {
canvas: renderer.domElement,
dispose() {
crt.dispose();
output.dispose();
render.dispose();
composer.dispose();
geometry.dispose();
material.dispose();
renderer.dispose();
renderer.domElement.remove();
},
};
}

Then mount it from your entry point:

main.js
import { mount } from './crt.js';
const demo = mount(document.body);

Call demo.dispose() when removing the scene. Reuse your existing renderer if you have one.

API reference · Presets and options

Native Three uses RenderPass, then OutputPass, then CRTPass from crt-shader/three. CRT expects display-encoded input and must stay last. Reuse your scene’s renderer; the pass does not create a second WebGL context.

For animation, call composer.render(delta) in your render loop. On resize, update the camera aspect and projection matrix, then resize both renderer and composer.

inputResolution: 'auto' chooses a reduced input size. To retain native resolution, use a number at least as large as the incoming buffer’s longest edge and update it after resize or DPR changes. mode: 'pixelated' shows prepared input; mode: 'original' bypasses the effect without disabling the terminal pass.

Scene API · Options and presets