Weighted-Blended.html (28226B)
1 <div id="content"> 2 <h1 id="content-title">Weighted Blended</h1> 3 <h1 id="content-url" style='display:none;'>Guest-Articles/2020/OIT/Weighted-Blended</h1> 4 <p> 5 Weighted, Blended is an approximate order-independent transparency technique which was published in the <a href="http://jcgt.org/published/0002/02/09/">journal of computer graphics techniques</a> in 2013 by Morgan McGuire and Louis Bavoil at NVIDIA to address the transparency problem on a broad class of then gaming platforms. 6 </p> 7 8 <p> 9 Their approach to avoid the cost of storing and sorting primitives or fragments is to alter the compositing operator so that it is order independent, thus allowing a pure streaming approach. 10 </p> 11 12 <p> 13 Most games have ad-hoc and scene-dependent ways of working around transparent surface rendering limitations. These include limited sorting, additive-only blending, and hard-coded render and composite ordering. Most of these methods also break at some point during gameplay and create visual artifacts. One not-viable alternative is <a href="http://developer.download.nvidia.com/SDK/10/opengl/screenshots/samples/dual_depth_peeling.html" target="_blank">depth peeling</a>, which produces good images, but is too slow for scenes with many layers both in theory and practice. 14 </p> 15 16 <p> 17 There are many <a href="https://en.wikipedia.org/wiki/Asymptotic_analysis" target="_blank">asymptotically</a> fast solutions for transparency rendering, such as bounded A-buffer approximations using programmable blending (e.g., <a href="http://software.intel.com/en-us/articles/multi-layer-alpha-blending">Marco Salvi's work</a>), stochastic transparency (as <a href="https://www.computer.org/csdl/journal/tg/2011/08/ttg2011081036/13rRUxBa55X" target="_blank">explained by Eric Enderton and others</a>), and ray tracing. One or more of these will probably dominate at some point, but all were impractical on the game platforms of five or six years ago, including PC DX11/GL4 GPUs, mobile devices with OpenGL ES 3.0 GPUs, and last-generation consoles like PlayStation 4. 18 </p> 19 20 <note> 21 In mathematical analysis, asymptotic analysis, also known as asymptotics, is a method of describing limiting behavior. 22 </note> 23 24 <p> 25 The below image is a transparent CAD view of a car engine rendered by this technique. 26 </p> 27 28 <img src="/img/guest/2020/oit/cad_view_of_an_engine.png" width="560" alt="A transparent CAD view of a car engine rendered by this technique."> 29 30 <h2>Theory</h2> 31 32 <p> 33 This technique renders non-refractive, monochrome transmission through surfaces that themselves have color, without requiring sorting or new hardware features. In fact, it can be implemented with a simple shader for any GPU that supports blending to render targets with more than 8 bits per channel. 34 </p> 35 36 <p> 37 It works best on GPUs with multiple render targets and floating-point texture, where it is faster than sorted transparency and avoids sorting artifacts and popping for particle systems. It also consumes less bandwidth than even a 4-deep RGBA8 K-buffer and allows mixing low-resolution particles with full-resolution surfaces such as glass. 38 </p> 39 40 <p> 41 For the mixed resolution case, the peak memory cost remains that of the higher resolution render target but bandwidth cost falls based on the proportional of low-resolution surfaces. 42 </p> 43 44 <p> 45 The basic idea of Weighted, Blended method is to compute the coverage of the background by transparent surfaces exactly, but to only approximate the light scattered towards the camera by the transparent surfaces themselves. The algorithm imposes a heuristic on inter-occlusion factor among transparent surfaces that increases with distance from the camera. 46 </p> 47 48 <note> 49 A heuristic technique, or a heuristic, is any approach to problem solving or self-discovery that employs a practical method that is not guaranteed to be optimal, perfect, or rational, but is nevertheless sufficient for reaching an immediate, short-term goal or approximation. In our case, the heuristic is the weighting function. 50 </note> 51 52 <p> 53 After all transparent surfaces have been rendered, it then performs a full-screen normalization and compositing pass to reduce errors where the heuristic was a poor approximation of the true inter-occlusion. 54 </p> 55 56 <p> 57 The below image is a glass chess set rendered with this technique. Note that the glass pieces are not refracting any light. 58 </p> 59 60 <img src="/img/guest/2020/oit/a_glass_chess_set.png" width="560" alt="A glass chess set rendered with this technique."> 61 62 <p> 63 For a better understanding and a more detailed explanation of the weight function, please refer to page 5, 6 and 7 of the original paper as the Blended OIT has been implemented and improved by different methods along the years. Link to the paper is provided at the end of this article. 64 </p> 65 66 <h2>Limitation</h2> 67 68 <p> 69 The primary limitation of the technique is that the weighting heuristic must be tuned for the anticipated depth range and opacity of transparent surfaces. 70 </p> 71 72 <p> 73 The technique was implemented in OpenGL for the <a href="http://g3d.sf.net/">G3D Innovation Engine</a> and DirectX for the <a href="http://www.unrealengine.com/">Unreal Engine</a> to produce the results live and in the paper. Dan Bagnell and Patrick Cozzi <a href="http://bagnell.github.io/cesium/Apps/Sandcastle/gallery/OIT.html">implemented it in WebGL</a> for their open-source Cesium engine (see also their <a href="http://cesiumjs.org/2014/03/14/Weighted-Blended-Order-Independent-Transparency/">blog post</a> discussing it). 74 </p> 75 76 <p> 77 From those implementations, a good set of weighting functions were found, which are reported in the journal paper. In the paper, they also discuss how to spot artifacts from a poorly-tuned weighting function and fix them. 78 </p> 79 80 <p> 81 Also, I haven't been able to find a proper way to implement this technique in a deferred renderer. Since pixels override each other in a deferred renderer, we lose information about the previous layers so we cannot correctly accumulate the color values for the lighting stage. 82 </p> 83 84 <p> 85 One feasible solution is to apply this technique as you would ordinarily do in a forward renderer. This is basically borrowing the transparency pass of a forward renderer and incorporate it in a deferred one. 86 </p> 87 88 <h2>Implementation</h2> 89 90 <p> 91 This technique is fairly straight forward to implement and the shader modifications are very simple. If you're familiar with how Framebuffers work in OpenGL, you're almost halfway there. 92 </p> 93 94 <p> 95 The only caveat is we need to write our code in OpenGL ^4.0 to be able to use blending to multiple render targets (e.g. utilizing <fun><function id='70'>glBlendFunc</function>i</fun>). In the paper, different ways of implementation have been discussed for libraries that do not support rendering or blending to multiple targets. 96 </p> 97 98 <warning>Don't forget to change your OpenGL version when initializng GLFW and also your GLSL version in your shaders.</warning> 99 100 <h3>Overview</h3> 101 102 <p> 103 During the transparent surface rendering, shade surfaces as usual, but output to two render targets. The first render target (<def>accum</def>) must have at least <var>RGBA16F</var> precision and the second (<def>revealage</def>) must have at least <var>R8</var> precision. Clear the first render target to <var>vec4(0)</var> and the second render target to 1 (using a pixel shader or <fun><function id='10'>glClear</function>Buffer</fun> + <fun><function id='10'>glClear</function></fun>). 104 </p> 105 106 <p> 107 Then, render the surfaces in any order to these render targets, adding the following to the bottom of the pixel shader and using the specified blending modes: 108 </p> 109 110 <pre><code> 111 // your first render target which is used to accumulate pre-multiplied color values 112 layout (location = 0) out vec4 accum; 113 114 // your second render target which is used to store pixel revealage 115 layout (location = 1) out float reveal; 116 117 ... 118 119 // output linear (not gamma encoded!), unmultiplied color from the rest of the shader 120 vec4 color = ... // regular shading code 121 122 // insert your favorite weighting function here. the color-based factor 123 // avoids color pollution from the edges of wispy clouds. the z-based 124 // factor gives precedence to nearer surfaces 125 float weight = 126 max(min(1.0, max(max(color.r, color.g), color.b) * color.a), color.a) * 127 clamp(0.03 / (1e-5 + pow(z / 200, 4.0)), 1e-2, 3e3); 128 129 // blend func: GL_ONE, GL_ONE 130 // switch to pre-multiplied alpha and weight 131 accum = vec4(color.rgb * color.a, color.a) * weight; 132 133 // blend func: GL_ZERO, GL_ONE_MINUS_SRC_ALPHA 134 reveal = color.a; 135 </code></pre> 136 137 <p> 138 Finally, after all surfaces have been rendered, composite the result onto the screen using a full-screen pass: 139 </p> 140 141 <pre><code> 142 // bind your accum render target to this texture unit 143 layout (binding = 0) uniform sampler2D rt0; 144 145 // bind your reveal render target to this texture unit 146 layout (binding = 1) uniform sampler2D rt1; 147 148 // shader output 149 out vec4 color; 150 151 // fetch pixel information 152 vec4 accum = texelFetch(rt0, int2(gl_FragCoord.xy), 0); 153 float reveal = texelFetch(rt1, int2(gl_FragCoord.xy), 0).r; 154 155 // blend func: GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA 156 color = vec4(accum.rgb / max(accum.a, 1e-5), reveal); 157 </code></pre> 158 159 <p> 160 Use this table as a reference for your render targets: 161 </p> 162 163 <table border="1"> 164 <tbody> 165 <tr><td>Render Target</td><td>Format</td><td>Clear</td><td>Src Blend</td><td>Dst Blend</td><td>Write ("Src")</td></tr> 166 <tr><td>accum</td><td>RGBA16F</td><td>(0,0,0,0)</td><td>ONE</td><td>ONE</td><td><code>(r*a, g*a, b*a, a) * w</code></td></tr> 167 <tr><td>revealage</td><td>R8</td><td>(1,0,0,0)</td><td>ZERO</td><td>ONE_MINUS_SRC_COLOR</td><td><code>a</code></td></tr> 168 </tbody> 169 </table> 170 171 <p> 172 A total of three rendering passes are needed to accomplish the finished result which is down below: 173 </p> 174 175 <img src="/img/guest/2020/oit/weighted_blended_result.png" width="640" alt="Weighted, Blended result."> 176 177 <h3>Details</h3> 178 179 <p> 180 To get started, we would have to setup a quad for our solid and transparent surfaces. The red quad will be the solid one, and the green and blue will be the transparent one. Since we're using the same quad for our screen quad as well, here we define UV values for texture mapping purposes at the screen pass. 181 </p> 182 183 <pre><code> 184 float quadVertices[] = { 185 // positions // uv 186 -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 187 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 188 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 189 190 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 191 -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 192 -1.0f, -1.0f, 0.0f, 0.0f, 0.0f 193 }; 194 195 // quad VAO 196 unsigned int quadVAO, quadVBO; 197 <function id='33'>glGenVertexArrays</function>(1, &quadVAO); 198 <function id='12'>glGenBuffers</function>(1, &quadVBO); 199 <function id='27'>glBindVertexArray</function>(quadVAO); 200 <function id='32'>glBindBuffer</function>(GL_ARRAY_BUFFER, quadVBO); 201 <function id='31'>glBufferData</function>(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW); 202 <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(0); 203 <function id='30'>glVertexAttribPointer</function>(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); 204 <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(1); 205 <function id='30'>glVertexAttribPointer</function>(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); 206 <function id='27'>glBindVertexArray</function>(0); 207 </code></pre> 208 209 <p> 210 Next, we will create two framebuffers for our solid and transparent passes. Our solid pass needs a color buffer and a depth buffer to store color and depth information. Our transparent pass needs two color buffers to store color accumulation and pixel revealage threshold. We will also attach the opaque framebuffer's depth texture to our transparent framebuffer, to utilize it for depth testing when rendering our transparent surfaces. 211 </p> 212 213 <pre><code> 214 // set up framebuffers 215 unsigned int opaqueFBO, transparentFBO; 216 <function id='76'>glGenFramebuffers</function>(1, &opaqueFBO); 217 <function id='76'>glGenFramebuffers</function>(1, &transparentFBO); 218 219 // set up attachments for opaque framebuffer 220 unsigned int opaqueTexture; 221 <function id='50'>glGenTextures</function>(1, &opaqueTexture); 222 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, opaqueTexture); 223 <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_HALF_FLOAT, NULL); 224 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 225 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 226 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); 227 228 unsigned int depthTexture; 229 <function id='50'>glGenTextures</function>(1, &depthTexture); 230 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, depthTexture); 231 <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT, 232 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); 233 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); 234 235 ... 236 237 // set up attachments for transparent framebuffer 238 unsigned int accumTexture; 239 <function id='50'>glGenTextures</function>(1, &accumTexture); 240 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, accumTexture); 241 <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_HALF_FLOAT, NULL); 242 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 243 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 244 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); 245 246 unsigned int revealTexture; 247 <function id='50'>glGenTextures</function>(1, &revealTexture); 248 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, revealTexture); 249 <function id='52'>glTexImage2D</function>(GL_TEXTURE_2D, 0, GL_R8, SCR_WIDTH, SCR_HEIGHT, 0, GL_RED, GL_FLOAT, NULL); 250 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 251 <function id='15'>glTexParameter</function>i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 252 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, 0); 253 254 ... 255 256 // don't forget to explicitly tell OpenGL that your transparent framebuffer has two draw buffers 257 const GLenum transparentDrawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; 258 glDrawBuffers(2, transparentDrawBuffers); 259 </code></pre> 260 261 <note> 262 For the sake of this article, we are creating two separate framebuffers, so it would be easier to understand how the technique unfolds. We could omit the opaque framebuffer and use backbuffer for our solid pass or just create a single framebuffer with four attachments all together (opaque, accumulation, revealage, depth) and render to different render targets at each pass. 263 </note> 264 265 <p> 266 Before rendering, setup some model matrices for your quads. You can set the Z axis however you want since this is an order-independent technique and objects closer or further to the camera would not impose any problem. 267 </p> 268 269 <pre><code>glm::mat4 redModelMat = calculate_model_matrix(glm::vec3(0.0f, 0.0f, 0.0f)); 270 glm::mat4 greenModelMat = calculate_model_matrix(glm::vec3(0.0f, 0.0f, 1.0f)); 271 glm::mat4 blueModelMat = calculate_model_matrix(glm::vec3(0.0f, 0.0f, 2.0f)); 272 </code></pre> 273 274 <p> 275 At this point, we have to perform our solid pass, so configure the render states and bind the opaque framebuffer. 276 </p> 277 278 <pre><code> 279 // configure render states 280 <function id='60'>glEnable</function>(GL_DEPTH_TEST); 281 <function id='66'>glDepthFunc</function>(GL_LESS); 282 <function id='65'>glDepthMask</function>(GL_TRUE); 283 glDisable(GL_BLEND); 284 <function id='13'><function id='10'>glClear</function>Color</function>(0.0f, 0.0f, 0.0f, 0.0f); 285 286 // bind opaque framebuffer to render solid objects 287 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, opaqueFBO); 288 <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 289 </code></pre> 290 291 <p> 292 We have to reset our depth function and depth mask for the solid pass at every frame since pipeline changes these states further down the line. 293 </p> 294 295 <p> 296 Now, draw the solid objects using the solid shader. You can draw alpha cutout objects both at this stage and the next stage as well. The solid shader is just a simple shader that transforms the vertices and draws the mesh with the supplied color. 297 </p> 298 299 <pre><code> 300 // use solid shader 301 solidShader.use(); 302 303 // draw red quad 304 solidShader.setMat4("mvp", vp * redModelMat); 305 solidShader.setVec3("color", glm::vec3(1.0f, 0.0f, 0.0f)); 306 <function id='27'>glBindVertexArray</function>(quadVAO); 307 <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); 308 </code></pre> 309 310 <p> 311 So far so good. For our transparent pass, like in the solid pass, configure the render states to blend to these render targets as below, then bind the transparent framebuffer and clear its two color buffers to <var>vec4(0.0f)</var> and <var>vec4(1.0)</var>. 312 </p> 313 314 <pre><code> 315 // configure render states 316 // disable depth writes so transparent objects wouldn't interfere with solid pass depth values 317 <function id='65'>glDepthMask</function>(GL_FALSE); 318 <function id='60'>glEnable</function>(GL_BLEND); 319 <function id='70'>glBlendFunc</function>i(0, GL_ONE, GL_ONE); // accumulation blend target 320 <function id='70'>glBlendFunc</function>i(1, GL_ZERO, GL_ONE_MINUS_SRC_COLOR); // revealge blend target 321 <function id='72'>glBlendEquation</function>(GL_FUNC_ADD); 322 323 // bind transparent framebuffer to render transparent objects 324 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, transparentFBO); 325 // use a four component float array or a glm::vec4(0.0) 326 <function id='10'>glClear</function>Bufferfv(GL_COLOR, 0, &zeroFillerVec[0]); 327 // use a four component float array or a glm::vec4(1.0) 328 <function id='10'>glClear</function>Bufferfv(GL_COLOR, 1, &oneFillerVec[0]); 329 </code></pre> 330 331 <p> 332 Then, draw the transparent surfaces with your preferred alpha values. 333 </p> 334 335 <pre><code> 336 // use transparent shader 337 transparentShader.use(); 338 339 // draw green quad 340 transparentShader.setMat4("mvp", vp * greenModelMat); 341 transparentShader.setVec4("color", glm::vec4(0.0f, 1.0f, 0.0f, 0.5f)); 342 <function id='27'>glBindVertexArray</function>(quadVAO); 343 <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); 344 345 // draw blue quad 346 transparentShader.setMat4("mvp", vp * blueModelMat); 347 transparentShader.setVec4("color", glm::vec4(0.0f, 0.0f, 1.0f, 0.5f)); 348 <function id='27'>glBindVertexArray</function>(quadVAO); 349 <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); 350 </code></pre> 351 352 <p> 353 The transparent shader is where half the work is done. It's primarily a shader that collects pixel information for our composite pass: 354 </p> 355 356 <pre><code> 357 // shader outputs 358 layout (location = 0) out vec4 accum; 359 layout (location = 1) out float reveal; 360 361 // material color 362 uniform vec4 color; 363 364 void main() 365 { 366 // weight function 367 float weight = clamp(pow(min(1.0, color.a * 10.0) + 0.01, 3.0) * 1e8 * 368 pow(1.0 - gl_FragCoord.z * 0.9, 3.0), 1e-2, 3e3); 369 370 // store pixel color accumulation 371 accum = vec4(color.rgb * color.a, color.a) * weight; 372 373 // store pixel revealage threshold 374 reveal = color.a; 375 } 376 </code></pre> 377 378 <p> 379 Note that, we are directly using the color passed to the shader as our final fragment color. Normally, if you are in a lighting shader, you want to use the final result of the lighting to store in accumulation and revealage render targets. 380 </p> 381 382 <p> 383 Now that everything has been rendered, we have to <def>composite</def> these two images so we can have the finished result. 384 </p> 385 386 <note> 387 Compositing is a common method in many techniques that use a post-processing quad drawn all over the screen. Think of it as merging two layers in a photo editing software like Photoshop or Gimp. 388 </note> 389 390 <p> 391 In OpenGL, we can achieve this by color blending feature: 392 </p> 393 394 <pre><code> 395 // set render states 396 <function id='66'>glDepthFunc</function>(GL_ALWAYS); 397 <function id='60'>glEnable</function>(GL_BLEND); 398 <function id='70'>glBlendFunc</function>(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 399 400 // bind opaque framebuffer 401 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, opaqueFBO); 402 403 // use composite shader 404 compositeShader.use(); 405 406 // draw screen quad 407 <function id='49'>glActiveTexture</function>(GL_TEXTURE0); 408 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, accumTexture); 409 <function id='49'>glActiveTexture</function>(GL_TEXTURE1); 410 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, revealTexture); 411 <function id='27'>glBindVertexArray</function>(quadVAO); 412 <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); 413 </code></pre> 414 415 <p> 416 Composite shader is where the other half of the work is done. We're basically merging two layers, one being the solid objects image and the other being the transparent objects image. Accumulation buffer tells us about the color and revealage buffer determines the visibility of the the underlying pixel: 417 </p> 418 419 <pre><code> 420 // shader outputs 421 layout (location = 0) out vec4 frag; 422 423 // color accumulation buffer 424 layout (binding = 0) uniform sampler2D accum; 425 426 // revealage threshold buffer 427 layout (binding = 1) uniform sampler2D reveal; 428 429 // epsilon number 430 const float EPSILON = 0.00001f; 431 432 // calculate floating point numbers equality accurately 433 bool isApproximatelyEqual(float a, float b) 434 { 435 return abs(a - b) <= (abs(a) < abs(b) ? abs(b) : abs(a)) * EPSILON; 436 } 437 438 // get the max value between three values 439 float max3(vec3 v) 440 { 441 return max(max(v.x, v.y), v.z); 442 } 443 444 void main() 445 { 446 // fragment coordination 447 ivec2 coords = ivec2(gl_FragCoord.xy); 448 449 // fragment revealage 450 float revealage = texelFetch(reveal, coords, 0).r; 451 452 // save the blending and color texture fetch cost if there is not a transparent fragment 453 if (isApproximatelyEqual(revealage, 1.0f)) 454 discard; 455 456 // fragment color 457 vec4 accumulation = texelFetch(accum, coords, 0); 458 459 // suppress overflow 460 if (isinf(max3(abs(accumulation.rgb)))) 461 accumulation.rgb = vec3(accumulation.a); 462 463 // prevent floating point precision bug 464 vec3 average_color = accumulation.rgb / max(accumulation.a, EPSILON); 465 466 // blend pixels 467 frag = vec4(average_color, 1.0f - revealage); 468 } 469 </code></pre> 470 471 <p> 472 Note that, we are using some helper functions like <fun>isApproximatelyEqual</fun> or <fun>max3</fun> to help us with the accurate calculation of floating-point numbers. Due to inaccuracy of floating-point numbers calculation in current generation processors, we need to compare our values with an extremely small amount called an <def>epsilon</def> to avoid underflows or overflows. 473 </p> 474 475 <p> 476 Also, we don't need an intermediate framebuffer to do compositing. We can use our opaque framebuffer as the base framebuffer and paint over it since it already has the opaque pass information. Plus, we're stating that all depth tests should pass since we want to paint over the opaque image. 477 </p> 478 479 <p> 480 Finally, draw your composited image (which is the opaque texture attachment since you rendered your transparent image over it in the last pass) onto the backbuffer and observe the result. 481 </p> 482 483 <pre><code> 484 // set render states 485 glDisable(GL_DEPTH); 486 <function id='65'>glDepthMask</function>(GL_TRUE); // enable depth writes so <function id='10'>glClear</function> won't ignore clearing the depth buffer 487 glDisable(GL_BLEND); 488 489 // bind backbuffer 490 <function id='77'>glBindFramebuffer</function>(GL_FRAMEBUFFER, 0); 491 <function id='13'><function id='10'>glClear</function>Color</function>(0.0f, 0.0f, 0.0f, 0.0f); 492 <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 493 494 // use screen shader 495 screenShader.use(); 496 497 // draw final screen quad 498 <function id='49'>glActiveTexture</function>(GL_TEXTURE0); 499 <function id='48'>glBindTexture</function>(GL_TEXTURE_2D, opaqueTexture); 500 <function id='27'>glBindVertexArray</function>(quadVAO); 501 <function id='1'>glDrawArrays</function>(GL_TRIANGLES, 0, 6); 502 </code></pre> 503 504 <p> 505 Screen shader is just a simple post-processing shader which draws a full-screen quad. 506 </p> 507 508 <p> 509 In a regular pipeline, you would also apply gamma-correction, tone-mapping, etc. in an intermediate post-processing framebuffer before you render to backbuffer, but ensure you are not applying them while rendering your solid and transparent surfaces and also not before composition since this transparency technique needs raw color values for calculating transparent pixels. 510 </p> 511 512 <p> 513 Now, the interesting part is to play with the Z axis of your objects to see order-independence in action. Try to place your transparent objects behind the solid object or mess up the orders entirely. 514 </p> 515 516 <img src="/img/guest/2020/oit/weighted_blended_reordered.png" width="640" alt="Weighted, Blended reordered."> 517 518 <p> 519 In the image above, the green quad is rendered after the red quad, but behind it, and if you move the camera around to see the green quad from behind, you won't see any artifacts. 520 </p> 521 522 <p> 523 As stated earlier, one limitation that this technique imposes is that for scenes with higher depth/alpha complexity we need to tune the weighting function to achieve the correct result. Luckily, a number of tested weighting functions are provided in the paper which you can refer and investigate them for your environment. 524 </p> 525 526 <p> 527 Be sure to also check the colored transmission transparency which is the improved version of this technique in the links below. 528 </p> 529 530 <p> 531 You can find the source code for this demo <a href="/code_viewer_gh.php?code=src/8.guest/2020/oit/weighted_blended.cpp" target="_blank">here</a>. 532 </p> 533 534 <h2>Further reading</h2> 535 536 <ul> 537 <li><a href="http://jcgt.org/published/0002/02/09" href="_blank">Weighted, Blended paper</a>: The original paper published in the journal of computer graphics. A brief history of the transparency and the emergence of the technique itself is provided. This is a must for the dedicated readers.</li> 538 <li><a href="http://casual-effects.blogspot.com/2014/03/weighted-blended-order-independent.html" href="_blank">Weighted, Blended introduction</a>: Casual Effects is Morgan McGuire's personal blog. This post is the introduction of their technique which goes into further details and is definitely worth to read. Plus, there are videos of their implementation live in action that you would not want to miss.</li> 539 <li><a href="http://casual-effects.blogspot.com/2015/03/implemented-weighted-blended-order.html" href="_blank">Weighted, Blended for implementors</a>: And also another blog post by him on implementing the technique for implementors.</li> 540 <li><a href="http://casual-effects.blogspot.com/2015/03/colored-blended-order-independent.html" href="_blank">Weighted, Blended and colored transmission</a>: And another blog post on colored transmission for transparent surfaces.</li> 541 <li><a href="http://bagnell.github.io/cesium/Apps/Sandcastle/gallery/OIT.html" href="_blank">A live implementation of the technique</a>: This is a live WebGL visualization from Cesium engine which accepts weighting functions for you to test in your browser!</li> 542 </ul> 543 544 <author> 545 <strong>Article by: </strong>Mahan Heshmati Moghaddam<br/> 546 <strong>Contact: </strong><a href="mailto:mahangm@gmail.com" target="_blank">e-mail</a> 547 </author> 548 549 </div> 550