I'm trying to use a framebuffer as a Geometry Buffer for deferred shading. I'm having issues with writing and reading from the framebuffer's color attachments.
All I am trying to do is verify that my framebuffer's color attachments have some data. I do this by binding one of the color attachments and drawing a fullscreen quad. Each color attachment results in a fully black screen even though I've verified that my uniform variables are receiving the data they need.
My framebuffer is setup as follows:
glGenFramebuffers(1, &FBOID);
glBindFramebuffer(GL_FRAMEBUFFER, FBOID);
int WIDTH = windowDetails->width;
int HEIGHT = windowDetails->height;
glGenTextures(1, &gPosition);
glBindTexture(GL_TEXTURE_2D, gPosition);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, WIDTH, HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0);
glGenTextures(1, &gAlbedo);
glBindTexture(GL_TEXTURE_2D, gAlbedo);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, WIDTH, HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gAlbedo, 0);
glGenTextures(1, &gNormal);
glBindTexture(GL_TEXTURE_2D, gNormal);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, WIDTH, HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gNormal, 0);
glGenTextures(1, &gEffects);
glBindTexture(GL_TEXTURE_2D, gEffects);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, WIDTH, HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D, gEffects, 0);
GLuint attachments[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
glDrawBuffers(4, attachments);
glGenRenderbuffers(1, &zBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, zBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, WIDTH, HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, zBuffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
std::cout << "Framebuffer not complete !" << std::endl;
}
Every frame, I will bind this framebuffer and draw to it using my geometry shader. Then I will bind a test shader to check if the contents of the color attachements have some data by binding all of the color attachments to their own texture unit and passing one of them to the test shader:
glDisable(GL_BLEND); // No blend for deffered rendering
glEnable(GL_DEPTH_TEST); // Enable depth testing for scene render
glBindFramebuffer(GL_FRAMEBUFFER, FBOID); // Start drawing to FBO
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(gShader);
SubmittedGeometry& geometry = defferedGeometry[0];
glm::mat4 projViewModel = projection * view * geometry.transform;
glm::mat4& prevProjViewModel = prevProjViewModels.count(geometry.handle) <= 0 ? projViewModel : prevProjViewModels.at(geometry.handle);
prevProjViewModels.insert({ geometry.handle, projViewModel });
glUniformMatrix4fv(matModelLoc, geometry.transform);
glUniformMatrix4fv(matProjViewLoc, projViewModel);
glUniformMatrix4fv(matPrevProjeViewLoc, prevProjViewModel);
// Bind albedo textures
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, albedoTexID);
glUniform1i(albedoLoc, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, normalTexID);
glUniform1i(normalLoc, 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, roughnessTexID);
glUniform1i(rougnessLoc, 2);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, metalnessTexID);
glUniform1i(metalnessLoc, 3);
glActiveTexture(GL_TEXTURE4);
glBindTexture(GL_TEXTURE_2D, aoTexID);
glUniform1i(aoLoc, 4);
glBindVertexArray(geometry.vaoID);
glDrawElements(GL_TRIANGLES, geometry.indices, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glBindFramebuffer(GL_FRAMEBUFFER, 0); // Done drawing to FBO
// Test FBO color attachments
glUseProgram(testShaderID);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gPosition);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gAlbedo);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_2D, gEffects);
glUniform1i(testTextureLoc, 1);
quad->Draw();
Assigning the testTexture sampler with 0, 1, 2, or 3 all result in a black screen.
Geometry Shader:
VERTEX SHADER
#version 420
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec3 vNormal;
layout (location = 2) in vec2 vTextureCoordinates;
layout (location = 3) in vec3 vBiNormal;
layout (location = 4) in vec3 vTangent;
uniform mat4 uMatModel;
uniform mat4 uMatView;
uniform mat4 uMatProjection;
uniform mat4 uMatProjViewModel;
uniform mat4 uMatPrevProjViewModel;
out vec3 mViewPosition;
out vec2 mTextureCoordinates;
out vec3 mNormal;
out vec4 mFragPosition;
out vec4 mPrevFragPosition;
void main()
{
// Translate to view space
vec4 viewFragmentPosition = uMatView * uMatModel * vec4(vPosition, 1.0f);
mViewPosition = viewFragmentPosition.xyz;
mTextureCoordinates = vTextureCoordinates;
// Apply transformation to normal
mat3 matNormal = transpose(inverse(mat3(uMatView * uMatModel)));
mNormal = matNormal * vNormal;
mFragPosition = uMatProjViewModel * vec4(vPosition, 1.0f);
mPrevFragPosition = uMatPrevProjViewModel * vec4(vPosition, 1.0f);
gl_Position = uMatProjection * viewFragmentPosition;
};
FRAGMENT SHADER
#version 420
layout (location = 0) out vec4 gPosition;
layout (location = 1) out vec4 gAlbedo;
layout (location = 2) out vec4 gNormal;
layout (location = 3) out vec3 gEffects;
in vec3 mViewPosition;
in vec2 mTextureCoordinates;
in vec3 mNormal;
in vec4 mFragPosition;
in vec4 mPrevFragPosition;
uniform sampler2D uAlbedoTexture1;
uniform sampler2D uNormalTexture;
uniform sampler2D uRoughnessTexture;
uniform sampler2D uMetalnessTexture;
uniform sampler2D uAmbientOcculsionTexture;
const float nearPlane = 1.0f;
const float farPlane = 1000.0f;
float LinearizeDepth(float depth);
vec3 ComputeTextureNormal(vec3 viewNormal, vec3 textureNormal);
void main()
{
vec3 normal = normalize(texture(uNormalTexture, mTextureCoordinates).rgb * 2.0f - 1.0f); // Sample normal texture and convert values in range from -1.0 to 1.0
vec2 fragPos = (mFragPosition.xy / mFragPosition.w) * 0.5f + 0.5f;
vec2 prevFragPos = (mPrevFragPosition.xy / mPrevFragPosition.w) * 0.5f + 0.5f;
gPosition = vec4(mViewPosition, LinearizeDepth(gl_FragCoord.z)); // Set position with adjusted depth
gAlbedo.rgb = vec3(texture(uAlbedoTexture1, mTextureCoordinates)); // Sample and assign albedo rgb colors
gAlbedo.a = vec3(texture(uRoughnessTexture, mTextureCoordinates)).r; // Sample and assign roughness value
gNormal.rgb = ComputeTextureNormal(mNormal, normal); // Assign normal
gNormal.a = vec3(texture(uMetalnessTexture, mTextureCoordinates)).r; // Sample and assign metalness value
gEffects.r = vec3(texture(uAmbientOcculsionTexture, mTextureCoordinates)).r;
gEffects.gb = fragPos - prevFragPos;
}
float LinearizeDepth(float depth)
{
float z = depth * 2.0f - 1.0f;
return (2.0f * nearPlane * farPlane) / (farPlane + nearPlane - z * (farPlane - nearPlane));
}
vec3 ComputeTextureNormal(vec3 viewNormal, vec3 textureNormal)
{
// Get partial derivatives
vec3 dPosX = dFdx(mViewPosition);
vec3 dPosY = dFdy(mViewPosition);
vec2 dTexX = dFdx(mTextureCoordinates);
vec2 dTexY = dFdy(mTextureCoordinates);
// Convert normal to tangent space
vec3 normal = normalize(viewNormal);
vec3 tangent = normalize(dPosX * dTexY.t - dPosY * dTexX.t);
vec3 binormal = -normalize(cross(normal, tangent));
mat3 TBN = mat3(tangent, binormal, normal);
return normalize(TBN * textureNormal);
}
And my test shader code is:
VERTEX SHADER
#version 420
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec2 vTextureCoordinates;
out vec2 mTextureCoordinates;
void main()
{
mTextureCoordinates = vTextureCoordinates; // Pass out texture coords
gl_Position = vec4(vPosition, 1.0f);
};
FRAGMENT SHADER
#version 420
in vec2 mTextureCoordinates;
out vec4 oColor;
uniform sampler2D testTexture;
void main()
{
vec3 color = texture(testTexture, mTextureCoordinates).rgb;
oColor = vec4(color, 1.0f);
}
I've made sure that glCheckFramebufferStatus is always complete and that all of my uniform variables are being passed correctly in to the shader
Turns out my code here is correct. My issue was that I was crossing the wrong vectors so my camera's view matrix was wrong.