Tutorial 08.2: Deferred Renderer

https://en.wikipedia.org/wiki/Deferred_shading

The idea behind it is to decouple the (expensive) lighting calculations from the geometry rendering.

Therefore all necessary attributes for the lighting calculation have to be rendered into separate textures (MRT):

    1. Geometry Position

    2. Geomtry Normal

    3. Material Ambient

    4. Material Diffuse

    5. Material Specular

    6. Material Shininess

    7. Object ID (used only to identify objects)

When the first pass, the so-called "Geometry Pass" is completed, we use these framebuffer textures in a second pass, the so-called "Lighting Pass". We dont need to render the (often) complex model geometry again, instead we just render a simple rectangle that covers the whole screen. Each pixel will then be processed again to do the lighting calculations. To skip the background in the lighting pass, we use theh stencil buffer: the geometry pass fills the stencilbuffer with the information which pixels are covered by models, all the other pixels (background) will be skipped in the lighting pass.

In order to be able to store the geometry data with sufficient precision, we use textures with 32bit floating-point per component (RGBA).

The Material Ambient, Diffuse and Specular are essentially just colors, so we can use lesser presitcion (8bit / component) for these.

The Material Shininess is stored in a 32bit single component texture, it can easily be merged with the last unused A-component of either the geometry position or normal texture (but i skipped it here to avoid confusion).

The lighting calculation is very much the same as in previous tutorials.

There is a way to further improve the rendering performance: instead of simply render a screen-wide rectangle, we could ...

    • render a simple low-poly-sphere for each point light with a radius that matches the area that would be affected by that point light

    • render a cone for each spot light

    • render a screen-wide rectangle and process all directional lights

During the process all partial results will be accumulated to get the final image.

Framebuffer Texture 0: Geometry Position

Framebuffer Texture 1: Geometry Normal

Framebuffer Texture 2: Material Ambient

Framebuffer Texture 3: Material Diffuse

Framebuffer Texture 4: Material Specular

Framebuffer Texture 5: Material Shininess

Complete Source Code:

Main.cpp

#include "Main.h"

#include "System_GLFW.h"

#include "Renderer_OpenGL.h"

#include "Renderer_OpenGL_Deferred.h"

int main(int argc, char* argv[])

{

System_GLFW& system = System_GLFW::Instance();

return Main::Instance(&system).MainLoop();

}

Main & Main::Instance(System* system)

{

static Main instance(system);

return instance;

}

Main::Main(System* system)

: m_system(system)

{

if (!m_system)

exit(EXIT_FAILURE);

}

Main::~Main()

{}

int Main::MainLoop()

{

double tnow = 0, tframe = 0, tlastframe = 0;

unsigned int tlastrenamed = 0, fpscounter = 0;

ivec2 windowsize = Framework->WindowSize();

//m_renderer = std::unique_ptr<Renderer>(new Renderer_OpenGL(windowsize.x, windowsize.y));

m_renderer = std::unique_ptr<Renderer>(new Renderer_OpenGL_Deferred(windowsize.x, windowsize.y));

Scene scene;

while (!Framework->IsCloseRequested())

{

// limit the FPS

tnow = Framework->Time();

tframe = tnow - tlastframe;

if (tframe < 1.0 / m_fps_goal)

continue;

tlastframe = tnow;

// rename the window title each second, show current FPS

fpscounter++;

if ((unsigned int)tnow != tlastrenamed)

{

tlastrenamed = (unsigned int)tnow;

Framework->SetWindowTitle("OpenGL FPS: " + std::to_string(fpscounter));

fpscounter = 0;

}

// simulate scene

static int tracked_ID = 0;

scene.AnimateNextFrame(tframe, tracked_ID);

// render scene

if (m_renderer.get())

m_renderer->Render(scene, tracked_ID);

Framework->SwapBuffers();

Framework->Update();

}

return EXIT_SUCCESS;

}

System * Main::system()

{

return m_system;

}

Renderer_OpenGL_Deferred.h

#pragma once

#include "Renderer.h"

#include <GL/glew.h>

class Renderer_OpenGL_Deferred : public Renderer

{

public:

Renderer_OpenGL_Deferred(unsigned int width, unsigned int height);

virtual ~Renderer_OpenGL_Deferred();

virtual void Render(const Scene& scene, int& tracked_ID);

protected:

unsigned int m_width{ 0 }, m_height{ 0 };

enum Buffers {

BufferVertices,

BufferInstances,

BufferMaterials,

BufferDirectionalLights,

BufferPointLights,

BufferSpotLights,

MAX_Buffers,

};

enum GBufferTextures {

GBufferPosition,

GBufferNormal,

GBufferAmbient,

GBufferDiffuse,

GBufferSpecular,

GBufferShininess,

GBufferObjectID,

GBufferDepthStencil,

MAX_GBufferTextures,

};

unsigned int m_gbuffer{ 0 };

unsigned int m_gbuffertextures[MAX_GBufferTextures]{ 0 };

unsigned int m_framebuffer{ 0 };

unsigned int m_framebuffertexture{ 0 };

unsigned int m_program_geometry{ 0 };

unsigned int m_program_lighting{ 0 };

unsigned int m_vertexarray{ 0 };

unsigned int m_buffers[MAX_Buffers]{ 0 };

unsigned int m_textureKd{ 0 };

std::vector<Model> m_models;

// uniform locations

struct {

struct {

int View = -1;

int Projection = -1;

int textureKd = -1;

} program_geometry;

struct {

int CameraPosition = -1;

int Ambient = -1;

int DirectionalLightCount = -1;

int PointLightCount = -1;

int SpotLightCount = -1;

int texturePosition = -1;

int textureNormal = -1;

int textureKa = -1;

int textureKd = -1;

int textureKs = -1;

int textureNs = -1;

} program_lighting;

} m_uniform_location;

void CheckFramebuffer(unsigned int framebuffer);

void CheckForGLError();

};

