three.js DotScreenPass 实战:3 个参数玩转复古网点滤镜
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
往渲染循环里加一个 Pass,三维场景就被啃成一片密集的圆点阵列——three.js 的DotScreenPass就是这么干的后处理滤镜,一行代码把屏幕变成复古半色调(halftone)质感。
先看效果:点阵从哪来
截图右下角那块就是DotScreenPass的输出:同一组人物模型里,其他三格还是常规光影,它却把色彩全部替换成圆点阵列,明暗随亮度变化,越看越像车站出发板上从文字切换到点阵显示的灯箱——这就是该滤镜卖点的"复古感"。
对照这张高饱和物体:它是仓库里另一个网点通道HalftonePass的输出,网点按 RGB 三通道分色。DotScreenPass则输出灰度单网点,两者是仓库半色调效果的两种形态。
最小可跑路径
import 路径决定它走不走 addons 入口:这个模块不在核心包里,必须用three/addons/别名引入,统一导出在 Addons.js#L187。
import * as THREE from 'three'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { DotScreenPass } from 'three/addons/postprocessing/DotScreenPass.js'; const composer = new EffectComposer( renderer ); composer.addPass( new RenderPass( scene, camera ) ); // 三个参数全部可选,漏传的部分会自动落在 Shader 默认值上 composer.addPass( new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ) );跑起来后,composer.render()循环里整屏色块会被旋转的圆点阵列取代。最后一行addPass就是这个滤镜的全部配置,参数语义下一节拆开讲。
3 个参数 3 个问
构造函数三参皆可选:源码只在参数不为undefined时才写入对应 uniform(DotScreenPass.js#L28-L47),漏传就沿用DotScreenShader声明的默认值。
- center : Vector2— 点阵中心,默认
(0.5, 0.5)即屏幕中心。官方示例传(0, 0),把网点相位原点挪到左上角,点阵看起来像"锚定"在画面角落。源码用.copy()拷贝,你构造后无需再留这个 Vector2 的引用。 - angle : number— 点阵旋转角,弧度,默认 1.57(≈π/2)。它改的不是"单个点的倾斜",而是整片点格旋转;传 0.5 会得到轻微斜向的排布,动态改它时差异最直观。
- scale : number— 反直觉点:数值越大,网点越小。直觉里"放大"应该变大,但这里它其实是采样网格的频率:越大点越细密,越小点越粗颗粒。示例里的 0.8 属于中等设置。
剩下的tDiffuse与tSize两个 uniform 是输入纹理和缓冲区尺寸,每次render()开头就被覆盖(DotScreenPass.js#L82-L83),你不用管。
让它动起来:运行时动态调参
pass.uniforms与material共享同一份引用——构造时this.uniforms直接传进了 ShaderMaterial(DotScreenPass.js#L54-L61)——所以改值下一帧生效,不必重建 pass:
const dot = new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ); composer.addPass( dot ); function animate() { requestAnimationFrame( animate ); dot.uniforms.angle.value += 0.01; // 角度持续累加,点阵会缓慢"转" dot.uniforms.scale.value = 0.6 + 0.3 * Math.sin( performance.now() * 0.001 ); // 密度沿正弦起伏,产生呼吸脉动 composer.render(); }参数与画面行为一一对应:angle管旋转,scale管密度,center管相位原点——移动它相当于把整片点阵屏幕滑动。前两者一起动,就是复古滤镜的经典形态。
片元着色器三步算完
整套算法就是DotScreenShader里的三步(DotScreenShader.js#L52-L71):
- 取亮度:
float average = ( color.r + color.g + color.b ) / 3.0;——RGB 平均,颜色信息从这里起被扔掉,这就是输出呈灰度点阵的原因。 - 旋坐标:
vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * scale;——像素坐标先按center平移、按angle旋转,再乘scale。"angle 管旋转、scale 管点疏密"的数学出处就在这里。 - 乘调制:
return ( sin( point.x ) * sin( point.y ) ) * 4.0;——两组正弦波像两把梳子交错相乘,得到周期性的明暗斑;最后加上average * 10.0 - 5.0这个放大 10 倍的亮度基底,即"亮处点稀疏、暗处点密集"的半色调观感。
想要 45° 新闻纸排布或 CMYK 分色,改pattern()函数、自制 ShaderMaterial 再交给 pass 即可。
放进管线里
DotScreenPass很少单独出场,卡在色调校正和渐晕之间是最稳的链路:伽马先修亮度,网点通道负责去色,渐晕收边。
composer.addPass( new RenderPass( scene, camera ) ); composer.addPass( new ShaderPass( GammaCorrectionShader ) ); const dot = new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ); composer.addPass( dot ); const vignette = new ShaderPass( VignetteShader ); vignette.uniforms[ 'offset' ].value = 1.6; // 官方示例的取值,暗角强度 vignette.uniforms[ 'darkness' ].value = 0.95; composer.addPass( vignette );webgl_postprocessing_advanced.html#L166 就是同一套组合思路:DotScreenPass与 Film、Sepia、Bloom、Bleach 并排对比。实践中再叠FilmPass(胶片颗粒)和SepiaShader(怀旧色调),老放映机的质感就齐了。
⚠️ 容易踩的坑
scale方向必错:默认1.0,数值越大点越小。想要"更大的点",去调小它。- 原色不可逆:片元输出的是单通道灰度
vec3( average * 10.0 - 5.0 + pattern() )。想保留场景色彩得换HalftonePass(examples/jsm/postprocessing/HalftonePass.js,网点按 RGB 分通道),两者同屏叠加也换不出彩色点阵。 dispose()不能省:DotScreenPass.js#L104-L110 会释放材质与全屏四边形。动态重建后处理链时不释放旧 pass,GPU 资源就漏了。- 适用范围:基类
Pass的注释明确只服务于 WebGLRenderer 管线;项目已迁 WebGPU 的话,仓库里另有基于 node 体系的实现,用法不共享本 pass 的 API。
🎯 一句话速查
- 从
three/addons/postprocessing/DotScreenPass.js引入,挂在EffectComposer上; - 构造 3 参
center / angle / scale全部可选,记住 scale 越大点越小; - 运行时直接改
pass.uniforms.center / angle / scale,下一帧生效; - 不用了调用
pass.dispose(),释放材质与全屏几何体。
DotScreenPass.js|DotScreenShader.js|Pass.js|官方文档|运行示例
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考