three.js DotScreenPass 完整上手:五分钟做出半色调网点复古效果

发布时间:2026/9/8 20:09:11
three.js DotScreenPass 完整上手:五分钟做出半色调网点复古效果 three.js DotScreenPass 完整上手五分钟做出半色调网点复古效果【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsDotScreenPass 是 three.js 后处理通道体系里负责画面去色的那位成员它把最终渲染结果压成一张报纸印刷式的半色调网点点阵图。把报纸照片放大看画面其实由密集的圆点排布而成亮的地方点更小、暗的地方点更粗——这个 pass 就是为任意 3D 画面重建这种网点化观感。它工作在管线的哪个位置三维场景照常渲染进渲染目标之后DotScreenPass 把上一通道的输出当纹理读进来用一张全屏片元着色器逐像素重写属于纯屏幕空间效果完全不碰几何体和材质。适合用它的项目复古街机风 UI、音乐可视化、老电视/老电影氛围、需要信号劣化质感的加载页与艺术化展示屏。截图来自官方示例 webgl_postprocessing_advanced.html四个象限分别是不同后处理通道的输出右下角象限的半色调网点正是 DotScreenPass 的最终形态。⚡ 五分钟接入three.js 后处理的最短通道链引入 addon、加一行addPass点阵就能看到。DotScreenPass 属于 addons附加模块不在核心包里必须显式 importimport * 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 ) ); const halftone new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ); composer.addPass( halftone ); function tick() { composer.render(); requestAnimationFrame( tick ); } tick();为什么习惯把它放在通道链末尾因为链上最后一个通道会被 composer 自动标记为直接渲染到屏幕而这个 pass 的输入就是上一通道递过来的画面——放在末尾它处理的才是最完整的最终图像。如果还想在它之上叠别的滤镜把后者加在它后面即可。 DotScreenPass 参数怎么调center、angle、scale 一次讲清三个构造参数全部可选默认值由着色器自带构造函数只覆盖你传入的那几个。new DotScreenPass( center, angle, scale )参数类型作用默认值调整后的视觉变化推荐值centerVector2点阵相位原点网点围绕它排布(0.5, 0.5)移动它相当于让整片网点平移并围绕新点对称(0, 0)或(0.5, 0.5)anglenumber网点网格的旋转角单位弧度1.57约 90°调大网格肉眼可见地转动给0.5得到轻微倾斜0.5轻微斜排1.57保持正交scalenumber点阵密度系数1.0反直觉值越大点越小越密调小则颗粒变粗0.6–1.0经典半色调0.2–0.4粗点阵其中scale最容易踩直觉坑。很多人下意识认为scale 大 点大但看 DotScreenPass.js 的源码注释A higher value means smaller dots。原理上它乘在坐标上相当于把整片点阵捏扁或放大值越大坐标被压缩得越狠采样网格就越密点看起来越小。想要粗颗粒的老 CRT 感往小了调才对。 运行时动态效果改 uniforms 网点立刻转起来pass.uniforms与material是同一份引用帧循环里改完的值下一帧渲染就被着色器读到零同步成本。构造函数里this.uniforms被原封不动地传给了ShaderMaterial见 DotScreenPass.js 第 57 行所以这两个属性指向同一个 uniform 对象。运行时调参就是官方注释里给出的那种写法halftone.uniforms.center.value.set( 0.5, 0.5 ); halftone.uniforms.angle.value 0; halftone.uniforms.scale.value 0.6;把它放进帧循环就能得到缓慢旋转、呼吸脉动的网点动画function tick() { halftone.uniforms.angle.value 0.004; // 网点慢速旋转 halftone.uniforms.scale.value 0.8 0.4 * Math.sin( performance.now() * 0.0012 ); // 呼吸式脉动 composer.render(); requestAnimationFrame( tick ); }不需要重建 pass也不需要碰材质对象逐帧写值就是标准用法。 半色调是怎么生成的片元着色器三步拆解片元着色器的核心逻辑就三步取亮度 → 坐标旋转缩放 → 周期函数调制全部发生在每个像素里。亮度提取把输入像素的 RGB 三通道取平均average作为该点的灰度亮度。坐标变换vUv * tSize把归一化坐标换算到像素空间tSize是渲染缓冲实际宽高减去center以它为新原点再用angle的正余弦做二维旋转最后整体乘scale压缩或拉伸。这正是angle转网点方向、scale改点阵密度的数学来源。周期调制变换后的两个坐标各自取sin再相乘得到在 ±1 之间振荡的周期值乘 4 放大振幅叠加到亮度基底average * 10 - 5上最终 RGB 同值输出、原 alpha 透传。关键 GLSL 节选DotScreenShader.js 源码注释标明算法借鉴自 glfx.js 项目的 sepia 着色器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 ); }两个值得注意的细节一是gl_FragColor的三个通道写入同一个值所以最终画面本质是灰度 网点原图的彩色信息在这里被压掉了——想调色sepia、滤镜色要放在这个 pass 之前或之后做二是pattern()的结构足够简单想改成 45° 新闻纸斜排或 CMYK 分色网点照着它重写片元着色器即可。 与其他滤镜的组合一条伽马→网点→渐晕后处理链通道顺序决定每个滤镜吃的是哪一帧画面复古链的常用排法是场景渲染 → 伽马校正 → 网点 → 渐晕。import { ShaderPass } from three/addons/postprocessing/ShaderPass.js; import { GammaCorrectionShader } from three/addons/shaders/GammaCorrectionShader.js; import { VignetteShader } from three/addons/shaders/VignetteShader.js; composer.addPass( new RenderPass( scene, camera ) ); composer.addPass( new ShaderPass( GammaCorrectionShader ) ); const halftone new DotScreenPass( new THREE.Vector2( 0.5, 0.5 ), 1.2, 0.9 ); composer.addPass( halftone ); const vignette new ShaderPass( VignetteShader ); vignette.uniforms.offset.value 1.4; vignette.uniforms.darkness.value 1.1; composer.addPass( vignette );顺序的意义伽马校正放在网点前面是为了让取 RGB 均值为亮度这一步算在最终的色调上而不是在线性空间里算错一次渐晕放在网点后面把四角压暗恰好遮住边缘最显粗的网点整幅画面更像一张投射出来的老照片。官方示例里这个滤镜还常和FilmPass胶片颗粒、sepia 怀旧色调、Bloom 同屏出现组合出老电影放映的质感。 工程注意事项导出入口、TSL 差异与 dispose 时机属于 addons不在核心包统一导出见 Addons.js从核心three入口 import 是找不到的必须显式引入 addon 路径。只服务于 WebGLRenderer 管线Pass.js 的模块注释明确写着本模块仅适用于 WebGLRenderer 的后处理。如果项目走 WebGPU TSLThree Shading Language对应的是另一套节点实现 DotScreenNode.js用法完全不同别把两边的写法混着抄。dispose() 别忘页面卸载或重建通道链时调用halftone.dispose()它会依次释放材质与全屏四边形几何体漏掉就是 GPU 资源泄漏。别以为tSize恒为 256render()每帧都会用readBuffer的真实宽高覆盖它readBuffer是上一通道的输出纹理缓冲所以同样的scale在低分辨率目标上点很明显、放到 4K 画面上就细得几乎看不见调参前先确认渲染目标分辨率。输出是灰度点阵如前所述 RGB 被压成亮度均值需要彩色网点必须自己改着色器不要指望调 uniform 就能出彩色。❓ DotScreenPass 常见问题可以只传 angle、省掉 center 吗可以三个参数全部可选。构造函数只为传入的值写 uniform省略的沿用着色器初始值center(0.5, 0.5)、angle1.57、scale1.0。为什么点细得像噪点画面接近纯白多半是scale与分辨率共同作用的结果tSize每帧被真实渲染目标尺寸覆盖高分辨率下同样的密度系数会算出极细的网点。把scale调小或先在低分辨率渲染目标上确定观感再迁移通常就解决了。每帧写 uniforms 会有性能开销吗没有。uniforms 与 material 共享同一份引用值在渲染时被重新读取逐帧写值是标准动态调参方式开销可以忽略。想改成彩色网点比如 CMYK 分色怎么做改main()的最后一行不再让三个通道取同一个值而是按average或原始 RGB 分量做通道选择或者直接照 DotScreenShader.js 的结构写一份自定义ShaderMaterial换进 passpattern()的点阵逻辑可以原样复用。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考