10 分钟调出复古半色调点阵:three.js DotScreenPass 实战指南
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
three.js 的DotScreenPass是一个半色调后处理通道:场景渲染完成后,它把整幅画面重绘成密集圆点阵列,让屏幕看起来像报纸印刷或老式点阵屏。读完这篇,你能拿到一段可直接粘贴的接入代码,以及一套运行时调节网点旋转与脉冲的配方。
先让画面"变味儿"——最小可运行接入
DotScreenPass是 addon 模块,不在核心包里,必须显式引入。下面这段代码把它接进场景渲染之后的后处理链:
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 ) ); composer.addPass( new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ) ); function animate() { requestAnimationFrame( animate ); composer.render(); } animate();跑起来后,整块画面被圆点阵列覆盖:亮的地方点又大又亮,暗的地方几乎沉进黑里,颜色信息被点阵的明暗代替——就是报纸放大看的那种质感。这个构造写法在官方运行示例 examples/webgl_postprocessing_advanced.html 里原样出现。
三个旋钮怎么拧——参数调优速查
构造函数收三个参数,对应三个旋钮:网点从哪开始旋转、点阵斜多少、点有多大。
new DotScreenPass( center, angle, scale );三个参数都是可选的,省略就沿用着色器默认值:
| 参数 | 含义 | 默认值 | 推荐范围 |
|---|---|---|---|
center | 网点相位原点 | ( 0.5, 0.5 )屏幕中心 | ( 0, 0 )屏幕角落 |
angle | 网点旋转角,弧度 | 1.57(约 π/2) | 0 ~ 6.28 |
scale | 网点密度,越大点越小 | 1.0 | 0.5 ~ 2.0 |
把scale从 1.0 拖到 0.5,颗粒感立刻粗一倍——相当于同一张网点底片是放大打印还是缩小打印的差别。angle设成0.5,点阵会微微倾斜,多几分手绘报纸的味道。
让网点"动"起来——运行时动态调参 ✨
pass.uniforms和材质共享同一份引用,改完下一帧就生效,无需重建 pass。把angle在动画帧里每次加一点,网点就开始缓慢转圈:
const dotScreen = new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ); composer.addPass( dotScreen ); function animate() { requestAnimationFrame( animate ); dotScreen.uniforms.angle.value += 0.01; dotScreen.uniforms.scale.value = 0.6 + 0.3 * Math.sin( Date.now() * 0.001 ); composer.render(); } animate();跑起来后,angle每帧加 0.01 让点阵慢转,scale由正弦波驱动,网点粗细来回"呼吸"。想让网点相位挪位置,用dotScreen.uniforms.center.value.copy( new THREE.Vector2( 0.3, 0.7 ) )一行搞定。
滤镜背后那 20 行着色器——半色调算法拆解
视觉效果的核心是 DotScreenShader.js 里的片元着色器,算法沿用了 glfx.js 的实现。关键代码就这么多:
float pattern() { float s = sin( angle ), c = cos( angle ); vec2 tex = vUv * tSize - center; vec2 point = vec2( c * tex.x - s * tex.y, s * tex.x + c * tex.y ) * scale; return ( sin( point.x ) * sin( point.y ) ) * 4.0; } void main() { vec4 color = texture2D( tDiffuse, vUv ); float average = ( color.r + color.g + color.b ) / 3.0; gl_FragColor = vec4( vec3( average * 10.0 - 5.0 + pattern() ), color.a ); }拆成三步理解:1)取 RGB 平均值average当这个像素的灰度亮度;2)把 UV 换算到像素空间,以center为原点按angle旋转、再乘scale——两个旋钮的数学来源就藏在这几行;3)对旋转后坐标分别取sin再相乘,得到 [-1, 1] 间的周期性调制值,放大 4 倍后叠加到average * 10.0 - 5.0的亮度基底上,形成"明暗靠点阵表达"的半色调观感,原图 alpha 原样保留。
想自定义网点造型——45° 新闻纸排布、CMYK 彩色分色——改pattern()这一个函数,再把自己的 ShaderMaterial 传给 pass 即可。
组合拳实战——后处理链级联配置
网点滤镜单飞能看,级联出味道:场景渲染 → 伽马校正 → 网点 → 渐晕。通道顺序直接决定最终观感,比如伽马校正放在网点后面,网点本身的黑白也会被校正,整体色调明显变亮。下面这条链可直接嵌入项目:
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js'; import { VignetteShader } from 'three/addons/shaders/VignetteShader.js'; const composer = new EffectComposer( renderer ); composer.addPass( new RenderPass( scene, camera ) ); composer.addPass( new ShaderPass( GammaCorrectionShader ) ); const dotScreen = new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ); composer.addPass( dotScreen ); const vignette = new ShaderPass( VignetteShader ); vignette.uniforms[ 'offset' ].value = 1.6; vignette.uniforms[ 'darkness' ].value = 0.95; composer.addPass( vignette );运行后画面是点阵质感加四角压暗,一股老电影放映机的味道。offset/darkness取 1.6 和 0.95 来自官方 advanced 示例,照抄不会出错。
踩坑清单与资源释放 ⚠️
- 漏调 dispose 会泄漏 GPU 资源:动态重建后处理链时,先把旧 pass 摘掉再释放,
chrome://gpu里 WebGL 纹理数不再上涨才算干净:
dotScreen.dispose();这一行会释放 ShaderMaterial 和全屏四边形几何体,不执行的话这两块显存就一直挂着。
scale与网点密度是反比:数值越大点越小越细密,越小点越大越粗。新手最常搞反,第一次想"调细"结果调出一颗大圆点。- 渲染管线边界:
Pass基类模块注释明确只服务WebGLRenderer后处理。项目走 WebGPU + TSL 的话,请改用 DotScreenNode 文档 里的 Node 实现,这个 Pass 不能原样搬过去。 - 别手动设
tDiffuse和tSize:render()每帧自动写入上一通道纹理和缓冲区宽高,你自己设了也会被覆盖,白忙活。
如果你只带走这几句
- 引入路径
three/addons/postprocessing/DotScreenPass.js,挂进EffectComposer才生效; - 构造三参
center/angle/scale全部可省,省了就用默认值; - 想要更细的网点,把
scale往大了调——它是反比; - 动画帧里改
uniforms,下一帧即生效,不用重建; - 移除通道前
dispose()一次,显存还给你。
参考:官方文档|Pass 源码|着色器源码|Pass 基类|运行示例
【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考