461 lines
14 KiB
C++
461 lines
14 KiB
C++
#include "sparrowrenderer.h"
|
|
#include "deferredpipeline.h"
|
|
|
|
// renderer
|
|
#include "texture.h"
|
|
#include "scene.h"
|
|
#include "mesh.h"
|
|
#include "shader.h"
|
|
#include "shadersource.h"
|
|
#include "pbrmaterial.h"
|
|
#include "camera.h"
|
|
#include "skybox.h"
|
|
#include "guimesh.h"
|
|
|
|
// GLM
|
|
#include <glm/ext.hpp>
|
|
|
|
// Stl
|
|
#include <algorithm>
|
|
|
|
// imgui
|
|
#include <imgui/imgui.h>
|
|
|
|
// resource packs
|
|
#include <resource.h>
|
|
|
|
RESOURCE_PACK(shaders)
|
|
|
|
GBuffer::GBuffer(int width, int height, GLuint renderBuffer) : FrameBuffer()
|
|
{
|
|
Texture* tex;
|
|
|
|
// - Position buffer
|
|
tex = new Texture(GL_RGBA, GL_RGBA16F, width, height, GL_FLOAT, GL_TEXTURE_RECTANGLE);
|
|
tex->setFiltering(GL_NEAREST);
|
|
tex->setUnit(POSITION);
|
|
addTexture(tex, GL_COLOR_ATTACHMENT0);
|
|
|
|
// - Albedo + Roughness
|
|
tex = new Texture(GL_RGBA, GL_RGBA, width, height, GL_UNSIGNED_BYTE, GL_TEXTURE_RECTANGLE);
|
|
tex->setFiltering(GL_NEAREST);
|
|
tex->setUnit(ALBEDO);
|
|
addTexture(tex, GL_COLOR_ATTACHMENT1);
|
|
|
|
// - Normal buffer
|
|
tex = new Texture(GL_RGB, GL_RGB16F, width, height, GL_FLOAT, GL_TEXTURE_RECTANGLE);
|
|
tex->setFiltering(GL_NEAREST);
|
|
tex->setUnit(NORMAL);
|
|
addTexture(tex, GL_COLOR_ATTACHMENT2);
|
|
|
|
// - Emission + Metallic
|
|
tex = new Texture(GL_RGBA, GL_RGBA, width, height, GL_UNSIGNED_BYTE, GL_TEXTURE_RECTANGLE);
|
|
tex->setFiltering(GL_NEAREST);
|
|
tex->setUnit(EMISSION);
|
|
addTexture(tex, GL_COLOR_ATTACHMENT3);
|
|
|
|
addRenderBuffer(renderBuffer, GL_DEPTH_ATTACHMENT);
|
|
addRenderBuffer(renderBuffer, GL_STENCIL_ATTACHMENT);
|
|
|
|
initColorAttachments();
|
|
}
|
|
|
|
void GBuffer::bindTextures()
|
|
{
|
|
for(int i=0; i<NB_BUFFERS; ++i)
|
|
getTexture(i)->bind();
|
|
}
|
|
|
|
void GBuffer::unbindTextures()
|
|
{
|
|
for(int i=0; i<NB_BUFFERS; ++i)
|
|
getTexture(i)->unbind();
|
|
}
|
|
|
|
LightingBuffer::LightingBuffer(int width, int height, GLuint renderBuffer) : FrameBuffer()
|
|
{
|
|
// colors are encoded in float to allow tonemapping
|
|
Texture* tex = new Texture(GL_RGBA, GL_RGBA16F, width, height, GL_FLOAT, GL_TEXTURE_RECTANGLE);
|
|
tex->setFiltering(GL_NEAREST);
|
|
addTexture(tex, GL_COLOR_ATTACHMENT0);
|
|
|
|
addRenderBuffer(renderBuffer, GL_DEPTH_ATTACHMENT);
|
|
addRenderBuffer(renderBuffer, GL_STENCIL_ATTACHMENT);
|
|
|
|
initColorAttachments();
|
|
}
|
|
|
|
DeferredPipeline::DeferredPipeline() :
|
|
m_camera(nullptr),
|
|
m_skybox(nullptr),
|
|
m_width(512),
|
|
m_height(512),
|
|
m_postEffectsShader(nullptr),
|
|
m_depth_stencil_renderBuffer(0),
|
|
m_gBuffer(nullptr),
|
|
m_lightingBuffer(nullptr),
|
|
m_renderTarget(nullptr),
|
|
m_debugGuiEnabled(false),
|
|
m_gammaCorrectEnabled(true),
|
|
m_hdrTonemappingEnabled(false)
|
|
{
|
|
Resource::ResourceMap shaderMap;
|
|
Resource::getResourcePack_shaders(shaderMap);
|
|
m_gBufferSource = new ShaderSource();
|
|
m_gBufferSource->setSource(shaderMap["shaders/gbuffer.vert.glsl"], ShaderSource::VERTEX);
|
|
m_gBufferSource->setSource(shaderMap["shaders/gbuffer.frag.glsl"], ShaderSource::FRAGMENT);
|
|
m_lightingSource = new ShaderSource();
|
|
m_lightingSource->setSource(shaderMap["shaders/lighting.vert.glsl"], ShaderSource::VERTEX);
|
|
m_lightingSource->setSource(shaderMap["shaders/lighting.frag.glsl"], ShaderSource::FRAGMENT);
|
|
m_mesh2DSource = new ShaderSource();
|
|
m_mesh2DSource->setSource(shaderMap["shaders/gui.vert.glsl"], ShaderSource::VERTEX);
|
|
m_mesh2DSource->setSource(shaderMap["shaders/gui.frag.glsl"], ShaderSource::FRAGMENT);
|
|
|
|
m_postEffectsShaders = new ShaderSource();
|
|
m_postEffectsShaders->setSource(shaderMap["shaders/posteffects.vert.glsl"], ShaderSource::VERTEX);
|
|
m_postEffectsShaders->setSource(shaderMap["shaders/posteffects.frag.glsl"], ShaderSource::FRAGMENT);
|
|
recompilePostEffectsShader();
|
|
|
|
m_debugShaders = new ShaderSource();
|
|
m_debugShaders->setSource(shaderMap["shaders/debug.vert.glsl"], ShaderSource::VERTEX);
|
|
m_debugShaders->setSource(shaderMap["shaders/debug.frag.glsl"], ShaderSource::FRAGMENT);
|
|
|
|
|
|
m_guiMesh = new GuiMesh();
|
|
m_guiMesh->initGL();
|
|
|
|
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
|
}
|
|
|
|
bool depthCompare(const GeometryNode* firstElem, const GeometryNode* secondElem) {
|
|
return firstElem->mesh->getDepth() < secondElem->mesh->getDepth();
|
|
}
|
|
|
|
void DeferredPipeline::renderGL(Scene *scene)
|
|
{
|
|
if(m_renderTarget == NULL)
|
|
return;
|
|
|
|
glViewport(0, 0, m_width, m_height);
|
|
|
|
// GEOMETRY PASS
|
|
std::vector<GeometryNode*> mesh2D;
|
|
m_gBuffer->bindFBO();
|
|
glEnable(GL_DEPTH_TEST);
|
|
glEnable(GL_STENCIL_TEST);
|
|
glDepthFunc(GL_LESS);
|
|
glDisable(GL_BLEND);
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.f);
|
|
glClearDepth(1.0f);
|
|
glClearStencil(0);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
|
glStencilMask(0x01);
|
|
glStencilFunc(GL_ALWAYS, 0x01, 0x01); // always draw fragments (ignore the stencil buffer values for now)
|
|
// draw skybox
|
|
if(m_skybox != nullptr)
|
|
{
|
|
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
|
m_skybox->renderGL(m_camera);
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
}
|
|
// loop on geometry
|
|
for(SceneIterator<GeometryNode*>* geometryIt = scene->getGeometry();
|
|
geometryIt->isValid(); geometryIt->next())
|
|
{
|
|
GeometryNode* node = geometryIt->getItem();
|
|
unsigned int type = node->mesh->getFlags();
|
|
if(type & (1 << Mesh::MESH_2D))
|
|
{
|
|
mesh2D.push_back(node); // TODO : not every frame
|
|
continue;
|
|
}
|
|
Shader *shader = m_mesh3DShaders[node->mesh->getFlags()];
|
|
if(shader == NULL)
|
|
{
|
|
fprintf(stderr, "no shader to render this 3D geometry, please call refreshScene to generate the shader\n");
|
|
continue;
|
|
}
|
|
shader->bind();
|
|
|
|
glm::mat4 modelViewMatrix = m_camera->getViewMatrix() * node->modelMatrix;
|
|
glm::mat4 normalMatrix = glm::transpose(glm::inverse(modelViewMatrix));
|
|
shader->bindMat4(shader->getLocation("projectionMatrix"), m_camera->getProjectionMatrix());
|
|
shader->bindMat4(shader->getLocation("modelViewMatrix"), modelViewMatrix);
|
|
shader->bindMat3(shader->getLocation("normalMatrix"), glm::mat3(normalMatrix));
|
|
// draw geometry
|
|
node->mesh->draw(shader);
|
|
}
|
|
|
|
// LIGHTING PASS
|
|
m_lightingBuffer->bindFBO();
|
|
|
|
glDepthFunc(GL_LEQUAL);
|
|
glEnable(GL_BLEND);
|
|
glEnable(GL_STENCIL_TEST);
|
|
glBlendFunc(GL_ONE, GL_ONE);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
|
|
m_gBuffer->bindTextures();
|
|
|
|
// draw unlit fragments
|
|
|
|
Shader *shader = m_lightShaders[1 << Light::UNLIT_FLAG];
|
|
if(shader != nullptr)
|
|
{
|
|
shader->bind();
|
|
shader->bindInteger(shader->getLocation("emissionBuffer"), GBuffer::EMISSION);
|
|
glStencilFunc(GL_EQUAL, 1, 0x01);
|
|
SparrowRenderer::drawQuad();
|
|
}
|
|
|
|
// loop on light sources
|
|
glStencilFunc(GL_EQUAL, 0, 0x01);
|
|
for(SceneIterator<Light*>* lightIt = scene->getLights();
|
|
lightIt->isValid(); lightIt->next())
|
|
{
|
|
Light* light = lightIt->getItem();
|
|
Shader *shader = m_lightShaders[light->getFlags()];
|
|
if(shader == nullptr)
|
|
{
|
|
fprintf(stderr, "no shader to render this light, please call refreshScene to generate the shader\n");
|
|
continue;
|
|
}
|
|
shader->bind();
|
|
|
|
// bind GBuffer
|
|
shader->bindInteger(shader->getLocation("positionBuffer"), GBuffer::POSITION);
|
|
shader->bindInteger(shader->getLocation("albedoBuffer"), GBuffer::ALBEDO);
|
|
shader->bindInteger(shader->getLocation("normalBuffer"), GBuffer::NORMAL);
|
|
shader->bindInteger(shader->getLocation("emissionBuffer"), GBuffer::EMISSION);
|
|
|
|
// bind light
|
|
light->bindAttributes(shader, m_camera);
|
|
|
|
// compute fragments
|
|
SparrowRenderer::drawQuad();
|
|
}
|
|
m_gBuffer->unbindTextures();
|
|
|
|
// POST EFFECTS PASS
|
|
m_renderTarget->bindFBO();
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_STENCIL_TEST);
|
|
glStencilMask(0);
|
|
m_lightingBuffer->getTexture(0)->bind(0);
|
|
m_postEffectsShader->bind();
|
|
m_postEffectsShader->bindInteger(m_postEffectsShader->getLocation("lightBuffer"), 0);
|
|
SparrowRenderer::drawQuad();
|
|
m_lightingBuffer->getTexture(0)->unbind();
|
|
|
|
// 2D PASS
|
|
glClear(GL_DEPTH_BUFFER_BIT);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
std::sort(mesh2D.begin(), mesh2D.end(), depthCompare);
|
|
for(GeometryNode* node : mesh2D)
|
|
{
|
|
Shader *shader = m_mesh2DShaders[node->mesh->getFlags()];
|
|
if(shader == NULL)
|
|
{
|
|
fprintf(stderr, "no shader to render this 2D geometry, please call refreshScene to generate the shader\n");
|
|
continue;
|
|
}
|
|
shader->bind();
|
|
|
|
shader->bindMat4(shader->getLocation("orthoMatrix"), m_orthoMatrix);
|
|
shader->bindMat4(shader->getLocation("transformMatrix"), node->modelMatrix);
|
|
// draw geometry
|
|
node->mesh->draw(shader);
|
|
}
|
|
|
|
if(m_debugGuiEnabled)
|
|
gui();
|
|
|
|
// IMGUI PASS
|
|
m_guiMesh->drawGL();
|
|
}
|
|
|
|
struct DebugCanvas
|
|
{
|
|
glm::ivec2 top_left;
|
|
glm::ivec2 bottom_right;
|
|
|
|
void gui()
|
|
{
|
|
if(ImGui::Button("Left"))
|
|
{
|
|
if(top_left.x != -1)
|
|
top_left.x = -1;
|
|
else
|
|
bottom_right.x = 0;
|
|
}
|
|
if(ImGui::Button("Right"))
|
|
{
|
|
if(bottom_right.x != 1)
|
|
bottom_right.x = 1;
|
|
else
|
|
top_left.x = 0;
|
|
}
|
|
if(ImGui::Button("Up"))
|
|
{
|
|
if(top_left.y != 1)
|
|
top_left.y = 1;
|
|
else
|
|
bottom_right.y = 0;
|
|
}
|
|
if(ImGui::Button("Down"))
|
|
{
|
|
if(bottom_right.y != -1)
|
|
bottom_right.y = -1;
|
|
else
|
|
top_left.y = 0;
|
|
}
|
|
}
|
|
|
|
void drawCanvas()
|
|
{
|
|
Shader* shader = nullptr;
|
|
shader->bind();
|
|
shader->bindVec2(shader->getLocation("origin"), (top_left+bottom_right)/2);
|
|
shader->bindVec2(shader->getLocation("origin"), top_left-bottom_right);
|
|
SparrowRenderer::drawQuad();
|
|
}
|
|
};
|
|
|
|
void DeferredPipeline::gui()
|
|
{
|
|
ImGui::Begin("Rendering Pipeline", &m_debugGuiEnabled);
|
|
if(ImGui::Checkbox("Gamma correction", &m_gammaCorrectEnabled))
|
|
recompilePostEffectsShader();
|
|
if(ImGui::Checkbox("HDR tonemapping", &m_hdrTonemappingEnabled))
|
|
recompilePostEffectsShader();
|
|
if(ImGui::Button("Add a debug canvas"))
|
|
{
|
|
|
|
}
|
|
ImGui::End();
|
|
|
|
// debug render
|
|
|
|
}
|
|
|
|
void DeferredPipeline::recompilePostEffectsShader()
|
|
{
|
|
std::vector<const char*> defines;
|
|
if(m_gammaCorrectEnabled)
|
|
defines.push_back("GAMMA_CORRECT");
|
|
if(m_hdrTonemappingEnabled)
|
|
defines.push_back("HDR_TONEMAPPING");
|
|
if(m_postEffectsShader != nullptr)
|
|
delete m_postEffectsShader;
|
|
m_postEffectsShader = m_postEffectsShaders->compile(defines);
|
|
}
|
|
|
|
void DeferredPipeline::setSkybox(Texture* texture)
|
|
{
|
|
if(m_skybox != nullptr)
|
|
delete m_skybox;
|
|
m_skybox = nullptr;
|
|
if(texture != nullptr)
|
|
{
|
|
m_skybox = new Skybox(texture);
|
|
m_skybox->resizeGL(m_width, m_height);
|
|
}
|
|
}
|
|
|
|
void DeferredPipeline::resizeGL(int w, int h)
|
|
{
|
|
// updating dimensions
|
|
m_width = w;
|
|
m_height = h;
|
|
|
|
if(m_skybox != nullptr)
|
|
m_skybox->resizeGL(w, h);
|
|
|
|
if(m_depth_stencil_renderBuffer != 0)
|
|
glDeleteRenderbuffers(1, &m_depth_stencil_renderBuffer);
|
|
|
|
// rebuilding FrameBuffers
|
|
if(m_gBuffer != NULL)
|
|
{
|
|
delete m_gBuffer;
|
|
delete m_lightingBuffer;
|
|
}
|
|
|
|
glGenRenderbuffers(1, &m_depth_stencil_renderBuffer);
|
|
glBindRenderbuffer(GL_RENDERBUFFER, m_depth_stencil_renderBuffer);
|
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
|
|
|
|
m_gBuffer = new GBuffer(w, h, m_depth_stencil_renderBuffer);
|
|
m_lightingBuffer = new LightingBuffer(w, h, m_depth_stencil_renderBuffer);
|
|
m_orthoMatrix = glm::ortho(0.f, float(m_width), float(m_height), 0.f);
|
|
m_guiMesh->resizeGL(w, h);
|
|
}
|
|
|
|
void DeferredPipeline::setSources(ShaderSource *gBufferSource, ShaderSource *lightingSource, ShaderSource *guiSource, Shader *postEffectsShader)
|
|
{
|
|
if(m_gBufferSource != NULL)
|
|
delete m_gBufferSource;
|
|
m_gBufferSource = gBufferSource;
|
|
if(m_lightingSource != NULL)
|
|
delete m_lightingSource;
|
|
m_lightingSource = lightingSource;
|
|
if(m_mesh2DSource != NULL)
|
|
delete m_mesh2DSource;
|
|
m_mesh2DSource = guiSource;
|
|
if(m_postEffectsShader != NULL)
|
|
delete m_postEffectsShader;
|
|
m_postEffectsShader = postEffectsShader;
|
|
}
|
|
|
|
void DeferredPipeline::refreshScene(Scene *scene)
|
|
{
|
|
// delete previous shaders
|
|
for(auto it : m_mesh3DShaders)
|
|
delete it.second;
|
|
m_mesh3DShaders.clear();
|
|
m_meshTypes.clear();
|
|
|
|
// gather all unique mesh flags
|
|
scene->getMeshTypes(m_meshTypes);
|
|
for(unsigned int type : m_meshTypes)
|
|
{
|
|
if(type & (1 << Mesh::MESH_2D))
|
|
m_mesh2DShaders[type] = m_mesh2DSource->compile(type, 0);
|
|
else
|
|
m_mesh3DShaders[type] = m_gBufferSource->compile(type, 0);
|
|
}
|
|
|
|
for(auto it : m_lightShaders)
|
|
delete it.second;
|
|
m_lightShaders.clear();
|
|
m_lightTypes.clear();
|
|
m_lightTypes.push_back(1 << Light::UNLIT_FLAG);
|
|
scene->getLightTypes(m_lightTypes);
|
|
for(unsigned int type : m_lightTypes)
|
|
m_lightShaders[type] = m_lightingSource->compile(0, type);
|
|
}
|
|
|
|
glm::vec4 DeferredPipeline::pick(int x, int y)
|
|
{
|
|
m_gBuffer->setTarget(GL_READ_FRAMEBUFFER);
|
|
m_gBuffer->bindFBO();
|
|
float values[4] = {1};
|
|
glReadPixels(x, y, 1, 1, GL_RGBA, GL_FLOAT, values);
|
|
GLenum err = glGetError();
|
|
while(err != GL_NO_ERROR)
|
|
{
|
|
switch(err)
|
|
{
|
|
case GL_INVALID_ENUM : printf("OpenGL error : GL_INVALID_ENUM\n"); break;
|
|
case GL_INVALID_VALUE : printf("OpenGL error : GL_INVALID_VALUE\n"); break;
|
|
case GL_INVALID_OPERATION : printf("OpenGL error : GL_INVALID_OPERATION\n"); break;
|
|
default : printf("OpenGL error : other\n"); break;
|
|
}
|
|
err = glGetError();
|
|
}
|
|
m_gBuffer->setTarget();
|
|
FrameBuffer::screen->bindFBO();
|
|
return glm::vec4(values[0], values[1], values[2], values[3]);
|
|
}
|