Bloom.html (33621B)
1 <!DOCTYPE html> 2 <html lang="ja"> 3 <head> 4 <meta charset="utf-8"/> 5 <title>LearnOpenGL</title> 6 <link rel="shortcut icon" type="image/ico" href="/favicon.ico" /> 7 <link rel="stylesheet" href="../static/style.css" /> 8 <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"> </script> 9 <script src="/static/functions.js"></script> 10 </head> 11 <body> 12 <nav> 13 <ol> 14 <li id="Introduction"> 15 <a href="https://learnopengl.com/Introduction">はじめに</a> 16 </li> 17 <li id="Getting-started"> 18 <span class="closed">入門</span> 19 <ol> 20 <li id="Getting-started/OpenGL"> 21 <a href="https://learnopengl.com/Getting-started/OpenGL">OpenGL </a> 22 </li> 23 <li id="Getting-started/Creating-a-window"> 24 <a href="https://learnopengl.com/Getting-started/Creating-a-window">ウィンドウの作成</a> 25 </li> 26 <li id="Getting-started/Hello-Window"> 27 <a href="https://learnopengl.com/Getting-started/Hello-Window">最初のウィンドウ</a> 28 </li> 29 <li id="Getting-started/Hello-Triangle"> 30 <a href="https://learnopengl.com/Getting-started/Hello-Triangle">最初の三角形</a> 31 </li> 32 <li id="Getting-started/Shaders"> 33 <a href="https://learnopengl.com/Getting-started/Shaders">シェーダー</a> 34 </li> 35 <li id="Getting-started/Textures"> 36 <a href="https://learnopengl.com/Getting-started/Textures">テクスチャ</a> 37 </li> 38 <li id="Getting-started/Transformations"> 39 <a href="https://learnopengl.com/Getting-started/Transformations">座標変換</a> 40 </li> 41 <li id="Getting-started/Coordinate-Systems"> 42 <a href="https://learnopengl.com/Getting-started/Coordinate-Systems">座標系</a> 43 </li> 44 <li id="Getting-started/Camera"> 45 <a href="https://learnopengl.com/Getting-started/Camera">カメラ</a> 46 </li> 47 <li id="Getting-started/Review"> 48 <a href="https://learnopengl.com/Getting-started/Review">まとめ</a> 49 </li> 50 </ol> 51 </li> 52 <li id="Lighting"> 53 <span class="closed">Lighting </span> 54 <ol> 55 <li id="Lighting/Colors"> 56 <a href="https://learnopengl.com/Lighting/Colors">Colors </a> 57 </li> 58 <li id="Lighting/Basic-Lighting"> 59 <a href="https://learnopengl.com/Lighting/Basic-Lighting">Basic Lighting </a> 60 </li> 61 <li id="Lighting/Materials"> 62 <a href="https://learnopengl.com/Lighting/Materials">Materials </a> 63 </li> 64 <li id="Lighting/Lighting-maps"> 65 <a href="https://learnopengl.com/Lighting/Lighting-maps">Lighting maps </a> 66 </li> 67 <li id="Lighting/Light-casters"> 68 <a href="https://learnopengl.com/Lighting/Light-casters">Light casters </a> 69 </li> 70 <li id="Lighting/Multiple-lights"> 71 <a href="https://learnopengl.com/Lighting/Multiple-lights">Multiple lights </a> 72 </li> 73 <li id="Lighting/Review"> 74 <a href="https://learnopengl.com/Lighting/Review">Review </a> 75 </li> 76 </ol> 77 </li> 78 <li id="Model-Loading"> 79 <span class="closed">Model Loading </span> 80 <ol> 81 <li id="Model-Loading/Assimp"> 82 <a href="https://learnopengl.com/Model-Loading/Assimp">Assimp </a> 83 </li> 84 <li id="Model-Loading/Mesh"> 85 <a href="https://learnopengl.com/Model-Loading/Mesh">Mesh </a> 86 </li> 87 <li id="Model-Loading/Model"> 88 <a href="https://learnopengl.com/Model-Loading/Model">Model </a> 89 </li> 90 </ol> 91 </li> 92 <li id="Advanced-OpenGL"> 93 <span class="closed">Advanced OpenGL </span> 94 <ol> 95 <li id="Advanced-OpenGL/Depth-testing"> 96 <a href="https://learnopengl.com/Advanced-OpenGL/Depth-testing">Depth testing </a> 97 </li> 98 <li id="Advanced-OpenGL/Stencil-testing"> 99 <a href="https://learnopengl.com/Advanced-OpenGL/Stencil-testing">Stencil testing </a> 100 </li> 101 <li id="Advanced-OpenGL/Blending"> 102 <a href="https://learnopengl.com/Advanced-OpenGL/Blending">Blending </a> 103 </li> 104 <li id="Advanced-OpenGL/Face-culling"> 105 <a href="https://learnopengl.cm/Advanced-OpenGL/Face-culling">Face culling </a> 106 </li> 107 <li id="Advanced-OpenGL/Framebuffers"> 108 <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers">Framebuffers </a> 109 </li> 110 <li id="Advanced-OpenGL/Cubemaps"> 111 <a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">Cubemaps </a> 112 </li> 113 <li id="Advanced-OpenGL/Advanced-Data"> 114 <a href="https://learnopengl.com/Advanced-OpenGL/Advanced-Data">Advanced Data </a> 115 </li> 116 <li id="Advanced-OpenGL/Advanced-GLSL"> 117 <a href="https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL">Advanced GLSL </a> 118 </li> 119 <li id="Advanced-OpenGL/Geometry-Shader"> 120 <a href="https://learnopengl.com/Advanced-OpenGL/Geometry-Shader">Geometry Shader </a> 121 </li> 122 <li id="Advanced-OpenGL/Instancing"> 123 <a href="https://learnopengl.com/Advanced-OpenGL/Instancing">Instancing </a> 124 </li> 125 <li id="Advanced-OpenGL/Anti-Aliasing"> 126 <a href="https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing">Anti Aliasing </a> 127 </li> 128 </ol> 129 </li> 130 <li id="Advanced-Lighting"> 131 <span class="closed">Advanced Lighting </span> 132 <ol> 133 <li id="Advanced-Lighting/Advanced-Lighting"> 134 <a href="https://learnopengl.com/Advanced-Lighting/Advanced-Lighting">Advanced Lighting </a> 135 </li> 136 <li id="Advanced-Lighting/Gamma-Correction"> 137 <a href="https://learnopengl.com/Advanced-Lighting/Gamma-Correction">Gamma Correction </a> 138 </li> 139 <li id="Advanced-Lighting/Shadows"> 140 <span class="closed">Shadows </span> 141 <ol> 142 <li id="Advanced-Lighting/Shadows/Shadow-Mapping"> 143 <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping">Shadow Mapping </a> 144 </li> 145 <li id="Advanced-Lighting/Shadows/Point-Shadows"> 146 <a href="https://learnopengl.com/Advanced-Lighting/Shadows/Point-Shadows">Point Shadows </a> 147 </li> 148 </ol> 149 </li> 150 <li id="Advanced-Lighting/Normal-Mapping"> 151 <a href="https://learnopengl.com/Advanced-Lighting/Normal-Mapping">Normal Mapping </a> 152 </li> 153 <li id="Advanced-Lighting/Parallax-Mapping"> 154 <a href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Mapping </a> 155 </li> 156 <li id="Advanced-Lighting/HDR"> 157 <a href="https://learnopengl.com/Advanced-Lighting/HDR">HDR </a> 158 </li> 159 <li id="Advanced-Lighting/Bloom"> 160 <a href="https://learnopengl.com/Advanced-Lighting/Bloom">Bloom </a> 161 </li> 162 <li id="Advanced-Lighting/Deferred-Shading"> 163 <a href="https://learnopengl.com/Advanced-Lighting/Deferred-Shading">Deferred Shading </a> 164 </li> 165 <li id="Advanced-Lighting/SSAO"> 166 <a href="https://learnopengl.com/Advanced-Lighting/SSAO">SSAO </a> 167 </li> 168 </ol> 169 </li> 170 <li id="PBR"> 171 <span class="closed">PBR </span> 172 <ol> 173 <li id="PBR/Theory"> 174 <a href="https://learnopengl.com/PBR/Theory">Theory </a> 175 </li> 176 <li id="PBR/Lighting"> 177 <a href="https://learnopengl.com/PBR/Lighting">Lighting </a> 178 </li> 179 <li id="PBR/IBL"> 180 <span class="closed">IBL </span> 181 <ol> 182 <li id="PBR/IBL/Diffuse-irradiance"> 183 <a href="https://learnopengl.com/PBR/IBL/Diffuse-irradiance">Diffuse irradiance </a> 184 </li> 185 <li id="PBR/IBL/Specular-IBL"> 186 <a href="https://learnopengl.com/PBR/IBL/Specular-IBL">Specular IBL </a> 187 </li> 188 </ol> 189 </li> 190 </ol> 191 </li> 192 <li id="In-Practice"> 193 <span class="closed">In Practice </span> 194 <ol> 195 <li id="In-Practice/Debugging"> 196 <a href="https://learnopengl.com/In-Practice/Debugging">Debugging </a> 197 </li> 198 <li id="In-Practice/Text-Rendering"> 199 <a href="https://learnopengl.com/In-Practice/Text-Rendering">Text Rendering </a> 200 </li> 201 <li id="In-Practice/2D-Game"> 202 <span class="closed">2D Game </span> 203 <ol> 204 <li id="In-Practice/2D-Game/Breakout"> 205 <a href="https://learnopengl.com/In-Practice/2D-Game/Breakout">Breakout </a> 206 </li> 207 <li id="In-Practice/2D-Game/Setting-up"> 208 <a href="https://learnopengl.com/In-Practice/2D-Game/Setting-up">Setting up </a> 209 </li> 210 <li id="In-Practice/2D-Game/Rendering-Sprites"> 211 <a href="https://learnopengl.com/In-Practice/2D-Game/Rendering-Sprites">Rendering Sprites </a> 212 </li> 213 <li id="In-Practice/2D-Game/Levels"> 214 <a href="https://learnopengl.com/In-Practice/2D-Game/Levels">Levels </a> 215 </li> 216 <li id="In-Practice/2D-Game/Collisions"> 217 <span class="closed">Collisions </span> 218 <ol> 219 <li id="In-Practice/2D-Game/Collisions/Ball"> 220 <a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Ball">Ball </a> 221 </li> 222 <li id="In-Practice/2D-Game/Collisions/Collision-detection"> 223 <a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-detection">Collision detection </a> 224 </li> 225 <li id="In-Practice/2D-Game/Collisions/Collision-resolution"> 226 <a href="https://learnopengl.com/In-Practice/2D-Game/Collisions/Collision-resolution">Collision resolution </a> 227 </li> 228 </ol> 229 </li> 230 <li id="In-Practice/2D-Game/Particles"> 231 <a href="https://learnopengl.com/In-Practice/2D-Game/Particles">Particles </a> 232 </li> 233 <li id="In-Practice/2D-Game/Postprocessing"> 234 <a href="https://learnopengl.com/In-Practice/2D-Game/Postprocessing">Postprocessing </a> 235 </li> 236 <li id="In-Practice/2D-Game/Powerups"> 237 <a href="https://learnopengl.com/In-Practice/2D-Game/Powerups">Powerups </a> 238 </li> 239 <li id="In-Practice/2D-Game/Audio"> 240 <a href="https://learnopengl.com/In-Practice/2D-Game/Audio">Audio </a> 241 </li> 242 <li id="In-Practice/2D-Game/Render-text"> 243 <a href="https://learnopengl.com/In-Practice/2D-Game/Render-text">Render text </a> 244 </li> 245 <li id="In-Practice/2D-Game/Final-thoughts"> 246 <a href="https://learnopengl.com/In-Practice/2D-Game/Final-thoughts">Final thoughts </a> 247 </li> 248 </ol> 249 </li> 250 </ol> 251 </li> 252 <li id="Guest-Articles"> 253 <span class="closed">Guest Articles </span> 254 <ol> 255 <li id="Guest-Articles/How-to-publish"> 256 <a href="https://learnopengl.com/Guest-Articles/How-to-publish">How to publish </a> 257 </li> 258 <li id="Guest-Articles/2020"> 259 <span class="closed">2020 </span> 260 <ol> 261 <li id="Guest-Articles/2020/OIT"> 262 <span class="closed">OIT </span> 263 <ol> 264 <li id="Guest-Articles/2020/OIT/Introduction"> 265 <a href="https://learnopengl.com/Guest-Articles/2020/OIT/Introduction">Introduction </a> 266 </li> 267 <li id="Guest-Articles/2020/OIT/Weighted-Blended"> 268 <a href="https://learnopengl.com/Guest-Articles/2020/OIT/Weighted-Blended">Weighted Blended </a> 269 </li> 270 </ol> 271 </li> 272 <li id="Guest-Articles/2020/Skeletal-Animation"> 273 <a href="https://learnopengl.com/Guest-Articles/2020/Skeletal-Animation">Skeletal Animation </a> 274 </li> 275 </ol> 276 </li> 277 <li id="Guest-Articles/2021"> 278 <span class="closed">2021 </span> 279 <ol> 280 <li id="Guest-Articles/2021/CSM"> 281 <a href="https://learnopengl.com/Guest-Articles/2021/CSM">CSM </a> 282 </li> 283 <li id="Guest-Articles/2021/Scene"> 284 <span class="closed">Scene </span> 285 <ol> 286 <li id="Guest-Articles/2021/Scene/Scene-Graph"> 287 <a href="https://learnopengl.com/Guest-Articles/2021/Scene/Scene-Graph">Scene Graph </a> 288 </li> 289 <li id="Guest-Articles/2021/Scene/Frustum-Culling"> 290 <a href="https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling">Frustum Culling </a> 291 </li> 292 </ol> 293 </li> 294 <li id="Guest-Articles/2021/Tessellation"> 295 <span class="closed">Tessellation </span> 296 <ol> 297 <li id="Guest-Articles/2021/Tessellation/Height-map"> 298 <a href="https://learnopengl.com/Guest-Articles/2021/Tessellation/Height-map">Height map </a> 299 </li> 300 </ol> 301 </li> 302 </ol> 303 </li> 304 </ol> 305 </li> 306 <li id="Code-repository"> 307 <a href="https://learnopengl.com/Code-repository">Code repository </a> 308 </li> 309 <li id="Translations"> 310 <a href="https://learnopengl.com/Translations">Translations </a> 311 </li> 312 <li id="About"> 313 <a href="https://learnopengl.com/About">About </a> 314 </li> 315 </ol> 316 </nav> 317 <main> 318 <h1 id="content-title">Bloom</h1> 319 <h1 id="content-url" style='display:none;'>Advanced-Lighting/Bloom</h1> 320 <p> 321 Bright light sources and brightly lit regions are often difficult to convey to the viewer as the intensity range of a monitor is limited. One way to distinguish bright light sources on a monitor is by making them glow; the light then <em>bleeds</em> around the light source. This effectively gives the viewer the illusion these light sources or bright regions are intensely bright. 322 </p> 323 324 <p> 325 This light bleeding, or glow effect, is achieved with a post-processing effect called <def>Bloom</def>. Bloom gives all brightly lit regions of a scene a glow-like effect. An example of a scene with and without glow can be seen below (image courtesy of Epic Games): 326 </p> 327 328 <img src="/img/advanced-lighting/bloom_example.png" "Example of Bloom or glow in OpenGL tutorial"/> 329 330 <p> 331 Bloom gives noticeable visual cues about the brightness of objects. When done in a subtle fashion (which some games drastically fail to do) Bloom significantly boosts the lighting of your scene and allows for a large range of dramatic effects. 332 </p> 333 334 <p> 335 Bloom works best in combination with <a href="https://learnopengl.com/Advanced-Lighting/HDR" target="_blank">HDR</a> rendering. A common misconception is that HDR is the same as Bloom as many people use the terms interchangeably. They are however completely different techniques used for different purposes. It is possible to implement Bloom with default 8-bit precision framebuffers, just as it is possible to use HDR without the Bloom effect. It is simply that HDR makes Bloom more effective to implement (as we'll later see). 336 </p> 337 338 <p> 339 To implement Bloom, we render a lit scene as usual and extract both the scene's HDR color buffer and an image of the scene with only its bright regions visible. This extracted brightness image is then blurred and the result added on top of the original HDR scene image. 340 </p> 341 342 <p> 343 Let's illustrate this process in a step by step fashion. We render a scene filled with 4 bright light sources, visualized as colored cubes. The colored light cubes have a brightness values between <code>1.5</code> and <code>15.0</code>. If we were to render this to an HDR color buffer the scene looks as follows: 344 </p> 345 346 <img src="/img/advanced-lighting/bloom_scene.png" class="clean" alt="Image of a HDR scene where we need to add the bloom or glow effect in OpenGL"/> 347 348 <p> 349 We take this HDR color buffer texture and extract all the fragments that exceed a certain brightness. This gives us an image that only show the bright colored regions as their fragment intensities exceeded a certain threshold: 350 </p> 351 352 <img src="/img/advanced-lighting/bloom_extracted.png" class="clean" alt="Bright regions extracted of a scene for the bloom or glow post-processing effect in OpenGL"/> 353 354 <p> 355 We then take this thresholded brightness texture and blur the result. The strength of the bloom effect is largely determined by the range and strength of the blur filter used. 356 </p> 357 358 <img src="/img/advanced-lighting/bloom_blurred.png" class="clean" alt="Bright regions extracted for glow or bloom effect are blurred in OpenGL"/> 359 360 <p> 361 The resulting blurred texture is what we use to get the glow or light-bleeding effect. This blurred texture is added on top of the original HDR scene texture. Because the bright regions are extended in both width and height due to the blur filter, the bright regions of the scene appear to glow or <em>bleed</em> light. 362 </p> 363 364 <img src="/img/advanced-lighting/bloom_small.png" class="clean" alt="Example of the Bloom or Glow post-processing effect in OpenGL with HDR"/> 365 366 <p> 367 Bloom by itself isn't a complicated technique, but difficult to get exactly right. Most of its visual quality is determined by the quality and type of blur filter used for blurring the extracted brightness regions. Simply tweaking the blur filter can drastically change the quality of the Bloom effect. 368 </p> 369 370 <p> 371 Following these steps gives us the Bloom post-processing effect. The next image briefly summarizes the required steps for implementing Bloom: 372 </p> 373 374 <img src="/img/advanced-lighting/bloom_steps.png" class="clean" alt="Steps required for implementing the bloom or glow post-processing effect in OpenGL"/> 375 376 <p> 377 The first step requires us to extract all the bright colors of a scene based on some threshold. Let's first delve into that. 378 </p> 379 380 <h2>Extracting bright color</h2> 381 <p> 382 The first step requires us to extract two images from a rendered scene. We could render the scene twice, both rendering to a different framebuffer with different shaders, but we can also use a neat little trick called <def>Multiple Render Targets (MRT)</def> that allows us to specify more than one fragment shader output; this gives us the option to extract the first two images in a single render pass. By specifying a layout location specifier before a fragment shader's output we can control to which color buffer a fragment shader writes to: 383 </p> 384 385 <pre><code> 386 layout (location = 0) out vec4 FragColor; 387 layout (location = 1) out vec4 BrightColor; 388 </code></pre> 389 390 <p> 391 This only works if we actually have multiple buffers to write to. As a requirement for using multiple fragment shader outputs we need multiple color buffers attached to the currently bound framebuffer object. You may remember from the <a href="https://learnopengl.com/Advanced-OpenGL/Framebuffers" target="_blank">framebuffers</a> chapter that we can specify a color attachment number when linking a texture as a framebuffer's color buffer. Up until now we've always used <var>GL_COLOR_ATTACHMENT0</var>, but by also using <var>GL_COLOR_ATTACHMENT1</var> we can have two color buffers attached to a framebuffer object: 392 </p> 393 394 <pre><code> 395 // set up floating point framebuffer to render scene to 396 unsigned int hdrFBO; 397 <function id='76'>glGenFramebuffers</function>(1, &hdrFBO); 398 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, hdrFBO); 399 unsigned int colorBuffers[2]; 400 <function id='50'>glGenTextures</function>(2, colorBuffers); 401 for (unsigned int i = 0; i < 2; i++) 402 { 403 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, colorBuffers[i]); 404 <function id='52'>glTexImage2D</function>( 405 GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL 406 ); 407 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 408 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 409 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 410 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 411 // attach texture to framebuffer 412 <function id='81'>glFramebufferTexture2D</function>( 413 GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, colorBuffers[i], 0 414 ); 415 } 416 </code></pre> 417 418 <p> 419 We do have to explicitly tell OpenGL we're rendering to multiple colorbuffers via <fun>glDrawBuffers</fun>. OpenGL, by default, only renders to a framebuffer's first color attachment, ignoring all others. We can do this by passing an array of color attachment enums that we'd like to render to in subsequent operations: 420 </p> 421 422 <pre><code> 423 unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; 424 glDrawBuffers(2, attachments); 425 </code></pre> 426 427 <p> 428 When rendering into this framebuffer, whenever a fragment shader uses the layout location specifier, the respective color buffer is used to render the fragment to. This is great as this saves us an extra render pass for extracting bright regions as we can now directly extract them from the to-be-rendered fragment: 429 </p> 430 431 <pre><code> 432 #version 330 core 433 layout (location = 0) out vec4 FragColor; 434 layout (location = 1) out vec4 BrightColor; 435 436 [...] 437 438 void main() 439 { 440 [...] // first do normal lighting calculations and output results 441 FragColor = vec4(lighting, 1.0); 442 // check whether fragment output is higher than threshold, if so output as brightness color 443 float brightness = dot(FragColor.rgb, vec3(0.2126, 0.7152, 0.0722)); 444 if(brightness > 1.0) 445 BrightColor = vec4(FragColor.rgb, 1.0); 446 else 447 BrightColor = vec4(0.0, 0.0, 0.0, 1.0); 448 } 449 </code></pre> 450 451 <p> 452 Here we first calculate lighting as normal and pass it to the first fragment shader's output variable <var>FragColor</var>. Then we use what is currently stored in <var>FragColor</var> to determine if its brightness exceeds a certain threshold. We calculate the brightness of a fragment by properly transforming it to grayscale first (by taking the dot product of both vectors we effectively multiply each individual component of both vectors and add the results together). If the brightness exceeds a certain threshold, we output the color to the second color buffer. We do the same for the light cubes. 453 </p> 454 455 <p> 456 This also shows why Bloom works incredibly well with HDR rendering. Because we render in high dynamic range, color values can exceed <code>1.0</code> which allows us to specify a brightness threshold outside the default range, giving us much more control over what is considered bright. Without HDR we'd have to set the threshold lower than <code>1.0</code>, which is still possible, but regions are much quicker considered bright. This sometimes leads to the glow effect becoming too dominant (think of white glowing snow for example). 457 </p> 458 459 <p> 460 With these two color buffers we have an image of the scene as normal, and an image of the extracted bright regions; all generated in a single render pass. 461 </p> 462 463 <img src="/img/advanced-lighting/bloom_attachments.png" alt="Image of two colorbuffers obtained from a single render pass with multiple color attachments for the bloom or glow effect in OpenGL"/> 464 465 <p> 466 With an image of the extracted bright regions we now need to blur the image. We can do this with a simple box filter as we've done in the post-processing section of the framebufers chapter, but we'd rather use a more advanced (and better-looking) blur filter called <def>Gaussian blur</def>. 467 </p> 468 469 <h2>Gaussian blur</h2> 470 <p> 471 In the post-processing chapter's blur we took the average of all surrounding pixels of an image. While it does give us an easy blur, it doesn't give the best results. A Gaussian blur is based on the Gaussian curve which is commonly described as a <em>bell-shaped curve</em> giving high values close to its center that gradually wear off over distance. The Gaussian curve can be mathematically represented in different forms, but generally has the following shape: 472 </p> 473 474 <img src="/img/advanced-lighting/bloom_gaussian.png" class="clean" alt="Image of a Gaussian Curve used for blurring a bloom or glow image in OpenGL"/> 475 476 <p> 477 As the Gaussian curve has a larger area close to its center, using its values as weights to blur an image give more natural results as samples close by have a higher precedence. If we for instance sample a 32x32 box around a fragment, we use progressively smaller weights the larger the distance to the fragment; this gives a better and more realistic blur which is known as a <def>Gaussian blur</def>. 478 </p> 479 480 <p> 481 To implement a Gaussian blur filter we'd need a two-dimensional box of weights that we can obtain from a 2 dimensional Gaussian curve equation. The problem with this approach however is that it quickly becomes extremely heavy on performance. Take a blur kernel of 32 by 32 for example, this would require us to sample a texture a total of 1024 times for each fragment! 482 </p> 483 484 <p> 485 Luckily for us, the Gaussian equation has a very neat property that allows us to separate the two-dimensional equation into two smaller one-dimensional equations: one that describes the horizontal weights and the other that describes the vertical weights. We'd then first do a horizontal blur with the horizontal weights on the scene texture, and then on the resulting texture do a vertical blur. Due to this property the results are exactly the same, but this time saving us an incredible amount of performance as we'd now only have to do 32 + 32 samples compared to 1024! This is known as <def>two-pass Gaussian blur</def>. 486 </p> 487 488 <img src="/img/advanced-lighting/bloom_gaussian_two_pass.png" class="clean" alt="Image of two-pass Gaussian blur with the same results as normal gaussian blur, but now saving a lot of performance in OpenGL"/> 489 490 <p> 491 This does mean we need to blur an image at least two times and this works best with the use of framebuffer objects. Specifically for the two-pass Gaussian blur we're going to implement <em>ping-pong</em> framebuffers. That is a pair of framebuffers where we render and swap, a given number of times, the other framebuffer's color buffer into the current framebuffer's color buffer with an alternating shader effect. We basically continuously switch the framebuffer to render to and the texture to draw with. This allows us to first blur the scene's texture in the first framebuffer, then blur the first framebuffer's color buffer into the second framebuffer, and then the second framebuffer's color buffer into the first, and so on. 492 </p> 493 494 <p> 495 Before we delve into the framebuffers let's first discuss the Gaussian blur's fragment shader: 496 </p> 497 498 <pre><code> 499 #version 330 core 500 out vec4 FragColor; 501 502 in vec2 TexCoords; 503 504 uniform sampler2D image; 505 506 uniform bool horizontal; 507 uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); 508 509 void main() 510 { 511 vec2 tex_offset = 1.0 / textureSize(image, 0); // gets size of single texel 512 vec3 result = texture(image, TexCoords).rgb * weight[0]; // current fragment's contribution 513 if(horizontal) 514 { 515 for(int i = 1; i < 5; ++i) 516 { 517 result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i]; 518 result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i]; 519 } 520 } 521 else 522 { 523 for(int i = 1; i < 5; ++i) 524 { 525 result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i]; 526 result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i]; 527 } 528 } 529 FragColor = vec4(result, 1.0); 530 } 531 </code></pre> 532 533 <p> 534 Here we take a relatively small sample of Gaussian weights that we each use to assign a specific weight to the horizontal or vertical samples around the current fragment. You can see that we split the blur filter into a horizontal and vertical section based on whatever value we set the <var>horizontal</var> uniform. We base the offset distance on the exact size of a texel obtained by the division of <code>1.0</code> over the size of the texture (a <code>vec2</code> from <fun>textureSize</fun>). 535 </p> 536 537 <p> 538 For blurring an image we create two basic framebuffers, each with only a color buffer texture: 539 </p> 540 541 <pre><code> 542 unsigned int pingpongFBO[2]; 543 unsigned int pingpongBuffer[2]; 544 <function id='76'>glGenFramebuffers</function>(2, pingpongFBO); 545 <function id='50'>glGenTextures</function>(2, pingpongBuffer); 546 for (unsigned int i = 0; i < 2; i++) 547 { 548 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, pingpongFBO[i]); 549 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, pingpongBuffer[i]); 550 <function id='52'>glTexImage2D</function>( 551 GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL 552 ); 553 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 554 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 555 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 556 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 557 <function id='81'>glFramebufferTexture2D</function>( 558 GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongBuffer[i], 0 559 ); 560 } 561 </code></pre> 562 563 <p> 564 Then after we've obtained an HDR texture and an extracted brightness texture, we first fill one of the ping-pong framebuffers with the brightness texture and then blur the image 10 times (5 times horizontally and 5 times vertically): 565 </p> 566 567 <pre><code> 568 bool horizontal = true, first_iteration = true; 569 int amount = 10; 570 shaderBlur.use(); 571 for (unsigned int i = 0; i < amount; i++) 572 { 573 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, pingpongFBO[horizontal]); 574 shaderBlur.setInt("horizontal", horizontal); 575 <function id='48'>glBindTexture</function>( 576 GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongBuffers[!horizontal] 577 ); 578 RenderQuad(); 579 horizontal = !horizontal; 580 if (first_iteration) 581 first_iteration = false; 582 } 583 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); 584 </code></pre> 585 586 <p> 587 Each iteration we bind one of the two framebuffers based on whether we want to blur horizontally or vertically and bind the other framebuffer's color buffer as the texture to blur. The first iteration we specifically bind the texture we'd like to blur (<var>brightnessTexture</var>) as both color buffers would else end up empty. By repeating this process 10 times, the brightness image ends up with a complete Gaussian blur that was repeated 5 times. This construct allows us to blur any image as often as we'd like; the more Gaussian blur iterations, the stronger the blur. 588 </p> 589 590 <p> 591 By blurring the extracted brightness texture 5 times, we get a properly blurred image of all bright regions of a scene. 592 </p> 593 594 <img src="/img/advanced-lighting/bloom_blurred_large.png" class="clean" alt="Blurred image using Gaussian Blur of extracted brightness regions for the glow or bloom effect in OpenGL"/> 595 596 <p> 597 The last step to complete the Bloom effect is to combine this blurred brightness texture with the original scene's HDR texture. 598 </p> 599 600 <h2>Blending both textures</h2> 601 <p> 602 With the scene's HDR texture and a blurred brightness texture of the scene we only need to combine the two to achieve the infamous Bloom or glow effect. In the final fragment shader (largely similar to the one we used in the <a href="https://learnopengl.com/Advanced-Lighting/HDR" target="_blank">HDR</a> chapter) we additively blend both textures: 603 </p> 604 605 <pre><code> 606 #version 330 core 607 out vec4 FragColor; 608 609 in vec2 TexCoords; 610 611 uniform sampler2D scene; 612 uniform sampler2D bloomBlur; 613 uniform float exposure; 614 615 void main() 616 { 617 const float gamma = 2.2; 618 vec3 hdrColor = texture(scene, TexCoords).rgb; 619 vec3 bloomColor = texture(bloomBlur, TexCoords).rgb; 620 hdrColor += bloomColor; // additive blending 621 // tone mapping 622 vec3 result = vec3(1.0) - exp(-hdrColor * exposure); 623 // also gamma correct while we're at it 624 result = pow(result, vec3(1.0 / gamma)); 625 FragColor = vec4(result, 1.0); 626 } 627 </code></pre> 628 629 <p> 630 Interesting to note here is that we add the Bloom effect before we apply tone mapping. This way, the added brightness of bloom is also softly transformed to LDR range with better relative lighting as a result. 631 </p> 632 633 <p> 634 With both textures added together, all bright areas of our scene now get a proper glow effect: 635 </p> 636 637 <img src="/img/advanced-lighting/bloom.png" class="clean" alt="Example of the Bloom or Glow post-processing effect in OpenGL with HDR"/> 638 639 <p> 640 The colored cubes now appear much more bright and give a better illusion as light emitting objects. This is a relatively simple scene so the Bloom effect isn't too impressive here, but in well lit scenes it can make a significant difference when properly configured. You can find the source code of this simple demo <a href="/code_viewer_gh.php?code=src/5.advanced_lighting/7.bloom/bloom.cpp" target="_blank">here</a>. 641 </p> 642 643 <p> 644 For this chapter we used a relatively simple Gaussian blur filter where we only take 5 samples in each direction. By taking more samples along a larger radius or repeating the blur filter an extra number of times we can improve the blur effect. As the quality of the blur directly correlates to the quality of the Bloom effect, improving the blur step can make a significant improvement. Some of these improvements combine blur filters with varying sized blur kernels or use multiple Gaussian curves to selectively combine weights. The additional resources from Kalogirou and Epic Games discuss how to significantly improve the Bloom effect by improving the Gaussian blur. 645 </p> 646 647 <h2>Additional resources</h2> 648 <ul> 649 <li><a href="http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/" target="_blank">Efficient Gaussian Blur with linear sampling</a>: descirbes the Gaussian blur very well and how to improve its performance using OpenGL's bilinear texture sampling.</li> 650 <li><a href="https://udk-legacy.unrealengine.com/udk/Three/Bloom.html" target="_blank">Bloom Post Process Effect</a>: article from Epic Games about improving the Bloom effect by combining multiple Gaussian curves for its weights.</li> 651 <li><a href="http://kalogirou.net/2006/05/20/how-to-do-good-bloom-for-hdr-rendering/" target="_blank">How to do good Bloom for HDR rendering</a>: Article from Kalogirou that describes how to improve the Bloom effect using a better Gaussian blur method.</li> 652 </ul> 653 654 655 656 657 </div> 658 659 </main> 660 </body> 661 </html>