Renderer_OpenGL_Deferred.cpp

#include "Renderer_OpenGL_Deferred.h"

#include "Shader.h"

#include "Model.h"

#include "BMP.h"

#include "Main.h"

#include <iostream>

#define MAX_MODEL_INSTANCES 1000

#define MAX_MATERIALS 1000

#define MAX_POINTLIGHTS 10

#define MAX_DIRECTIONALLIGHTS 10

#define MAX_SPOTLIGHTS 10

Renderer_OpenGL_Deferred::Renderer_OpenGL_Deferred(unsigned int width, unsigned int height)

{

// settings

//-----------------------------------------------------------------------------------------------------

glEnable(GL_DEPTH_TEST);

glEnable(GL_CULL_FACE);

glEnable(GL_STENCIL_TEST);

glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);

//-----------------------------------------------------------------------------------------------------

// create all objects:

//-----------------------------------------------------------------------------------------------------

glGenFramebuffers(1, &m_gbuffer);

glGenTextures(MAX_GBufferTextures, m_gbuffertextures);

glGenFramebuffers(1, &m_framebuffer);

glGenTextures(1, &m_framebuffertexture);

m_program_geometry = glCreateProgram();

m_program_lighting = glCreateProgram();

glGenVertexArrays(1, &m_vertexarray);

glGenBuffers(MAX_Buffers, m_buffers);

glGenTextures(1, &m_textureKd);

//-----------------------------------------------------------------------------------------------------

// framebuffer size

//-----------------------------------------------------------------------------------------------------

m_width = max2(100, width);

m_height = max2(100, height);

glViewport(0, 0, m_width, m_height);

//-----------------------------------------------------------------------------------------------------

// setup GBuffer (geometry buffer)

//-----------------------------------------------------------------------------------------------------

for (unsigned int i = 0; i < MAX_GBufferTextures; i++)

{

glBindTexture(GL_TEXTURE_2D, m_gbuffertextures[i]);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glBindTexture(GL_TEXTURE_2D, 0);

}

glBindTexture(GL_TEXTURE_2D, m_gbuffertextures[GBufferPosition]);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

glBindTexture(GL_TEXTURE_2D, m_gbuffertextures[GBufferNormal]);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

glBindTexture(GL_TEXTURE_2D, m_gbuffertextures[GBufferAmbient]);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

glBindTexture(GL_TEXTURE_2D, m_gbuffertextures[GBufferDiffuse]);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

glBindTexture(GL_TEXTURE_2D, m_gbuffertextures[GBufferSpecular]);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

glBindTexture(GL_TEXTURE_2D, m_gbuffertextures[GBufferShininess]);

glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, m_width, m_height, 0, GL_RED, GL_FLOAT, NULL);

glBindTexture(GL_TEXTURE_2D, m_gbuffertextures[GBufferObjectID]);

glTexImage2D(GL_TEXTURE_2D, 0, GL_R32I, m_width, m_height, 0, GL_RED_INTEGER, GL_INT, NULL);

glBindTexture(GL_TEXTURE_2D, m_gbuffertextures[GBufferDepthStencil]);

glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_width, m_height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);

glBindTexture(GL_TEXTURE_2D, 0);

glBindFramebuffer(GL_FRAMEBUFFER, m_gbuffer);

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_gbuffertextures[GBufferPosition], 0);

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, m_gbuffertextures[GBufferNormal], 0);

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, m_gbuffertextures[GBufferAmbient], 0);

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, m_gbuffertextures[GBufferDiffuse], 0);

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT4, m_gbuffertextures[GBufferSpecular], 0);

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT5, m_gbuffertextures[GBufferShininess], 0);

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT6, m_gbuffertextures[GBufferObjectID], 0);

glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, m_gbuffertextures[GBufferDepthStencil], 0);

std::vector<GLenum> drawbuffers = {

GL_COLOR_ATTACHMENT0,

GL_COLOR_ATTACHMENT1,

GL_COLOR_ATTACHMENT2,

GL_COLOR_ATTACHMENT3,

GL_COLOR_ATTACHMENT4,

GL_COLOR_ATTACHMENT5,

GL_COLOR_ATTACHMENT6,

};

glDrawBuffers(drawbuffers.size(), drawbuffers.data());

glBindFramebuffer(GL_FRAMEBUFFER, 0);

CheckFramebuffer(m_gbuffer);

//-----------------------------------------------------------------------------------------------------

// setup framebuffer

//-----------------------------------------------------------------------------------------------------

glBindTexture(GL_TEXTURE_2D, m_framebuffertexture);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

glBindTexture(GL_TEXTURE_2D, 0);

glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_framebuffertexture, 0);

