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:
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.
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.
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);
}
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.
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 DEPTH_TEST disabled | BLEND enabled | DEPTH_MASK false
DEPTH_TEST enabled | BLEND enabled | DEPTH_MASK false
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?
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.
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.
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.
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?).