Supermodel/Src/Graphics/New3D/R3DFrameBuffers.cpp
Ian Curtis cdf5e4b2b2 Fix transparency depth testing
The two transparency layers, might not be separate layers at all. We believe the hardware is writing each layer to every other pixel (stipple alpha), then using an anti-aliasing filter to effectively blend the pixels. Because the pixels don't overlap they don't depth test against each other. We are using separate layers to emulate this, so the depth buffer must be saved and restored between the layers.
2023-11-17 16:07:20 +00:00

348 lines
9.1 KiB
C++

#include "R3DFrameBuffers.h"
namespace New3D {
R3DFrameBuffers::R3DFrameBuffers()
{
m_frameBufferID = 0;
m_renderBufferID = 0;
m_frameBufferIDCopy = 0;
m_renderBufferIDCopy = 0;
m_width = 0;
m_height = 0;
m_vao = 0;
for (auto &i : m_texIDs) {
i = 0;
}
m_lastLayer = Layer::none;
AllocShaderTrans();
AllocShaderBase();
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
// no states needed since we do it in the shader
glBindVertexArray(0);
}
R3DFrameBuffers::~R3DFrameBuffers()
{
DestroyFBO();
m_shaderTrans.UnloadShaders();
m_shaderBase.UnloadShaders();
if (m_vao) {
glDeleteVertexArrays(1, &m_vao);
m_vao = 0;
}
}
bool R3DFrameBuffers::CreateFBO(int width, int height)
{
m_width = width;
m_height = height;
m_texIDs[0] = CreateTexture(width, height); // colour buffer
m_texIDs[1] = CreateTexture(width, height); // trans layer1
m_texIDs[2] = CreateTexture(width, height); // trans layer2
glGenFramebuffers(1, &m_frameBufferID);
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID);
// colour attachments
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texIDs[0], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, m_texIDs[1], 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, m_texIDs[2], 0);
// depth/stencil attachment
glGenRenderbuffers(1, &m_renderBufferID);
glBindRenderbuffer(GL_RENDERBUFFER, m_renderBufferID);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_renderBufferID);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_renderBufferID);
// check setup was successful
auto fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, 0); //created R3DFrameBuffers now disable it
CreateFBODepthCopy(width, height);
return (fboStatus == GL_FRAMEBUFFER_COMPLETE);
}
bool R3DFrameBuffers::CreateFBODepthCopy(int width, int height)
{
glGenFramebuffers(1, &m_frameBufferIDCopy);
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferIDCopy);
glGenRenderbuffers(1, &m_renderBufferIDCopy);
glBindRenderbuffer(GL_RENDERBUFFER, m_renderBufferIDCopy);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_renderBufferIDCopy);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, m_renderBufferIDCopy);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// check setup was successful
auto fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
return (fboStatus == GL_FRAMEBUFFER_COMPLETE);
}
void R3DFrameBuffers::StoreDepth()
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_frameBufferID);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_frameBufferIDCopy);
glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
}
void R3DFrameBuffers::RestoreDepth()
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_frameBufferIDCopy);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_frameBufferID);
glBlitFramebuffer(0, 0, m_width, m_height, 0, 0, m_width, m_height, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);
}
void R3DFrameBuffers::DestroyFBO()
{
if (m_frameBufferID) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteRenderbuffers(1, &m_renderBufferID);
glDeleteFramebuffers(1, &m_frameBufferID);
}
if (m_frameBufferIDCopy) {
glDeleteRenderbuffers(1, &m_renderBufferIDCopy);
glDeleteFramebuffers(1, &m_frameBufferIDCopy);
}
for (auto &i : m_texIDs) {
if (i) {
glDeleteTextures(1, &i);
i = 0;
}
}
m_frameBufferID = 0;
m_renderBufferID = 0;
m_frameBufferIDCopy = 0;
m_renderBufferIDCopy = 0;
m_width = 0;
m_height = 0;
}
GLuint R3DFrameBuffers::CreateTexture(int width, int height)
{
GLuint texId;
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
return texId;
}
void R3DFrameBuffers::BindTexture(Layer layer)
{
glBindTexture(GL_TEXTURE_2D, m_texIDs[(int)layer]);
}
void R3DFrameBuffers::SetFBO(Layer layer)
{
if (m_lastLayer == layer) {
return;
}
switch (layer)
{
case Layer::colour:
{
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID);
GLenum buffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers((GLsizei)std::size(buffers), buffers);
break;
}
case Layer::trans1:
{
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID);
GLenum buffers[] = { GL_NONE, GL_COLOR_ATTACHMENT1, GL_NONE };
glDrawBuffers((GLsizei)std::size(buffers), buffers);
break;
}
case Layer::trans2:
{
glBindFramebuffer(GL_FRAMEBUFFER, m_frameBufferID);
GLenum buffers[] = { GL_NONE, GL_NONE, GL_COLOR_ATTACHMENT2 };
glDrawBuffers((GLsizei)std::size(buffers), buffers);
break;
}
case Layer::none:
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
break;
}
}
m_lastLayer = layer;
}
void R3DFrameBuffers::AllocShaderBase()
{
const char *vertexShader = R"glsl(
#version 410 core
// outputs
out vec2 fsTexCoord;
void main(void)
{
const vec4 vertices[] = vec4[](vec4(-1.0, -1.0, 0.0, 1.0),
vec4(-1.0, 1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0),
vec4( 1.0, 1.0, 0.0, 1.0));
fsTexCoord = (vertices[gl_VertexID % 4].xy + 1.0) / 2.0;
gl_Position = vertices[gl_VertexID % 4];
}
)glsl";
const char *fragmentShader = R"glsl(
#version 410 core
// inputs
uniform sampler2D tex1; // base tex
in vec2 fsTexCoord;
// outputs
out vec4 fragColor;
void main()
{
fragColor = texture(tex1, fsTexCoord);
}
)glsl";
m_shaderBase.LoadShaders(vertexShader, fragmentShader);
m_shaderBase.uniformLoc[0] = m_shaderTrans.GetUniformLocation("tex1");
}
void R3DFrameBuffers::AllocShaderTrans()
{
const char *vertexShader = R"glsl(
#version 410 core
// outputs
out vec2 fsTexCoord;
void main(void)
{
const vec4 vertices[] = vec4[](vec4(-1.0, -1.0, 0.0, 1.0),
vec4(-1.0, 1.0, 0.0, 1.0),
vec4( 1.0, -1.0, 0.0, 1.0),
vec4( 1.0, 1.0, 0.0, 1.0));
fsTexCoord = (vertices[gl_VertexID % 4].xy + 1.0) / 2.0;
gl_Position = vertices[gl_VertexID % 4];
}
)glsl";
const char *fragmentShader = R"glsl(
#version 410 core
uniform sampler2D tex1; // trans layer 1
uniform sampler2D tex2; // trans layer 2
in vec2 fsTexCoord;
// outputs
out vec4 fragColor;
void main()
{
vec4 colTrans1 = texture(tex1, fsTexCoord);
vec4 colTrans2 = texture(tex2, fsTexCoord);
// if both transparency layers overlap, the result is opaque
if (colTrans1.a * colTrans2.a > 0.0) {
// are the two lines functionally identical? Need to check.
//vec3 mixCol = mix(colTrans1.rgb, colTrans2.rgb, (colTrans2.a + (1.0 - colTrans1.a)) / 2.0);
vec3 mixCol = (((colTrans1.rgb * colTrans1.a) + (colTrans2.rgb * (1.0-colTrans1.a))) + ((colTrans2.rgb * colTrans2.a) + (colTrans1 .rgb * (1.0-colTrans2.a)))) / 2.0;
fragColor = vec4(mixCol, 1.0);
}
else if (colTrans1.a > 0.0) {
fragColor = colTrans1;
}
else {
fragColor = colTrans2; // if alpha is zero it will have no effect anyway
}
}
)glsl";
m_shaderTrans.LoadShaders(vertexShader, fragmentShader);
m_shaderTrans.uniformLoc[0] = m_shaderTrans.GetUniformLocation("tex1");
m_shaderTrans.uniformLoc[1] = m_shaderTrans.GetUniformLocation("tex2");
}
void R3DFrameBuffers::Draw()
{
SetFBO (Layer::none); // make sure to draw on the back buffer
glViewport (0, 0, m_width, m_height); // cover the entire screen
glDisable (GL_DEPTH_TEST); // disable depth testing / writing
glDisable (GL_CULL_FACE);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable (GL_BLEND);
for (int i = 0; i < (int)std::size(m_texIDs); i++) { // bind our textures to correct texture units
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, m_texIDs[i]);
}
glActiveTexture (GL_TEXTURE0);
glBindVertexArray (m_vao);
DrawBaseLayer ();
DrawAlphaLayer ();
glDisable (GL_BLEND);
glBindVertexArray (0);
}
void R3DFrameBuffers::DrawBaseLayer()
{
m_shaderBase.EnableShader();
glUniform1i(m_shaderTrans.uniformLoc[0], 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_shaderBase.DisableShader();
}
void R3DFrameBuffers::DrawAlphaLayer()
{
m_shaderTrans.EnableShader();
glUniform1i(m_shaderTrans.uniformLoc[0], 1); // tex unit 1
glUniform1i(m_shaderTrans.uniformLoc[1], 2); // tex unit 2
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_shaderTrans.DisableShader();
}
}