// the depth-stencil texture will be shared by both framebuffers (used for stenciltest)

glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, m_gbuffertextures[GBufferDepthStencil], 0);

glDrawBuffer(GL_COLOR_ATTACHMENT0);

glBindFramebuffer(GL_FRAMEBUFFER, 0);

CheckFramebuffer(m_framebuffer);

//-----------------------------------------------------------------------------------------------------

// setup programs (for geometry and lighting pass)

//-----------------------------------------------------------------------------------------------------

unsigned int vertexshader_geometry = glCreateShader(GL_VERTEX_SHADER);

unsigned int fragmentshader_geometry = glCreateShader(GL_FRAGMENT_SHADER);

unsigned int vertexshader_lighting = glCreateShader(GL_VERTEX_SHADER);

unsigned int fragmentshader_lighting = glCreateShader(GL_FRAGMENT_SHADER);

CompileShader(vertexshader_geometry, LoadTextFile("shader_deferred_geometry.vs"));

CompileShader(fragmentshader_geometry, LoadTextFile("shader_deferred_geometry.fs"));

LinkProgram(m_program_geometry, { vertexshader_geometry , fragmentshader_geometry });

CompileShader(vertexshader_lighting, LoadTextFile("shader_deferred_lighting.vs"));

CompileShader(fragmentshader_lighting, LoadTextFile("shader_deferred_lighting.fs"));

LinkProgram(m_program_lighting, { vertexshader_lighting , fragmentshader_lighting });

glDeleteShader(vertexshader_geometry);

glDeleteShader(fragmentshader_geometry);

glDeleteShader(vertexshader_lighting);

glDeleteShader(fragmentshader_lighting);

// get all uniform locations (program geometry)

m_uniform_location.program_geometry.View = glGetUniformLocation(m_program_geometry, "View");

m_uniform_location.program_geometry.Projection = glGetUniformLocation(m_program_geometry, "Projection");

m_uniform_location.program_geometry.textureKd = glGetUniformLocation(m_program_geometry, "textureKd");

// get all uniform locations (program lighting)

m_uniform_location.program_lighting.CameraPosition = glGetUniformLocation(m_program_lighting, "CameraPosition");

m_uniform_location.program_lighting.Ambient = glGetUniformLocation(m_program_lighting, "Ambient");

m_uniform_location.program_lighting.DirectionalLightCount = glGetUniformLocation(m_program_lighting, "DirectionalLightCount");

m_uniform_location.program_lighting.PointLightCount = glGetUniformLocation(m_program_lighting, "PointLightCount");

m_uniform_location.program_lighting.SpotLightCount = glGetUniformLocation(m_program_lighting, "SpotLightCount");

m_uniform_location.program_lighting.texturePosition = glGetUniformLocation(m_program_lighting, "texturePosition");

m_uniform_location.program_lighting.textureNormal = glGetUniformLocation(m_program_lighting, "textureNormal");

m_uniform_location.program_lighting.textureKa = glGetUniformLocation(m_program_lighting, "textureKa");

m_uniform_location.program_lighting.textureKd = glGetUniformLocation(m_program_lighting, "textureKd");

m_uniform_location.program_lighting.textureKs = glGetUniformLocation(m_program_lighting, "textureKs");

m_uniform_location.program_lighting.textureNs = glGetUniformLocation(m_program_lighting, "textureNs");

// for lighting pass: bind gbuffer textures to textureunits 1, 2, 3, 4, 5, 6:

glBindTextureUnit(1, m_gbuffertextures[GBufferPosition]);

glBindTextureUnit(2, m_gbuffertextures[GBufferNormal]);

glBindTextureUnit(3, m_gbuffertextures[GBufferAmbient]);

glBindTextureUnit(4, m_gbuffertextures[GBufferDiffuse]);

glBindTextureUnit(5, m_gbuffertextures[GBufferSpecular]);

glBindTextureUnit(6, m_gbuffertextures[GBufferShininess]);

glProgramUniform1i(m_program_lighting, m_uniform_location.program_lighting.texturePosition, 1);

glProgramUniform1i(m_program_lighting, m_uniform_location.program_lighting.textureNormal, 2);

glProgramUniform1i(m_program_lighting, m_uniform_location.program_lighting.textureKa, 3);

glProgramUniform1i(m_program_lighting, m_uniform_location.program_lighting.textureKd, 4);

glProgramUniform1i(m_program_lighting, m_uniform_location.program_lighting.textureKs, 5);

glProgramUniform1i(m_program_lighting, m_uniform_location.program_lighting.textureNs, 6);

//-----------------------------------------------------------------------------------------------------

// setup vertexarray

//-----------------------------------------------------------------------------------------------------

glBindVertexArray(m_vertexarray);

glBindBuffer(GL_ARRAY_BUFFER, m_buffers[BufferVertices]);

glVertexAttribPointer(0, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 0));

glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 3));

glVertexAttribPointer(2, 3, GL_FLOAT, false, sizeof(Vertex), (void*)(sizeof(float) * 5));

glVertexAttribIPointer(3, 1, GL_INT, sizeof(Vertex), (void*)(sizeof(float) * 8));

glBindBuffer(GL_ARRAY_BUFFER, 0);

glBindBuffer(GL_ARRAY_BUFFER, m_buffers[BufferInstances]);

glVertexAttribPointer(4, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 0));

