void PostProcessor::render() const
{
	if (!frameBuffer.get() || !tempFrameBuffer.get() || !bloomFrameBuffer.get() || !depthOfFieldFrameBuffer.get() || !blurTexture1DArray.get() || !bloomTexture1DArray.get() || !depthOfFieldTexture1DArray.get() )
	{
		return;
	}

	CameraSP currentCamera = CameraManager::getInstance()->getDefaultOrthographicCamera();

	Matrix4x4 modelMatrix;
	modelMatrix.scale(static_cast<GLfloat>(frameBuffer->getWidth()), static_cast<GLfloat>(frameBuffer->getHeight()), 1.0f);

	program->use();

	setUniforms();

	glDepthMask(GL_FALSE);
	glDisable(GL_DEPTH_TEST);

	glUniformMatrix4fv(program->getUniformLocation(u_projectionMatrix), 1, GL_FALSE, currentCamera->getProjectionMatrix().getM());
	glUniformMatrix4fv(program->getUniformLocation(u_viewMatrix), 1, GL_FALSE, currentCamera->getViewMatrix().getM());
	glUniformMatrix4fv(program->getUniformLocation(u_modelMatrix), 1, GL_FALSE, modelMatrix.getM());

	//

	const CameraSP& defaultCamera = CameraManager::getInstance()->getDefaultPerspectiveCamera();

	Point4 focalPoint = defaultCamera->getEye() + (defaultCamera->getDirection() * focal);
	Point4 focusedObjectPoint = defaultCamera->getEye() + (defaultCamera->getDirection() * focusedObject);

	Point4 biasedFocalPoint = defaultCamera->getBiasedProjectionMatrix() * defaultCamera->getViewMatrix() * focalPoint;
	Point4 biasedFocusedObjectPoint = defaultCamera->getBiasedProjectionMatrix() * defaultCamera->getViewMatrix() * focusedObjectPoint;

	glUniform1f(program->getUniformLocation(u_aperture), aperture);
	glUniform1f(program->getUniformLocation(u_focal), glusClampf(biasedFocalPoint.getZ(), 0.0f, 1.0f));
	glUniform1f(program->getUniformLocation(u_focusedObject), glusClampf(biasedFocusedObjectPoint.getZ(), 0.0f, 1.0f));

	//

	// Depth texture
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(target, frameBuffer->getDepthTexture()->getTextureName());
	glUniform1i(program->getUniformLocation(depthTexture), 3);

	glActiveTexture(GL_TEXTURE0);

	// Create bloom textures
	if (useBloom)
	{
		use(false);

		// Only the bright texture is needed
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(target, frameBuffer->getColor1Texture()->getTextureName());
		glUniform1i(program->getUniformLocation(screenTexture), 0);

		// Not used. Just take the color texture as a dummy value
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(target, frameBuffer->getColor0Texture()->getTextureName());
		glUniform1i(program->getUniformLocation(bloomTexture), 1);

		// Blur using the bloom texture
		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_1D_ARRAY, bloomTexture1DArray->getTextureName());
		glUniform1i(program->getUniformLocation(u_blurTexture), 2);

		glActiveTexture(GL_TEXTURE0);

		glUniform1i(program->getUniformLocation(u_useDoF), false);

		glUniform1i(program->getUniformLocation(u_useBlur), true);

		glUniform1i(program->getUniformLocation(u_useBloom), false);
		glUniform1f(program->getUniformLocation(u_bloomLevel), bloomLevel);

		glUniform1i(program->getUniformLocation(u_useExposure), false);
		glUniform1f(program->getUniformLocation(u_exposure), exposure);

		glUniform1i(program->getUniformLocation(u_useGamma), false);
		glUniform1f(program->getUniformLocation(u_gamma), gamma);

		postProcessorVAO->bind();

		// First pass ...

		glUniform1i(program->getUniformLocation(u_blurHorizontal), 1);
		glUniform1i(program->getUniformLocation(u_blurVertical), 0);
		tempFrameBuffer->use(true);

		glDrawElements(GL_TRIANGLES, numberIndices, GL_UNSIGNED_INT, 0);

		tempFrameBuffer->use(false);

		// ... and final pass

		glBindTexture(target, tempFrameBuffer->getColor0Texture()->getTextureName());

		glUniform1i(program->getUniformLocation(u_blurHorizontal), 0);
		glUniform1i(program->getUniformLocation(u_blurVertical), 1);

		bloomFrameBuffer->use(true);

		glDrawElements(GL_TRIANGLES, numberIndices, GL_UNSIGNED_INT, 0);

		bloomFrameBuffer->use(false);

		//

		postProcessorVAO->unbind();

		glBindTexture(target, 0);
	}

	FrameBufferSP usedFrameBuffer = frameBuffer;

	// Bloom the framebuffer
	if (useBloom)
	{
		use(false);

		// Original frame buffer
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(target, frameBuffer->getColor0Texture()->getTextureName());
		glUniform1i(program->getUniformLocation(screenTexture), 0);

		// Bloom
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(target, bloomFrameBuffer->getColor0Texture()->getTextureName());
		glUniform1i(program->getUniformLocation(bloomTexture), 1);

		// Dummy
		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_1D_ARRAY, bloomTexture1DArray->getTextureName());
		glUniform1i(program->getUniformLocation(u_blurTexture), 2);

		glActiveTexture(GL_TEXTURE0);

		glUniform1i(program->getUniformLocation(u_useDoF), false);

		glUniform1i(program->getUniformLocation(u_useBlur), false);

		glUniform1i(program->getUniformLocation(u_useBloom), true);
		glUniform1f(program->getUniformLocation(u_bloomLevel), bloomLevel);

		glUniform1i(program->getUniformLocation(u_useExposure), false);
		glUniform1f(program->getUniformLocation(u_exposure), exposure);

		glUniform1i(program->getUniformLocation(u_useGamma), false);
		glUniform1f(program->getUniformLocation(u_gamma), gamma);

		glUniform1i(program->getUniformLocation(u_blurHorizontal), 0);
		glUniform1i(program->getUniformLocation(u_blurVertical), 0);

		postProcessorVAO->bind();

		// One pass

		tempFrameBuffer->use(true);

		glDrawElements(GL_TRIANGLES, numberIndices, GL_UNSIGNED_INT, 0);

		tempFrameBuffer->use(false);

		//

		postProcessorVAO->unbind();

		glBindTexture(target, 0);

		usedFrameBuffer = tempFrameBuffer;
	}

	// Depth of field
	if (useDoF)
	{
		use(false);

		// Only the color texture is needed
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(target, usedFrameBuffer->getColor0Texture()->getTextureName());
		glUniform1i(program->getUniformLocation(screenTexture), 0);

		// Not used. Just take the color texture as a dummy value
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(target, frameBuffer->getColor0Texture()->getTextureName());
		glUniform1i(program->getUniformLocation(bloomTexture), 1);

		// Blur using the DOF texture
		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_1D_ARRAY, depthOfFieldTexture1DArray->getTextureName());
		glUniform1i(program->getUniformLocation(u_blurTexture), 2);

		glActiveTexture(GL_TEXTURE0);

		glUniform1i(program->getUniformLocation(u_useDoF), true);

		glUniform1i(program->getUniformLocation(u_useBlur), false);

		glUniform1i(program->getUniformLocation(u_useBloom), false);
		glUniform1f(program->getUniformLocation(u_bloomLevel), bloomLevel);

		glUniform1i(program->getUniformLocation(u_useExposure), false);
		glUniform1f(program->getUniformLocation(u_exposure), exposure);

		glUniform1i(program->getUniformLocation(u_useGamma), false);
		glUniform1f(program->getUniformLocation(u_gamma), gamma);

		postProcessorVAO->bind();

		// First pass ...

		glUniform1i(program->getUniformLocation(u_blurHorizontal), 1);
		glUniform1i(program->getUniformLocation(u_blurVertical), 0);
		tempFrameBuffer->use(true);

		glDrawElements(GL_TRIANGLES, numberIndices, GL_UNSIGNED_INT, 0);

		tempFrameBuffer->use(false);

		// ... and final pass

		glBindTexture(target, tempFrameBuffer->getColor0Texture()->getTextureName());

		glUniform1i(program->getUniformLocation(u_blurHorizontal), 0);
		glUniform1i(program->getUniformLocation(u_blurVertical), 1);
		depthOfFieldFrameBuffer->use(true);

		glDrawElements(GL_TRIANGLES, numberIndices, GL_UNSIGNED_INT, 0);

		depthOfFieldFrameBuffer->use(false);

		//

		postProcessorVAO->unbind();

		glBindTexture(target, 0);

		usedFrameBuffer = depthOfFieldFrameBuffer;
	}

	//

	use(false);

	// Color
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(target, usedFrameBuffer->getColor0Texture()->getTextureName());
	glUniform1i(program->getUniformLocation(screenTexture), 0);

	// Not used.
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(target, bloomFrameBuffer->getColor0Texture()->getTextureName());
	glUniform1i(program->getUniformLocation(bloomTexture), 1);

	// Blur
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_1D_ARRAY, blurTexture1DArray->getTextureName());
	glUniform1i(program->getUniformLocation(u_blurTexture), 2);

	glActiveTexture(GL_TEXTURE0);

	glUniform1i(program->getUniformLocation(u_useDoF), false);

	glUniform1i(program->getUniformLocation(u_useBlur), useBlur);
	glUniform1i(program->getUniformLocation(u_blurHorizontal), 1);
	glUniform1i(program->getUniformLocation(u_blurVertical), 1);

	glUniform1i(program->getUniformLocation(u_useBloom), false);
	glUniform1f(program->getUniformLocation(u_bloomLevel), bloomLevel);

	glUniform1i(program->getUniformLocation(u_useExposure), useExposure);
	glUniform1f(program->getUniformLocation(u_exposure), exposure);

	glUniform1i(program->getUniformLocation(u_useGamma), useGamma);
	glUniform1f(program->getUniformLocation(u_gamma), gamma);

	postProcessorVAO->bind();

	glDrawElements(GL_TRIANGLES, numberIndices, GL_UNSIGNED_INT, 0);

	postProcessorVAO->unbind();

	//

	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);

	glBindTexture(target, 0);
}