Skip to content

Commit e9f3ce4

Browse files
pashaydevPashaDev2
andauthored
TSL: Introduce Chromatic Aberration (#31236)
* Chromatic aberration TSL node Example page for ca node * Fix comma * Remove unused delta from render loop in ca example * Rename CANode.js to ChromaticAberrationNode.js Add nodeObject in ca params, for ability to use a procedural node Update example gui to toggle procedural / static * Fix the gui in example * cleanup code style * use nodeObject() * Update webgpu_postprocessing_ca.html * cleanup --------- Co-authored-by: PashaDev2 <[email protected]>
1 parent f5b96f8 commit e9f3ce4

File tree

6 files changed

+555
-0
lines changed

6 files changed

+555
-0
lines changed

examples/files.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@
405405
"webgpu_postprocessing_fxaa",
406406
"webgpu_postprocessing_lensflare",
407407
"webgpu_postprocessing_masking",
408+
"webgpu_postprocessing_ca",
408409
"webgpu_postprocessing_motion_blur",
409410
"webgpu_postprocessing_outline",
410411
"webgpu_postprocessing_smaa",
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import { Vector2, TempNode } from 'three/webgpu';
2+
import {
3+
nodeObject,
4+
Fn,
5+
uniform,
6+
convertToTexture,
7+
float,
8+
vec4,
9+
uv,
10+
NodeUpdateType,
11+
} from 'three/tsl';
12+
13+
/**
14+
* Post processing node for applying chromatic aberration effect.
15+
* This effect simulates the color fringing that occurs in real camera lenses
16+
* by separating and offsetting the red, green, and blue channels.
17+
*
18+
* @augments TempNode
19+
* @three_import import { chromaticAberration } from 'three/addons/tsl/display/ChromaticAberrationNode.js';
20+
*/
21+
class ChromaticAberrationNode extends TempNode {
22+
23+
static get type() {
24+
25+
return 'ChromaticAberrationNode';
26+
27+
}
28+
29+
/**
30+
* Constructs a new chromatic aberration node.
31+
*
32+
* @param {TextureNode} textureNode - The texture node that represents the input of the effect.
33+
* @param {Node} strengthNode - The strength of the chromatic aberration effect as a node.
34+
* @param {Node} centerNode - The center point of the effect as a node.
35+
* @param {Node} scaleNode - The scale factor for stepped scaling from center as a node.
36+
*/
37+
constructor( textureNode, strengthNode, centerNode, scaleNode ) {
38+
39+
super( 'vec4' );
40+
41+
/**
42+
* The texture node that represents the input of the effect.
43+
*
44+
* @type {texture}
45+
*/
46+
this.textureNode = textureNode;
47+
48+
/**
49+
* The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates
50+
* its internal uniforms once per frame in `updateBefore()`.
51+
*
52+
* @type {string}
53+
* @default 'frame'
54+
*/
55+
this.updateBeforeType = NodeUpdateType.FRAME;
56+
57+
/**
58+
* A node holding the strength of the effect.
59+
*
60+
* @type {Node}
61+
*/
62+
this.strengthNode = strengthNode;
63+
64+
/**
65+
* A node holding the center point of the effect.
66+
*
67+
* @type {Node}
68+
*/
69+
this.centerNode = centerNode;
70+
71+
/**
72+
* A node holding the scale factor for stepped scaling.
73+
*
74+
* @type {Node}
75+
*/
76+
this.scaleNode = scaleNode;
77+
78+
/**
79+
* A uniform node holding the inverse resolution value.
80+
*
81+
* @private
82+
* @type {UniformNode<vec2>}
83+
*/
84+
this._invSize = uniform( new Vector2() );
85+
86+
}
87+
88+
/**
89+
* This method is used to update the effect's uniforms once per frame.
90+
*
91+
* @param {NodeFrame} frame - The current node frame.
92+
*/
93+
updateBefore( /* frame */ ) {
94+
95+
const map = this.textureNode.value;
96+
this._invSize.value.set( 1 / map.image.width, 1 / map.image.height );
97+
98+
}
99+
100+
/**
101+
* This method is used to setup the effect's TSL code.
102+
*
103+
* @param {NodeBuilder} builder - The current node builder.
104+
* @return {ShaderCallNodeInternal}
105+
*/
106+
setup( /* builder */ ) {
107+
108+
const textureNode = this.textureNode;
109+
const uvNode = textureNode.uvNode || uv();
110+
111+
const ApplyChromaticAberration = Fn( ( [ uv, strength, center, scale ] ) => {
112+
113+
// Calculate distance from center
114+
const offset = uv.sub( center );
115+
const distance = offset.length();
116+
117+
// Create stepped scaling zones based on distance
118+
// Each channel gets different scaling steps
119+
const redScale = float( 1.0 ).add( scale.mul( 0.02 ).mul( strength ) ); // Red channel scaled outward
120+
const greenScale = float( 1.0 ); // Green stays at original scale
121+
const blueScale = float( 1.0 ).sub( scale.mul( 0.02 ).mul( strength ) ); // Blue channel scaled inward
122+
123+
// Create radial distortion based on distance from center
124+
const aberrationStrength = strength.mul( distance );
125+
126+
// Calculate scaled UV coordinates for each channel
127+
const redUV = center.add( offset.mul( redScale ) );
128+
const greenUV = center.add( offset.mul( greenScale ) );
129+
const blueUV = center.add( offset.mul( blueScale ) );
130+
131+
// Apply additional chromatic offset based on aberration strength
132+
const rOffset = offset.mul( aberrationStrength ).mul( float( 0.01 ) );
133+
const gOffset = offset.mul( aberrationStrength ).mul( float( 0.0 ) );
134+
const bOffset = offset.mul( aberrationStrength ).mul( float( - 0.01 ) );
135+
136+
// Final UV coordinates combining scale and chromatic aberration
137+
const finalRedUV = redUV.add( rOffset );
138+
const finalGreenUV = greenUV.add( gOffset );
139+
const finalBlueUV = blueUV.add( bOffset );
140+
141+
// Sample texture for each channel
142+
const r = textureNode.sample( finalRedUV ).r;
143+
const g = textureNode.sample( finalGreenUV ).g;
144+
const b = textureNode.sample( finalBlueUV ).b;
145+
146+
// Get original alpha
147+
const a = textureNode.sample( uv ).a;
148+
149+
return vec4( r, g, b, a );
150+
151+
} ).setLayout( {
152+
name: 'ChromaticAberrationShader',
153+
type: 'vec4',
154+
inputs: [
155+
{ name: 'uv', type: 'vec2' },
156+
{ name: 'strength', type: 'float' },
157+
{ name: 'center', type: 'vec2' },
158+
{ name: 'scale', type: 'float' },
159+
{ name: 'invSize', type: 'vec2' }
160+
]
161+
} );
162+
163+
const chromaticAberrationFn = Fn( () => {
164+
165+
return ApplyChromaticAberration(
166+
uvNode,
167+
this.strengthNode,
168+
this.centerNode,
169+
this.scaleNode,
170+
this._invSize
171+
);
172+
173+
} );
174+
175+
const outputNode = chromaticAberrationFn();
176+
177+
return outputNode;
178+
179+
}
180+
181+
}
182+
183+
export default ChromaticAberrationNode;
184+
185+
/**
186+
* TSL function for creating a chromatic aberration node for post processing.
187+
*
188+
* @tsl
189+
* @function
190+
* @param {Node<vec4>} node - The node that represents the input of the effect.
191+
* @param {Node|number} [strength=1.0] - The strength of the chromatic aberration effect as a node or value.
192+
* @param {Node|Vector2} [center=null] - The center point of the effect as a node or value. If null, uses screen center (0.5, 0.5).
193+
* @param {Node|number} [scale=1.1] - The scale factor for stepped scaling from center as a node or value.
194+
* @returns {ChromaticAberrationNode}
195+
*/
196+
export const chromaticAberration = ( node, strength = 1.0, center = null, scale = 1.1 ) => {
197+
198+
return nodeObject(
199+
new ChromaticAberrationNode(
200+
convertToTexture( node ),
201+
nodeObject( strength ),
202+
nodeObject( center ),
203+
nodeObject( scale )
204+
)
205+
);
206+
};
Loading

examples/tags.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"webgpu_postprocessing_dof": [ "bokeh" ],
149149
"webgpu_postprocessing_fxaa": [ "msaa", "multisampled" ],
150150
"webgpu_postprocessing_motion_blur": [ "mrt" ],
151+
"webgpu_postprocessing_ca": [ "chromatic aberration" ],
151152
"webgpu_postprocessing_sobel": [ "filter", "edge detection" ],
152153
"webgpu_postprocessing_ssaa": [ "msaa", "multisampled" ],
153154
"webgpu_refraction": [ "water" ],

0 commit comments

Comments
 (0)