glVertexAttribPointer(5, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 4));

glVertexAttribPointer(6, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 8));

glVertexAttribPointer(7, 4, GL_FLOAT, false, sizeof(ModelInstance), (void*)(sizeof(float) * 12));

glVertexAttribIPointer(8, 1, GL_INT, sizeof(ModelInstance), (void*)(sizeof(float) * 16));

glBindBuffer(GL_ARRAY_BUFFER, 0);

glEnableVertexAttribArray(0);

glEnableVertexAttribArray(1);

glEnableVertexAttribArray(2);

glEnableVertexAttribArray(3);

glEnableVertexAttribArray(4);

glEnableVertexAttribArray(5);

glEnableVertexAttribArray(6);

glEnableVertexAttribArray(7);

glEnableVertexAttribArray(8);

// sent these attributes only once per instance to the program:

glVertexAttribDivisor(4, 1);

glVertexAttribDivisor(5, 1);

glVertexAttribDivisor(6, 1);

glVertexAttribDivisor(7, 1);

glVertexAttribDivisor(8, 1);

glBindVertexArray(0);

//-----------------------------------------------------------------------------------------------------

// setup vertexbuffer

//-----------------------------------------------------------------------------------------------------

std::vector<Vertex> vertices;

std::vector<Material> materials;

std::vector<std::string> texturesKd = { "" };

// first put a rectangle into vertexbuffer (needed for lighting pass)

vertices.push_back({ { -1, -1, 0 },{ 0, 0 },{ 0, 0, 1 },{ 0 } });

vertices.push_back({ { +1, -1, 0 },{ 1, 0 },{ 0, 0, 1 },{ 0 } });

vertices.push_back({ { +1, +1, 0 },{ 1, 1 },{ 0, 0, 1 },{ 0 } });

vertices.push_back({ { -1, +1, 0 },{ 0, 1 },{ 0, 0, 1 },{ 0 } });

// list of all model filenames + base paths

std::list<std::pair<std::string, std::string>> listmodelfiles;

listmodelfiles.push_back({ { "sphere.obj" },{ "" } });

listmodelfiles.push_back({ { "monkey.obj" },{ "" } });

// create models:

for (auto& modelfile : listmodelfiles)

{

Model model;

model.Primitive = GL_TRIANGLES;

model.Offset = vertices.size();

LoadModel(modelfile.first, modelfile.second, vertices, materials, texturesKd);

model.Count = vertices.size() - model.Offset;

// check if model has any vertex data

if (model.Count < 3)

continue;

// now set the size information of this model:

float rmax_sq = 0;

for (unsigned int i = model.Offset; i < model.Offset + model.Count; i++)

{

model.Size.Min.x = min2(model.Size.Min.x, vertices[i].Position.x);

model.Size.Min.y = min2(model.Size.Min.y, vertices[i].Position.y);

model.Size.Min.z = min2(model.Size.Min.z, vertices[i].Position.z);

model.Size.Max.x = max2(model.Size.Max.x, vertices[i].Position.x);

model.Size.Max.y = max2(model.Size.Max.y, vertices[i].Position.y);

model.Size.Max.z = max2(model.Size.Max.z, vertices[i].Position.z);

rmax_sq = max2(rmax_sq, dot(vertices[i].Position, vertices[i].Position));

}

model.Size.RMax = std::sqrt(rmax_sq);

// append model

m_models.push_back(model);

}

// show all model infos:

for (auto& model : m_models)

{

static int index = 0;

std::cout << "model " << index++ << std::endl;

std::cout << "x: " << model.Size.Min.x << " ... " << model.Size.Max.x << std::endl;

std::cout << "y: " << model.Size.Min.y << " ... " << model.Size.Max.y << std::endl;

std::cout << "z: " << model.Size.Min.z << " ... " << model.Size.Max.z << std::endl;

std::cout << "r max: " << model.Size.RMax << std::endl;

std::cout << "vertex count: " << model.Count << std::endl << std::endl;

}

// make sure that there is at least 1 model available:

if (vertices.size() <= 4)

{

vertices.push_back({ { 0, 0, 0 },{ 0, 0 },{ 0, 0, 1 },{ 0 } });

vertices.push_back({ { 1, 0, 0 },{ 0, 1 },{ 0, 0, 1 },{ 0 } });

vertices.push_back({ { 0, 1, 0 },{ 0, 0 },{ 0, 0, 1 },{ 0 } });

Model model;

model.Primitive = GL_TRIANGLES;

model.Offset = vertices.size() - 3;

model.Count = vertices.size();

m_models.push_back(model);

}

glBindBuffer(GL_ARRAY_BUFFER, m_buffers[BufferVertices]);

glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * vertices.size(), vertices.data(), GL_STATIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER, 0);

//-----------------------------------------------------------------------------------------------------

// setup instance buffer

// ----------------------------------------------------------------------------------------------------------

glBindBuffer(GL_ARRAY_BUFFER, m_buffers[BufferInstances]);

glBufferData(GL_ARRAY_BUFFER, sizeof(ModelInstance) * MAX_MODEL_INSTANCES, NULL, GL_STREAM_DRAW);

glBindBuffer(GL_ARRAY_BUFFER, 0);

// ----------------------------------------------------------------------------------------------------------

// setup material and light buffers

