đď¸ 16062024 1600
đ
r3f_post_processing
Issues with vanilla post processingâ
- Each pass results in a re-render
- The same work might be done in each render
- Depth renders
- Normal renders
- etc.
Post Processingâ
- Aims to solves issue by merging various passes into the least number of passes possible
- Uses the term 'effects' instead
- Order of adding effects is preserved
Dependencies â
@react-three/postprocessing
postprocessing
import { EffectComposer } from '@react-three/postprocessing'
export default function Experience()
{
return <>
<EffectComposer>
</EffectComposer>
{/* ... */}
</>
}
EffectComposer
 is now running, but the colors are now completely off.
Fixing tone mappingâ
- Problems:
- EffectComposer deactivates tone mapping in post-processing
- Causes the color to be off
ToneMapping
usesAgX
tone mapping by default- Not the one that R3F uses by default
- Causes the color to be gray-ish
- EffectComposer deactivates tone mapping in post-processing
<EffectComposer>
<ToneMapping mode={ ToneMappingMode.ACES_FILMIC } />
</EffectComposer>
Tone mapping is the process of converting HDR colors to LDR output colors
Multisamplingâ
multisampling
is used to prevent the aliasing effect- Default value is 8
// To disable
<EffectComposer multisampling={ 0 }>
<ToneMapping mode={ ToneMappingMode.ACES_FILMIC } />
</EffectComposer>
Resourcesâ
-
Post Processing:
-
React-postprocessing:
Vignette effect â
Makes the corners of the render a little darker.
import { Vignette, EffectComposer } from "@react-three/postprocessing"
<EffectComposer>
<ToneMapping mode={ToneMappingMode.ACES_FILMIC} />
<Vignette offset={0.3} darkness={0.9} />
</EffectComposer>
Blending â
- a special attribute namedÂ
blendFunction
Â- available in every effect
- for controlling how a color merges with another color behind it
- there are many blend functions
import { BlendFunction, ToneMappingMode } from 'postprocessing'
// To see all available blend functions
console.log(BlendFunction)
<Vignette
offset={ 0.3 }
darkness={ 0.9 }
blendFunction={ BlendFunction.COLOR_BURN }
/>
Background bug â
- Vignette effect doesn't work on background
- This is because background is transparent by default (nothing to render there)
- Can fix by adding a color to bg
export default function Experience()
{
return <>
<color args={ [ '#ffffff' ] } attach="background" />
{/* ... */}
<>
}
Glitch effectâ
import { Glitch, ToneMapping, Vignette, EffectComposer } from '@react-three/postprocessing'
import { GlitchMode, BlendFunction, ToneMappingMode } from 'postprocessing'
<EffectComposer>
<Glitch
delay={[0.5, 1]}
duration={[0.1, 0.3]}
strength={[0.2, 0.4]}
mode={GlitchMode.CONSTANT_MILD}
/>
</EffectComposer>
Noise effect â
import { Noise, Glitch, ToneMapping, Vignette, EffectComposer } from '@react-three/postprocessing'
<EffectComposer>
{/* ... */}
<Noise
blendFunction={ BlendFunction.SOFT_LIGHT }
/>
</EffectComposer>
Enhanced with blend function
<Noise
premultiply
blendFunction={ BlendFunction.SOFT_LIGHT }
/>
premultiply
 will multiply the noise with the input color before applying the blending.
Bloom effectâ
- The defaultÂ
Bloom
 tends to make things glow too easily - Fix by increasing the threshold above which things start to glow using theÂ
luminanceThreshold
Dark background setup for nicer effect
import { Bloom, Noise, Glitch, ToneMapping, Vignette, EffectComposer } from '@react-three/postprocessing'
<EffectComposer>
{/* ... */}
<Bloom />
</EffectComposer>
How to target objects to glowâ
- Must set a limitation i.e.
luminanceThreshold
<meshStandardMaterial color={ [ 1.5, 1, 4 ] } />
Configuring RGB valuesâ
- Set a
luminanceThreshold
With
luminenceThreshold
increased
Seems like setting the
r
andb
values to be > 1 cause it to start glowing more
Adding
mipmapBlur
to<Bloom>
allows the object to glow more brightly
Emissive Propertyâ
-
Setup (make everything orange)
-
Add
emissive
, ezpz
<meshStandardMaterial color="orange" emissive="orange" />
- Making it brighter!
<meshStandardMaterial color="orange" emissive="orange" emissiveIntensity={ 2 } />
- EVENNN BRIGHTERRRRRR (set color to white)
<meshStandardMaterial color="#ffffff" emissive="orange" emissiveIntensity={ 2 } />
Uniform emissiveâ
- What is this even (?)
- Use
<meshBasicMaterial
- Cannot use
emissive
andemissiveIntensity
- Can control the
intensity
on<Bloom>
instead
Setting the
luminanceThreshold
to 0 causes everything to glow
DepthOfField effect â
- This effect will blur whatâs closer or farther from a set distance.
- Main attributes
Attribute | Description |
---|---|
focusDistance | At which distance should the image be sharp |
focalLength | The distance to travel from the focusDistance  before reaching the maximum blur |
bokehScale | blur radius |
Values are normalized according to
near
andfar
import { DepthOfField, Bloom, Noise, Glitch, ToneMapping, Vignette, EffectComposer } from '@react-three/postprocessing'
;<EffectComposer>
{/* ... */}
<DepthOfField focusDistance={0.025} focalLength={0.025} bokehScale={6} />
</EffectComposer>
Custom effectsâ
- Need to follow Post Processing rules (w.r.t how they merge effects into one shader)
- Resources
Remember that postprocessing
 will take our shader and merge it with the other effect shaders.
