space-game001/src/render/ShadowMap.cpp
2026-06-04 13:27:52 +03:00

171 lines
6.5 KiB
C++

#include "ShadowMap.h"
#include "Environment.h"
#include <iostream>
#include <cmath>
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