//-----------------------------------------------------------------------------------------------------

if (materials.size() == 0)

materials.push_back(Material(vec3(1, 1, 1), vec3(1, 1, 1), vec3(1, 1, 1), 1.0f, 1.0f, 0));

materials.resize(MAX_MATERIALS);

glBindBufferBase(GL_UNIFORM_BUFFER, 1, m_buffers[BufferMaterials]);

glBufferData(GL_UNIFORM_BUFFER, sizeof(Material) * materials.size(), materials.data(), GL_STATIC_DRAW);

glBindBufferBase(GL_UNIFORM_BUFFER, 2, m_buffers[BufferDirectionalLights]);

glBufferData(GL_UNIFORM_BUFFER, sizeof(DirectionalLight) * MAX_DIRECTIONALLIGHTS, NULL, GL_STREAM_DRAW);

glBindBufferBase(GL_UNIFORM_BUFFER, 3, m_buffers[BufferPointLights]);

glBufferData(GL_UNIFORM_BUFFER, sizeof(PointLight) * MAX_POINTLIGHTS, NULL, GL_STREAM_DRAW);

glBindBufferBase(GL_UNIFORM_BUFFER, 4, m_buffers[BufferSpotLights]);

glBufferData(GL_UNIFORM_BUFFER, sizeof(SpotLight) * MAX_SPOTLIGHTS, NULL, GL_STREAM_DRAW);

glBindBuffer(GL_UNIFORM_BUFFER, 0);

//-----------------------------------------------------------------------------------------------------

// setup textures

//-----------------------------------------------------------------------------------------------------

unsigned int mipmapcount = 1;

unsigned int texturewidth = 1024;

unsigned int textureheight = 1024;

unsigned int numberoflayers = texturesKd.size();

struct Pixel { unsigned char r{ 255 }, g{ 255 }, b{ 255 }, a{ 255 }; };

std::vector<Pixel> whitetexture(texturewidth * textureheight);

glBindTexture(GL_TEXTURE_2D_ARRAY, m_textureKd);

glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipmapcount, GL_RGBA8, texturewidth, textureheight, numberoflayers);

for (unsigned int i = 0; i < texturesKd.size(); i++)

{

// this c++ class to load images was written by Benjamin Kalytta, http://www.kalytta.com/bitmap.h

CBitmap bmp;

void* texturedata = whitetexture.data();

if (bmp.Load(texturesKd[i].c_str()))

if (bmp.GetWidth() == texturewidth && bmp.GetHeight() == textureheight)

texturedata = bmp.GetBits();

glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, texturewidth, textureheight, 1, GL_RGBA, GL_UNSIGNED_BYTE, texturedata);

}

glBindTexture(GL_TEXTURE_2D_ARRAY, 0);

// bind diffuse texture to textureunit 7:

glBindTextureUnit(7, m_textureKd);

glProgramUniform1i(m_program_geometry, m_uniform_location.program_geometry.textureKd, 7);

//-----------------------------------------------------------------------------------------------------

}

Renderer_OpenGL_Deferred::~Renderer_OpenGL_Deferred()

{

// destroy all objects:

//-----------------------------------------------------------------------------------------------------

glDeleteFramebuffers(1, &m_gbuffer);

glDeleteTextures(MAX_GBufferTextures, m_gbuffertextures);

glDeleteFramebuffers(1, &m_framebuffer);

glDeleteTextures(1, &m_framebuffertexture);

glDeleteProgram(m_program_geometry);

glDeleteProgram(m_program_lighting);

glDeleteVertexArrays(1, &m_vertexarray);

glDeleteBuffers(MAX_Buffers, m_buffers);

glDeleteTextures(1, &m_textureKd);

//-----------------------------------------------------------------------------------------------------

}

void Renderer_OpenGL_Deferred::Render(const Scene & scene, int & tracked_ID)

{

// 0. update uniform variables and buffers

// 0.1 geometry pass: set camera

// ----------------------------------------------------------------------------------------------------------

ivec2 windowsize = Framework->WindowSize();

float aspect_ratio = (float)windowsize.x / windowsize.y;

glProgramUniformMatrix4fv(m_program_geometry, m_uniform_location.program_geometry.View, 1, false, value_ptr(scene.Camera.View()));

glProgramUniformMatrix4fv(m_program_geometry, m_uniform_location.program_geometry.Projection, 1, false, value_ptr(perspective(scene.Camera.FoV, aspect_ratio, scene.Camera.ZNear, scene.Camera.ZFar)));

// ----------------------------------------------------------------------------------------------------------

// 0.2 lighting pass: set light sources:

// ----------------------------------------------------------------------------------------------------------

glProgramUniform3fv(m_program_lighting, m_uniform_location.program_lighting.CameraPosition, 1, value_ptr(scene.Camera.Position()));

glProgramUniform3fv(m_program_lighting, m_uniform_location.program_lighting.Ambient, 1, value_ptr(scene.Light.Ambient));

unsigned int directionallightcount = min2(MAX_DIRECTIONALLIGHTS, scene.Light.DirectionalLights.size());

unsigned int pointlightcount = min2(MAX_POINTLIGHTS, scene.Light.PointLights.size());

unsigned int spotlightcount = min2(MAX_SPOTLIGHTS, scene.Light.SpotLights.size());

glProgramUniform1i(m_program_lighting, m_uniform_location.program_lighting.DirectionalLightCount, directionallightcount);

glProgramUniform1i(m_program_lighting, m_uniform_location.program_lighting.PointLightCount, pointlightcount);

glProgramUniform1i(m_program_lighting, m_uniform_location.program_lighting.SpotLightCount, spotlightcount);

if (directionallightcount > 0)

{

glBindBuffer(GL_UNIFORM_BUFFER, m_buffers[BufferDirectionalLights]);

glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(DirectionalLight) * directionallightcount, scene.Light.DirectionalLights.data());

glBindBuffer(GL_UNIFORM_BUFFER, 0);

}

