171 lines
6.5 KiB
C++
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
|