void AmbientOcclusionManager::generateNoiseTexture()
	{
		unsigned int seed = 0; //no need to generate different random numbers at each start
        std::uniform_real_distribution<float> randomFloats(0.0, 1.0);
        std::default_random_engine generator(seed);

        std::vector<Vector3<float>> ssaoNoise;
        for (unsigned int i = 0; i < 16; i++)
        {
            Vector3<float> noise(
                    randomFloats(generator) * 2.0f - 1.0f,
                    randomFloats(generator) * 2.0f - 1.0f,
                    0.0f);
            ssaoNoise.push_back(noise.normalize());
        }

        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glGenTextures(1, &noiseTexId);
        glBindTexture(GL_TEXTURE_2D, noiseTexId);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, noiseTextureSize, noiseTextureSize, 0, GL_RGB, GL_FLOAT, &ssaoNoise[0]);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        ShaderManager::instance()->bind(ambientOcclusionShader);
        int noiseTexLoc = glGetUniformLocation(ambientOcclusionShader, "noiseTex");
        glUniform1i(noiseTexLoc, GL_TEXTURE2-GL_TEXTURE0);
	}
	void AmbientOcclusionManager::generateKernelSamples()
    {
		unsigned int seed = 0; //no need to generate different random numbers at each start
        std::uniform_real_distribution<float> randomFloats(0.0, 1.0);
        std::default_random_engine generator(seed);

        std::vector<Vector3<float>> ssaoKernel;
        for (unsigned int i = 0; i < kernelSamples; ++i)
        {
            Vector3<float> sample(
                    randomFloats(generator) * 2.0f - 1.0f,
                    randomFloats(generator) * 2.0f - 1.0f,
                    randomFloats(generator));
            sample = sample.normalize();

            float scale = static_cast<float>(i) / kernelSamples;
            scale = MathAlgorithm::lerp<float>(0.1f, 1.0f, scale * scale); //use square function to bring most of sample closer to center
            sample *= scale;
            ssaoKernel.push_back(sample);
        }

        ShaderManager::instance()->bind(ambientOcclusionShader);
        int samplesLoc = glGetUniformLocation(ambientOcclusionShader, "samples");
        glUniform3fv(samplesLoc, ssaoKernel.size(), (const float *)ssaoKernel[0]);

        #ifdef _DEBUG
//      	exportSVG(std::string(std::getenv("HOME")) + "/ssaoKernel.html", ssaoKernel);
        #endif
    }