if (pointlightcount > 0)

{

glBindBuffer(GL_UNIFORM_BUFFER, m_buffers[BufferPointLights]);

glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(PointLight) * pointlightcount, scene.Light.PointLights.data());

glBindBuffer(GL_UNIFORM_BUFFER, 0);

}

if (spotlightcount > 0)

{

glBindBuffer(GL_UNIFORM_BUFFER, m_buffers[BufferSpotLights]);

glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(SpotLight) * spotlightcount, scene.Light.SpotLights.data());

glBindBuffer(GL_UNIFORM_BUFFER, 0);

}

// ----------------------------------------------------------------------------------------------------------

// 1. geometry pass

glBindFramebuffer(GL_FRAMEBUFFER, m_gbuffer);

// write to stencil buffer (if depth test passes)

glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);

// 1.1 explicitly clear each buffer:

//-----------------------------------------------------------------------------------------------------

float clearvalue0[] = { 0, 0, 0, 0 };

float clearvalue1[] = { 0, 0, 0, 0 };

float clearvalue2[] = { 0, 0, 0, 0 };

float clearvalue3[] = { 0, 0, 0, 0 };

float clearvalue4[] = { 0, 0, 0, 0 };

float clearvalue5[] = { 0 };

int clearvalue6[] = { -1 };

glClearBufferfv(GL_COLOR, 0, clearvalue0); // position

glClearBufferfv(GL_COLOR, 1, clearvalue1); // normal

glClearBufferfv(GL_COLOR, 2, clearvalue2); // ambient

glClearBufferfv(GL_COLOR, 3, clearvalue3); // diffuse

glClearBufferfv(GL_COLOR, 4, clearvalue4); // specular

glClearBufferfv(GL_COLOR, 5, clearvalue5); // shininess

glClearBufferiv(GL_COLOR, 6, clearvalue6); // objectID

glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

//-----------------------------------------------------------------------------------------------------

// 1.2 activate program (geometry pass) and vertexarray

glUseProgram(m_program_geometry);

glBindVertexArray(m_vertexarray);

// 1.3 draw scene into GBuffer textures:

//-----------------------------------------------------------------------------------------------------

for (auto& object : scene.Objects)

if (object.ModelIndex < m_models.size())

m_models[object.ModelIndex].Instances.push_back({ object.ModelMatrix(), (int)object.ID() });

for (auto& model : m_models)

{

// determine instance count:

unsigned int instancecount = min2(model.Instances.size(), MAX_MODEL_INSTANCES);

// upload instance buffer data:

glBindBuffer(GL_ARRAY_BUFFER, m_buffers[BufferInstances]);

glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(ModelInstance) * instancecount, model.Instances.data());

glBindBuffer(GL_ARRAY_BUFFER, 0);

// render objects in scene

glDrawArraysInstanced(model.Primitive, model.Offset, model.Count, instancecount);

}

for (auto& model : m_models)

{

model.Instances.clear();

model.Instances.reserve(MAX_MODEL_INSTANCES);

}

//-----------------------------------------------------------------------------------------------------

// 1.4 read objectID at cursor position:

//-----------------------------------------------------------------------------------------------------

ivec2 cursorposition = Framework->CursorPosition();

int x = cursorposition.x * (float)m_width / windowsize.x;

int y = cursorposition.y * (float)m_height / windowsize.y;

glReadBuffer(GL_COLOR_ATTACHMENT6);

glReadPixels(x, y, 1, 1, GL_RED_INTEGER, GL_INT, &tracked_ID);

//-----------------------------------------------------------------------------------------------------

// 2. lighting pass

// ----------------------------------------------------------------------------------------------------------

glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

// use stencil buffer to skip scene background (in lighting pass)

glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);

// 2.1 clear framebuffer (but keep stencilbuffer)

glClearColor(0.1f, 0.2f, 0.7f, 0.0f);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 2.2 activate program

glUseProgram(m_program_lighting);

// 2.3 render fullscreen quad (background will be skipped because of stenciltest)

glDrawArrays(GL_QUADS, 0, 4);

glBindVertexArray(0);

glUseProgram(0);

// ----------------------------------------------------------------------------------------------------------

// 3. copy to default framebuffer

//-----------------------------------------------------------------------------------------------------

glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);

glBindFramebuffer(GL_READ_FRAMEBUFFER, m_framebuffer);

glReadBuffer(GL_COLOR_ATTACHMENT0);

glBlitFramebuffer(

0, 0, m_width, m_height,

0, 0, windowsize.x, windowsize.y,

GL_COLOR_BUFFER_BIT,

GL_NEAREST);

//-----------------------------------------------------------------------------------------------------

