pmndrs postprocessing
pnpm add crt-shader three@^0.186.0 postprocessing@^6.39.5Live example · Click to inspect
Save as crt.js in your browser app.
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:
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.
Do not swap the two CRTPass imports
Section titled “Do not swap the two CRTPass imports”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.
Choose preparation separately
Section titled “Choose preparation separately”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.