Interactive WebGL painting demo by mrdoob with brush strokes rendered using shaders and particle-like effects on CodePen.

import * as THREE from 'three/webgpu';import { pass, mrt, output, normalView, metalness, roughness, diffuseColor, velocity, vec2, vec4, add, directionToColor, colorToDirection, sample } from 'three/tsl';import { ssgi } from 'three/addons/tsl/display/SSGINode.js';import { ssr } from 'three/addons/tsl/display/SSRNode.js';import { traa } from 'three/addons/tsl/display/TRAANode.js';import { TubePainter } from 'three/addons/misc/TubePainter.js';import { OrbitControls } from 'three/addons/controls/OrbitControls.js';import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';let camera, scene, renderer, postProcessing, controls, painter;let isDrawing = false, drawMode = true;let lastPosition = new THREE.Vector3();let lastTime = 0;let currentSize = 1;const pointer = new THREE.Vector2();const raycaster = new THREE.Raycaster();const wallPlane = new THREE.Plane( new THREE.Vector3( 0, 0, 1 ), - 0.25 );const intersectionPoint = new THREE.Vector3();const easedPosition = new THREE.Vector3();const currentColor = new THREE.Color();function getPointerCoords( clientX, clientY ) { pointer.x = ( clientX / window.innerWidth ) * 2 - 1; pointer.y = - ( clientY / window.innerHeight ) * 2 + 1;}init();async function init() { camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.1, 100 ); camera.position.set( 0, 0, 5 ); camera.lookAt( 0, 0, 0 ); scene = new THREE.Scene(); scene.background = new THREE.Color( 0x222222 ); renderer = new THREE.WebGPURenderer( { requiredLimits: { maxColorAttachmentBytesPerSample: 40 } } ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setAnimationLoop( animate ); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.5; renderer.domElement.style.cursor = 'crosshair'; document.body.appendChild( renderer.domElement ); await renderer.init(); const environment = new RoomEnvironment(); con