From bedd6c3ca0f00601d8ae7bbf6c94b9a14e6a8702 Mon Sep 17 00:00:00 2001 From: admin Date: Sat, 11 Oct 2025 20:15:31 +0200 Subject: [PATCH] feat: shadow integration --- assets/plane.mtl | 2 + assets/plane.obj | 16 ++++ include/components/light.h | 7 +- include/renderer/renderer.h | 12 +++ out.txt | 66 ++++++++++++++++ src/main.cpp | 42 +++++++---- src/renderer/renderer.cpp | 145 +++++++++++++++++++++++++++++------ src/shaders/depth.fs | 6 ++ src/shaders/depth.vs | 13 ++++ src/shaders/pbr.fs | 146 +++++++++++++++++++++++------------- src/shaders/simple.fs | 52 ++++++++----- src/shaders/simple.vs | 4 + 12 files changed, 399 insertions(+), 112 deletions(-) create mode 100644 assets/plane.mtl create mode 100644 assets/plane.obj create mode 100644 out.txt create mode 100644 src/shaders/depth.fs create mode 100644 src/shaders/depth.vs diff --git a/assets/plane.mtl b/assets/plane.mtl new file mode 100644 index 0000000..24c0d6a --- /dev/null +++ b/assets/plane.mtl @@ -0,0 +1,2 @@ +# Blender 4.3.2 MTL File: 'None' +# www.blender.org diff --git a/assets/plane.obj b/assets/plane.obj new file mode 100644 index 0000000..1471db5 --- /dev/null +++ b/assets/plane.obj @@ -0,0 +1,16 @@ +# Blender 4.3.2 +# www.blender.org +mtllib plane.mtl +o Plane +v -5.000000 0.000000 5.000000 +v 5.000000 0.000000 5.000000 +v -5.000000 0.000000 -5.000000 +v 5.000000 0.000000 -5.000000 +vn -0.0000 1.0000 -0.0000 +vt 1.000000 0.000000 +vt 0.000000 1.000000 +vt 0.000000 0.000000 +vt 1.000000 1.000000 +s 0 +f 2/1/1 3/2/1 1/3/1 +f 2/1/1 4/4/1 3/2/1 diff --git a/include/components/light.h b/include/components/light.h index 007621f..724de44 100644 --- a/include/components/light.h +++ b/include/components/light.h @@ -1,6 +1,11 @@ #ifndef COMPONENTS_LIGHT_H_ #define COMPONENTS_LIGHT_H_ -struct light {}; +#include + +struct light { + glm::vec3 color; + float intensity; +}; #endif // COMPONENTS_LIGHT_H_ \ No newline at end of file diff --git a/include/renderer/renderer.h b/include/renderer/renderer.h index 0751a00..0488c38 100644 --- a/include/renderer/renderer.h +++ b/include/renderer/renderer.h @@ -12,10 +12,22 @@ public: Renderer(); void Render(entt::registry& registry); + void GenerateShadowMaps(entt::registry& registry); void OnWindowResized(int w, int h); +private: + void ApplyLights(entt::registry& registry); + void UpdateView(entt::registry& registry); + void RenderScene(entt::registry& registry); + + void SwitchShader(Shader* newShader); private: Shader m_shader; + Shader m_depthShader; + + Shader* m_currentShader; + unsigned int m_depth_fbo; + unsigned int m_depthMap; glm::mat4 m_model; glm::mat4 m_proj; diff --git a/out.txt b/out.txt new file mode 100644 index 0000000..d848af3 --- /dev/null +++ b/out.txt @@ -0,0 +1,66 @@ +GL_VENDOR: NVIDIA Corporation +GL_RENDERER: NVIDIA GeForce RTX 3050 Ti Laptop GPU/PCIe/SSE2 +GL_VERSION: 4.6.0 NVIDIA 550.163.01 +Object name: Sphere +Vertices count: 482 +Normals count: 530 +TexCoords count: 559 +Meshes count: 2 +Materials count: 2 +GL CALLBACK: type = 0x33361, severity = 0x33387, message = Buffer detailed info: Buffer object 3 (bound to GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB (0), GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB (1), GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB (2), and GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations. +GL CALLBACK: type = 0x33361, severity = 0x33387, message = Buffer detailed info: Buffer object 4 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations. +Object name: Cube +Vertices count: 8 +Normals count: 6 +TexCoords count: 14 +Meshes count: 1 +Materials count: 1 +GL CALLBACK: type = 0x33361, severity = 0x33387, message = Buffer detailed info: Buffer object 5 (bound to GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB (0), GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB (1), GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB (2), and GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations. +GL CALLBACK: type = 0x33361, severity = 0x33387, message = Buffer detailed info: Buffer object 6 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations. +Object name: Plane +Vertices count: 4 +Normals count: 1 +TexCoords count: 4 +Meshes count: 1 +Materials count: 1 +GL CALLBACK: type = 0x33361, severity = 0x33387, message = Buffer detailed info: Buffer object 7 (bound to GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB (0), GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB (1), GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB (2), and GL_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations. +GL CALLBACK: type = 0x33361, severity = 0x33387, message = Buffer detailed info: Buffer object 8 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_STATIC_DRAW) will use VIDEO memory as the source for buffer object operations. +Game initialized +GL CALLBACK: ** GL ERROR ** type = 0x33356, severity = 0x37190, message = GL_INVALID_OPERATION error generated. is invalid. +GL CALLBACK: ** GL ERROR ** type = 0x33356, severity = 0x37190, message = GL_INVALID_OPERATION error generated. is invalid. +GL CALLBACK: ** GL ERROR ** type = 0x33356, severity = 0x37190, message = GL_INVALID_OPERATION error generated. is invalid. +GL CALLBACK: ** GL ERROR ** type = 0x33356, severity = 0x37190, message = GL_INVALID_OPERATION error generated. is invalid. +GL CALLBACK: ** GL ERROR ** type = 0x33356, severity = 0x37190, message = GL_INVALID_OPERATION error generated. is invalid. +GL CALLBACK: ** GL ERROR ** type = 0x33356, severity = 0x37190, message = GL_INVALID_OPERATION error generated. is invalid. +GL CALLBACK: ** GL ERROR ** type = 0x33356, severity = 0x37190, message = GL_INVALID_OPERATION error generated. is invalid. +GL CALLBACK: type = 0x33360, severity = 0x37191, message = Program/shader state performance warning: Vertex shader in program 6 is being recompiled based on GL state. +GL CALLBACK: type = 0x33360, severity = 0x37191, message = Program/shader state performance warning: Vertex shader in program 3 is being recompiled based on GL state. +FPS: 160.359 +FPS: 165 +FPS: 165 +FPS: 165 +FPS: 165 +FPS: 165 +FPS: 165 +FPS: 165.01 +FPS: 165 +FPS: 164.835 +FPS: 165.01 +FPS: 165 +FPS: 165.174 +FPS: 165 +FPS: 165 +FPS: 165 +FPS: 165 +FPS: 165 +FPS: 165 +FPS: 165.174 +FPS: 164.835 +FPS: 165.174 +FPS: 164.835 +FPS: 165 +FPS: 165 +FPS: 165 +FPS: 165.01 +FPS: 165 +FPS: 165.174 diff --git a/src/main.cpp b/src/main.cpp index 6ff81db..c847ade 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,19 +28,29 @@ class Game : public IApplication { public: Game() { Object* lightObj = Object::LoadFile("./assets/sphere.obj"); - const auto lightEntity = m_registry.create(); - m_registry.emplace(lightEntity, glm::vec3(-5.f, 5.f, 5.f), glm::vec3(0.f)); - m_registry.emplace(lightEntity); - m_registry.emplace(lightEntity, std::unique_ptr(lightObj)); + // const auto lightEntity = m_registry.create(); + // m_registry.emplace(lightEntity, glm::vec3(-5.f, 5.f, 5.f), glm::vec3(0.f)); + // m_registry.emplace(lightEntity, glm::vec3(1.f, 0.f, 0.f), 1.f); + // m_registry.emplace(lightEntity, std::unique_ptr(lightObj)); + + const auto lEntt2 = m_registry.create(); + m_registry.emplace(lEntt2, glm::vec3(5.f, 5.f, 5.f), glm::vec3(0.f)); + m_registry.emplace(lEntt2, glm::vec3(1.f, 1.f, 1.f), 1.5f); + m_registry.emplace(lEntt2, std::unique_ptr(lightObj)); const auto cameraEntity = m_registry.create(); - m_registry.emplace(cameraEntity, glm::vec3(0.f, 0.f, 2.f)); - m_registry.emplace(cameraEntity, glm::vec3(0.f, 0.f, 2.f)); + m_registry.emplace(cameraEntity, glm::vec3(0.f, 2.f, 2.f)); + m_registry.emplace(cameraEntity); - Object* targetObj = Object::LoadFile("./assets/monkey.obj"); + Object* targetObj = Object::LoadFile("./assets/cube.obj"); const auto targetEntity = m_registry.create(); - m_registry.emplace(targetEntity, glm::vec3(0.f)); + m_registry.emplace(targetEntity, glm::vec3(0.f, 0.5f, 0.f)); m_registry.emplace(targetEntity, std::unique_ptr(targetObj)); + + Object* floorObj = Object::LoadFile("./assets/plane.obj"); + const auto floorEntt = m_registry.create(); + m_registry.emplace(floorEntt, glm::vec3(0.f)); + m_registry.emplace(floorEntt, std::unique_ptr(floorObj)); } ~Game() override {} @@ -58,6 +68,8 @@ public: // FPS tracking m_startTicks = SDL_GetTicks(); m_frameCount = 0; + + m_renderer.GenerateShadowMaps(m_registry); } void OnWindowResized(const WindowResized& event) override { @@ -119,13 +131,13 @@ public: } } - auto rotateEntts = m_registry.view(); - for (auto [entity, transform, mesh] : rotateEntts.each()) { - // auto targetTransform = rotateEntts.get(entity); - if (!m_registry.all_of(entity)) { - transform.rotation.y = m_angle; - } - } + // auto rotateEntts = m_registry.view(); + // for (auto [entity, transform, mesh] : rotateEntts.each()) { + // // auto targetTransform = rotateEntts.get(entity); + // if (!m_registry.all_of(entity)) { + // transform.rotation.y = m_angle; + // } + // } } void OnRender() override { diff --git a/src/renderer/renderer.cpp b/src/renderer/renderer.cpp index 0878065..fe54c79 100644 --- a/src/renderer/renderer.cpp +++ b/src/renderer/renderer.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "renderer/renderer.h" #include "window/window.h" @@ -26,13 +27,23 @@ Renderer::Renderer() FileManager::read("./src/shaders/pbr.fs") ); + m_depthShader.init( + FileManager::read("./src/shaders/depth.vs"), + FileManager::read("./src/shaders/depth.fs") + ); + m_model = glm::mat4(1.f); - m_shader.use(); + SwitchShader(&m_shader); m_shader.setMat4("u_projection", m_proj); } +void Renderer::SwitchShader(Shader *newShader) { + m_currentShader = newShader; + m_currentShader->use(); +} + void Renderer::OnWindowResized(int w, int h) { m_proj = glm::perspective( static_cast(M_PI_2), @@ -41,17 +52,30 @@ void Renderer::OnWindowResized(int w, int h) { 100.0f ); m_shader.setMat4("u_projection", m_proj); + m_depthShader.setMat4("u_projection", m_proj); } -void Renderer::Render(entt::registry& registry) { - auto view = registry.view(); +void Renderer::ApplyLights(entt::registry& registry) { + auto lights = registry.view(); + // TODO: Pass Lights Data to depth shader as well + m_shader.setInt("lightsCount", static_cast(lights.size())); + size_t lightIndex = 0; + for (auto entity : lights) { + auto &comp = registry.get(entity); + auto &transf = registry.get(entity); + m_shader.setVec3("lights[" + std::to_string(lightIndex) + "].position", transf.position); + m_shader.setVec3("lights[" + std::to_string(lightIndex) + "].color", comp.color); + m_shader.setFloat("lights[" + std::to_string(lightIndex) + "].intensity", comp.intensity); + + ++lightIndex; + } +} + +void Renderer::UpdateView(entt::registry& registry) { auto cam = registry.view().back(); auto camTransform = registry.get(cam); - auto lightEntt = registry.view().back(); - auto lightTransform = registry.get(lightEntt); - m_view = glm::lookAt( camTransform.position, camTransform.position + camTransform.rotation, @@ -59,15 +83,11 @@ void Renderer::Render(entt::registry& registry) { ); m_shader.setMat4("u_view", m_view); - m_shader.setVec3("lightColor", glm::vec3(1.0f, 1.0f, 1.0f)); - m_shader.setVec3("lightPos", lightTransform.position); m_shader.setVec3("viewPos", camTransform.position); +} - // std::cout << "cam pos: " << "vec(" << camTransform.position.x << ", " << camTransform.position.y << ", " << camTransform.position.z << ")" << std::endl; - // std::cout << "cam rot: " << "vec(" << camTransform.rotation.x << ", " << camTransform.rotation.y << ", " << camTransform.rotation.z << ")" << std::endl; - - // std::cout << "light pos: " << "vec(" << lightTransform.position.x << ", " << lightTransform.position.y << ", " << lightTransform.position.z << ")" << std::endl; - // std::cout << "light rot: " << "vec(" << lightTransform.rotation.x << ", " << lightTransform.rotation.y << ", " << lightTransform.rotation.z << ")" << std::endl; +void Renderer::RenderScene(entt::registry& registry) { + auto view = registry.view(); for (auto [entity, transf, mesh] : view.each()) { if (mesh.object == nullptr) { @@ -75,20 +95,95 @@ void Renderer::Render(entt::registry& registry) { return; } - m_shader.setBool("isLight", registry.all_of(entity)); + if (registry.all_of(entity)) { + auto &comp = registry.get(entity); + m_currentShader->setBool("isLight", true); + m_currentShader->setVec3("currentLightColor", comp.color); + } else { + m_currentShader->setBool("isLight", false); + m_currentShader->setVec3("currentLightColor", glm::vec3(0.f)); + } - m_model = glm::mat4(1.0f); + glm::mat4 rotation = glm::yawPitchRoll(transf.rotation.y, transf.rotation.x, transf.rotation.z); + m_model = glm::translate(glm::mat4(1.f), transf.position) * rotation; - // Apply translation - m_model = glm::translate(m_model, transf.position); + m_currentShader->setMat4("u_model", m_model); - // Apply rotations (order matters!) - m_model = glm::rotate(m_model, transf.rotation.x, glm::vec3(1, 0, 0)); // pitch - m_model = glm::rotate(m_model, transf.rotation.y, glm::vec3(0, 1, 0)); // yaw - m_model = glm::rotate(m_model, transf.rotation.z, glm::vec3(0, 0, 1)); // roll - - m_shader.setMat4("u_model", m_model); - - mesh.object->Render(m_shader); + mesh.object->Render(*m_currentShader); } +} + +void Renderer::GenerateShadowMaps(entt::registry& registry) { + SwitchShader(&m_depthShader); + + ApplyLights(registry); + UpdateView(registry); + + glGenFramebuffers(1, &m_depth_fbo); + + const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024; + + glGenTextures(1, &m_depthMap); + glBindTexture(GL_TEXTURE_2D, m_depthMap); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, + 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_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + float borderColor[] = {1.0f, 1.0f, 1.0f, 1.0f}; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + + glBindFramebuffer(GL_FRAMEBUFFER, m_depth_fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthMap, 0); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + m_shader.setInt("shadowMap", 31); +} + +void Renderer::Render(entt::registry& registry) { + const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024; + + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + auto shadowLight = registry.view().back(); + auto &comp = registry.get(shadowLight); + + float near_plane = -10.0f, far_plane = 20.0f; + glm::mat4 lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane); + + glm::mat4 lightView = glm::lookAt(comp.position, + glm::vec3( 0.0f, 0.0f, 0.0f), + glm::vec3( 0.0f, 1.0f, 0.0f)); + + glm::mat4 lightSpaceMatrix = lightProjection * lightView; + SwitchShader(&m_depthShader); + m_currentShader->setMat4("u_lightSpace", lightSpaceMatrix); + + glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); + glBindFramebuffer(GL_FRAMEBUFFER, m_depth_fbo); + glClear(GL_DEPTH_BUFFER_BIT); + RenderScene(registry); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glViewport(0, 0, Window::GetWidth(), Window::GetHeight()); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + SwitchShader(&m_shader); + + ApplyLights(registry); + + UpdateView(registry); + + m_currentShader->setMat4("u_lightSpace", lightSpaceMatrix); + + glActiveTexture(GL_TEXTURE31); + glBindTexture(GL_TEXTURE_2D, m_depthMap); + + RenderScene(registry); } \ No newline at end of file diff --git a/src/shaders/depth.fs b/src/shaders/depth.fs new file mode 100644 index 0000000..7025373 --- /dev/null +++ b/src/shaders/depth.fs @@ -0,0 +1,6 @@ +#version 410 core + +void main() +{ + // gl_FragDepth = gl_FragCoord.z; +} \ No newline at end of file diff --git a/src/shaders/depth.vs b/src/shaders/depth.vs new file mode 100644 index 0000000..040b142 --- /dev/null +++ b/src/shaders/depth.vs @@ -0,0 +1,13 @@ +#version 410 core + +// Input vertex attributes +layout (location = 0) in vec3 position; // Vertex position in local space (model space) + +// Uniforms for transformation matrices +uniform mat4 u_model; // Model matrix: transforms from local space to world space +uniform mat4 u_lightSpace; + +void main() +{ + gl_Position = u_lightSpace * u_model * vec4(position, 1.0); +} \ No newline at end of file diff --git a/src/shaders/pbr.fs b/src/shaders/pbr.fs index 803f50f..458c375 100644 --- a/src/shaders/pbr.fs +++ b/src/shaders/pbr.fs @@ -1,22 +1,32 @@ #version 410 core - out vec4 FragColor; in vec3 vertexPos; in vec3 vertexNormal; in vec2 TexCoords; +in vec4 fragPosLightSpace; -// Lighting inputs -uniform vec3 lightPos; uniform vec3 viewPos; -// Material parameters -uniform vec3 albedo; // Base color (replaces diffuseColor) -uniform float metallic; // 0 = dielectric, 1 = metal -uniform float roughness; // 0 = smooth mirror, 1 = rough -uniform float ao; // Ambient occlusion +// Lights +struct Light { + vec3 position; + vec3 color; + float intensity; +}; +#define MAX_LIGHTS 10 +uniform int lightsCount; +uniform Light lights[MAX_LIGHTS]; + +uniform bool isLight; +uniform vec3 currentLightColor; + +// Material parameters +uniform vec3 albedo; +uniform float metallic; +uniform float roughness; +uniform float ao; -// Textures uniform sampler2D albedoTex; uniform sampler2D metallicTex; uniform sampler2D roughnessTex; @@ -26,17 +36,50 @@ uniform bool useMetallicMap; uniform bool useRoughnessMap; uniform bool useAoMap; -uniform float opacity; +// Shadows +uniform sampler2D shadowMap; -// Used for emissive light sources -uniform bool isLight; +uniform float opacity; +// uniform int currentLight; #define PI 3.14159265359 #define LIGHT_COLOR vec3(1.0, 1.0, 1.0) +float ShadowCalculation(vec4 fragPosLightSpace, vec3 N, vec3 L) +{ + // transform to [0,1] + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + projCoords = projCoords * 0.5 + 0.5; + + // if outside light's orthographic frustum => not in shadow + if (projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 || projCoords.y < 0.0 || projCoords.y > 1.0) + return 0.0; + + // get depth from shadow map + float closestDepth = texture(shadowMap, projCoords.xy).r; + float currentDepth = projCoords.z; + + // bias to prevent self-shadowing (depend on slope) + float bias = max(0.05 * (1.0 - dot(N, L)), 0.005); + + // PCF (3x3) + float shadow = 0.0; + vec2 texelSize = 1.0 / textureSize(shadowMap, 0); + for(int x = -1; x <= 1; ++x) + { + for(int y = -1; y <= 1; ++y) + { + float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += (currentDepth - bias > pcfDepth ? 1.0 : 0.0); + } + } + shadow /= 9.0; + + return shadow; +} + // ---------------------------------------------------------------------------- -// Helper functions -// ---------------------------------------------------------------------------- +// Helper functions (GGX, Fresnel, Geometry) float DistributionGGX(vec3 N, vec3 H, float roughness) { float a = roughness * roughness; @@ -53,22 +96,18 @@ float DistributionGGX(vec3 N, vec3 H, float roughness) float GeometrySchlickGGX(float NdotV, float roughness) { - float r = (roughness + 1.0); + float r = roughness + 1.0; float k = (r * r) / 8.0; float num = NdotV; float denom = NdotV * (1.0 - k) + k; - return num / denom; } float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { - float NdotV = max(dot(N, V), 0.0); - float NdotL = max(dot(N, L), 0.0); - float ggx2 = GeometrySchlickGGX(NdotV, roughness); - float ggx1 = GeometrySchlickGGX(NdotL, roughness); - + float ggx1 = GeometrySchlickGGX(max(dot(N,L),0.0), roughness); + float ggx2 = GeometrySchlickGGX(max(dot(N,V),0.0), roughness); return ggx1 * ggx2; } @@ -79,60 +118,65 @@ vec3 fresnelSchlick(float cosTheta, vec3 F0) // ---------------------------------------------------------------------------- // Main -// ---------------------------------------------------------------------------- void main() { if (isLight) { - vec3 emissive = LIGHT_COLOR * 10.0; // bright light source + vec3 emissive = currentLightColor * 10.0; FragColor = vec4(emissive, 1.0); return; } - // Inputs vec3 N = normalize(vertexNormal); vec3 V = normalize(viewPos - vertexPos); - vec3 L = normalize(lightPos - vertexPos); - vec3 H = normalize(V + L); - // Base color (albedo) vec3 baseColor = useAlbedoMap ? texture(albedoTex, TexCoords).rgb : albedo; + float metal = useMetallicMap ? texture(metallicTex, TexCoords).r : metallic; + float rough = useRoughnessMap ? texture(roughnessTex, TexCoords).r : roughness; + float aoValue = useAoMap ? texture(aoTex, TexCoords).r : ao; - float metal = useMetallicMap ? texture(metallicTex, TexCoords).r : metallic; - float rough = useRoughnessMap ? texture(roughnessTex, TexCoords).r : roughness; - float aoValue = useAoMap ? texture(aoTex, TexCoords).r : ao; + vec3 F0 = mix(vec3(0.04), baseColor, metal); - // Reflectance at normal incidence (F0) - vec3 F0 = vec3(0.04); // typical dielectric reflectance - F0 = mix(F0, baseColor, metal); // metals use albedo as F0 + vec3 Lo = vec3(0.0); + // FragColor = vec4(1.0 - shadow, 1.0 - shadow, 1.0 - shadow, 1.0); + // return; - // Cook-Torrance BRDF - float NDF = DistributionGGX(N, H, rough); - float G = GeometrySmith(N, V, L, rough); - vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); + float shadow = 0.0; - vec3 numerator = NDF * G * F; - float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; - vec3 specular = numerator / denominator; + // Loop over all lights + for (int i = 0; i < lightsCount; i++) + { + vec3 L = normalize(lights[i].position - vertexPos); + vec3 H = normalize(V + L); - // kS is specular reflection, kD is diffuse reflection (energy conservation) - vec3 kS = F; - vec3 kD = vec3(1.0) - kS; - kD *= 1.0 - metal; + float NDF = DistributionGGX(N, H, rough); + float G = GeometrySmith(N, V, L, rough); + vec3 F = fresnelSchlick(max(dot(H,V),0.0), F0); - float NdotL = max(dot(N, L), 0.0); + shadow = ShadowCalculation(fragPosLightSpace, N, L); - vec3 radiance = LIGHT_COLOR; // single light source color/intensity + vec3 numerator = NDF * G * F; + float denominator = 4.0 * max(dot(N,V),0.0) * max(dot(N,L),0.0) + 0.001; + vec3 specular = numerator / denominator; - vec3 Lo = (kD * baseColor / PI + specular) * radiance * NdotL; + vec3 kS = F; + vec3 kD = vec3(1.0) - kS; + kD *= 1.0 - metal; - // Ambient (IBL approximation using ao) + float NdotL = max(dot(N,L), 0.0); + + vec3 radiance = lights[i].color * lights[i].intensity; + Lo += (kD * baseColor / PI + specular) * radiance * NdotL; + } + + // Ambient vec3 ambient = vec3(0.03) * baseColor * aoValue; - vec3 color = ambient + Lo; + // TODO: apply shadow + vec3 color = ambient + (1.0 - shadow) * Lo; - // HDR tonemapping and gamma correction + // HDR tonemapping + gamma color = color / (color + vec3(1.0)); - color = pow(color, vec3(1.0 / 2.2)); + color = pow(color, vec3(1.0/2.2)); FragColor = vec4(color, opacity); } diff --git a/src/shaders/simple.fs b/src/shaders/simple.fs index de95a02..3354918 100644 --- a/src/shaders/simple.fs +++ b/src/shaders/simple.fs @@ -7,9 +7,19 @@ in vec3 vertexPos; in vec3 vertexNormal; in vec2 TexCoords; -uniform vec3 lightPos; uniform vec3 viewPos; +// Lights +struct Light { + vec3 position; + vec3 color; + float intensity; +}; + +#define MAX_LIGHTS 10 +uniform int lightsCount; +uniform Light lights[MAX_LIGHTS]; + // From Object Renderer uniform vec3 ambientColor; @@ -29,38 +39,40 @@ uniform bool useTexture; uniform bool isLight; -#define LIGHT_COLOR vec3(1.0, 1.0, 1.0) - void main() { // Lighting vectors - vec3 lightDir = normalize(lightPos - vertexPos); vec3 norm = normalize(vertexNormal); vec3 viewDir = normalize(viewPos - vertexPos); - vec3 reflectDir = reflect(-lightDir, norm); - - // Phong components - // float spec = pow(max(dot(viewDir, reflectDir), 0.0), clamp(shininess, 2, 256)); - // vec3 specular = (useSpecular) ? specularStrength * spec * specularColor : vec3(0.0); - - // Blinn Phong - vec3 halfDir = normalize(lightDir + viewDir); - float spec = pow(max(dot(norm, halfDir), 0.0), clamp(shininess, 2.0, 256.0)); - vec3 specular = (useSpecular) ? specularStrength * spec * specularColor : vec3(0.0); - - float diff = max(dot(norm, lightDir), 0.0); - vec3 diffuse = diff * diffuseColor; + // vec3 viewDir = normalize(-vertexPos); vec3 ambient = ambientStrength * ambientColor; - vec3 texColor = (useTexture) ? texture(diffuseTex, TexCoords).rgb : diffuseColor; - vec3 result = (ambient + diffuse + specular) * texColor; + vec3 result = ambient; + + for (int i = 0; i < lightsCount; i++) { + vec3 lightDir = normalize(lights[i].position - vertexPos); + vec3 halfDir = normalize(lightDir + viewDir); + + // Blinn Phong + float diff = max(dot(norm, lightDir), 0.0); + vec3 diffuse = diff * diffuseColor * lights[i].color * lights[i].intensity; + + float spec = pow(max(dot(norm, halfDir), 0.0), clamp(shininess, 2.0, 256.0)); + vec3 specular = (useSpecular) ? + specularStrength * spec * specularColor * lights[i].color * lights[i].intensity + : vec3(0.0); + + result += (diffuse + specular); + } + + result *= texColor; if (isLight) { - vec3 emissive = LIGHT_COLOR * 10.0; // big intensity + vec3 emissive = vec3(1.0, 1.0, 1.0) * 10.0; // big intensity FragColor = vec4(emissive, 1.0); return; } diff --git a/src/shaders/simple.vs b/src/shaders/simple.vs index da5681d..6b5bd7a 100644 --- a/src/shaders/simple.vs +++ b/src/shaders/simple.vs @@ -9,11 +9,13 @@ layout (location = 2) in vec2 texCoord; // Vertex texture uv out vec3 vertexPos; out vec3 vertexNormal; out vec2 TexCoords; +out vec4 fragPosLightSpace; // Uniforms for transformation matrices uniform mat4 u_model; // Model matrix: transforms from local space to world space uniform mat4 u_view; // View matrix: transforms from world space to camera space (view space) uniform mat4 u_projection; // Projection matrix: transforms from camera space to clip space +uniform mat4 u_lightSpace; void main() { @@ -25,5 +27,7 @@ void main() TexCoords = texCoord; + fragPosLightSpace = u_lightSpace * vec4(vertexPos, 1.0); + gl_Position = u_projection * u_view * vec4(vertexPos, 1.0); } \ No newline at end of file