#include "ShadowMap.h" #include "Environment.h" #include #include namespace ZL { // Build a look-at view matrix (column-major, same convention as the engine). static Eigen::Matrix4f lookAt(const Eigen::Vector3f& eye, const Eigen::Vector3f& center, const Eigen::Vector3f& up) { Eigen::Vector3f f = (center - eye).normalized(); Eigen::Vector3f s = f.cross(up).normalized(); Eigen::Vector3f u = s.cross(f); Eigen::Matrix4f m = Eigen::Matrix4f::Identity(); m(0, 0) = s.x(); m(0, 1) = s.y(); m(0, 2) = s.z(); m(1, 0) = u.x(); m(1, 1) = u.y(); m(1, 2) = u.z(); m(2, 0) = -f.x(); m(2, 1) = -f.y(); m(2, 2) = -f.z(); m(0, 3) = -s.dot(eye); m(1, 3) = -u.dot(eye); m(2, 3) = f.dot(eye); return m; } // Build an orthographic projection matrix that matches the engine's // MakeOrthoMatrix(xmin, xmax, ymin, ymax, zNear, zFar) exactly. static Eigen::Matrix4f ortho(float left, float right, float bottom, float top, float zNear, float zFar) { float width = right - left; float height = top - bottom; float depthRange = zFar - zNear; Eigen::Matrix4f m = Eigen::Matrix4f::Zero(); m(0, 0) = 2.0f / width; m(1, 1) = 2.0f / height; m(2, 2) = -1.0f / depthRange; m(0, 3) = -(right + left) / width; m(1, 3) = -(top + bottom) / height; m(2, 3) = zNear / depthRange; m(3, 3) = 1.0f; return m; } ShadowMap::ShadowMap(int res, float orthoSz, float zNear, float zFar) : resolution(res) , orthoSize(orthoSz) , nearPlane(zNear) , farPlane(zFar) , lightDirection(Eigen::Vector3f(-0.5f, -1.0f, -0.3f).normalized()) , lightViewMatrix(Eigen::Matrix4f::Identity()) , lightProjectionMatrix(Eigen::Matrix4f::Identity()) , lightSpaceMatrix(Eigen::Matrix4f::Identity()) { glGenFramebuffers(1, &fbo); #ifdef EMSCRIPTEN glGenTextures(1, &depthTexture); glBindTexture(GL_TEXTURE_2D, depthTexture); // Используем GL_DEPTH_COMPONENT24 для WebGL 2 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); // ВАЖНО: Используем GL_NEAREST, так как GL_LINEAR часто ломает FBO в WebGL 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_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); #else glGenTextures(1, &depthTexture); glBindTexture(GL_TEXTURE_2D, depthTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); 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); #endif glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0); // No color buffer for this FBO — depth only. #ifdef EMSCRIPTEN GLenum drawBuffers[] = { GL_NONE }; glDrawBuffers(1, drawBuffers); #else glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); #endif GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { std::cerr << "Error: Shadow map framebuffer is not complete! Status: 0x" << std::hex << status << std::endl; } glBindFramebuffer(GL_FRAMEBUFFER, 0); } ShadowMap::~ShadowMap() { if (fbo) glDeleteFramebuffers(1, &fbo); if (depthTexture) glDeleteTextures(1, &depthTexture); } void ShadowMap::setLightDirection(const Eigen::Vector3f& dir) { lightDirection = dir.normalized(); } void ShadowMap::updateLightSpaceMatrix(const Eigen::Vector3f& sceneCenter) { // Place the light "camera" looking at the scene center from the light direction. Eigen::Vector3f lightPos = sceneCenter - lightDirection * (farPlane * 0.5f); // Choose an up vector that isn't parallel to the light direction. Eigen::Vector3f up(0.0f, 1.0f, 0.0f); if (std::abs(lightDirection.dot(up)) > 0.99f) { up = Eigen::Vector3f(0.0f, 0.0f, 1.0f); } lightViewMatrix = lookAt(lightPos, sceneCenter, up); lightProjectionMatrix = ortho( -orthoSize, orthoSize, -orthoSize, orthoSize, nearPlane, farPlane); lightSpaceMatrix = lightProjectionMatrix * lightViewMatrix; } static Eigen::Matrix4f perspective(float fovY, float aspect, float zNear, float zFar) { float t = std::tan(fovY * 0.5f); Eigen::Matrix4f m = Eigen::Matrix4f::Zero(); m(0, 0) = 1.0f / (aspect * t); m(1, 1) = 1.0f / t; m(2, 2) = -(zFar + zNear) / (zFar - zNear); m(2, 3) = -(2.0f * zFar * zNear) / (zFar - zNear); m(3, 2) = -1.0f; return m; } void ShadowMap::updateSpotlightMatrix(const Eigen::Vector3f& lightPos, const Eigen::Vector3f& lightDir, float fovY, float nearZ, float farZ) { Eigen::Vector3f d = lightDir.normalized(); Eigen::Vector3f up(0.f, 1.f, 0.f); if (std::abs(d.dot(up)) > 0.99f) up = Eigen::Vector3f(1.f, 0.f, 0.f); lightViewMatrix = lookAt(lightPos, lightPos + d, up); lightProjectionMatrix = perspective(fovY, 1.0f, nearZ, farZ); lightSpaceMatrix = lightProjectionMatrix * lightViewMatrix; } void ShadowMap::bind() { glBindFramebuffer(GL_FRAMEBUFFER, fbo); glViewport(0, 0, resolution, resolution); glClear(GL_DEPTH_BUFFER_BIT); } void ShadowMap::unbind() { glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, Environment::width, Environment::height); } } // namespace ZL