加权混合顺序无关的透明度渲染问题

I'm implementing the OIT method from this article and I just cannot seem the get the correct results based on his reference image. When I try reproduce his reference (well, not EXACTLY) it comes out like this:

enter image description here

Issue

So the revealage output looks good but its the accumulation one that is all wrong. For starters, there is no depth testing. Both the blue and red sqaures should be behind or in front of the yellow square. There also isn't any alpha blending with the black background due to the three middle squares all having 0.75 alpha values.

Then in the composite pass it also doesn't seem to use the revealage texture to dictate the depths of each object.

From what I can tell with most of the OpenGL code, is that execution order is crucial and I when debugging (been going on for three days now), I get different results when moving something like gl.DepthMask(false) or gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) up by one line.

Code

func (o *OpenGL) draw() {   
    gl.ClearColor(clearColor.R, clearColor.G, clearColor.B, clearColor.A)
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)


    // DRAW OPAQUE FIRST (although the issue isn't really here, I just put it in incase)
    gl.BindFramebuffer(gl.FRAMEBUFFER, o.opaqueFBO)
    gl.DrawBuffer(gl.COLOR_ATTACHMENT0)
    gl.Enable(gl.DEPTH_TEST)
    gl.DepthMask(true)
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
    gl.Disable(gl.BLEND)
    gl.UseProgram(o.opaqueShader)

    // Set uniforms
    // Draw opaque objects

    gl.UseProgram(0)

    // THEN TRANSPARENT
    gl.BindFramebuffer(gl.FRAMEBUFFER, o.transparentFBO)
    gl.DrawBuffers(2, &[]uint32{gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1}[0])
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
    gl.ClearBufferfv(gl.COLOR, 0, &[]float32{0, 0, 0, 1}[0])
    gl.ClearBufferfv(gl.COLOR, 1, &[]float32{1, 1, 1, 1}[0])
    gl.Enable(gl.DEPTH_TEST)
    gl.DepthMask(false)
    gl.Enable(gl.BLEND)
    gl.BlendFunci(0, gl.ONE, gl.ONE)
    gl.BlendFunci(1, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA)

    gl.UseProgram(o.transparentShader)

    // Set uniforms
    // Draw opaque objects

    gl.UseProgram(0)

    // COMPOSITE PASS

    gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
    gl.Disable(gl.DEPTH_TEST)
    gl.Disable(gl.BLEND)

    gl.UseProgram(o.compositeShader)

    gl.ActiveTexture(gl.TEXTURE0)
    gl.BindTexture(gl.TEXTURE_2D, o.transparentTex0)
    gl.Uniform1i(uniformLocation(o.compositeShader, "ColorTex0\x00"), int32(0))

    gl.ActiveTexture(gl.TEXTURE1)
    gl.BindTexture(gl.TEXTURE_2D, o.transparentTex1)
    gl.Uniform1i(uniformLocation(o.compositeShader, "ColorTex1\x00"), int32(1))

    gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, gl.PtrOffset(0))
    gl.BindVertexArray(0)
    gl.UseProgram(0)


    // RENDER A SPECIFIC TEXTURE FROM A FRAMEBUFFER. FOR TESTING THIS DRAWS OVER WHAT HAS ALREADY BEEN DRAWN.

    // A.
    // o.renderFramebuffer(o.transparentFBO, o.transparentTex0)

    // B.
    // o.renderFramebuffer(o.transparentFBO, o.transparentTex1)
}


func (o *OpenGL) renderFramebuffer(fbo uint32, tid uint32) {
    // FBO DEBUGGER
    gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
    gl.UseProgram(o.framebufferShader)

    gl.ActiveTexture(gl.TEXTURE0)
    gl.BindTexture(gl.TEXTURE_2D, tid)
    gl.Uniform1i(uniformLocation(o.framebufferShader, "Texture\x00"), int32(0))

    // draw fullscreen quad

    gl.UseProgram(0)
    o.window.SwapBuffers()
}

At the bottom of the draw function are the two functions I used to generate the revealage and accumu images from my testing reference.

Shaders

Here is the fragment shader from o.transparentShader

#version 450 core
layout(location=0) in vec2 vTexCoord;

uniform vec4 uColor;

layout(location=0, index=0) out vec4 oFragData0;
layout(location=0, index=1) out vec4 oFragData1;

float w(float z, float a) {
    return a * max(
        pow(10.0, -2.0),
        min(
            3.0 * pow(10.0, 3.0),
            10.0 /
            (
                pow(10.0, -5.0) + 
                pow(abs(z) / 5.0, 2.0) +
                pow(abs(z) / 200.0, 6.0)
            )
        )
    );
}
void main() {
    vec4 Ci = uColor;

    float ai = Ci.a;
    float zi = gl_FragCoord.z;

    float wresult = w(zi, ai);
    oFragData0 = vec4(Ci.rgb * wresult, ai);
    oFragData1.r = ai * wresult;
}

And the fragment shader from o.compositeShader

#version 450 core
layout(location=0) out vec4 outColor;

uniform sampler2D ColorTex0;
uniform sampler2D ColorTex1;

out vec4 oFragColor;

void main() {
    vec4 accum = texelFetch(ColorTex0, ivec2(gl_FragCoord.xy), 0);
    float r = accum.a;
    accum.a = texelFetch(ColorTex1, ivec2(gl_FragCoord.xy), 0).r;
    vec4 transparentColor = vec4(accum.rgb / clamp(accum.a, 1e-4, 5e4), r);
    oFragColor = transparentColor * (1.0 - r);
}

Final Note

I stripped out all the error checking to keep the question minimal. I can confirm there are no errors with the shaders, framebuffers attachments, or gl.GetError() during the execution of my test output.

I also removed most of the code relating to the drawing of opaque objects since this issue is about the transparent objects.

Update #1 (testing without framebuffers)

As an act of desperation I've stripped out all the code related to the FBOs and I came across some interesting results relating to blending...

DEPTH_TEST enabled | BLEND disabled | DEPTH_MASK false enter image description hereDEPTH_TEST disabled | BLEND enabled | DEPTH_MASK false enter image description hereDEPTH_TEST enabled | BLEND enabled | DEPTH_MASK false enter image description here

As ridiculous as it might sound, it actually looks like it can do blending and depth testing separately, but when combined it just does a little bit of both and then gives up... Could this have something to do with the blend functions?

Update #2 (near perfect results based on the NVidia reference)

With a few brand new debugging tools and a better understanding of how OIT works, I've been able to get very close to the results I am hoping for. The final flaw being that the opaque objects (the scaled yellow square and the rotated cyan one) are darkened.

enter image description here

I got this far by tweaking the BlendFunc values and learning a few hard lessons about when to use which from each of these pairs:

  • texture vs texelFetch
  • GL_TEXTURE_2D vs GL_TEXTURE_RECT
  • RGBA16F vs RGBA
  • ONE_MINUS_SRC_ALPHA vs ONE_MINUS_SRC_COLOR

I highly recommend fully understanding the differences before attempting MRT, Framebuffers, or OIT. Every reference made use of a different combination of these values.

Update #3 (final)

So after completely distancing myself from all the references and avoiding trying to copy their code across, I looked at the reference image from the first paragraph and just implemented it my own way.

enter image description here

I'm going to leave this question for a while before I submit an answer because I'm still a bit skeptic and I want to run more tests (or perhaps hear from you guys?).