diff --git a/src/8.guest/2021/2.csm/10.debug_quad.vs b/src/8.guest/2021/2.csm/10.debug_quad.vs new file mode 100644 index 0000000..13cd765 --- /dev/null +++ b/src/8.guest/2021/2.csm/10.debug_quad.vs @@ -0,0 +1,11 @@ +#version 460 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoords; + +out vec2 TexCoords; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2021/2.csm/10.debug_quad_depth.fs b/src/8.guest/2021/2.csm/10.debug_quad_depth.fs new file mode 100644 index 0000000..afa9207 --- /dev/null +++ b/src/8.guest/2021/2.csm/10.debug_quad_depth.fs @@ -0,0 +1,23 @@ +#version 460 core +out vec4 FragColor; + +in vec2 TexCoords; + +uniform sampler2DArray depthMap; +uniform float near_plane; +uniform float far_plane; +uniform int layer; + +// required when using a perspective projection matrix +float LinearizeDepth(float depth) +{ + float z = depth * 2.0 - 1.0; // Back to NDC + return (2.0 * near_plane * far_plane) / (far_plane + near_plane - z * (far_plane - near_plane)); +} + +void main() +{ + float depthValue = texture(depthMap, vec3(TexCoords, layer)).r; + // FragColor = vec4(vec3(LinearizeDepth(depthValue) / far_plane), 1.0); // perspective + FragColor = vec4(vec3(depthValue), 1.0); // orthographic +} \ No newline at end of file diff --git a/src/8.guest/2021/2.csm/10.shadow_mapping.fs b/src/8.guest/2021/2.csm/10.shadow_mapping.fs new file mode 100644 index 0000000..f4a50fe --- /dev/null +++ b/src/8.guest/2021/2.csm/10.shadow_mapping.fs @@ -0,0 +1,114 @@ +#version 460 core +out vec4 FragColor; + +in VS_OUT { + vec3 FragPos; + vec3 Normal; + vec2 TexCoords; +} fs_in; + +uniform sampler2D diffuseTexture; +uniform sampler2DArray shadowMap; + +uniform vec3 lightDir; +uniform vec3 viewPos; +uniform float farPlane; + +uniform mat4 view; + +layout (std140, binding = 0) uniform LightSpaceMatrices +{ + mat4 lightSpaceMatrices[16]; +}; +uniform float cascadePlaneDistances[16]; +uniform int cascadeCount; // number of frusta - 1 + +float ShadowCalculation(vec3 fragPosWorldSpace) +{ + // select cascade layer + vec4 fragPosViewSpace = view * vec4(fragPosWorldSpace, 1.0); + float depthValue = abs(fragPosViewSpace.z); + + int layer = -1; + for (int i = 0; i < cascadeCount; ++i) + { + if (depthValue < cascadePlaneDistances[i]) + { + layer = i; + break; + } + } + if (layer == -1) + { + layer = cascadeCount; + } + + vec4 fragPosLightSpace = lightSpaceMatrices[layer] * vec4(fragPosWorldSpace, 1.0); + // perform perspective divide + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + // transform to [0,1] range + projCoords = projCoords * 0.5 + 0.5; + + // get depth of current fragment from light's perspective + float currentDepth = projCoords.z; + if (currentDepth > 1.0) + { + return 0.0; + } + // calculate bias (based on depth map resolution and slope) + vec3 normal = normalize(fs_in.Normal); + float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); + if (layer == cascadeCount) + { + bias *= 1 / (farPlane * 0.5f); + } + else + { + bias *= 1 / (cascadePlaneDistances[layer] * 0.5f); + } + + // PCF + float shadow = 0.0; + vec2 texelSize = 1.0 / vec2(textureSize(shadowMap, 0)); + for(int x = -1; x <= 1; ++x) + { + for(int y = -1; y <= 1; ++y) + { + float pcfDepth = texture(shadowMap, vec3(projCoords.xy + vec2(x, y) * texelSize, layer)).r; + shadow += (currentDepth - bias) > pcfDepth ? 1.0 : 0.0; + } + } + shadow /= 9.0; + + // keep the shadow at 0.0 when outside the far_plane region of the light's frustum. + if(projCoords.z > 1.0) + { + shadow = 0.0; + } + + return shadow; +} + +void main() +{ + vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb; + vec3 normal = normalize(fs_in.Normal); + vec3 lightColor = vec3(0.3); + // ambient + vec3 ambient = 0.3 * color; + // diffuse + float diff = max(dot(lightDir, normal), 0.0); + vec3 diffuse = diff * lightColor; + // specular + vec3 viewDir = normalize(viewPos - fs_in.FragPos); + vec3 reflectDir = reflect(-lightDir, normal); + float spec = 0.0; + vec3 halfwayDir = normalize(lightDir + viewDir); + spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0); + vec3 specular = spec * lightColor; + // calculate shadow + float shadow = ShadowCalculation(fs_in.FragPos); + vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color; + + FragColor = vec4(lighting, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2021/2.csm/10.shadow_mapping.vs b/src/8.guest/2021/2.csm/10.shadow_mapping.vs new file mode 100644 index 0000000..9289266 --- /dev/null +++ b/src/8.guest/2021/2.csm/10.shadow_mapping.vs @@ -0,0 +1,24 @@ +#version 460 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoords; + +out vec2 TexCoords; + +out VS_OUT { + vec3 FragPos; + vec3 Normal; + vec2 TexCoords; +} vs_out; + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; + +void main() +{ + vs_out.FragPos = vec3(model * vec4(aPos, 1.0)); + vs_out.Normal = transpose(inverse(mat3(model))) * aNormal; + vs_out.TexCoords = aTexCoords; + gl_Position = projection * view * model * vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2021/2.csm/10.shadow_mapping_depth.fs b/src/8.guest/2021/2.csm/10.shadow_mapping_depth.fs new file mode 100644 index 0000000..4ce1aac --- /dev/null +++ b/src/8.guest/2021/2.csm/10.shadow_mapping_depth.fs @@ -0,0 +1,5 @@ +#version 460 core + +void main() +{ +} \ No newline at end of file diff --git a/src/8.guest/2021/2.csm/10.shadow_mapping_depth.gs b/src/8.guest/2021/2.csm/10.shadow_mapping_depth.gs new file mode 100644 index 0000000..2ed6436 --- /dev/null +++ b/src/8.guest/2021/2.csm/10.shadow_mapping_depth.gs @@ -0,0 +1,20 @@ +#version 460 core + +layout(triangles, invocations = 5) in; +layout(triangle_strip, max_vertices = 3) out; + +layout (std140, binding = 0) uniform LightSpaceMatrices +{ + mat4 lightSpaceMatrices[16]; +}; + +void main() +{ + for (int i = 0; i < 3; ++i) + { + gl_Position = lightSpaceMatrices[gl_InvocationID] * gl_in[i].gl_Position; + gl_Layer = gl_InvocationID; + EmitVertex(); + } + EndPrimitive(); +} \ No newline at end of file diff --git a/src/8.guest/2021/2.csm/10.shadow_mapping_depth.vs b/src/8.guest/2021/2.csm/10.shadow_mapping_depth.vs new file mode 100644 index 0000000..85db956 --- /dev/null +++ b/src/8.guest/2021/2.csm/10.shadow_mapping_depth.vs @@ -0,0 +1,9 @@ +#version 460 core +layout (location = 0) in vec3 aPos; + +uniform mat4 model; + +void main() +{ + gl_Position = model * vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2021/2.csm/shadow_mapping.cpp b/src/8.guest/2021/2.csm/shadow_mapping.cpp new file mode 100644 index 0000000..7a5f52d --- /dev/null +++ b/src/8.guest/2021/2.csm/shadow_mapping.cpp @@ -0,0 +1,671 @@ +#include +#include +#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); +void renderScene(const Shader &shader); +void renderCube(); +void renderQuad(); +std::vector getLightSpaceMatrices(); + +// settings +const unsigned int SCR_WIDTH = 2560; +const unsigned int SCR_HEIGHT = 1440; + +// camera +Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); +float lastX = (float)SCR_WIDTH / 2.0; +float lastY = (float)SCR_HEIGHT / 2.0; +bool firstMouse = true; +float cameraNearPlane = 0.1f; +float cameraFarPlane = 500.0f; + +// timing +float deltaTime = 0.0f; +float lastFrame = 0.0f; + +std::vector shadowCascadeLevels{ cameraFarPlane / 50.0f, cameraFarPlane / 25.0f, cameraFarPlane / 10.0f, cameraFarPlane / 2.0f }; +int debugLayer = 0; + +// meshes +unsigned int planeVAO; + +// lighting info +// ------------- +const glm::vec3 lightDir = glm::normalize(glm::vec3(20.0f, 50, 20.0f)); +unsigned int lightFBO; +unsigned int lightDepthMaps; +constexpr unsigned int depthMapResolution = 4096; + +bool showQuad = false; + +std::random_device device; +std::mt19937 generator = std::mt19937(device()); + +int main() +{ + // glfw: initialize and configure + // ------------------------------ + glfwInit(); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + +#ifdef __APPLE__ + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); +#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 shader("10.shadow_mapping.vs", "10.shadow_mapping.fs"); + Shader simpleDepthShader("10.shadow_mapping_depth.vs", "10.shadow_mapping_depth.fs", "10.shadow_mapping_depth.gs"); + Shader debugDepthQuad("10.debug_quad.vs", "10.debug_quad_depth.fs"); + + // set up vertex data (and buffer(s)) and configure vertex attributes + // ------------------------------------------------------------------ + float planeVertices[] = { + // positions // normals // texcoords + 25.0f, -2.0f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f, + -25.0f, -2.0f, 25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, + -25.0f, -2.0f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f, + 25.0f, -2.0f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f, + -25.0f, -2.0f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f, + 25.0f, -2.0f, -25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 25.0f + }; + // plane VAO + unsigned int planeVBO; + glGenVertexArrays(1, &planeVAO); + glGenBuffers(1, &planeVBO); + glBindVertexArray(planeVAO); + glBindBuffer(GL_ARRAY_BUFFER, planeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW); + 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))); + glBindVertexArray(0); + + // load textures + // ------------- + unsigned int woodTexture = loadTexture(FileSystem::getPath("resources/textures/wood.png").c_str()); + + // configure light FBO + // ----------------------- + glGenFramebuffers(1, &lightFBO); + + glGenTextures(1, &lightDepthMaps); + glBindTexture(GL_TEXTURE_2D_ARRAY, lightDepthMaps); + glTexImage3D( + GL_TEXTURE_2D_ARRAY, 0, GL_DEPTH_COMPONENT32F, depthMapResolution, depthMapResolution, int(shadowCascadeLevels.size()) + 1, + 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); + + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + constexpr float bordercolor[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + glTexParameterfv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, bordercolor); + + glBindFramebuffer(GL_FRAMEBUFFER, lightFBO); + glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, lightDepthMaps, 0); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + + int status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) + { + std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!"; + throw 0; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // configure UBO + // -------------------- + unsigned int matricesUBO; + glGenBuffers(1, &matricesUBO); + glBindBuffer(GL_UNIFORM_BUFFER, matricesUBO); + glBufferData(GL_UNIFORM_BUFFER, sizeof(glm::mat4x4) * 16, nullptr, GL_STATIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, 0, matricesUBO); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + // shader configuration + // -------------------- + shader.use(); + shader.setInt("diffuseTexture", 0); + shader.setInt("shadowMap", 1); + debugDepthQuad.use(); + debugDepthQuad.setInt("depthMap", 0); + + // render loop + // ----------- + while (!glfwWindowShouldClose(window)) + { + // per-frame time logic + // -------------------- + float currentFrame = glfwGetTime(); + deltaTime = currentFrame - lastFrame; + lastFrame = currentFrame; + + // input + // ----- + processInput(window); + + // change light position over time + //lightPos.x = sin(glfwGetTime()) * 3.0f; + //lightPos.z = cos(glfwGetTime()) * 2.0f; + //lightPos.y = 5.0 + cos(glfwGetTime()) * 1.0f; + + // render + // ------ + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // 0. UBO setup + const auto lightMatrices = getLightSpaceMatrices(); + glBindBuffer(GL_UNIFORM_BUFFER, matricesUBO); + for (size_t i = 0; i < lightMatrices.size(); ++i) + { + glBufferSubData(GL_UNIFORM_BUFFER, i * sizeof(glm::mat4x4), sizeof(glm::mat4x4), &lightMatrices[i]); + } + glBindBuffer(GL_UNIFORM_BUFFER, 0); + + // 1. render depth of scene to texture (from light's perspective) + // -------------------------------------------------------------- + //lightProjection = glm::perspective(glm::radians(45.0f), (GLfloat)SHADOW_WIDTH / (GLfloat)SHADOW_HEIGHT, near_plane, far_plane); // note that if you use a perspective projection matrix you'll have to change the light position as the current light position isn't enough to reflect the whole scene + // render scene from light's point of view + simpleDepthShader.use(); + + glBindFramebuffer(GL_FRAMEBUFFER, lightFBO); + glFramebufferTexture(GL_FRAMEBUFFER, GL_TEXTURE_2D_ARRAY, lightDepthMaps, 0); + glViewport(0, 0, depthMapResolution, depthMapResolution); + glClear(GL_DEPTH_BUFFER_BIT); + glCullFace(GL_FRONT); // peter panning + renderScene(simpleDepthShader); + glCullFace(GL_BACK); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // reset viewport + glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // 2. render scene as normal using the generated depth/shadow map + // -------------------------------------------------------------- + glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + shader.use(); + const glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, cameraNearPlane, cameraFarPlane); + const glm::mat4 view = camera.GetViewMatrix(); + shader.setMat4("projection", projection); + shader.setMat4("view", view); + // set light uniforms + shader.setVec3("viewPos", camera.Position); + shader.setVec3("lightDir", lightDir); + shader.setFloat("farPlane", cameraFarPlane); + shader.setInt("cascadeCount", shadowCascadeLevels.size()); + for (size_t i = 0; i < shadowCascadeLevels.size(); ++i) + { + shader.setFloat("cascadePlaneDistances[" + std::to_string(i) + "]", shadowCascadeLevels[i]); + } + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, woodTexture); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D_ARRAY, lightDepthMaps); + renderScene(shader); + + // render Depth map to quad for visual debugging + // --------------------------------------------- + debugDepthQuad.use(); + debugDepthQuad.setInt("layer", debugLayer); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D_ARRAY, lightDepthMaps); + if (showQuad) + { + renderQuad(); + } + std::cout << glm::length(camera.Position) << "\n"; + + // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) + // ------------------------------------------------------------------------------- + glfwSwapBuffers(window); + glfwPollEvents(); + } + + // optional: de-allocate all resources once they've outlived their purpose: + // ------------------------------------------------------------------------ + glDeleteVertexArrays(1, &planeVAO); + glDeleteBuffers(1, &planeVBO); + + glfwTerminate(); + return 0; +} + +// renders the 3D scene +// -------------------- +void renderScene(const Shader &shader) +{ + // floor + glm::mat4 model = glm::mat4(1.0f); + shader.setMat4("model", model); + glBindVertexArray(planeVAO); + glDrawArrays(GL_TRIANGLES, 0, 6); + + static std::vector modelMatrices; + if (modelMatrices.size() == 0) + { + for (int i = 0; i < 10; ++i) + { + static std::uniform_real_distribution offsetDistribution = std::uniform_real_distribution(-10, 10); + static std::uniform_real_distribution scaleDistribution = std::uniform_real_distribution(1.0, 2.0); + static std::uniform_real_distribution rotationDistribution = std::uniform_real_distribution(0, 180); + + auto model = glm::mat4(1.0f); + model = glm::translate(model, glm::vec3(offsetDistribution(generator), offsetDistribution(generator) + 10.0f, offsetDistribution(generator))); + model = glm::rotate(model, glm::radians(rotationDistribution(generator)), glm::normalize(glm::vec3(1.0, 0.0, 1.0))); + model = glm::scale(model, glm::vec3(scaleDistribution(generator))); + modelMatrices.push_back(model); + } + } + + for (const auto& model : modelMatrices) + { + shader.setMat4("model", model); + renderCube(); + } + + + // cubes + //model = glm::mat4(1.0f); + //model = glm::translate(model, glm::vec3(-5.0f, -3.0f, 0.0)); + //model = glm::scale(model, glm::vec3(2)); + //shader.setMat4("model", model); + //renderCube(); + //model = glm::mat4(1.0f); + //model = glm::translate(model, glm::vec3(2.0f, 3.0f, 1.0)); + //model = glm::scale(model, glm::vec3(2)); + //shader.setMat4("model", model); + //renderCube(); + //model = glm::mat4(1.0f); + //model = glm::translate(model, glm::vec3(-1.0f, 3.0f, 2.0)); + //model = glm::rotate(model, glm::radians(60.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0))); + //model = glm::scale(model, glm::vec3(2)); + //shader.setMat4("model", model); + //renderCube(); +} + + +// 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); + + camera.MovementSpeed = glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS ? 2.5 * 10 : 2.5; + + 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); + + static int fPress = GLFW_RELEASE; + if (glfwGetKey(window, GLFW_KEY_F) == GLFW_RELEASE && fPress == GLFW_PRESS) + { + showQuad = !showQuad; + } + fPress = glfwGetKey(window, GLFW_KEY_F); + + static int plusPress = GLFW_RELEASE; + if (glfwGetKey(window, GLFW_KEY_KP_ADD) == GLFW_RELEASE && plusPress == GLFW_PRESS) + { + debugLayer++; + if (debugLayer > shadowCascadeLevels.size()) + { + debugLayer = 0; + } + } + plusPress = glfwGetKey(window, GLFW_KEY_KP_ADD); +} + +// 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); +} + +// utility function for loading a 2D texture from file +// --------------------------------------------------- +unsigned int loadTexture(char const * path) +{ + unsigned int textureID; + glGenTextures(1, &textureID); + + int width, height, nrComponents; + unsigned char *data = stbi_load(path, &width, &height, &nrComponents, 0); + if (data) + { + GLenum format; + if (nrComponents == 1) + format = GL_RED; + else if (nrComponents == 3) + format = GL_RGB; + else if (nrComponents == 4) + format = GL_RGBA; + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT); // for this tutorial: use GL_CLAMP_TO_EDGE to prevent semi-transparent borders. Due to interpolation it takes texels from next repeat + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, format == GL_RGBA ? GL_CLAMP_TO_EDGE : GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + stbi_image_free(data); + } + else + { + std::cout << "Texture failed to load at path: " << path << std::endl; + stbi_image_free(data); + } + + return textureID; +} + + +std::vector getFrustumCornersWorldSpace(const glm::mat4& proj, const glm::mat4& view) +{ + const auto inv = glm::inverse(proj * view); + + std::vector frustumCorners; + for (unsigned int x = 0; x < 2; ++x) + { + for (unsigned int y = 0; y < 2; ++y) + { + for (unsigned int z = 0; z < 2; ++z) + { + const glm::vec4 pt = inv * glm::vec4(2.0f * x - 1.0f, 2.0f * y - 1.0f, 2.0f * z - 1.0f, 1.0f); + frustumCorners.push_back(pt / pt.w); + } + } + } + + return frustumCorners; +} + +glm::mat4 getLightSpaceMatrix(const float nearPlane, const float farPlane) +{ + const auto proj = glm::perspective( + glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, nearPlane, + farPlane); + const auto corners = getFrustumCornersWorldSpace(proj, camera.GetViewMatrix()); + + glm::vec3 center = glm::vec3(0, 0, 0); + for (const auto& v : corners) + { + center += glm::vec3(v); + } + center /= corners.size(); + + const auto lightView = glm::lookAt(center + lightDir, center, glm::vec3(0.0f, 1.0f, 0.0f)); + + float minX = std::numeric_limits::max(); + float maxX = std::numeric_limits::min(); + float minY = std::numeric_limits::max(); + float maxY = std::numeric_limits::min(); + float minZ = std::numeric_limits::max(); + float maxZ = std::numeric_limits::min(); + for (const auto& v : corners) + { + const auto trf = lightView * v; + minX = std::min(minX, trf.x); + maxX = std::max(maxX, trf.x); + minY = std::min(minY, trf.y); + maxY = std::max(maxY, trf.y); + minZ = std::min(minZ, trf.z); + maxZ = std::max(maxZ, trf.z); + } + + // Tune this parameter according to the scene + constexpr float zMult = 10.0f; + if (minZ < 0) + { + minZ *= zMult; + } + else + { + minZ /= zMult; + } + if (maxZ < 0) + { + maxZ /= zMult; + } + else + { + maxZ *= zMult; + } + + const glm::mat4 lpMatrix = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f, minZ, maxZ); + + const float scaleX = 2.0f / (maxX - minX); + const float scaleY = 2.0f / (maxY - minY); + const float offsetX = -0.5f * (minX + maxX) * scaleX; + const float offsetY = -0.5f * (minY + maxY) * scaleY; + + glm::mat4 cropMatrix(1.0f); + cropMatrix[0][0] = scaleX; + cropMatrix[1][1] = scaleY; + cropMatrix[3][0] = offsetX; + cropMatrix[3][1] = offsetY; + + return cropMatrix * lpMatrix * lightView; +} + +std::vector getLightSpaceMatrices() +{ + std::vector ret; + for (size_t i = 0; i < shadowCascadeLevels.size() + 1; ++i) + { + if (i == 0) + { + ret.push_back(getLightSpaceMatrix(cameraNearPlane, shadowCascadeLevels[i])); + } + else if (i < shadowCascadeLevels.size()) + { + ret.push_back(getLightSpaceMatrix(shadowCascadeLevels[i - 1], shadowCascadeLevels[i])); + } + else + { + ret.push_back(getLightSpaceMatrix(shadowCascadeLevels[i - 1], cameraFarPlane)); + } + } + return ret; +}