Post-processing effects are a crucial aspect of modern computer graphics, adding polish and realism to rendered scenes. By applying these effects after the initial rendering pass, developers can achieve a wide range of visual enhancements, from subtle color corrections to dramatic lighting effects. In this blog, we’ll explore the fundamentals of post-processing effects using shaders, providing key concepts, practical examples, and tips to optimize their performance.
What Are Post-Processing Effects?
Post-processing effects are applied after the initial rendering of a scene, hence the term "post-processing." These effects are executed in screen space, meaning they operate on the final image rather than individual 3D objects within the scene. This approach allows for a wide range of visual enhancements without the need for complex modifications to the scene itself.
Common post-processing effects include:
- Bloom: Adds a glowing effect to bright areas, simulating how bright lights bleed over into surrounding areas.
- Motion Blur: Blurs moving objects to give a sense of speed and motion.
- Depth of Field: Blurs distant objects while keeping the focal point sharp, mimicking camera lens behavior.
- Color Grading: Adjusts colors to achieve a specific look or mood.
- Screen Space Ambient Occlusion (SSAO): Simulates subtle shadows in crevices and corners to add depth. ## Implementing Post-Processing Effects Post-processing effects are typically implemented using fragment shaders. The rendered scene is first captured into a texture, which is then processed by the shader to apply the desired effect. Below, we will go through examples of implementing some common post-processing effects using GLSL (OpenGL Shading Language).
Example 1: Bloom Effect
The bloom effect makes bright areas of the scene appear to glow. This is achieved by blurring bright areas and then adding them back to the original image.
// Bloom Fragment Shader
uniform sampler2D scene;
uniform float threshold;
uniform float intensity;
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec3 color = texture(scene, uv).rgb;
vec3 bloom = vec3(0.0);
// Extract bright areas
if (dot(color, vec3(0.299, 0.587, 0.114)) > threshold) {
bloom = color;
}
// Blur bright areas (simple box blur for demonstration)
vec3 blurred = vec3(0.0);
float blurRadius = 1.0 / resolution.xy;
for (float x = -1.0; x <= 1.0; x += 1.0) {
for (float y = -1.0; y <= 1.0; y += 1.0) {
blurred += texture(scene, uv + vec2(x, y) * blurRadius).rgb;
}
}
blurred /= 9.0;
// Combine original scene with blurred bloom
vec3 finalColor = color + blurred * intensity;
gl_FragColor = vec4(finalColor, 1.0);
}
Example 2: Motion Blur
Motion blur simulates the blurring of moving objects. This effect requires velocity information for each pixel, which can be precomputed and passed to the shader.
// Motion Blur Fragment Shader
uniform sampler2D scene;
uniform sampler2D velocity;
uniform float blurAmount;
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
vec3 color = texture(scene, uv).rgb;
vec2 vel = texture(velocity, uv).xy * blurAmount;
// Accumulate colors along the motion vector
vec3 blurredColor = vec3(0.0);
int samples = 10;
for (int i = 0; i < samples; ++i) {
blurredColor += texture(scene, uv + vel * (i / float(samples - 1))).rgb;
}
blurredColor /= samples;
gl_FragColor = vec4(blurredColor, 1.0);
}
Example 3: Depth of Field
Depth of field simulates the blurring of objects based on their distance from the camera, focusing on a specific depth range.
// Depth of Field Fragment Shader
uniform sampler2D scene;
uniform sampler2D depth;
uniform float focalDepth;
uniform float focalRange;
uniform float blurAmount;
void main() {
vec2 uv = gl_FragCoord.xy / resolution.xy;
float depthValue = texture(depth, uv).r;
float blur = clamp(abs(depthValue - focalDepth) / focalRange, 0.0, 1.0) * blurAmount;
// Accumulate blurred colors
vec3 color = vec3(0.0);
int samples = 16;
for (int i = 0; i < samples; ++i) {
float angle = float(i) * 3.14159265 * 2.0 / float(samples);
vec2 offset = vec2(cos(angle), sin(angle)) * blur;
color += texture(scene, uv + offset).rgb;
}
color /= samples;
gl_FragColor = vec4(color, 1.0);
}
Top comments (0)