CheckForGLError();

}

void Renderer_OpenGL_Deferred::CheckFramebuffer(unsigned int framebuffer)

{

GLenum status = glCheckNamedFramebufferStatus(framebuffer, GL_FRAMEBUFFER);

if (status != GL_FRAMEBUFFER_COMPLETE)

{

std::cout << "OpenGL Framebuffer Error: \t";

if (status == GL_FRAMEBUFFER_UNDEFINED)

std::cout << "undefined framebuffer";

if (status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT)

std::cout << "a necessary attachment is uninitialized";

if (status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT)

std::cout << "no attachments";

if (status == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER)

std::cout << "incomplete draw buffer";

if (status == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER)

std::cout << "incomplete read buffer";

if (status == GL_FRAMEBUFFER_UNSUPPORTED)

std::cout << "combination of attachments is not supported";

if (status == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE)

std::cout << "number if samples for all attachments does not match";

std::cout << (char)7 << std::endl; /*play sound*/

std::cin.get();

}

}

void Renderer_OpenGL_Deferred::CheckForGLError()

{

for (GLenum error; (error = glGetError()) != GL_NO_ERROR;)

{

std::cout << "OpenGL Error: \t";

if (error == GL_INVALID_ENUM)

std::cout << "GL_INVALID_ENUM";

if (error == GL_INVALID_VALUE)

std::cout << "GL_INVALID_VALUE";

if (error == GL_INVALID_OPERATION)

std::cout << "GL_INVALID_OPERATION";

if (error == GL_STACK_OVERFLOW)

std::cout << "GL_STACK_OVERFLOW";

if (error == GL_STACK_UNDERFLOW)

std::cout << "GL_STACK_UNDERFLOW";

if (error == GL_OUT_OF_MEMORY)

std::cout << "GL_OUT_OF_MEMORY";

if (error == GL_INVALID_FRAMEBUFFER_OPERATION)

std::cout << "GL_INVALID_FRAMEBUFFER_OPERATION";

if (error == GL_CONTEXT_LOST)

std::cout << "GL_CONTEXT_LOST";

std::cout << (char)7 << std::endl; /*play sound*/

std::cin.get();

}

}

Shader Source Code:

Vertex Shader (for geometry pass): "shader_deferred_geometry.vs"

#version 450 core

in layout (location = 0) vec3 in_position;

in layout (location = 1) vec2 in_texcoord;

in layout (location = 2) vec3 in_normal;

in layout (location = 3) int in_materialID;

in layout (location = 4) mat4 in_model;

//in layout (location = 5) ... in use ...

//in layout (location = 6) ... in use ...

//in layout (location = 7) ... in use ...

in layout (location = 8) int in_objectID;

uniform mat4 View = mat4(1);

uniform mat4 Projection = mat4(1);

out VS_FS_INTERFACE {

vec3 position;

vec2 texcoord;

vec3 normal;

flat int materialID;

flat int objectID;

} vs_out;

void main()

{

mat4 MVP = Projection * View * in_model;

gl_Position = MVP * vec4(in_position, 1);

vs_out.position = (in_model * vec4(in_position, 1)).xyz;

vs_out.texcoord = in_texcoord;

vs_out.normal = (in_model * vec4(in_normal, 0)).xyz;

vs_out.materialID = in_materialID;

vs_out.objectID = in_objectID;

}

Fragment Shader (for geometry pass): "shader_deferred_geometry.fs"

#version 450 core

#define MAX_MATERIALS 1000

in VS_FS_INTERFACE {

vec3 position;

vec2 texcoord;

vec3 normal;

flat int materialID;

flat int objectID;

} vs_out;

struct Material {

vec4 Ka, Kd, Ks;

float Ns, d;

int map_Kd;

};

layout (std140, binding = 1) uniform MaterialBlock {

Material Materials[MAX_MATERIALS];

};

uniform sampler2DArray textureKd;

out layout (location = 0) vec4 out_position;

out layout (location = 1) vec4 out_normal;

out layout (location = 2) vec4 out_ambient;

out layout (location = 3) vec4 out_diffuse;

out layout (location = 4) vec4 out_specular;

out layout (location = 5) float out_shininess;

out layout (location = 6) int out_objectID;

void main()

{

Material material = Materials[vs_out.materialID];

out_position = vec4(vs_out.position, 0);

out_normal = vec4(vs_out.normal, 0);

out_ambient = material.Ka;

out_diffuse = material.Kd * texture(textureKd, vec3(vs_out.texcoord, material.map_Kd));

out_specular = material.Ks;

out_shininess = material.Ns;

out_objectID = vs_out.objectID;

}

Vertex Shader (for lighting pass): "shader_deferred_lighting.vs"

#version 450 core

in layout (location = 0) vec3 in_position;

in layout (location = 1) vec2 in_texcoord;

out VS_FS_INTERFACE {

vec2 texcoord;

} vs_out;

void main()

{

gl_Position = vec4(in_position, 1);

vs_out.texcoord = in_texcoord;

}

Fragment Shader (for lighting pass): "shader_deferred_lighting.fs"

#version 450 core

// forces the fragment shader to execute AFTER stencil test

layout(early_fragment_tests) in;

#define MAX_POINTLIGHTS 10

#define MAX_DIRECTIONALLIGHTS 10

