Hotdry.
systems-engineering

浏览器中复现70年代矢量工作站CRT荧光粉余辉衰减与扫描时序

通过Canvas和Shader复现1970s矢量图形工作站CRT的磷光余辉衰减与束扫描时序,提供交互式浏览器模拟器参数与实现要点。

在 1970 年代,矢量图形工作站如 Evans & Sutherland 的 Line Drawing System(LDS)或 DEC GT40 等设备,使用阴极射线管(CRT)直接驱动电子束绘制线条,形成随机扫描(random-scan 或 calligraphic)显示。这种不同于现代光栅扫描(raster-scan)的机制,避免了全屏刷新,而是仅在需要的位置 “画线”,依赖荧光粉(phosphor)的余辉特性维持图像可见性。磷光余辉衰减(phosphor persistence decay)是关键:电子束激发荧光粉后,光强呈指数衰减,直至 1/10 峰值的时间常为短余辉(short persistence,10-100μs)以减少拖影(smear),或中余辉(medium persistence,1-10ms)平衡亮度和清晰度。扫描时序(scan-out timing)则涉及束速度(beam velocity,通常 10-100km/s)和驻留时间(dwell time,每点停留 μs 级),决定了线条亮度均匀性。

复现这些特性在浏览器中,能直观理解历史硬件极限,并为现代图形模拟(如 retro gaming 滤镜)提供基础。核心观点:使用 HTML Canvas 的 2D 上下文或 WebGL fragment shader 模拟持久缓冲(persistence buffer),每帧衰减前帧像素值,叠加当前束轨迹,实现真实余辉与时序效果。证据显示,这种方法已在 Blur Busters 的 TestUFO 和 Shadertoy 项目中验证,例如其 CRT 电子束模拟 shader 精确重现了 60Hz 刷新下的运动模糊与磷光衰减,与 70s 矢量 CRT 的非均匀刷新类似。

实现时,先构建持久缓冲:创建一个 offscreen Canvas(或 WebGL texture),尺寸匹配显示(如 512x512 模拟圆形 CRT 面)。每 requestAnimationFrame(RAF,~60Hz)循环:

  1. 清空当前绘制 Canvas。
  2. 从持久缓冲读取前帧,应用衰减因子 α(0.90-0.98,根据余辉 τ=1/ln (1/α) per frame 调整;τ=5ms 时,60Hz 下 α≈exp (-16.67ms/5ms)≈0.70)。
  3. 叠加束轨迹:用鼠标 / 触摸输入矢量路径,模拟束从起点高速扫到终点(速度 v = 像素 / 帧数,e.g., 跨屏 1000px/16ms≈62px/frame)。
  4. 束宽度(spot size)1-3px,高斯模糊模拟聚焦;亮度随速度反比(慢速驻留亮,高速度暗)。
  5. 写回持久缓冲,实现指数衰减累积。

关键参数清单(可落地):

  • 余辉时间 τ:短余辉 10μs(α=0.999 per frame,高清但易闪烁);中余辉 1ms(α=0.94,平衡);长余辉 100ms(α=0.72,拖影明显)。公式:α = exp (-Δt/τ),Δt=16.67ms (60Hz)。
  • 束速度:模拟 70s 硬件 15-50km/s,浏览器中映射为像素 / 帧(屏幕 2000px 对角,v=50km/s≈5px/μs→300px/frame 防过曝)。
  • 扫描时序:非实时绘制,用 Path2D 记录矢量命令(moveTo/lineTo),分帧插值渲染(lerp 位置),驻留时间 dt = 长度 /v,亮度 = 1/dt。
  • 几何畸变:圆形 CRT 加 pin-cushion 校正(x' = x*(1 + k*x^2)),扫描线弯曲模拟磁偏转。
  • 交互控制:键盘绑定 “draw mode”(鼠标拖拽画线),速度滑块(0.1-10x),余辉预设(P1 绿短余辉、P31 绿中余辉)。
  • 性能优化:WebGL 用 float texture 存持久值,fragment shader 并行衰减 / 叠加;阈值裁剪(<0.01 设黑)防噪声;VRR 模拟(动态 FPS)。

代码骨架(Vanilla JS + Canvas):

const canvas = document.getElementById('crt');
const ctx = canvas.getContext('2d');
const persist = document.createElement('canvas'); persist.width = persist.height = canvas.width = 512;
const pctx = persist.getContext('2d');
let alpha = 0.95; // 衰减因子
let path = []; // 当前矢量路径
let t = 0; // 插值时间

function loop() {
  ctx.clearRect(0,0,512,512);
  // 衰减持久缓冲
  const img = pctx.getImageData(0,0,512,512);
  for(let i=0; i<img.data.length; i+=4) {
    img.data[i] *= alpha; img.data[i+1] *= alpha; img.data[i+2] *= alpha; // RGB衰减,A=255
  }
  pctx.putImageData(img,0,0);
  // 绘制到主屏
  ctx.drawImage(persist,0,0);
  // 渲染束轨迹(简化)
  if(path.length) {
    const p1 = path[t], p2 = path[t+1] || p1;
    ctx.beginPath(); ctx.moveTo(p1.x,p1.y); ctx.lineTo(p2.x,p2.y); ctx.strokeStyle='lime'; ctx.lineWidth=2;
    ctx.stroke();
    t += 0.1; if(t >=1) { t=0; path.shift(); } // 推进路径
  }
  requestAnimationFrame(loop);
}
canvas.addEventListener('mousemove', e => { /* 记录path */ });

扩展到 WebGL:shader 中gl_FragColor = mix(texture(prev,uv)*decay, beam(uv), glow);,支持高斯辉光卷积。

监控要点:FPS 稳定 > 58(用 Performance API);拖影测试(快速画线,测尾迹长度≈v*τ);颜色溢出(clamp 0-1);移动端适配(touch + 低分辨率)。

回滚策略:若 decay 不准,从历史数据校准(如 P4 磷光 τ=0.1ms);兼容 fallback 到 CSS 滤镜粗仿。

这种模拟不仅教育性强,还可用于 Web retro emulator 验证(如模拟 Asteroids 矢量游戏的磷光尾迹)。实际部署见 Shadertoy “CRT Persistence Simulator” 变体,或自建 CodePen。

资料来源:Hacker News 讨论(id=41992815);Blur Busters TestUFO CRT shader;计算机图形学教材中 vector CRT 章节(如 Foley “Computer Graphics” 对 phosphor decay 描述)。

(正文约 1250 字)

查看归档