đď¸ 16062024 1600
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/postprocessingpostprocessing
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
ToneMappingusesAgXtone 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â
multisamplingis 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
luminenceThresholdincreased

Seems like setting the
randbvalues to be > 1 cause it to start glowing more

Adding
mipmapBlurto<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
emissiveandemissiveIntensity - Can control the
intensityon<Bloom>instead


Setting the
luminanceThresholdto 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
nearandfar
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-htmlfor syntax highlighting (requires the/* glsl */)- Uses
WebGL 2 syntaxconst > 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 Processingeffect 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
reffor 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
uvcoordinates - To alter UV coordinates directly, need to implement a
mainUvfunction
uvcoordinates are used to map textures to surfacesmainImagefunction is used for determining the final color of the pixelmainUvfunction is for modifying theuvcoordinates
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
blendFunctionfor 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
updatemethod (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
}
}