Opengl - How to Create Order Independent Transparency

OpenGL - How to create Order Independent transparency?

I am using something similar to that answer linked by @RetoKoradi comment but I got double layered transparent models with textures (glass with both inner and outer surface) with fully solid machinery and stuff around.

For such scenes I am using also multi pass approach and the Z-sorting is done by sequence of setting front face.

  1. render all solid objects
  2. render all transparent objects

    This is the tricky part first I set

    glGetIntegerv(GL_DEPTH_FUNC,&depth_funct);
    glDepthFunc(GL_ALWAYS);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_CULL_FACE);

    I got the geometry layers stored separately (inner outer) so The Z-sorting is done like this:

    • Render outer layer back faces with glFrontFace(GL_CW);
    • Render inner layer back faces with glFrontFace(GL_CW);
    • Render inner layer front faces with glFrontFace(GL_CCW);
    • Render outer layer front faces with glFrontFace(GL_CCW);

    And lastly restore

    glDisable(GL_BLEND);
    glDepthFunc(depth_funct);
  3. render all solid objects again

It is far from perfect but enough for my purposes it looks like this:

example

Order independent transparency in legacy OpenGL

First things first: There are no dedicated legacy OpenGL extensions for order independent transparency. Period.

However there is one technique that can be used to implement depth peeling using the fixed function pipeline. The paper can be found here: https://my.eng.utah.edu/~cs5610/handouts/order_independent_transparency.pdf

Weighted, blended order independent transparency

The assumption is that you are rendering a number of "surfaces", indexed by i = 1, ..., n. Each surface has a color Ci and an alpha (opacity, or coverage) αi.

As for Multiple render targets, you could look it up on Wikipedia, but it's basically multiple buffers you can render to at once. For instance in OpenGL, a framebuffer object (FBO) can have multiple renderbuffer or texture objects attached to it.

Order independent transparency with MSAA

Without knowing how your code actually works, you can do it very much the same way that your linked DX11 demo does, since OpenGL provides the same features needed.

So in the first shader that just stores all the rendered fragments, you also store the sample coverage mask for each fragment (along with the color and depth, of course). This is given as fragment shader input variable int gl_SampleMaskIn[] and for each sample with id 32*i+j, bit j of glSampleMaskIn[i] is set if the fragment covers that sample (since you probably won't use >32xMSAA, you can usually just use glSampleMaskIn[0] and only need to store a single int as coverage mask).

...
fragment.color = inColor;
fragment.depth = gl_FragCoord.z;
fragment.coverage = gl_SampleMaskIn[0];
...

Then the final sort and render shader is run for each sample instead of just for each fragment. This is achieved implicitly by making use of the input variable int gl_SampleID, which gives us the ID of the current sample. So what we do in this shader (in addition to the non-MSAA version) is that the sorting step just accounts for the sample, by only adding a fragment to the final (to be sorted) fragment list if the current sample is actually covered by this fragment:

What was something like (beware, pseudocode extrapolated from your small snippet and the DX-link):

while(fragment.next != 0xFFFFFFFF)
{
fragment_list[count++] = vec2(fragment.depth, fragment.color);
fragment = fragments[fragment.next];
}

is now

while(fragment.next != 0xFFFFFFFF)
{
if(fragment.coverage & (1 << gl_SampleID))
fragment_list[count++] = vec2(fragment.depth, fragment.color);
fragment = fragments[fragment.next];
}

Or something along those lines.

EDIT: To your updated code, you have to increment fragment_count only inside the if(covered) block, since we don't want to add the fragment to the list if the sample is not covered. Incrementing it always will likely result in the artifacts you see at the edges, which are the regions where the MSAA (and thus the coverage) comes into play.

On the other hand the list pointer has to be forwarded (current_index = fragment.x) in each loop iteration and not only if the sample is covered, as otherwise it can result in an infinite loop, like in your case. So your code should look like:

while (current_index != 0 && fragment_count < MAX_FRAGMENTS )
{
uvec4 fragment = imageLoad(list_buffer, int(current_index));
uint coverage = fragment.w;
if((coverage &(1 << gl_SampleID))!=0)
fragment_list[fragment_count++] = fragment;
current_index = fragment.x;
}

opengl transparency order algorithm

After investigation it would appear NO there is no such way. See opengl.org/wiki/Transparency_Sorting. Basically the problem is ill defined since we are talking about 2d overlaps in 3d space so can get pathological cases where A>B B>C C>A: here there is no possible order which works.

The problem is that the alpha blending function

glBlendEquation(GL_FUNC_ADD);
glBendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

is not commutative: it relies on subtraction. This is why order matters: A-B != B-A. This however is the best way to do transparency which lots of complicated color effects etc. The way round this is either order independent transparency (eg depth peeling which is apprently costly) or brute force eg. divide up your polygons into arbitrary smaller ones until the sort can be done. This could be made more sophisticated by only dividing up your polygons along the lines where they overlap in the x-y dimensions of the view port. But it's still all a bit of a pain.

If on the other hand your transparency is a simple example (ie. when it explicitly doesn't make a difference to the visuals what order the transparencies are in eg. for me all transparent objects were rectangles of the same grey colour ) none of this matters! It's all a red herring: the problems comes from THINKING it does matter. What we need to do is stop providing information to the z-buffer and telling opengl that it needs to consider the order (which then subsequently gets messed up). So in this case we just need to write

glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);   
glEnable( GL_BLEND );
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glBegin(GL_TRIANGLES);

// draw here
glEnd();
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);

where the glDepthMask(GL_FALSE) is ensuring depth information isn't being written. You can rotate all you want and the transparencies look fine! But this is dependent on being in a situation where the image outcome DOESN'T depend on the order: it's just that with all the documentation you might think your situation does depend on it when it might not.

If it does depend on the order (it would look different from one angle as opposed to another using the above and you don't want it to) and you don't want to do fancy things or complicated sorts you can use a 'lesser' form of transparency by using commutative blending (multiplicative or additive). You still have to disable z writing and you can do things like

Multiplicative

glBlendFunc(GL_ZERO,GL_SRC_COLOR);   
glEnable( GL_BLEND );
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glBegin(GL_TRIANGLES);

// draw here
glEnd();
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);

Additive

glBlendFunc(GL_ONE,GL_ONE);   
glEnable( GL_BLEND );
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glBegin(GL_TRIANGLES);

// draw here
glEnd();
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);

and play about with these to see if they look OK. The point is try these 'lesser' approaches to transparency before looking into complicated sorts/order independent approaches because these will have much lower overheads and might work for your situation depending on what you are doing.



Related Topics



Leave a reply



Submit