#include #include #include #include #include #include #include #include #include #include void framebuffer_size_callback(GLFWwindow* window, int width, int height); void mouse_callback(GLFWwindow* window, double xpos, double ypos); void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); void processInput(GLFWwindow *window); unsigned int loadTexture(const char *path, bool gammaCorrection); void renderQuad(); void renderCube(); // settings const unsigned int SCR_WIDTH = 1280; const unsigned int SCR_HEIGHT = 720; // camera Camera camera(glm::vec3(0.0f, 0.0f, 5.0f)); float lastX = (float)SCR_WIDTH / 2.0; float lastY = (float)SCR_HEIGHT / 2.0; bool firstMouse = true; // timing float deltaTime = 0.0f; float lastFrame = 0.0f; int main() { // glfw: initialize and configure // ------------------------------ glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X #endif // glfw window creation // -------------------- GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetCursorPosCallback(window, mouse_callback); glfwSetScrollCallback(window, scroll_callback); // tell GLFW to capture our mouse glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // glad: load all OpenGL function pointers // --------------------------------------- if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; } // configure global opengl state // ----------------------------- glEnable(GL_DEPTH_TEST); // build and compile shaders // ------------------------- Shader shaderGeometryPass("8.1.g_buffer.vs", "8.1.g_buffer.fs"); Shader shaderLightingPass("8.1.deferred_shading.vs", "8.1.deferred_shading.fs"); Shader shaderLightBox("8.1.deferred_light_box.vs", "8.1.deferred_light_box.fs"); // load models // ----------- Model nanosuit(FileSystem::getPath("resources/objects/nanosuit/nanosuit.obj")); std::vector objectPositions; objectPositions.push_back(glm::vec3(-3.0, -3.0, -3.0)); objectPositions.push_back(glm::vec3( 0.0, -3.0, -3.0)); objectPositions.push_back(glm::vec3( 3.0, -3.0, -3.0)); objectPositions.push_back(glm::vec3(-3.0, -3.0, 0.0)); objectPositions.push_back(glm::vec3( 0.0, -3.0, 0.0)); objectPositions.push_back(glm::vec3( 3.0, -3.0, 0.0)); objectPositions.push_back(glm::vec3(-3.0, -3.0, 3.0)); objectPositions.push_back(glm::vec3( 0.0, -3.0, 3.0)); objectPositions.push_back(glm::vec3( 3.0, -3.0, 3.0)); // configure g-buffer framebuffer // ------------------------------ unsigned int gBuffer; glGenFramebuffers(1, &gBuffer); glBindFramebuffer(GL_FRAMEBUFFER, gBuffer); unsigned int gPosition, gNormal, gAlbedoSpec; // position color buffer glGenTextures(1, &gPosition); glBindTexture(GL_TEXTURE_2D, gPosition); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 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, gPosition, 0); // normal color buffer glGenTextures(1, &gNormal); glBindTexture(GL_TEXTURE_2D, gNormal); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, 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); // color + specular color buffer glGenTextures(1, &gAlbedoSpec); glBindTexture(GL_TEXTURE_2D, gAlbedoSpec); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, 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, gAlbedoSpec, 0); // tell OpenGL which color attachments we'll use (of this framebuffer) for rendering unsigned int attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 }; glDrawBuffers(3, attachments); // create and attach depth buffer (renderbuffer) unsigned int 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 << "Framebuffer not complete!" << std::endl; glBindFramebuffer(GL_FRAMEBUFFER, 0); // lighting info // ------------- const unsigned int NR_LIGHTS = 32; std::vector lightPositions; std::vector lightColors; srand(13); for (unsigned int i = 0; i < NR_LIGHTS; i++) { // calculate slightly random offsets float xPos = ((rand() % 100) / 100.0) * 6.0 - 3.0; float yPos = ((rand() % 100) / 100.0) * 6.0 - 4.0; float zPos = ((rand() % 100) / 100.0) * 6.0 - 3.0; lightPositions.push_back(glm::vec3(xPos, yPos, zPos)); // also calculate random color float rColor = ((rand() % 100) / 200.0f) + 0.5; // between 0.5 and 1.0 float gColor = ((rand() % 100) / 200.0f) + 0.5; // between 0.5 and 1.0 float bColor = ((rand() % 100) / 200.0f) + 0.5; // between 0.5 and 1.0 lightColors.push_back(glm::vec3(rColor, gColor, bColor)); } // shader configuration // -------------------- shaderLightingPass.use(); shaderLightingPass.setInt("gPosition", 0); shaderLightingPass.setInt("gNormal", 1); shaderLightingPass.setInt("gAlbedoSpec", 2); // render loop // ----------- while (!glfwWindowShouldClose(window)) { // per-frame time logic // -------------------- float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; // input // ----- processInput(window); // render // ------ glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 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(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); glm::mat4 view = camera.GetViewMatrix(); glm::mat4 model; shaderGeometryPass.use(); shaderGeometryPass.setMat4("projection", projection); shaderGeometryPass.setMat4("view", view); for (unsigned int i = 0; i < objectPositions.size(); i++) { model = glm::mat4(); model = glm::translate(model, objectPositions[i]); model = glm::scale(model, glm::vec3(0.25f)); shaderGeometryPass.setMat4("model", model); nanosuit.Draw(shaderGeometryPass); } glBindFramebuffer(GL_FRAMEBUFFER, 0); // 2. lighting pass: calculate lighting by iterating over a screen filled quad pixel-by-pixel using the gbuffer's content. // ----------------------------------------------------------------------------------------------------------------------- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); shaderLightingPass.use(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, gPosition); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, gNormal); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, gAlbedoSpec); // send light relevant uniforms for (unsigned int i = 0; i < lightPositions.size(); i++) { shaderLightingPass.setVec3("lights[" + std::to_string(i) + "].Position", lightPositions[i]); shaderLightingPass.setVec3("lights[" + std::to_string(i) + "].Color", lightColors[i]); // update attenuation parameters and calculate radius const float constant = 1.0; // note that we don't send this to the shader, we assume it is always 1.0 (in our case) const float linear = 0.7; const float quadratic = 1.8; shaderLightingPass.setFloat("lights[" + std::to_string(i) + "].Linear", linear); shaderLightingPass.setFloat("lights[" + std::to_string(i) + "].Quadratic", quadratic); } shaderLightingPass.setVec3("viewPos", camera.Position); // finally render quad renderQuad(); // 2.5. copy content of geometry's depth buffer to default framebuffer's depth buffer // ---------------------------------------------------------------------------------- glBindFramebuffer(GL_READ_FRAMEBUFFER, gBuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // write to default framebuffer // blit to default framebuffer. Note that this may or may not work as the internal formats of both the FBO and default framebuffer have to match. // the internal formats are implementation defined. This works on all of my systems, but if it doesn't on yours you'll likely have to write to the // depth buffer in another shader stage (or somehow see to match the default framebuffer's internal format with the FBO's internal format). glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_FRAMEBUFFER, 0); // 3. render lights on top of scene // -------------------------------- shaderLightBox.use(); shaderLightBox.setMat4("projection", projection); shaderLightBox.setMat4("view", view); for (unsigned int i = 0; i < lightPositions.size(); i++) { model = glm::mat4(); model = glm::translate(model, lightPositions[i]); model = glm::scale(model, glm::vec3(0.125f)); shaderLightBox.setMat4("model", model); shaderLightBox.setVec3("lightColor", lightColors[i]); renderCube(); } // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) // ------------------------------------------------------------------------------- glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; } // renderCube() renders a 1x1 3D cube in NDC. // ------------------------------------------------- unsigned int cubeVAO = 0; unsigned int cubeVBO = 0; void renderCube() { // initialize (if necessary) if (cubeVAO == 0) { float vertices[] = { // back face -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // bottom-left -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, // top-left // front face -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, // top-left -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left // left face -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-right -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right // right face 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left // bottom face -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right // top face -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left 1.0f, 1.0f , 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, // top-left -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left }; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &cubeVBO); // fill buffer glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // link vertex attributes glBindVertexArray(cubeVAO); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } // render Cube glBindVertexArray(cubeVAO); glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0); } // renderQuad() renders a 1x1 XY quad in NDC // ----------------------------------------- unsigned int quadVAO = 0; unsigned int quadVBO; void renderQuad() { if (quadVAO == 0) { float quadVertices[] = { // positions // texture Coords -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, }; // setup plane VAO glGenVertexArrays(1, &quadVAO); glGenBuffers(1, &quadVBO); glBindVertexArray(quadVAO); glBindBuffer(GL_ARRAY_BUFFER, quadVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); } glBindVertexArray(quadVAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); } // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly // --------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.ProcessKeyboard(FORWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(BACKWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(LEFT, deltaTime); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(RIGHT, deltaTime); } // glfw: whenever the window size changed (by OS or user resize) this callback function executes // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) { // make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays. glViewport(0, 0, width, height); } // glfw: whenever the mouse moves, this callback is called // ------------------------------------------------------- void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; } float xoffset = xpos - lastX; float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(xoffset, yoffset); } // glfw: whenever the mouse scroll wheel scrolls, this callback is called // ---------------------------------------------------------------------- void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { camera.ProcessMouseScroll(yoffset); }