Fix metal roughness texture on streaked metal material
[opengl.git] / skybox.cpp
index bd097cb473dc85f92189a354325de78f6b4c4ba5..d668d76d7025fc1f865ba477632ade63b72d7f85 100644 (file)
 #include "shapes.hpp"
 #include "skybox.hpp"
-#include "image.hpp"
-#include <GL/glew.h>
 #include <glm/gtc/type_ptr.hpp>
 
-Skybox::Skybox(const std::vector<std::string> faces): program("skyboxvert.glsl", "skyboxfrag.glsl") {
-       glUseProgram(program.progId);
-       glGenTextures(1, &texId);
-       glBindTexture(GL_TEXTURE_CUBE_MAP, texId);
+template <std::size_t N>
+GLuint setupVertices(GLuint progId, std::array<glm::vec3, N> vertices, bool reverse = false);
+
+// matrices used when capturing various environment maps
+glm::mat4 captureProj = glm::perspective(glm::radians(90.f), 1.f, 0.1f, 10.f);
+glm::mat4 captureViews[] = {
+       glm::lookAt(glm::vec3(0, 0, 0), glm::vec3( 1,  0,  0), glm::vec3(0, -1,  0)),
+       glm::lookAt(glm::vec3(0, 0, 0), glm::vec3(-1,  0,  0), glm::vec3(0, -1,  0)),
+       glm::lookAt(glm::vec3(0, 0, 0), glm::vec3( 0,  1,  0), glm::vec3(0,  0,  1)),
+       glm::lookAt(glm::vec3(0, 0, 0), glm::vec3( 0, -1,  0), glm::vec3(0,  0, -1)),
+       glm::lookAt(glm::vec3(0, 0, 0), glm::vec3( 0,  0,  1), glm::vec3(0, -1,  0)),
+       glm::lookAt(glm::vec3(0, 0, 0), glm::vec3( 0,  0, -1), glm::vec3(0, -1,  0))
+};
+
+void Skybox::generatePrefilterMap() const {
+       glBindTexture(GL_TEXTURE_CUBE_MAP, prefilterTexId);
+       for (GLuint i = 0; i < 6; i++)
+               glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0 , GL_RGB16F, 128, 128, 0, GL_RGB, GL_FLOAT, nullptr);
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+       glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
+
+       Program prefilterProg("skyboxvert.glsl", "prefilterfrag.glsl");
+       glUseProgram(prefilterProg.progId);
+       glUniform1i(glGetUniformLocation(prefilterProg.progId, "environmentMap"), 0);
+       glUniformMatrix4fv(glGetUniformLocation(prefilterProg.progId, "projection"), 1, GL_FALSE, glm::value_ptr(captureProj));
+       glActiveTexture(GL_TEXTURE0);
+       glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexId);
+       
+       setupVertices(prefilterProg.progId, cube());
+       
+       glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
+       constexpr GLuint MAX_MIP_LEVELS = 5;
+       for (GLuint mip = 0; mip < MAX_MIP_LEVELS; mip++) {
+               GLuint mipWidth = 128 * std::pow(0.5, mip);
+               GLuint mipHeight = 128 * std::pow(0.5, mip);
+               glBindRenderbuffer(GL_RENDERBUFFER, captureRBO);
+               glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, mipWidth, mipHeight);
+               glViewport(0, 0, mipWidth, mipHeight);
+
+               float roughness = (float)mip / (MAX_MIP_LEVELS - 1.f);
+               glUniform1f(glGetUniformLocation(prefilterProg.progId, "roughness"), roughness);
+               
+               for (GLuint i = 0; i < 6; i++) {
+                       glUniformMatrix4fv(glGetUniformLocation(prefilterProg.progId, "view"), 1, GL_FALSE, glm::value_ptr(captureViews[i]));
+                       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, prefilterTexId, mip);
+                       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+                       glDrawArrays(GL_TRIANGLES, 0, 36);
+               }
+       }
+}
+
+void Skybox::generateBRDFMap() const {
+       glBindTexture(GL_TEXTURE_2D, brdfMapTexId);
+       // allocate memory
+       glTexImage2D(GL_TEXTURE_2D, 0, GL_RG16F, 512, 512, 0, GL_RG, GL_FLOAT, 0);
+       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_LINEAR);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
 