bool SSAOBuffer::load(unsigned int screenWidth, unsigned int screenHeight)
{
	glGenFramebuffers(2, FBOs);

	for (unsigned int i = 0; i < 2; ++i)
	{
		glBindFramebuffer(GL_FRAMEBUFFER, FBOs[i]);

		glGenTextures(1, &textures[i]);
	
		glBindTexture(GL_TEXTURE_2D, textures[i]);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, screenWidth, screenHeight, 0, GL_RGB, GL_FLOAT, NULL);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[i], 0);
	
		if (i == 0)
		{
			glGenTextures(1, &noiseTexture);
			std::uniform_real_distribution<GLfloat> randomFloats(0.0f, 1.0f); // generates random floats between 0.0 and 1.0
			std::default_random_engine generator;
			std::vector<glm::vec3> ssaoNoise;

			for (GLuint i = 0; i < 16; i++)
			{
				glm::vec3 noise(randomFloats(generator) * 2.0f - 1.0f, randomFloats(generator) * 2.0f - 1.0f, 0.0f); // rotate around z-axis (in tangent space)
				ssaoNoise.push_back(noise);
			}

			glBindTexture(GL_TEXTURE_2D, noiseTexture);
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 4, 4, 0, GL_RGB, GL_FLOAT, &ssaoNoise[0]);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
			glBindTexture(GL_TEXTURE_2D, 0);
		}
	}

	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	if (!Error::checkGL()) return false;
	return true;
}
std::vector<glm::vec3> SSAOScene::generateSampleKernal(int numSamples)
{
   std::vector<glm::vec3> sampleKernal; //Generate a sample within a unit sphere
   std::uniform_real_distribution<GLfloat> randomFloats(0.0, 1.0); // random floats between 0.0 - 1.0
   std::default_random_engine generator;

   for (GLuint i = 0; i < numSamples; ++i)
   {
      glm::vec3 sample(
         randomFloats(generator) * 2.0 - 1.0,
         randomFloats(generator) * 2.0 - 1.0,
         randomFloats(generator)
      );
      sample = glm::normalize(sample);
      GLfloat scale = GLfloat(i) / numSamples;
      scale = GlmUtil::lerp(0.1f, 1.0f, scale * scale);
      sample *= scale;
      sampleKernal.push_back(sample);
   }
   return sampleKernal;
}
Exemple #5
0
void Scene::initThisDemo(void)
{
	initCub(cub);
	initScreenQuad(screenQuatID);

	std::uniform_real_distribution<GLfloat> randomFloats(0.0, 1.0); // generates random floats between 0.0 and 1.0
	std::default_random_engine generator;
	
	for (GLuint i = 0; i < 64; ++i)
	{
		glm::vec3 sample(randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, randomFloats(generator));
		sample = glm::normalize(sample);
		sample *= randomFloats(generator);
		GLfloat scale = GLfloat(i) / 64.0;

		// Scale samples s.t. they're more aligned to center of kernel
		scale = lerp(0.1f, 1.0f, scale * scale);
		sample *= scale;
		ssaoKernel.push_back(sample);
	}

	// Noise texture
	for (GLuint i = 0; i < 16; i++)
	{
		glm::vec3 noise(randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, 0.0f); // rotate around z-axis (in tangent space)
		ssaoNoise.push_back(noise);
	}

	glGenTextures(1, &noiseTexture);
	glBindTexture(GL_TEXTURE_2D, noiseTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 4, 4, 0, GL_RGB, GL_FLOAT, &ssaoNoise[0]);
	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_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

}
bool SceneRenderer::loadShaderPrograms(const Camera *camera)
{
	assert(camera);
	assert(ssaoBuffer);
	assert(standardVertexShader);
	assert(geometryVertexShader);
	assert(skinnedGeometryVertexShader);
	assert(directionalLightVertexShader);
	assert(ambientOcclusionVertexShader);
	assert(blurVertexShader);
	assert(standardFragmentShader);
	assert(geometryFragmentShader);
	assert(directionalLightFragmentShader);
	assert(ambientOcclusionFragmentShader);
	assert(blurFragmentShader);

	if (!Error::checkMemory(geometryShaderProgram = new ShaderProgram())) return false;
	if (!geometryShaderProgram->load(geometryVertexShader, geometryFragmentShader)) return false;
	glUseProgram(geometryShaderProgram->getHandle());
	glUniformMatrix4fv(geometryShaderProgram->getUniform("projectionMatrix"), 1, GL_FALSE, glm::value_ptr(camera->getProjectionMatrix()));
	glUniform2f(geometryShaderProgram->getUniform("cameraNearFar"), camera->getNear(), camera->getFar());

	if (!Error::checkMemory(skinnedGeometryShaderProgram = new ShaderProgram())) return false;
	if (!skinnedGeometryShaderProgram->load(skinnedGeometryVertexShader, geometryFragmentShader)) return false;

	if (!Error::checkMemory(directionalLightShaderProgram = new ShaderProgram())) return false;
	if (!directionalLightShaderProgram->load(directionalLightVertexShader, directionalLightFragmentShader)) return false;
	glUseProgram(directionalLightShaderProgram->getHandle());
	glUniform2f(directionalLightShaderProgram->getUniform("screenSize"), 1280.0f, 720.0f);
	glUniform1i(directionalLightShaderProgram->getUniform("inNormal"), 1);
	glUniform1i(directionalLightShaderProgram->getUniform("inColor"), 2);

	if (!Error::checkMemory(ambientOcclusionShaderProgram = new ShaderProgram())) return false;
	if (!ambientOcclusionShaderProgram->load(ambientOcclusionVertexShader, ambientOcclusionFragmentShader)) return false;
	glUseProgram(ambientOcclusionShaderProgram->getHandle());
	glUniform2f(ambientOcclusionShaderProgram->getUniform("screenSize"), 1280.0f, 720.0f);
	glUniformMatrix4fv(ambientOcclusionShaderProgram->getUniform("projectionMatrix"), 1, GL_FALSE, glm::value_ptr(camera->getProjectionMatrix()));
	glUniform1i(ambientOcclusionShaderProgram->getUniform("inPosition"), 0);
	glUniform1i(ambientOcclusionShaderProgram->getUniform("inNormal"), 1);
	glUniform1i(ambientOcclusionShaderProgram->getUniform("inNoise"), 3);

	// sample kernel
	std::uniform_real_distribution<GLfloat> randomFloats(0.0f, 1.0f);
	std::default_random_engine generator;
	glm::vec3 samples[64];
	for (GLuint i = 0; i < 64; ++i)
	{
		glm::vec3 sample(randomFloats(generator) * 2.0f - 1.0f, randomFloats(generator) * 2.0f - 1.0f, randomFloats(generator));
		sample = glm::normalize(sample);
		sample *= randomFloats(generator);
		GLfloat scale = GLfloat(i) / 64.0f;

		// scale samples so that they are more aligned to the center of the kernel
		scale = lerp(0.1f, 1.0f, scale * scale);
		sample *= scale;
		samples[i] = sample;
	}
	glUniform3fv(ambientOcclusionShaderProgram->getUniform("samples"), 64, glm::value_ptr(samples[0]));

	if (!Error::checkMemory(blurShaderProgram = new ShaderProgram())) return false;
	if (!blurShaderProgram->load(blurVertexShader, blurFragmentShader)) return false;
	glUseProgram(blurShaderProgram->getHandle());
	glUniform2f(blurShaderProgram->getUniform("screenSize"), 1280.0f, 720.0f);
	glUniform1i(blurShaderProgram->getUniform("inAmbientOcclusion"), 0);

	if (!Error::checkMemory(standardShaderProgram = new ShaderProgram())) return false;
	if (!standardShaderProgram->load(standardVertexShader, standardFragmentShader)) return false;
	glUseProgram(standardShaderProgram->getHandle());
	glUniform2f(standardShaderProgram->getUniform("screenSize"), 1280.0f, 720.0f);
	glUniform1i(standardShaderProgram->getUniform("inTexture"), 0);

	return true;
}
SSAOScene::SSAOScene(Context * ctx):
CameraScene(ctx),
cryModel("assets/models/nanosuit/nanosuit.obj")
{
   FramebufferConfiguration cfg(ctx->getWindowWidth(),ctx->getWindowHeight());
   TextureConfig posBfrConfig("positionDepth",GL_RGBA16F,GL_RGBA,GL_FLOAT);
   posBfrConfig.setTextureFilter(GL_NEAREST);
   posBfrConfig.setWrapMode(GL_CLAMP_TO_EDGE);
   TextureAttachment posBfr(posBfrConfig,GL_COLOR_ATTACHMENT0);

   TextureConfig norBfrConfig("normal",GL_RGB16F,GL_RGB,GL_FLOAT);
   posBfrConfig.setTextureFilter(GL_NEAREST);
   TextureAttachment norBfr(norBfrConfig,GL_COLOR_ATTACHMENT1);

   TextureConfig spec_albedoConfig("color",GL_RGBA,GL_RGBA,GL_FLOAT);
   posBfrConfig.setTextureFilter(GL_NEAREST);


   TextureAttachment spec_albedoBfr(spec_albedoConfig,GL_COLOR_ATTACHMENT2);

   cfg.addTexturebuffer(posBfr);
   cfg.addTexturebuffer(norBfr);
   cfg.addTexturebuffer(spec_albedoBfr);
   cfg.addRenderbuffer(RenderbufferAttachment(GL_DEPTH24_STENCIL8,GL_DEPTH_STENCIL_ATTACHMENT));
   gBuffer.init(cfg);


   FramebufferConfiguration aoConfig(ctx->getWindowWidth(),ctx->getWindowHeight());
   TextureConfig aoTexture("occlusion",GL_RED,GL_RGB,GL_FLOAT);
   aoTexture.setTextureFilter(GL_NEAREST);
   TextureAttachment ssaoBufferAttachment(aoTexture,GL_COLOR_ATTACHMENT0);
   aoConfig.addTexturebuffer(ssaoBufferAttachment);
   ssaoBuffer.init(aoConfig);
   GL_Logger::LogError("Could not configure ssaoBuffer");

   //Blur buffer has same properties as ao buffer.
   FramebufferConfiguration blurConfiguration(ctx->getWindowWidth(),ctx->getWindowHeight());
   TextureConfig blurTexture("occlusion",GL_RED,GL_RGB,GL_FLOAT);
   blurTexture.setTextureFilter(GL_NEAREST);
   TextureAttachment blurBufferAttachment(blurTexture,GL_COLOR_ATTACHMENT0);
   blurConfiguration.addTexturebuffer(blurBufferAttachment);
   ssaoBlurBuffer.init(blurConfiguration);

   std::uniform_real_distribution<GLfloat> randomFloats(0.0, 1.0); // random floats between 0.0 - 1.0
   std::default_random_engine generator;
   //Set up a noise texture
   std::vector<glm::vec3> ssaoNoiseVec;
   for (GLuint i = 0; i < 16; i++)
   {
       glm::vec3 noise(
           randomFloats(generator) * 2.0 - 1.0,
           randomFloats(generator) * 2.0 - 1.0,
           0.0f);
       ssaoNoiseVec.push_back(noise);
   }

   TextureConfig ssaoConfig("ssaoNoise",GL_RGB16F,GL_RGB,GL_FLOAT);
   ssaoConfig.setWrapMode(GL_REPEAT);
   ssaoConfig.setTextureFilter(GL_NEAREST);
   ssaoNoise.init(ssaoConfig,&ssaoNoiseVec[0],4,4);
   GL_Logger::LogError("Could not configure ssaoNoise");

   deferredGBufferProg = createProgram("SSAO GBuffer fill program");
   ssaoProgram = createProgram("SSAO creation program");
   
   postProcessProg = createProgram("Display AO map program");
   ssaoBlurProgram = createProgram("SSAO blur program");
   finalPassProgram = createProgram("Final Pass Program");

   geomPlane.transform.setScale(glm::vec3(20.0));
   geomPlane.transform.setPosition(glm::vec3(0,-1,0));

   cryModel.transform.setScale(glm::vec3(0.5));
   cryModel.transform.setPosition(glm::vec3(0,0,3.5));
   cryModel.transform.rotate(-M_PI/2, glm::vec3(1.0,0,0));


}
Exemple #8
0
// The MAIN function, from here we start our application and run our Game loop
int main()
{
    // Init GLFW
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed
    glfwMakeContextCurrent(window);

    // Set the required callback functions
    glfwSetKeyCallback(window, key_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // Options
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // Initialize GLEW to setup the OpenGL Function pointers
    glewExperimental = GL_TRUE;
    glewInit();
    glGetError();

    // Define the viewport dimensions
    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);

    // Setup some OpenGL options
    glEnable(GL_DEPTH_TEST);

    // Setup and compile our shaders
    Shader shaderGeometryPass("ssao_geometry.vs", "ssao_geometry.frag");
    Shader shaderLightingPass("ssao.vs", "ssao_lighting.frag");
    Shader shaderSSAO("ssao.vs", "ssao.frag");
    Shader shaderSSAOBlur("ssao.vs", "ssao_blur.frag");

    // Set samplers
    shaderLightingPass.Use();
    glUniform1i(glGetUniformLocation(shaderLightingPass.Program, "gPositionDepth"), 0);
    glUniform1i(glGetUniformLocation(shaderLightingPass.Program, "gNormal"), 1);
    glUniform1i(glGetUniformLocation(shaderLightingPass.Program, "gAlbedo"), 2); 
    glUniform1i(glGetUniformLocation(shaderLightingPass.Program, "ssao"), 3); 
    shaderSSAO.Use();
    glUniform1i(glGetUniformLocation(shaderSSAO.Program, "gPositionDepth"), 0);
    glUniform1i(glGetUniformLocation(shaderSSAO.Program, "gNormal"), 1);
    glUniform1i(glGetUniformLocation(shaderSSAO.Program, "texNoise"), 2);

    // Objects
    Model nanosuit(FileSystem::getPath("resources/objects/nanosuit/nanosuit.obj").c_str());

    // Lights
    glm::vec3 lightPos = glm::vec3(2.0, 4.0, -2.0);
    glm::vec3 lightColor = glm::vec3(0.2, 0.2, 0.7);

    // Set up G-Buffer
    // 3 textures:
    // 1. Positions + depth (RGBA)
    // 2. Color (RGB) 
    // 3. Normals (RGB) 
    GLuint gBuffer;
    glGenFramebuffers(1, &gBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
    GLuint gPositionDepth, gNormal, gAlbedo;
    // - Position + linear depth color buffer
    glGenTextures(1, &gPositionDepth);
    glBindTexture(GL_TEXTURE_2D, gPositionDepth);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_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);
    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, gPositionDepth, 0);
    // - Normal color buffer
    glGenTextures(1, &gNormal);
    glBindTexture(GL_TEXTURE_2D, gNormal);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_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_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);
    // - Albedo color buffer
    glGenTextures(1, &gAlbedo);
    glBindTexture(GL_TEXTURE_2D, gAlbedo);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_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, gAlbedo, 0);
    // - Tell OpenGL which color attachments we'll use (of this framebuffer) for rendering 
    GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
    glDrawBuffers(3, attachments);
    // - Create and attach depth buffer (renderbuffer)
    GLuint rboDepth;
    glGenRenderbuffers(1, &rboDepth);
    glBindRenderbuffer(GL_RENDERBUFFER, rboDepth);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth);
    // - Finally check if framebuffer is complete
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "GBuffer Framebuffer not complete!" << std::endl;

    // Also create framebuffer to hold SSAO processing stage 
    GLuint ssaoFBO, ssaoBlurFBO;
    glGenFramebuffers(1, &ssaoFBO);  glGenFramebuffers(1, &ssaoBlurFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, ssaoFBO);
    GLuint ssaoColorBuffer, ssaoColorBufferBlur;
    // - SSAO color buffer
    glGenTextures(1, &ssaoColorBuffer);
    glBindTexture(GL_TEXTURE_2D, ssaoColorBuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_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_ATTACHMENT0, GL_TEXTURE_2D, ssaoColorBuffer, 0);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "SSAO Framebuffer not complete!" << std::endl;
    // - and blur stage
    glBindFramebuffer(GL_FRAMEBUFFER, ssaoBlurFBO);
    glGenTextures(1, &ssaoColorBufferBlur);
    glBindTexture(GL_TEXTURE_2D, ssaoColorBufferBlur);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, SCR_WIDTH, SCR_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_ATTACHMENT0, GL_TEXTURE_2D, ssaoColorBufferBlur, 0);
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        std::cout << "SSAO Blur Framebuffer not complete!" << std::endl;
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // Sample kernel
    std::uniform_real_distribution<GLfloat> randomFloats(0.0, 1.0); // generates random floats between 0.0 and 1.0
    std::default_random_engine generator;
    std::vector<glm::vec3> ssaoKernel;
    for (GLuint i = 0; i < 64; ++i)
    {
        glm::vec3 sample(randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, randomFloats(generator));
        sample = glm::normalize(sample);
        sample *= randomFloats(generator);
        GLfloat scale = GLfloat(i) / 64.0;

		// Scale samples s.t. they're more aligned to center of kernel
        scale = lerp(0.1f, 1.0f, scale * scale);
        sample *= scale;
        ssaoKernel.push_back(sample);
    }

    // Noise texture
    std::vector<glm::vec3> ssaoNoise;
    for (GLuint i = 0; i < 16; i++)
    {
        glm::vec3 noise(randomFloats(generator) * 2.0 - 1.0, randomFloats(generator) * 2.0 - 1.0, 0.0f); // rotate around z-axis (in tangent space)
        ssaoNoise.push_back(noise);
    }
    GLuint noiseTexture; glGenTextures(1, &noiseTexture);
    glBindTexture(GL_TEXTURE_2D, noiseTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 4, 4, 0, GL_RGB, GL_FLOAT, &ssaoNoise[0]);
    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_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);


    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    // Game loop
    while (!glfwWindowShouldClose(window))
    {
        // Set frame time
        GLfloat currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // Check and call events
        glfwPollEvents();
        Do_Movement();


        // 1. Geometry Pass: render scene's geometry/color data into gbuffer
        glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glm::mat4 projection = glm::perspective(camera.Zoom, (GLfloat)SCR_WIDTH / (GLfloat)SCR_HEIGHT, 0.1f, 50.0f);
            glm::mat4 view = camera.GetViewMatrix();
            glm::mat4 model;
            shaderGeometryPass.Use();
            glUniformMatrix4fv(glGetUniformLocation(shaderGeometryPass.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
            glUniformMatrix4fv(glGetUniformLocation(shaderGeometryPass.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
            // Floor cube
            model = glm::translate(model, glm::vec3(0.0, -1.0f, 0.0f));
            model = glm::scale(model, glm::vec3(20.0f, 1.0f, 20.0f));
            glUniformMatrix4fv(glGetUniformLocation(shaderGeometryPass.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
            RenderCube();
            // Nanosuit model on the floor
            model = glm::mat4();
            model = glm::translate(model, glm::vec3(0.0f, 0.0f, 5.0));
            model = glm::rotate(model, -90.0f, glm::vec3(1.0, 0.0, 0.0));
            model = glm::scale(model, glm::vec3(0.5f));
            glUniformMatrix4fv(glGetUniformLocation(shaderGeometryPass.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
            nanosuit.Draw(shaderGeometryPass);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);


        // 2. Create SSAO texture
        glBindFramebuffer(GL_FRAMEBUFFER, ssaoFBO);
            glClear(GL_COLOR_BUFFER_BIT);
            shaderSSAO.Use();
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, gPositionDepth);
            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D, gNormal);
            glActiveTexture(GL_TEXTURE2);
            glBindTexture(GL_TEXTURE_2D, noiseTexture);
            // Send kernel + rotation 
            for (GLuint i = 0; i < 64; ++i)
                glUniform3fv(glGetUniformLocation(shaderSSAO.Program, ("samples[" + std::to_string(i) + "]").c_str()), 1, &ssaoKernel[i][0]);
            glUniformMatrix4fv(glGetUniformLocation(shaderSSAO.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
            RenderQuad();
        glBindFramebuffer(GL_FRAMEBUFFER, 0);


        // 3. Blur SSAO texture to remove noise
        glBindFramebuffer(GL_FRAMEBUFFER, ssaoBlurFBO);
            glClear(GL_COLOR_BUFFER_BIT);
            shaderSSAOBlur.Use();
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, ssaoColorBuffer);
            RenderQuad();
        glBindFramebuffer(GL_FRAMEBUFFER, 0);


        // 4. Lighting Pass: traditional deferred Blinn-Phong lighting now with added screen-space ambient occlusion
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        shaderLightingPass.Use();
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, gPositionDepth);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, gNormal);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, gAlbedo);
        glActiveTexture(GL_TEXTURE3); // Add extra SSAO texture to lighting pass
        glBindTexture(GL_TEXTURE_2D, ssaoColorBufferBlur);
        // Also send light relevant uniforms
        glm::vec3 lightPosView = glm::vec3(camera.GetViewMatrix() * glm::vec4(lightPos, 1.0));
        glUniform3fv(glGetUniformLocation(shaderLightingPass.Program, "light.Position"), 1, &lightPosView[0]);
        glUniform3fv(glGetUniformLocation(shaderLightingPass.Program, "light.Color"), 1, &lightColor[0]);
        // Update attenuation parameters
        const GLfloat constant = 1.0; // Note that we don't send this to the shader, we assume it is always 1.0 (in our case)
        const GLfloat linear = 0.09;
        const GLfloat quadratic = 0.032;
        glUniform1f(glGetUniformLocation(shaderLightingPass.Program, "light.Linear"), linear);
        glUniform1f(glGetUniformLocation(shaderLightingPass.Program, "light.Quadratic"), quadratic);
        glUniform1i(glGetUniformLocation(shaderLightingPass.Program, "draw_mode"), draw_mode);
        RenderQuad();


        // Swap the buffers
        glfwSwapBuffers(window);
    }

    glfwTerminate();
    return 0;
}