当前位置: 首页 > news >正文

Three.js 后处理管线与自定义着色器:从基础渲染到电影级特效

Three.js 后处理管线与自定义着色器:从基础渲染到电影级特效

一、后处理的"视觉跃迁":为什么 3D 场景总差一口气

Three.js 的基础渲染能呈现几何体、材质和光照,但画面总缺少一种"电影感"——缺少景深模糊、缺少光晕泛光、缺少色彩校正、缺少噪点颗粒。这些视觉效果都需要后处理(Post-processing)来实现。

后处理的原理是:将场景渲染到帧缓冲(FBO)而非屏幕,然后对帧缓冲中的图像应用一系列图像处理效果,最终输出到屏幕。Three.js 的 EffectComposer 封装了这一流程,通过组合多个 Pass 实现管线化的后处理。

二、后处理管线的架构

后处理管线由多个 Pass 串联组成,每个 Pass 读取上一级的输出,执行特定的图像处理,输出到下一级。

flowchart LR A[场景渲染 RenderPass] --> B[泛光效果 UnrealBloomPass] B --> C[自定义着色器 ShaderPass] C --> D[色彩校正 ShaderPass] D --> E[胶片颗粒 ShaderPass] E --> F[色调映射 OutputPass] F --> G[屏幕输出]

管线的关键约束是 Pass 的顺序:泛光必须在色彩校正之前(否则泛光颜色会被错误映射),胶片颗粒必须在最后(否则颗粒会被后续处理模糊)。Pass 的数量也需控制——每个 Pass 都是一次全屏绘制,5 个 Pass 意味着 5 次全屏像素计算。

三、工程化实现

3.1 基础后处理管线

// postprocessing.ts import * as THREE from 'three'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { OutputPass } from 'three/addons/postprocessing/OutputPass.js'; class PostProcessingPipeline { private composer: EffectComposer; constructor( renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.PerspectiveCamera ) { const size = renderer.getSize(new THREE.Vector2()); const pixelRatio = renderer.getPixelRatio(); this.composer = new EffectComposer(renderer); // Pass 1:场景渲染 const renderPass = new RenderPass(scene, camera); this.composer.addPass(renderPass); // Pass 2:泛光效果 const bloomPass = new UnrealBloomPass( new THREE.Vector2(size.x, size.y), 0.8, // 强度 0.3, // 半径 0.85 // 阈值 ); this.composer.addPass(bloomPass); // Pass 3:赛博朋克色彩校正 const cyberpunkColorShader = { uniforms: { tDiffuse: { value: null }, uTime: { value: 0 }, uIntensity: { value: 0.6 }, }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform sampler2D tDiffuse; uniform float uTime; uniform float uIntensity; varying vec2 vUv; void main() { vec4 color = texture2D(tDiffuse, vUv); // 赛博朋克色调:增强青色和品红色 vec3 cyberpunkTint = vec3(0.0, 0.8, 1.0) * color.r + vec3(1.0, 0.0, 0.8) * color.b; color.rgb = mix(color.rgb, cyberpunkTint, uIntensity * 0.3); // 暗角效果 float vignette = 1.0 - smoothstep(0.4, 1.2, length(vUv - 0.5)); color.rgb *= mix(0.6, 1.0, vignette); // 扫描线效果 float scanline = sin(vUv.y * 800.0 + uTime * 2.0) * 0.03; color.rgb -= scanline; gl_FragColor = color; } `, }; const cyberpunkPass = new ShaderPass(cyberpunkColorShader); this.composer.addPass(cyberpunkPass); // Pass 4:胶片颗粒 const filmGrainShader = { uniforms: { tDiffuse: { value: null }, uTime: { value: 0 }, uGrainIntensity: { value: 0.05 }, }, vertexShader: ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform sampler2D tDiffuse; uniform float uTime; uniform float uGrainIntensity; varying vec2 vUv; // 伪随机噪声 float random(vec2 st) { return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453); } void main() { vec4 color = texture2D(tDiffuse, vUv); float grain = random(vUv + uTime) * uGrainIntensity; color.rgb += grain - uGrainIntensity * 0.5; gl_FragColor = color; } `, }; const grainPass = new ShaderPass(filmGrainShader); this.composer.addPass(grainPass); // Pass 5:输出(色调映射 + 颜色空间转换) const outputPass = new OutputPass(); this.composer.addPass(outputPass); } render(deltaTime: number): void { // 更新时间 uniform const passes = this.composer.passes; for (const pass of passes) { if (pass instanceof ShaderPass && pass.uniforms.uTime) { pass.uniforms.uTime.value += deltaTime; } } this.composer.render(); } resize(width: number, height: number): void { this.composer.setSize(width, height); } }

3.2 选择性泛光

// selective-bloom.ts // 只让特定物体泛光,其他物体不受影响 class SelectiveBloom { private bloomLayer: THREE.Layers; constructor() { // 创建泛光层:只有在该层的物体才会泛光 this.bloomLayer = new THREE.Layers(); this.bloomLayer.set(1); } // 将物体添加到泛光层 enableBloom(object: THREE.Object3D): void { object.layers.enable(1); // 子对象也需要启用 object.traverse((child) => { child.layers.enable(1); }); } // 从泛光层移除 disableBloom(object: THREE.Object3D): void { object.layers.disable(1); object.traverse((child) => { child.layers.disable(1); }); } }

