Delayed Alpha blending

In this article I want to talk about methods for mixing rasterized geometry. The classic mixing models of translucent objects - Alpha, Additive, Multiplicative - are united by the same drawing principle: sequentially draw one primitive after another, mixing the pixels received at the output of the fragment shader with what is in the current buffer. Each new primitive updates the area of ​​the buffer into which it is drawn; in the case of alpha blending, the objects that are higher upshade previously drawn ones. But what if you want to do something with a group of objects drawn on top of the scene — for example, crop them by mask or highlight them? Here two decisions immediately come to mind: either make changes to their material (i.e. change the shader, expand the set of textures), for example, adding a projection of another texture,which will be responsible for the transparency mask. However, if we have a lot of mottled objects, changing each unique material is inconvenient and fraught with errors. The second option is to draw all the objects of interest to us into a separate full-screen target and draw it already on the final scene. Here we can do whatever we want with its contents, but this requires the allocation of extra memory and, most unpleasantly, switching target rendering. This is not the “cheapest” operation on mobile devices, which will need to be performed twice. And if you want to work with several layers like this?Here we can do whatever we want with its contents, but this requires the allocation of extra memory and, most unpleasantly, switching target rendering. This is not the “cheapest” operation on mobile devices, which will need to be performed twice. And if you want to work with several layers like this?Here we can do whatever we want with its contents, but this requires the allocation of extra memory and, most unpleasantly, switching target rendering. This is not the “cheapest” operation on mobile devices, which will need to be performed twice. And if you want to work with several layers like this?



There is another, simpler and more elegant way to solve these problems. We can paint the scene in reverse!

A small digression to remind you how the classic rendering method works
- Alpha Blending , , , . 4 RGBA, RGB — A(Alpha) — ( ). . :


ColorSrc — RGB , ( , ), ColorDst — , , Color_Result — , , , . Variable1 Variable2, ? , ( , ). , : , ... 

:


AlphaSrc — - (), OneMinusAlphaSrc, , 1.0 — AlphaSrc. : 1 * + 2 * (1 — ). alpha (). = 1, , = 0, . OpenGL .

OpenGL ES 2.0 — .

How the image is formed in steps: first we draw a background, then we draw all the objects in layers one after another. What is rendered last overwrites the previous pixels: 





What is the trick? 


The essence of the technology of reverse rendering or, as it can also be called, delayed mixing is as follows. We draw the scene backwards using a different blending formula. Moreover, the final image will remain exactly the same as with the classical approach.

How it works?


The method of mixing through the transparency channel of the image that we draw was described above. Now we will turn it the other way around: we will use the transparency of already drawn pixels (or rather, mixing the drawn transparency with the already drawn). That is, instead of AlphaSrc we will use AlphaSaturate, and instead of OneMinusAlphaSrc - One. It turns out that if there is already something with transparency = 1 in the buffer, then the contribution will be zero, and the color of such a pixel will not change. If there was zero transparency, let's add both colors together (for this we will need to clear the frame buffer with zeros or black with zero transparency). With this addition, the resulting color will be equal to the drawn. The final formula looks like this:
 

(approx. AlphaSaturate = min (AlphaSrc, 1 - AlphaDst))

The transparency values ​​need to be added: it must accumulate layer by layer, that is, we will have One and One in the mix variables for the alpha channel. Why don't we modify ColorDst and clear the buffer with zeros? This is necessary for Additive mixing, AdditiveBlending will only differ in that it will have Zero in the AlphaSrc variable. It should not modify transparency, only color.

For clarity, the reverse rendering scheme looks like this: 

First, we clear the frame buffer. Then we set the mixing function given above and start drawing from the topmost objects (in the classical approach, they would be drawn last), going down to the bottom ones. The background image will be drawn last.


How can this be used?


I will describe several tasks solved by this method, using our project as an example:

  1. Clipping objects by mask with transparency. Smooth clipping of the game room:


    After drawing the playing field, it is enough to clear the transparency in those places of the image that we want to hide. This is done using the mixing formula, in which the drawn mask object overwrites color and transparency inversely with its own transparency, and the degree of cleaning can be continuously adjusted. In this case, the following geometry is used for clipping:


    It changes its shape as the camera moves between rooms. The mixing formula for cleaning is as follows:

    ColorSrc = GL_ZERO,
    ColorDst = GL_ONE_MINUS_SRC_ALPHA,
    AlphaSrc = GL_ZERO,
    AlphaDst = GL_ONE_MINUS_SRC_ALPHA

    You can use any geometry with any textures, starting cleaning from the layer from which you will need:

  2. The smooth disappearance of the field is done similarly. The issue price is one DrawCall.
  3. :


    , UI, . , «» , , « », . , : , . :

    ColorSrc = GL_SRC_ALPHA,
    ColorDst = GL_ONE_MINUS_SRC_ALPHA,
    AlphaSrc = GL_ZERO,
    AlphaDst = GL_ONE
  4. , :


  5. «»:



    . , . — 2 DrawCalls.



Ambient occlusion





There is a downside, or rather restrictions: not all blends can be repeated for such a technique. Alpha mixing and additive are definitely possible, but you have to adapt or not use your own special blends. But there is a way out: you can separate the stages of rendering the scene. Part of it should be done by the reverse method, part of it is the usual one, and this is what we did for special effects on top of the field and post-process.

An important point with Additive and mixed rendering techniques: if it is drawn BEFORE the reverse rendering passage, and if there is no transparency information in the texture (texture like “white spot on a black background”), then such an object will overwrite transparency. In the “return” passage, information about this area will be lost, and visually it will look like a “dark square” or a black border around a light additive spot:


This can be overcome by modifying additive blending in terms of mixing the alpha channel:

AlphaSrc = GL_ONE_MINUS_DST_ALPHA
AlphaDst = GL_ONE

But this is not suitable for all types of mixing, and it will be more reliable to modify the texture itself. What is meant:

If there are textures of the form: 


Then you need to do one of them:


That is, the brightness of the color channels must be converted into transparency and stretch the colors inversely with transparency. The resulting and old texture should look the same on a black background. Manually this is unlikely to succeed, it makes sense to make an automatic converter. In this case, the channel conversion pseudo code will look like this:

RGB_old = Texel_in.rgb
A_old = Texel_in.a
A_middle = 1.0 / ((RGB_old) / 3.0) * A_old // linear color space
RGB_new = RGB_old * A_middle;
A_shift = minimum( 1.0 / RGB_new.r, 1.0)
A_shift = minimum( 1.0 / RGB_new.g, A_shift)
A_shift = minimum( 1.0 / RGB_new.b, A_shift)
RGB_result = RGB_new * A_shift; 
A_result = (RGB_result) / 3.0)
Texel_out = Vector4(RGB_result, A_result)



Here I will go through the steps of rendering the scene of our project
 
  1. . , -.

  2. , , , «» :


  3. UI:

  4. , , , :

  5. :

  6. :

  7. , .




Conclusion


The method makes it possible to work quite simply and cheaply with the layers of the scene being drawn, using the alpha channel as a mask. It is relatively simple to implement it in an already working project: it does not require deep modification of the graphics subsystem code, it is enough to change the rendering order and the mixing formula. In places, it can significantly save performance. There are limitations, but in most cases you can come to terms with them.

Source: https://habr.com/ru/post/undefined/


All Articles