From 0254b81882a764e32a3e6a2801c3cf2e88363935 Mon Sep 17 00:00:00 2001 From: karl Date: Tue, 27 Oct 2020 21:13:07 +0100 Subject: [PATCH] Implement basic shadows Still shadow acne, but working in principle --- ECS/Components/MouseLook.h | 2 +- ECS/Components/Movement.h | 2 +- ECS/Components/PathMove.h | 2 +- ECS/Systems/RenderSystem.h | 135 ++++++++++++++++++++++++++++++++---- Shaders/debug-fragment.fs | 23 ++++++ Shaders/debug-vertex.vs | 12 ++++ Shaders/default-fragment.fs | 31 ++++++++- Shaders/default-vertex.vs | 3 + Shaders/shadow-fragment.fs | 7 ++ Shaders/shadow-vertex.vs | 10 +++ main.cpp | 5 +- 11 files changed, 212 insertions(+), 20 deletions(-) create mode 100644 Shaders/debug-fragment.fs create mode 100644 Shaders/debug-vertex.vs create mode 100644 Shaders/shadow-fragment.fs create mode 100644 Shaders/shadow-vertex.vs diff --git a/ECS/Components/MouseLook.h b/ECS/Components/MouseLook.h index 3b079c8..8c2c05c 100644 --- a/ECS/Components/MouseLook.h +++ b/ECS/Components/MouseLook.h @@ -15,7 +15,7 @@ struct MouseLook { glm::quat rotation; - bool is_active; + bool is_active = true; }; #endif //ECSGAME_MOUSELOOK_H diff --git a/ECS/Components/Movement.h b/ECS/Components/Movement.h index 625d430..a72a976 100644 --- a/ECS/Components/Movement.h +++ b/ECS/Components/Movement.h @@ -14,7 +14,7 @@ struct Movement { glm::vec3 velocity; - bool is_active; + bool is_active = true; }; #endif //ECSGAME_MOVEMENT_H diff --git a/ECS/Components/PathMove.h b/ECS/Components/PathMove.h index aa17150..39a904c 100644 --- a/ECS/Components/PathMove.h +++ b/ECS/Components/PathMove.h @@ -35,7 +35,7 @@ struct PathMove { PathMove(double speed, Path path, Views views) : speed(speed), path(path), views(views) {} - bool is_active; + bool is_active = false; double speed; float time_passed = 0.0; int current_point_index = 0; diff --git a/ECS/Systems/RenderSystem.h b/ECS/Systems/RenderSystem.h index 7bc4988..223f2b3 100644 --- a/ECS/Systems/RenderSystem.h +++ b/ECS/Systems/RenderSystem.h @@ -18,6 +18,38 @@ using namespace ECS; +// For debugging: +// 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); +} + class RenderSystem : public EntitySystem { public: struct RenderObject { @@ -36,9 +68,11 @@ public: // 0 can't be a valid texture name, so we use it for meshes without textures here if (texture_id != 0) { + glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture_id); } + // TODO: Not always required (not when rendering shadows) - make functions separate? shader.setFloat("diffuseStrength", material.diffuse); shader.setFloat("specularStrength", material.specular); @@ -125,40 +159,113 @@ public: return renderObjects; } - void render(World *pWorld, Shader shader) { + RenderSystem() { + // Configure depth map + glGenFramebuffers(1, &depthMapFBO); + // create depth texture + glGenTextures(1, &depthMap); + glBindTexture(GL_TEXTURE_2D, depthMap); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadow_width, shadow_height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + // attach depth texture as FBO's depth buffer + glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + + void render(World *pWorld, Shader normalShader, Shader shadowShader, Shader debugShader) { pWorld->each([&](Entity *ent, ComponentHandle camera, ComponentHandle cameraTransform) { + // Common glClearColor(0.6f, 0.9f, 0.9f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - shader.use(); + std::vector> allRenderObjects = getRenderObjects(pWorld, cameraTransform->get_origin()); + std::vector renderObjects = allRenderObjects[0]; + std::vector transparentRenderObjects = allRenderObjects[1]; + + // Calculate matrix for lighting + // Get light direction + // TODO: Currently only the last light is used! + glm::vec3 lightDirection; + pWorld->each([&](Entity *ent, ComponentHandle light) { + lightDirection = light->direction; + }); + float near_plane = 1.0f, far_plane = 100.0f; + glm::mat4 lightProjection = glm::ortho(-20.0f, 20.0f, -20.0f, 20.0f, near_plane, far_plane); + glm::mat4 lightView = glm::lookAt(lightDirection * 40.0f, -lightDirection, glm::vec3(0.0, 1.0, 0.0)); + glm::mat4 lightSpaceMatrix = lightProjection * lightView; + + // Render shadows + shadowShader.use(); + + shadowShader.setMat4("lightSpaceMatrix", lightSpaceMatrix); + + glViewport(0, 0, shadow_width, shadow_height); + glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); + for (const RenderObject &obj : renderObjects) { + obj.render(shadowShader); + } + for (const RenderObject &obj : transparentRenderObjects) { + obj.render(shadowShader); + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // reset viewport + glViewport(0, 0, screen_width, screen_height); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + + // Render normal + normalShader.use(); // Lighting - // TODO: Currently only the last light is used! - pWorld->each([&](Entity *ent, ComponentHandle light) { - shader.setVec3("lightDirection", light->direction); - }); + normalShader.setVec3("lightDirection", lightDirection); + normalShader.setMat4("lightSpaceMatrix", lightSpaceMatrix); glm::vec3 cameraPos = cameraTransform->get_origin(); glm::mat4 view = cameraTransform->matrix; view[3] = glm::vec4(cameraPos, 1.0); - shader.setMat4("projection", camera->projection); - shader.setMat4("view", glm::inverse(view)); - shader.setVec3("cameraPosition", cameraTransform->get_origin()); + normalShader.setMat4("projection", camera->projection); + normalShader.setMat4("view", glm::inverse(view)); + normalShader.setVec3("cameraPosition", cameraTransform->get_origin()); - std::vector> allRenderObjects = getRenderObjects(pWorld, cameraPos); - std::vector renderObjects = allRenderObjects[0]; - std::vector transparentRenderObjects = allRenderObjects[1]; + // Bind shadow texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, depthMap); for (const RenderObject &obj : renderObjects) { - obj.render(shader); + obj.render(normalShader); } for (const RenderObject &obj : transparentRenderObjects) { - obj.render(shader); + obj.render(normalShader); } + + // Render the light's depth map to a quad for debugging + debugShader.use(); + debugShader.setFloat("near_plane", near_plane); + debugShader.setFloat("far_plane", far_plane); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, depthMap); + //renderQuad(); // TODO: Add actual code switch instead of commenting }); } + + int screen_width = 1280; + int screen_height = 720; + + int shadow_width = 1024; + int shadow_height = 1024; + + unsigned int depthMap; + unsigned int depthMapFBO; }; #endif //ECSGAME_RENDERSYSTEM_H diff --git a/Shaders/debug-fragment.fs b/Shaders/debug-fragment.fs new file mode 100644 index 0000000..4a49f3a --- /dev/null +++ b/Shaders/debug-fragment.fs @@ -0,0 +1,23 @@ +#version 330 core +out vec4 FragColor; + +in vec2 TexCoords; + +uniform sampler2D depthMap; +uniform float near_plane; +uniform float far_plane; + +// 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, TexCoords).r; + // FragColor = vec4(vec3(LinearizeDepth(depthValue) / far_plane), 1.0); // perspective + FragColor = vec4(vec3(depthValue), 1.0); // orthographic +} + diff --git a/Shaders/debug-vertex.vs b/Shaders/debug-vertex.vs new file mode 100644 index 0000000..ad1f290 --- /dev/null +++ b/Shaders/debug-vertex.vs @@ -0,0 +1,12 @@ +#version 330 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); +} + diff --git a/Shaders/default-fragment.fs b/Shaders/default-fragment.fs index 597b0b4..f0f2097 100644 --- a/Shaders/default-fragment.fs +++ b/Shaders/default-fragment.fs @@ -4,14 +4,38 @@ out mediump vec4 FragColor; in mediump vec2 TexCoord; in mediump vec3 Normal; in mediump vec3 FragPos; +in mediump vec4 FragPosLightSpace; + +layout(binding=0) uniform sampler2D shadowMap; +layout(binding=1) uniform sampler2D tex; -uniform sampler2D tex; uniform mediump vec3 lightDirection; uniform mediump vec3 cameraPosition; uniform mediump float diffuseStrength; uniform mediump float specularStrength; +mediump float ShadowCalculation(vec4 fragPosLightSpace) +{ + // perform perspective divide + mediump vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + // transform to [0,1] range + projCoords = projCoords * 0.5 + 0.5; + + // If we're outside of [0.0, 1.0] in the coordinates, return 0 + if (projCoords.x < 0.0 || projCoords.y < 0.0 || projCoords.x > 1.0 || projCoords.y > 1.0) return 0.0; + + // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) + mediump float closestDepth = texture(shadowMap, projCoords.xy).r; + // get depth of current fragment from light's perspective + mediump float currentDepth = projCoords.z; + // check whether current frag pos is in shadow + mediump float shadow = currentDepth > closestDepth ? 1.0 : 0.0; + + return shadow; +} + + void main() { mediump vec4 texColor = texture(tex, TexCoord); @@ -33,8 +57,11 @@ void main() mediump float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0); + // Shadow + mediump float shadow = ShadowCalculation(FragPosLightSpace); + // Total - mediump float light = min(diff * diffuseStrength + ambient + spec * specularStrength, 1.0); + mediump float light = min(diff * diffuseStrength + ambient + spec * specularStrength - shadow * 0.5 + 0.5, 2.0); // Assign resulting color mediump vec3 color = texColor.xyz * light; diff --git a/Shaders/default-vertex.vs b/Shaders/default-vertex.vs index 2a974ea..2f8d8ed 100644 --- a/Shaders/default-vertex.vs +++ b/Shaders/default-vertex.vs @@ -6,10 +6,12 @@ layout (location = 2) in vec2 UV; out vec2 TexCoord; out vec3 Normal; out vec3 FragPos; +out vec4 FragPosLightSpace; uniform mat4 model; uniform mat4 view; uniform mat4 projection; +uniform mat4 lightSpaceMatrix; void main() { @@ -17,4 +19,5 @@ void main() TexCoord = UV; Normal = NORMAL; FragPos = vec3(model * vec4(aPos, 1.0)); + FragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0); } \ No newline at end of file diff --git a/Shaders/shadow-fragment.fs b/Shaders/shadow-fragment.fs new file mode 100644 index 0000000..acbcf76 --- /dev/null +++ b/Shaders/shadow-fragment.fs @@ -0,0 +1,7 @@ +#version 330 core + +void main() +{ + // This happens implicitly anyways + // gl_FragDepth = gl_FragCoord.z; +} \ No newline at end of file diff --git a/Shaders/shadow-vertex.vs b/Shaders/shadow-vertex.vs new file mode 100644 index 0000000..947562f --- /dev/null +++ b/Shaders/shadow-vertex.vs @@ -0,0 +1,10 @@ +#version 330 core +layout (location = 0) in vec3 aPos; + +uniform mat4 lightSpaceMatrix; +uniform mat4 model; + +void main() +{ + gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/main.cpp b/main.cpp index 13752d7..0f5e2aa 100644 --- a/main.cpp +++ b/main.cpp @@ -112,6 +112,7 @@ int main() { glm::angleAxis(glm::radians(180.f), glm::vec3(0.f, 1.f, 0.f)) }) ); + player->get()->set_origin(glm::vec3(0.0, 3.0, 4.0)); Entity *monkey = world->create(); monkey->assign(); @@ -172,6 +173,8 @@ int main() { sun->assign(glm::normalize(glm::vec3(1.0, 1.0, 1.0))); Shader defaultShader("Shaders/default-vertex.vs", "Shaders/default-fragment.fs"); + Shader shadowShader("Shaders/shadow-vertex.vs", "Shaders/shadow-fragment.fs"); + Shader debugShader("Shaders/debug-vertex.vs", "Shaders/debug-fragment.fs"); double timeInLastFrame = glfwGetTime(); double elapsed_time = 0.0; @@ -184,7 +187,7 @@ int main() { elapsed_time += delta; world->tick(delta); - renderSystem->render(world, defaultShader); + renderSystem->render(world, defaultShader, shadowShader, debugShader); /* Swap front and back buffers */ glfwSwapBuffers(window);