-       int width, height, numChans;
-       for (int i = 0; i < faces.size(); i++) {
-               Image img(faces[i]);
+       glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
+       glBindRenderbuffer(GL_RENDERBUFFER, captureRBO);
+       glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512);
+       glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, brdfMapTexId, 0);
 
-               glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, img.width(), img.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, img.data());
+       glViewport(0, 0, 512, 512);
+       Program prog("brdfvert.glsl", "brdffrag.glsl");
+       glUseProgram(prog.progId);
+       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+       
+       setupVertices(prog.progId, plane());
+       glDrawArrays(GL_TRIANGLES, 0, 6);
 }
 
+Skybox::Skybox(const Image img): program("skyboxvert.glsl", "skyboxfrag.glsl") {
+       Program equiProg("skyboxvert.glsl", "equirectangularfrag.glsl");
+       glUseProgram(equiProg.progId);
+
+       GLuint allTexIds[5];
+       glGenTextures(5, allTexIds);
+       hdrTexId = allTexIds[0];
+       cubemapTexId = allTexIds[1];
+       irradianceTexId = allTexIds[2];
+       prefilterTexId = allTexIds[3];
+       brdfMapTexId = allTexIds[4];
+       
+       glBindTexture(GL_TEXTURE_2D, hdrTexId);
+
+       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, img.width(), img.height(), 0, GL_RGB, GL_HALF_FLOAT, (uint16_t*)img.data());
+
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+       // generate framebuffers to store cubemap in
+       glGenFramebuffers(1, &captureFBO);
+       glGenRenderbuffers(1, &captureRBO);
+       
+       constexpr GLuint CUBEMAP_WIDTH = 1024, CUBEMAP_HEIGHT = 1024;
+
+       glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
+       glBindRenderbuffer(GL_RENDERBUFFER, captureRBO);
+       glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, CUBEMAP_WIDTH, CUBEMAP_HEIGHT);
+       glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, captureFBO);
+
+       // setup cubemap texture
+       glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexId);
+       for (GLuint i = 0; i < 6; i++)
+               glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, CUBEMAP_WIDTH, CUBEMAP_HEIGHT, 0, GL_RGB, GL_FLOAT, nullptr);
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+       // generate vertices
+       setupVertices(equiProg.progId, cube());
+
+       // render the cube
+
+       glUniform1i(glGetUniformLocation(equiProg.progId, "equirectangularMap"), 0);
+       glUniformMatrix4fv(glGetUniformLocation(equiProg.progId, "projection"), 1, GL_FALSE, glm::value_ptr(captureProj));
+       glActiveTexture(GL_TEXTURE0);
+       glBindTexture(GL_TEXTURE_2D, hdrTexId);
+
+       glViewport(0, 0, CUBEMAP_WIDTH, CUBEMAP_HEIGHT);
+       glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
+       for (GLuint i = 0; i < 6; i++) {
+               glUniformMatrix4fv(glGetUniformLocation(equiProg.progId, "view"), 1, GL_FALSE, glm::value_ptr(captureViews[i]));
+               glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, cubemapTexId, 0);
+               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+               glDrawArrays(GL_TRIANGLES, 0, 36);
+       }
+
+       // setup irradiance map texture
+       glBindTexture(GL_TEXTURE_CUBE_MAP, irradianceTexId);
+       for (GLuint i = 0; i < 6; i++)
+               glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, 32, 32, 0, GL_RGB, GL_FLOAT, nullptr);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+       glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
-       glGenVertexArrays(1, &vao);
-       glBindVertexArray(vao);
-
-       GLuint vbo;
-       glGenBuffers(1, &vbo);
+       // bind framebuffers for rendering irradiance map into
+       glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
+       glBindRenderbuffer(GL_RENDERBUFFER, captureRBO);
+       glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 32, 32);
        