#define MAX_SPOTLIGHTS 10

in VS_FS_INTERFACE {

vec2 texcoord;

} vs_out;

struct DirectionalLight {

vec4 Direction;

vec4 Intensity;

};

struct PointLight {

vec4 Position;

vec4 Intensity;

float AttenuationLinear;

};

struct SpotLight {

vec4 Position;

vec4 Direction;

vec4 Intensity;

int Exponent;

};

layout (std140, binding = 2) uniform DirectionalLightBlock {

DirectionalLight DirectionalLights[MAX_DIRECTIONALLIGHTS];

};

layout (std140, binding = 3) uniform PointLightBlock {

PointLight PointLights[MAX_POINTLIGHTS];

};

layout (std140, binding = 4) uniform SpotLightBlock {

SpotLight SpotLights[MAX_SPOTLIGHTS];

};

uniform vec3 CameraPosition = vec3(0, 0, 0);

uniform vec3 Ambient = vec3(0, 0, 0);

uniform int DirectionalLightCount = 0;

uniform int PointLightCount = 0;

uniform int SpotLightCount = 0;

uniform sampler2D texturePosition;

uniform sampler2D textureNormal;

uniform sampler2D textureKa;

uniform sampler2D textureKd;

uniform sampler2D textureKs;

uniform sampler2D textureNs;

out layout (location = 0) vec4 out_color;

void ProcessDirectionalLights( inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera, float Ns);

void ProcessPointLights( inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera, float Ns, vec3 Position);

void ProcessSpotLights( inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera, float Ns, vec3 Position);

void main()

{

// read data from GBuffer

vec3 Position = texture(texturePosition, vs_out.texcoord).rgb;

vec3 Normal = texture(textureNormal, vs_out.texcoord).rgb;

vec3 Ka = texture(textureKa, vs_out.texcoord).rgb;

vec3 Kd = texture(textureKd, vs_out.texcoord).rgb;

vec3 Ks = texture(textureKs, vs_out.texcoord).rgb;

float Ns = texture(textureNs, vs_out.texcoord).r;

vec3 SurfaceNormal = normalize(Normal);

vec3 SurfaceToCamera = normalize(CameraPosition - Position);

// total intensities

vec3 TotalDiffuse = vec3(0, 0, 0);

vec3 TotalSpecular = vec3(0, 0, 0);

ProcessDirectionalLights( TotalDiffuse, TotalSpecular, SurfaceNormal, SurfaceToCamera, Ns);

ProcessPointLights( TotalDiffuse, TotalSpecular, SurfaceNormal, SurfaceToCamera, Ns, Position);

ProcessSpotLights( TotalDiffuse, TotalSpecular, SurfaceNormal, SurfaceToCamera, Ns, Position);

vec3 Intensity = Ambient * Ka + TotalDiffuse * Kd + TotalSpecular * Ks;

out_color = vec4(Intensity, 1);

}

void ProcessDirectionalLights(inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera, float Ns)

{

for (int i = 0; i < DirectionalLightCount; i++)

{

// for specular:

vec3 ReflectedLight = normalize(reflect(DirectionalLights[i].Direction.xyz, SurfaceNormal));

Diffuse += DirectionalLights[i].Intensity.rgb * max(0, dot(SurfaceNormal, -normalize(DirectionalLights[i].Direction.xyz)));

Specular += DirectionalLights[i].Intensity.rgb * max(0, pow(dot(ReflectedLight, SurfaceToCamera), max(1, Ns)));

}

}

void ProcessPointLights(inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera, float Ns, vec3 Position)

{

for (int i = 0; i < PointLightCount; i++)

{

// for diffuse:

vec3 SurfaceToLight = PointLights[i].Position.xyz - Position;

float DistanceLightSurface = length(SurfaceToLight);

SurfaceToLight /= DistanceLightSurface;

float Attenuation = 1.0f + PointLights[i].AttenuationLinear * DistanceLightSurface;

// for specular:

vec3 ReflectedLight = normalize(reflect(-SurfaceToLight, SurfaceNormal));

Diffuse += PointLights[i].Intensity.rgb * max(0, dot(SurfaceNormal, SurfaceToLight)) / Attenuation;

Specular += PointLights[i].Intensity.rgb * max(0, pow(dot(ReflectedLight, SurfaceToCamera), max(1, Ns))) / Attenuation;

}

}

void ProcessSpotLights(inout vec3 Diffuse, inout vec3 Specular, vec3 SurfaceNormal, vec3 SurfaceToCamera, float Ns, vec3 Position)

{

for (int i = 0; i < SpotLightCount; i++)

{

// for diffuse:

vec3 SurfaceToLight = normalize(SpotLights[i].Position.xyz - Position);

float CosAngle = dot(-SurfaceToLight, normalize(SpotLights[i].Direction.xyz));

float Multiplier = pow(max(0, CosAngle), max(1, SpotLights[i].Exponent));

// for specular:

vec3 ReflectedLight = normalize(reflect(-SurfaceToLight, SurfaceNormal));

Diffuse += SpotLights[i].Intensity.rgb * max(0, dot(SurfaceNormal, SurfaceToLight)) * Multiplier;

Specular += SpotLights[i].Intensity.rgb * max(0, pow(dot(ReflectedLight, SurfaceToCamera), max(1, Ns))) * Multiplier;

}

}