three.js 后期处理之 RGBShiftShader:源码解析与色差偏移实战指南
RGBShiftShader(RGB 色差偏移 / 色散效果)是 three.js 提供的经典后期处理着色器,它把红、蓝通道从画面中心沿相反方向错位,模拟出镜头色差与故障艺术(Glitch)的"重影"感。本文以该着色器的官方文档为主体,结合仓库中的着色器源码、
ShaderPass后期管线与 WebGPU/TSL 版本实现,讲解导入方式、uniform 参数语义、两种主流用法(EffectComposer 管线与 RenderPipeline 节点)以及工程调参建议,读完即可在自己的 three.js 项目中复现可运行、可调节的 RGB Shift 特效。
RGBShiftShader 是什么
RGBShiftShader 是一个典型的全屏后期处理着色器(full-screen pass shader),它通过 把输入的渲染结果按 RGB 三个通道拆分采样,产生"色散"(chromatic aberration)般的视觉错位:红通道与蓝通道各自朝相反方向偏移,绿通道保持不变,而三个通道的输出又最终合成为单帧像素。其源码位于 examples/jsm/shaders/RGBShiftShader.js,描述信息明确写道它是 从 Tom Butterworth 的公开文章 "RGB Shift" 移植而来 的经典算法,适合实现复古 CRT 效果、故障 / 失真转场、音乐可视化,以及增强科幻风格的镜头感。
在项目中的定位上,它属于 addon(附加模块)而非核心库的一部分:与 MeshLambertMaterial 这类核心类不同,它不会被默认的构建产物自动导出,需要使用者显式导入,正如官方安装指南中"Addons"一节的说明(可参考 docs/index.html 中对应文档)。
导入方式
由于是附加模块,RGBShiftShader 必须显式导入。以项目的 import map / npm 别名 three/addons/ 为例(可参考 examples/webgl_postprocessing.html 中 "three/addons/": "./jsm/" 的映射写法):
import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js';
在源码层面,examples/jsm/Addons.js 通过 export * from './shaders/RGBShiftShader.js'; 将其聚合进 addons 的统一出口,因此两种引入方式等价:
// 方式一:单独路径(推荐,便于 tree-shaking)
import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js';
// 方式二:从 addons 聚合入口导入
import { RGBShiftShader } from 'three/addons/Addons.js';
需要注意的是,RGBShiftShader 本身只是一份纯 Shader 对象定义(含 uniforms、顶点着色器、片元着色器三个字段),它并不会直接产生任何画面效果。它必须被包装进 ShaderPass、EffectComposer 等后期处理容器后,才能作用于渲染结果。
数据结构与属性详解
RGBShiftShader 的类型被文档标注为 ShaderMaterial~Shader(即 ShaderMaterial 内部约定的着色器描述对象),并且是 constant(常量)——这意味着它作为一份可复用的静态模板存在,运行时通过 ShaderPass 对它的 uniforms 进行克隆,因此多个 pass 共用同一份 RGBShiftShader 也不会互相污染(见下文 ShaderPass 分析)。
对照 examples/jsm/shaders/RGBShiftShader.js 的源码,完整的对象结构为:
| 字段 | 内容 |
|---|---|
name |
'RGBShiftShader',着色器唯一标识 |
uniforms.tDiffuse |
输入纹理,默认 null,运行时由后期管线注入上一级的渲染缓冲(read buffer)纹理 |
uniforms.amount |
偏移距离,默认 0.005;文档说明 "1 即输入画面的整个宽度",即该值表示相对于画布宽度的位移比例 |
uniforms.angle |
偏移方向角,单位 弧度(radians),默认 0.0 |
vertexShader |
标准全屏四边形顶点着色器,透传 UV |
fragmentShader |
实现红 / 绿 / 蓝分通道偏移采样的片元着色器 |
顶点着色器:全屏后处理的固定样板
顶点着色器非常简单,只负责把屏幕空间的 UV 坐标交给片元阶段:
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
全屏后处理 pass 绘制的是一个铺满视口的四边形,因此这一部分几乎与所有后处理着色器通用,关键逻辑全部在片元着色器。
片元着色器:三通道分离采样的核心算法
片元着色器是本效果的精髓所在,源码如下:
uniform sampler2D tDiffuse;
uniform float amount;
uniform float angle;
varying vec2 vUv;
void main() {
vec2 offset = amount * vec2( cos(angle), sin(angle) );
vec4 cr = texture2D(tDiffuse, vUv + offset);
vec4 cga = texture2D(tDiffuse, vUv);
vec4 cb = texture2D(tDiffuse, vUv - offset);
gl_FragColor = vec4(cr.r, cga.g, cb.b, cga.a);
}
逐行解读其工作原理:
vec2 offset = amount * vec2( cos(angle), sin(angle) );—— 把angle(弧度角)分解为 X、Y 两个方向上的单位向量,再乘以amount得到实际像素偏移量。因此angle决定"往哪个方向掰",amount决定"掰多开"。cr = texture2D(tDiffuse, vUv + offset)—— 在偏移后的坐标采样一次,作为红色通道来源;cga = texture2D(tDiffuse, vUv)—— 在原坐标采样一次,绿通道与 Alpha 均取自这里(保持画面主体不动);cb = texture2D(tDiffuse, vUv - offset)—— 在反方向偏移的坐标采样,作为蓝色通道来源;gl_FragColor = vec4(cr.r, cga.g, cb.b, cga.a);—— 用红通道的 R、原画面的 G、反向采样点的 B 重组颜色,Alpha 沿用原图。
当 angle = 0 时,偏移纯沿 +X 方向,即红通道右移、蓝通道左移,形成标准的水平色差;把 angle 设为 Math.PI / 2 则变为垂直方向的上下分离;而在动画中持续旋转 angle,就会出现经典"故障风"的动态色散抖动。
在 WebGL 后期管线中使用(EffectComposer + ShaderPass)
RGBShiftShader 最常见的用法是接入 EffectComposer 后期处理链。参考仓库示例 examples/webgl_postprocessing.html,其标准流程为:
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
// 1. 创建后期处理链
const composer = new EffectComposer( renderer );
// 2. 先把正常场景渲染进缓冲区
composer.addPass( new RenderPass( scene, camera ) );
// 3. 加入 RGBShift 效果(此处示例在点阵效果之后再叠加一层)
const effect = new ShaderPass( RGBShiftShader );
effect.uniforms[ 'amount' ].value = 0.0015;
composer.addPass( effect );
// 4. 最终输出到屏幕(负责色调映射 / 色彩空间转换)
composer.addPass( new OutputPass() );
随后只需在动画循环中把原来的 renderer.render( scene, camera ) 替换为 composer.render() 即可(参考同一示例的 animate())。
关于 uniforms 的修改入口,可以追溯到 examples/jsm/postprocessing/ShaderPass.js 的实现细节:
- 当传入对象是
RGBShiftShader这类 Shader 描述对象时,ShaderPass会执行UniformsUtils.clone( shader.uniforms )复制一份独立 uniforms,并用它们构建一个ShaderMaterial(内部this.uniforms = UniformsUtils.clone(...)、this.material = new ShaderMaterial({ ... })); - 在每次渲染前,
ShaderPass会把上级缓冲纹理写入this.uniforms[ 'tDiffuse' ].value(对应源码中if ( this.uniforms[ this.textureID ] ) ... readBuffer.texture的分支逻辑); - 因此用户只需访问 pass 实例上的
uniforms(而非全局那份常量)来动态调参:effect.uniforms[ 'amount' ].value = ...。
amount 取值建议
由于注释明确"1 是输入画面的整个宽度",amount 的常用区间集中在 0.0005 ~ 0.01 之间:
0.0005 ~ 0.002:轻微色散,用作写实风格下的镜头色差润色;0.0015:示例 webgl_postprocessing.html 的取值,属于可见但不过度夸张的强度;0.005(默认值):画面边缘已出现明显重影,适合复古 CRT / 梦境氛围;0.01及以上:严重分离,配合脉冲动画可以做出故障 / 失灵转场。
动态旋转 angle 实现故障抖动
若要做动态效果,只需在动画帧里更新角度与强度,例如把 angle 换算为随时间增长的弧度,并让强度按噪声或正弦函数脉动:
const clock = new THREE.Clock();
function animate() {
const t = clock.getElapsedTime();
effect.uniforms[ 'angle' ].value = t * 2.0; // 让分离方向持续旋转
effect.uniforms[ 'amount' ].value = 0.005 * ( 0.5 + 0.5 * Math.sin( t * 30.0 ) ); // 强度脉动
composer.render();
}
注意:修改后需调用 renderer.setAnimationLoop( animate ) 或以 requestAnimationFrame 驱动循环;在组合链中,RGBShift 应放在渲染缓冲之后、最终 OutputPass(负责 tone mapping 与 sRGB 输出)之前,这样输入的是 HDR 场景缓冲,输出再交给色彩空间转换环节。
在 WebGPU / TSL 管线中使用(rgbShift 节点)
对于使用 WebGPU 后端(THREE.WebGPURenderer)与节点材质 / TSL 的新式管线,项目提供了功能等价的函数式节点封装 rgbShift,其实现位于 examples/jsm/tsl/display/RGBShiftNode.js。二者的算法完全一致:
const offset = vec2( cos( this.angle ), sin( this.angle ) ).mul( this.amount );
const cr = sampleTexture( uvNode.add( offset ) );
const cga = sampleTexture( uvNode );
const cb = sampleTexture( uvNode.sub( offset ) );
return vec4( cr.r, cga.g, cb.b, cga.a );
对应 WebGL 版 RGBShiftShader.js 的 GLSL 实现,可看出两者输出公式一致,只是从"uniform 字符串 + 着色器"换成了可组合的 TSL 节点图。
TSL 版的用法可以参考仓库的 WebGPU 示例 examples/webgpu_postprocessing.html:
import { rgbShift } from 'three/addons/tsl/display/RGBShiftNode.js';
// ... 构建 renderPipeline 与前置 pass ...
const rgbShiftPass = rgbShift( dotScreenPass ); // 把前一 pass 的输出作为输入纹理
rgbShiftPass.amount.value = 0.001; // 直接修改 amount 节点
renderPipeline.outputNode = rgbShiftPass; // 挂到渲染管线末端
TSL 版本在构造参数与属性上有如下对应关系:
| 概念 | WebGL(RGBShiftShader) | WebGPU / TSL(rgbShiftNode) |
|---|---|---|
| 默认强度 | uniforms.amount = 0.005 |
构造函数第二个参数 amount = 0.005,类型为 UniformNode<float> |
| 默认方向角 | uniforms.angle = 0.0 |
构造函数第三个参数 angle = 0,类型为 UniformNode<float> |
| 输入 | 运行时注入 tDiffuse |
第一个参数传入纹理节点(内部经 convertToTexture 归一化) |
| 调节方式 | pass.uniforms['amount'].value = x |
node.amount.value = x、node.angle.value = x |
说明:TSL 节点属于新一代 WebGPU 渲染管线能力,需要基于
three/webgpu构建的应用环境;传统的WebGLRenderer项目则使用前文的ShaderPass方案。两种方案的着色算法与视觉目标相同,可按渲染后端选用。
RGBShiftShader 的完整对象定义(可直接复制)
若希望脱离 three/addons 独立研究或二次封装,可以基于源码 RGBShiftShader.js 直接复用如下完整定义:
const RGBShiftShader = {
name: 'RGBShiftShader',
uniforms: {
'tDiffuse': { value: null },
'amount': { value: 0.005 },
'angle': { value: 0.0 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform sampler2D tDiffuse;
uniform float amount;
uniform float angle;
varying vec2 vUv;
void main() {
vec2 offset = amount * vec2( cos(angle), sin(angle) );
vec4 cr = texture2D(tDiffuse, vUv + offset);
vec4 cga = texture2D(tDiffuse, vUv);
vec4 cb = texture2D(tDiffuse, vUv - offset);
gl_FragColor = vec4(cr.r, cga.g, cb.b, cga.a);
}`
};
export { RGBShiftShader };
name: 'RGBShiftShader' 字段对调试着色器缓存、在 profiler 中辨识 pass 很有用;若改造成自定义 ShaderMaterial,需注意 GLSL 中纹理采样函数 texture2D 对应 GLSL1 后处理材质上下文,这与 three.js 内置后处理着色器的统一写法保持一致。
综合实战:一个可运行的极简 Demo
将上述要素串起来,一个可直接运行的完整 WebGL 示例骨架如下(组合 EffectComposer、RenderPass、RGBShiftShader 与 OutputPass,并支持滑块调参与逐帧旋转方向):
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 400;
const mesh = new THREE.Mesh(
new THREE.SphereGeometry( 120, 24, 24 ),
new THREE.MeshStandardMaterial( { color: 0x88aaff, roughness: 0.4, metalness: 0.3 } )
);
scene.add( mesh );
scene.add( new THREE.AmbientLight( 0xffffff, 0.6 ) );
// ---- 后期处理链 ----
const composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) );
const rgbShiftPass = new ShaderPass( RGBShiftShader );
rgbShiftPass.uniforms[ 'amount' ].value = 0.002;
composer.addPass( rgbShiftPass );
composer.addPass( new OutputPass() );
const clock = new THREE.Clock();
renderer.setAnimationLoop( () => {
const t = clock.getElapsedTime();
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
// 让色散方向缓慢旋转,呈现动态分离效果
rgbShiftPass.uniforms[ 'angle' ].value = t * 1.5;
composer.render();
} );
</script>
运行后你会看到:场景正常渲染进缓冲,RGBShiftShader 在其中做红蓝通道反向错位,最后经 OutputPass 正确输出到屏幕——旋转立方体/球体的边缘会拖出红蓝重影,这就是 RGB Shift 最典型的视觉特征。
调参与边界情况速查
- 画面看不出任何变化:检查
amount是否过小(如低于0.0005),以及 pass 是否真的被composer.addPass挂载、composer.render()是否在驱动。 - 想要更复杂的故障效果:RGB Shift 通常与
DotScreenShader、GlitchShader、FilmShader等叠加使用,各 pass 顺序即管线执行顺序;可参考示例 webgl_postprocessing.html 先加DotScreenShader再叠加 RGB Shift 的链路写法。 - 颜色失真明显:效果本身即通过丢弃部分通道信息实现(Alpha 透传、绿通道取中心采样),属于有损特效;
amount增大到边缘时,画面四边的红蓝采样可能越过纹理边界,因此不适合在极端强度下作为"校正型"色差使用。 - 渲染到自定义大小时:
EffectComposer需要同步调用composer.setSize(),否则缓冲与屏幕尺寸不一致会导致偏移比例失真(参考示例中onWindowResize对composer.setSize的调用)。
小结
RGBShiftShader 用不到 30 行 GLSL 实现了一个历久弥新的视觉特效,其核心思想是"按通道分别采样、方向相反的偏移重组"。理解它有助于触类旁通地掌握 three.js 后处理体系的三件事:Shader 描述对象的 uniforms 结构、ShaderPass 对 uniforms 的克隆与 tDiffuse 注入机制,以及 EffectComposer 链式 pass 的执行顺序。若项目切换到 WebGPU/TSL 管线,同一算法对应 RGBShiftNode/rgbShift 节点,用法与调参思路可以无缝迁移。相关代码与示例均可在仓库中对照查阅:着色器源码位于 examples/jsm/shaders/RGBShiftShader.js,TSL 封装位于 examples/jsm/tsl/display/RGBShiftNode.js,完整集成示例见 examples/webgl_postprocessing.html 与 examples/webgpu_postprocessing.html。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00