四、后处理管线的 Trade-offs

性能开销的累积效应:每个 Pass 都是一次全屏像素着色器执行。在 1920×1080 分辨率下,一个 Pass 约处理 200 万像素。5 个 Pass 的总计算量约 1000 万像素 × 着色器复杂度。在移动端 GPU 上,这可能导致帧率从 60fps 降到 30fps 以下。建议在移动端减少 Pass 数量或降低渲染分辨率。

Pass 顺序的依赖性:某些 Pass 的效果依赖前置 Pass 的输出。例如,泛光效果需要场景中的高亮区域,如果色彩校正在泛光之前,高亮阈值会失效。建议按照"渲染→泛光→色彩校正→特效→输出"的标准顺序排列 Pass。

自定义着色器的调试困难:GLSL 着色器无法断点调试,错误只能在运行时通过视觉异常发现。建议先在 ShaderToy 上验证着色器逻辑,再移植到 Three.js。同时使用console.log输出 uniform 值,确保数据传递正确。

帧缓冲的内存开销:每个 Pass 需要独立的帧缓冲,在 1080p 分辨率下每个 FBO 约 8MB。5 个 Pass 需要 40MB 额外显存。在显存有限的设备上,需要降低 FBO 分辨率或减少 Pass 数量。

五、总结

Three.js 后处理管线通过组合多个 Pass,将基础渲染升级为电影级视觉效果。落地路线上,建议先搭建 RenderPass + OutputPass 的最小管线,再逐步添加泛光、色彩校正等 Pass。关键原则:Pass 数量与性能成反比,Pass 顺序影响效果正确性,自定义着色器需要充分测试,移动端必须考虑性能降级。

http://www.jsqmd.com/news/995911/

相关文章:

  • 常用插件引进unity方法,亲测好用
  • 5分钟掌握Save Image as Type:浏览器图片格式转换的现代解决方案
  • 数字人切入,我用魔珐星云搭建政务大厅咨询数字人,低成本落地便民接待
  • 2026年6月激光喷码机厂家推荐,喷码机/激光喷码机/大字符喷码机,激光喷码机直销厂家口碑推荐 - 品牌推荐师
  • 把“AI 依赖”变成一个可计算的量:Offloading Score 论文精读
  • 6月推荐!成都正规护栏网生产厂家哪家好的选择,格宾网/石笼网/钢筋网片/钢丝网/边坡防护网,护栏网生产厂家怎么选择 - 品牌推荐师
  • Nav2行为树实战:如何用Recovery和RoundRobin节点打造“打不死”的机器人导航?
  • 别再乱用BRAM了!Vivado里BRAM和URAM到底怎么选?一个视频处理实例讲清楚
  • 别再死记硬背了!用Wireshark抓包实战,5分钟搞懂USB的四种端点和传输类型
  • 如何快速搭建智能交易系统:TradingAgents-CN实战指南
  • 编写程序对接智能温湿计数据,划分居家舒适区,提醒调整空调,加湿器。
  • 跨平台NTRIP协议C++实现:含客户端、服务端与广播服务器三合一工具包
  • 2026年沾益区驾校学车报名条件全解析:如何选择靠谱驾校? - 品牌鉴赏官2026
  • Windows Defender终极禁用指南:使用no-defender工具的3步完整教程
  • 手把手搭建首个React项目
  • 从环境变量到接口文件:深入拆解Amesim与Simulink联合仿真的底层通信原理与配置逻辑
  • BallonTranslator:5分钟掌握AI漫画本地化,开启免费智能翻译新时代
  • 无人机、手机定位都离不开它:一文讲透GDOP如何影响你的位置精度
  • 111111111111111111111111111测试
  • GD32启动文件与链接脚本深度解析:从复位到main()函数到底发生了什么?
  • Keyboard Chatter Blocker终极指南:Windows键盘连击问题的免费解决方案
  • 如何搭建个人游戏串流服务器:Sunshine完整实战指南
  • DDrawCompat:让经典DirectX游戏在现代Windows上重获新生的兼容性神器
  • 2026年西南地区UPS不间断电源服务商实用选择指南:本地化服务与一线品牌授权分析 - 优质品牌商家
  • 乳腺癌二分类预测Python工程:含数据、训练脚本、评估与演示全流程
  • GraphRAG 技术选型:小白工程师必看,你的数据是否适合用它?(含收藏)
  • 别再死记硬背了!用LabVIEW的移位寄存器+数组,5分钟搞定波形生成与切片
  • AI 生产力工具产品化:用户行为分析与功能迭代的闭环实践
  • 硬件工程师避坑指南:开关电源电感选型,从‘烧管子’到纹波超标,这5个参数你算对了吗?
  • Spring Security实战:手把手教你为若依系统添加会员登录(双用户表隔离)