-       auto vertices = cube();
+       Program irradianceProg("skyboxvert.glsl", "irradiancefrag.glsl");
+       glUseProgram(irradianceProg.progId);
+       glUniform1i(glGetUniformLocation(irradianceProg.progId, "environmentMap"), 0);
+       glUniformMatrix4fv(glGetUniformLocation(irradianceProg.progId, "projection"), 1, GL_FALSE, glm::value_ptr(captureProj));
+       glActiveTexture(GL_TEXTURE0);
+       glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexId);
 
-       glBindBuffer(GL_ARRAY_BUFFER, vbo);
-       glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);
+       // generate vertices
+       setupVertices(irradianceProg.progId, cube());
 
-       GLuint posLoc = glGetAttribLocation(program.progId, "pos");
-       glEnableVertexAttribArray(posLoc);
-       glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
-}
-void validatePrograms(GLuint progId) {
-       glValidateProgram(progId);
-       
-       GLint success;
-       glGetProgramiv(progId, GL_VALIDATE_STATUS, &success);
-       if (!success) {
-               GLchar log[1024];
-               glGetProgramInfoLog(progId, sizeof(log), NULL, log);
-               fprintf(stderr, "error: %s\n", log);
-               exit(1);
+       // render irradiance map
+       glViewport(0, 0, 32, 32);
+       glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
+       for (GLuint i = 0; i < 6; i++) {
+               glUniformMatrix4fv(glGetUniformLocation(irradianceProg.progId, "view"), 1, GL_FALSE, glm::value_ptr(captureViews[i]));
+               glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, irradianceTexId, 0);
+               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+               glDrawArrays(GL_TRIANGLES, 0, 36);
        }
+
+       generatePrefilterMap();
+
+       generateBRDFMap();
+
+       // switch back to regular skybox shader
+       glUseProgram(program.progId);
+       glDepthFunc(GL_LEQUAL);
+
+       // reverse so facing inside out
+       vao = setupVertices(program.progId, cube(), true);
+
+       // restore default framebuffer
+       glBindFramebuffer(GL_FRAMEBUFFER, 0);
 }
 
 void Skybox::draw(glm::mat4 proj, glm::mat4 view) const {
        glUseProgram(program.progId);
 
-       glDepthMask(GL_FALSE);
-       glDisable(GL_CULL_FACE);
        glBindVertexArray(vao);
-       validatePrograms(program.progId);
 
        GLuint projLoc = glGetUniformLocation(program.progId, "projection");
        glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(proj));
@@ -65,8 +204,31 @@ void Skybox::draw(glm::mat4 proj, glm::mat4 view) const {
        view = glm::mat4(glm::mat3(view));
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
 
-       glBindTexture(GL_TEXTURE_CUBE_MAP, texId);
+       glActiveTexture(GL_TEXTURE0);
+       glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexId);
        glDrawArrays(GL_TRIANGLES, 0, 36);
-       glEnable(GL_CULL_FACE);
-       glDepthMask(GL_TRUE);
+
+       if (glGetError()) exit(1);
+}
+
+template <std::size_t N>
+GLuint setupVertices(GLuint progId, std::array<glm::vec3, N> vertices, bool reverse) {
+       GLuint vao;
+       glGenVertexArrays(1, &vao);
+       glBindVertexArray(vao);
+
+       GLuint vbo;
+       glGenBuffers(1, &vbo);
+
+       if (reverse)
+               std::reverse(vertices.begin(), vertices.end());
+
+       glBindBuffer(GL_ARRAY_BUFFER, vbo);
+       glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(glm::vec3), &vertices[0], GL_STATIC_DRAW);
+       
+       GLuint posLoc = glGetAttribLocation(progId, "pos");
+       glEnableVertexAttribArray(posLoc);
+       glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
+       
+       return vao;
 }