1
$\begingroup$

I'm trying to implement voxel world using OpenGL (Core 3.3). I've come across a problem with transparency. It looks like sometimes GPU decides to not render stuff that would be important. Can you help me diagnose the problem, please? (I thought this problem can be related to sorting (I don't perform any extra sorting at the moment), but I've changed "clear color" to glClearColor(1.0f, 1.0f, 1.0f, 0.01f); and the "gaps" are still noticeable)

EDIT: I've found a resource that claims that it is indeed sorting problem and drawing background triangles at first should solve this issue. However every 'chunk' (16x16 blocks' column) is rendered as a separate batch with single draw call. Is there a way to preserve this batch rendering?

Here are some pictures:

enter image description here enter image description here

enter image description here enter image description here

The Ftagment Shader is very simple:

#version 330 core
out vec4 FragColor;
in vec2 v_TexCoord;

uniform sampler2D u_Texture;

void main()
{
    vec4 texColor = texture(u_Texture, v_TexCoord);
    if(texColor.a < 0.2)
        discard;
    FragColor = texColor;
}

Depth test and blending:

void Renderer::clear() const {
    glClearColor(0.53f, 0.8f, 0.92f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

//Before render loop

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);

Do you have any idea how can I fix this problem?

$\endgroup$

2 Answers 2

1
$\begingroup$

It depends on the order of rendering. When first rendering the object which is far away and then rendering the closer object, the transparency will work. Otherwise when first rendering the close object, you depthbuffer will block the object which is behind. There are different ways to manage this issue:

1:First render all opaque geometries. and then render the transparent.

This gives you NOT the correct output, because other transparent objects which are behind transparent objects will cause the same problem you described. But opaque objects will always be visible.

2:Use other rendertechniques like Order independent transparencies (OIT)

Here you create a list of fragments (pixels) for each pixel on your screen. The list of fragments must hold the depth and color value. when all geometry is rendered, you can iterate through each list and sort the fragments by the depth value and render them again from back to front. This will give you a very good final image.

This technique can be improved when first enable depth writing, depth testing and rendering the opaque objects. Disable depth writing, enable depth testing. And then render the transparent ones. This will shrink your list size.

$\endgroup$
4
  • 1
    $\begingroup$ In case using OIT: you can store the list in a Shader Storage Buffer Object (SSBO) and use atomicAdd(), atomicCompSwap() and other atomic functions within GLSL (shader) to avoid writing to the same memory location from different GPU "Threads" . $\endgroup$
    – Thomas
    Commented Sep 23, 2021 at 6:32
  • 1
    $\begingroup$ I'll implement this and hopefully it'll solve the problem. (Unfortunately I do not have enough reputation to cast a vote for you). Thank you for your answers. $\endgroup$ Commented Sep 23, 2021 at 10:06
  • $\begingroup$ @PawełPomierski you are welcome =) $\endgroup$
    – Thomas
    Commented Sep 24, 2021 at 11:39
  • $\begingroup$ @PawełPomierski have in mind, that you can also render your blocks from back to front... if your datastructure (chunks) allow this $\endgroup$
    – Thomas
    Commented Sep 24, 2021 at 11:40
0
$\begingroup$

I can't actually spot the problem from your pictures, but the problem you describe is a very common one for new developers trying to implement transparency, so I'll assume it's there.

The problem:

Let's consider what happens to a single pixel. In boring old opaque rendering, we expect the pixel to have the frontmost triangle at that pixel (closest to the camera, lowest Z value). This can be done with a depth buffer: the GPU stores the Z coordinate of the frontmost triangle that touches the pixel so far, and overwrites the pixel when drawing a triangle in front of that, and doesn't overwrite it when drawing a triangle behind that. Therefore the final result has the colour from the frontmost triangle.

However this simple algorithm only works for fully opaque triangles. With a translucent triangle, instead of just overwriting the colour of the pixel, the GPU combines it with the colour already in the colour buffer. If some of the triangles are skipped because of the depth buffer algorithm, the final colour is wrong. The only case when it's right is when the triangles are drawn in back-to-front order. And if you use the typical alpha blending algorithm for translucency, then you can't just solve it by turning off the depth buffer, because the colour is calculated differently depending on which order the triangles are rendered in, and it's only correct when they are drawn in back-to-front order (when you get the correct result whether the depth buffer is on or off).

The solution:

If you only have fully opaque and fully transparent pixels, a really easy solution is to enable alpha test. Pixels with 0 alpha will be fully discarded and won't update the depth buffer. Pixels with 1.0 alpha will act as opaque.

glEnable(GL_ALPHA_TEST); // <- literally that simple

If you have partially translucent pixels, then you have two options: either actually render front-to-back or implement one of the schemes of order-independent transparency. All of these options have significant tradeoffs. Fortunately, you appear to have only fully transparent pixels, so it is not necessary to delve into either.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.