X-Git-Url: https://git.lukelau.me/?p=opengl.git;a=blobdiff_plain;f=skybox.cpp;h=83f130855fa668919ea9ca938230a117bb839e5f;hp=bd097cb473dc85f92189a354325de78f6b4c4ba5;hb=dbd855720a9af7d6e599ddc50bbbb0dee85458a5;hpb=c44e69ec78367fb2957324026894aef970f2481a diff --git a/skybox.cpp b/skybox.cpp index bd097cb..83f1308 100644 --- a/skybox.cpp +++ b/skybox.cpp @@ -1,62 +1,201 @@ #include "shapes.hpp" #include "skybox.hpp" -#include "image.hpp" -#include #include -Skybox::Skybox(const std::vector faces): program("skyboxvert.glsl", "skyboxfrag.glsl") { - glUseProgram(program.progId); - glGenTextures(1, &texId); - glBindTexture(GL_TEXTURE_CUBE_MAP, texId); +template +GLuint setupVertices(GLuint progId, std::array vertices, bool reverse = false); + +// matrices used when capturing various environment maps +const glm::mat4 captureProj = glm::perspective(glm::radians(90.f), 1.f, 0.1f, 10.f); +const 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_HALF_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, img.internalFormat(), img.width(), img.height(), 0, img.format(), img.type(), 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_HALF_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_HALF_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,29 @@ 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); +} + +template +GLuint setupVertices(GLuint progId, std::array 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; }