Skip to content

pmndrs postprocessing

Terminal window
pnpm add crt-shader three@^0.186.0 postprocessing@^6.39.5

Live example · Click to inspect

Save as crt.js in your browser app.

crt.js
import * as THREE from 'three';
import {
EffectComposer, EffectPass, RenderPass, ToneMappingEffect, ToneMappingMode,
} from 'postprocessing';
import { CRTPass } from 'crt-shader/postprocessing';
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, {
frameBufferType: THREE.HalfFloatType, multisampling: 0,
});
composer.addPass(new RenderPass(scene, camera));
composer.addPass(new EffectPass(camera,
new ToneMappingEffect({ mode: ToneMappingMode.LINEAR }),
)); // Neutral tone mapping; CRT receives linear RGB, no OutputPass.
composer.addPass(new CRTPass({ preset: 'reference', inputResolution: 160 }));
composer.render(); // For animation, call this in your render loop.
return {
canvas: renderer.domElement,
dispose() {
composer.dispose(); // pmndrs also disposes its passes.
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

The pmndrs composer needs crt-shader/postprocessing, not crt-shader/three. Keep renderer tone mapping disabled, add tone mapping in the composer, then CRT last. ToneMappingMode.LINEAR is neutral; choose another mode for your scene before CRT. Do not add Three’s OutputPass.

CRT receives tone-mapped linear RGB and encodes screen output once. Offscreen output remains tone-mapped linear RGB, not scene-referred HDR.

For animation, call composer.render(delta) in your loop. Resize the renderer and composer together, updating the camera projection. composer.dispose() disposes its passes; your renderer and scene resources remain yours.

inputResolution: 'auto' reduces the input. A positive integer sets its longest edge, capped at the incoming buffer size. Use mode: 'pixelated' to inspect that input or mode: 'original' to bypass the effect. Keep the terminal pass enabled.

Scene API · Options and presets