首页
/ three.js 后期处理之 RGBShiftShader:源码解析与色差偏移实战指南

three.js 后期处理之 RGBShiftShader:源码解析与色差偏移实战指南

2026-09-08 14:10:26作者:袁立春Spencer

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、顶点着色器、片元着色器三个字段),它并不会直接产生任何画面效果。它必须被包装进 ShaderPassEffectComposer 等后期处理容器后,才能作用于渲染结果。

数据结构与属性详解

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);

}

逐行解读其工作原理:

  1. vec2 offset = amount * vec2( cos(angle), sin(angle) ); —— 把 angle(弧度角)分解为 X、Y 两个方向上的单位向量,再乘以 amount 得到实际像素偏移量。因此 angle 决定"往哪个方向掰",amount 决定"掰多开"
  2. cr = texture2D(tDiffuse, vUv + offset) —— 在偏移后的坐标采样一次,作为红色通道来源;
  3. cga = texture2D(tDiffuse, vUv) —— 在原坐标采样一次,绿通道与 Alpha 均取自这里(保持画面主体不动);
  4. cb = texture2D(tDiffuse, vUv - offset) —— 在反方向偏移的坐标采样,作为蓝色通道来源;
  5. 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 = xnode.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 通常与 DotScreenShaderGlitchShaderFilmShader 等叠加使用,各 pass 顺序即管线执行顺序;可参考示例 webgl_postprocessing.html 先加 DotScreenShader 再叠加 RGB Shift 的链路写法。
  • 颜色失真明显:效果本身即通过丢弃部分通道信息实现(Alpha 透传、绿通道取中心采样),属于有损特效;amount 增大到边缘时,画面四边的红蓝采样可能越过纹理边界,因此不适合在极端强度下作为"校正型"色差使用。
  • 渲染到自定义大小时EffectComposer 需要同步调用 composer.setSize(),否则缓冲与屏幕尺寸不一致会导致偏移比例失真(参考示例中 onWindowResizecomposer.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.htmlexamples/webgpu_postprocessing.html

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391