Setupâ
import { Effect } from 'postprocessing'
export default class DrunkEffect extends Effect
{
constructor()
{
}
}
Effect
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor)
{
}
Basic vertex shader
const fragmentShader = /* glsl */`
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor)
{
}
`
// ...
Fragment shader
es6-string-html
for syntax highlighting (requires the/* glsl */
)- Uses
WebGL 2 syntax
const
 > parameter not writeablein
Â- copy of the actual variable
- changing it wonât affect the source variable
out
Â- changing this value will change the variable sent when calling the function.
Lets start boisâ
- Basic purple gradient
const fragmentShader = /* glsl */`
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor)
{
outputColor = vec4(uv, 1.0, 1.0);
}
`
- Hit up the
Effect
export default class DrunkEffect extends Effect
{
constructor()
{
super(
'DrunkEffect',
fragmentShader,
{
}
)
}
}
basic
Post Processing
effect donez
React-postprocessing implementationâ
Now, we need to implement it in react-postprocessing.
import DrunkEffect from './DrunkEffect.jsx'
export default function Drunk()
{
const effect = new DrunkEffect()
return <primitive object={ effect } />
}
...
Parent
<EffectComposer>
{/* ... */}
<Drunk />
</EffectComposer>
Boop
Propsâ
Goal: make the effect wiggle with a sinus function with a configurable amplitude / frequency
export default class DrunkEffect extends Effect
{
constructor(props)
{
super(
'DrunkEffect',
fragmentShader,
{
}
)
console.log(props)
}
}
- Use props to pass value
Referencing the primitiveâ
import { useRef } from 'react'
export default function Experience()
{
const drunkRef = useRef()
// ...
}
<Drunk
ref={ drunkRef }
frequency={ 2 }
amplitude={ 0.1 }
/>
- Use
ref
for easy manipulation - UseÂ
forwardRef
 from React (?) because of the warning above ^
import { forwardRef } from 'react'
export default forwardRef(function Drunk(props, ref)
{
const effect = new DrunkEffect(props)
return <primitive ref={ ref } object={ effect } />
Getting back the render and make it look greenishâ
Just need to update the vertexShader
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor)
{
vec4 color = inputColor;
color.rgb *= vec3(0.8, 1.0, 0.5);
outputColor = color;
}
Wiggle wiggle wiggleâ
- Make the image wiggle by tweaking
uv
coordinates - To alter UV coordinates directly, need to implement a
mainUv
function
uv
coordinates are used to map textures to surfacesmainImage
function is used for determining the final color of the pixelmainUv
function is for modifying theuv
coordinates
const fragmentShader = /* glsl */`
void mainUv(inout vec2 uv)
{
uv.y += sin(uv.x * 10.0) * 0.1;
}
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor)
{
// ...
}
`
Using attributesâ
import { Uniform } from 'three'
export default class DrunkEffect extends Effect {
constructor({ frequency, amplitude }) {
console.log(frequency, amplitude)
super(
'DrunkEffect',
fragmentShader,
{
uniforms: new Map([
[ 'frequency', new Uniform(frequency) ],
[ 'amplitude', new Uniform(amplitude) ]
])
}
)
// ...
}
}
- Need to use Map
Uniform
- standard way to do it
- enables some methods although we wonât use them.
const fragmentShader = /* glsl */`
uniform float frequency;
uniform float amplitude;
void mainUv(inout vec2 uv)
{
uv.y += sin(uv.x * frequency) * amplitude;
}
// ...
`
Controlling attributes with Leva
â
import { useControls } from 'leva'
export default function Experience()
{
// ...
const drunkProps = useControls('Drunk Effect', {
frequency: { value: 2, min: 1, max: 20 },
amplitude: { value: 0.1, min: 0, max: 1 }
})
// ...
<Drunk
ref={ drunkRef }
{ ...drunkProps }
/>
}
Blending the colorâ
- First, in theÂ
fragmentShader
, we are going to send the green color directly in theÂoutputColor
 and keep the alpha from theÂinputColor
:
const fragmentShader = /* glsl */`
// ...
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor)
{
outputColor = vec4(0.8, 1.0, 0.5, inputColor.a);
}
`
Everything is now green, but here comes the trick. (?? Idk why)
- Need to specify the
blendFunction
for the image to be back
<Drunk
ref={ drunkRef }
{ ...drunkProps }
blendFunction={ BlendFunction.DARKEN }
/>
super(
'DrunkEffect',
fragmentShader,
{
blendFunction: blendFunction,
// ...
}
)
Animatingâ
- Add
time
super(
'DrunkEffect',
fragmentShader,
{
blendFunction: blendFunction,
uniforms: new Map([
[ 'frequency', new Uniform(frequency) ],
[ 'amplitude', new Uniform(amplitude) ],
[ 'time', new Uniform(0) ]
])
}
)
- Update
fragmentShader
const fragmentShader = /* glsl */`
uniform float frequency;
uniform float amplitude;
uniform float time;
void mainUv(inout vec2 uv)
{
uv.y += sin(uv.x * frequency + time) * amplitude;
}
// ...
`
- Make use of
update
method (called every frame)
export default class DrunkEffect extends Effect
{
// ...
update()
{
console.log('update')
}
}
- Update
Map
export default class DrunkEffect extends Effect
{
// ...
update()
{
this.uniforms.get('time').value += 0.02
}
}
- Take into account frame rate
export default class DrunkEffect extends Effect
{
// ...
update(renderer, inputBuffer, deltaTime)
{
this.uniforms.get('time').value += deltaTime
}
}