Basic lightmap baking

This commit is contained in:
tovjemam 2025-08-13 21:27:56 +02:00
parent c941ed9525
commit be42793021
15 changed files with 265 additions and 34 deletions

View File

@ -16,13 +16,22 @@ App::App()
auto room001 = assets::SectorDef::LoadFromFile("data/room_001");
size_t s1 = world_.AddSector(room001);
size_t s2 = world_.AddSector(room001);
size_t s1i = world_.AddSector(room001);
game::Sector& s1 = world_.GetSector(s1i);
s1.AddLight(glm::vec3(0.0f, 0.0f, 1.8f), glm::vec3(0.0f, 1.0f, 1.0f), 5.0f);
size_t s2i = world_.AddSector(room001);
game::Sector& s2 = world_.GetSector(s2i);
s2.AddLight(glm::vec3(0.0f, 0.0f, 1.8f), glm::vec3(1.0f, 1.0f, 0.9f), 6.0f);
world_.LinkPortals(s1, "NDoor", s2, "WDoor");
world_.LinkPortals(s2, "NDoor", s2, "SDoor");
world_.LinkPortals(s1i, "NDoor", s2i, "WDoor", 0);
world_.LinkPortals(s2i, "NDoor", s2i, "SDoor", 0);
world_.LinkPortals(s2i, "EDoor", s2i, "EDoor", game::LINK_ROTATE180);
world_.Bake();
player_ = world_.Spawn<game::Player>(s1i, glm::vec3(0.0f, 0.0f, 1.0f));
player_ = world_.Spawn<game::Player>(s1, glm::vec3(0.0f, 0.0f, 1.0f));
}
void App::Frame()

View File

@ -8,9 +8,12 @@
assets::Mesh::Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials) :
va_(gfx::VA_POSITION | gfx::VA_NORMAL | gfx::VA_UV | gfx::VA_LIGHTMAP_UV, gfx::VF_CREATE_EBO)
{
verts_.assign(verts.begin(), verts.end());
tris_.assign(tris.begin(), tris.end());
materials_.assign(materials.begin(), materials.end());
va_.SetVBOData(verts.data(), verts.size_bytes());
va_.SetIndices(reinterpret_cast<const GLuint*>(tris.data()), tris.size() * 3);
materials_.assign(materials.begin(), materials.end());
}
std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision)

View File

@ -34,8 +34,11 @@ namespace assets
class Mesh
{
gfx::VertexArray va_;
std::vector<MeshVertex> verts_;
std::vector<MeshTriangle> tris_;
std::vector<MeshMaterial> materials_;
gfx::VertexArray va_;
std::shared_ptr<collision::TriangleMesh> collision_mesh_;
public:
@ -50,7 +53,10 @@ namespace assets
}
const gfx::VertexArray& GetVA() { return va_; }
const std::span<MeshMaterial> GetMaterials() { return materials_; }
std::span<const MeshVertex> GetVertices() const { return verts_; }
std::span<const MeshTriangle> GetTriangles() const { return tris_; }
std::span<const MeshMaterial> GetMaterials() const { return materials_; }
static std::shared_ptr<Mesh> LoadFromFile(const std::string& filename, bool load_collision);
};

View File

@ -9,7 +9,6 @@ namespace collision
struct AABB
{
static constexpr float C_INFINITY = std::numeric_limits<float>::infinity();
static constexpr float C_DEFAULT_MARGIN = 0.01f; // Margin for AABBs
using Vec = glm::vec<dim, float, glm::defaultp>;
@ -19,10 +18,10 @@ namespace collision
AABB() = default;
AABB(const Vec& min, const Vec& max) : min(min), max(max) {}
void AddPoint(const Vec& point, float margin = C_DEFAULT_MARGIN)
void AddPoint(const Vec& point)
{
min = glm::min(min, point - margin);
max = glm::max(max, point + margin);
min = glm::min(min, point);
max = glm::max(max, point);
}
bool CollidesWith(const AABB<dim>& other) const;

View File

@ -38,7 +38,6 @@ void game::Entity::Move(glm::vec3& velocity, float dt)
if (hit_portal != touching_portal_)
{
printf("Entity::Move: Touching portal changed from %p to %p\n", touching_portal_, hit_portal);
touching_portal_ = hit_portal;
if (hit_portal)

View File

@ -28,8 +28,6 @@ void game::Player::Rotate(float delta_yaw, float delta_pitch)
void game::Player::Update(float dt)
{
const glm::mat3& occu_basis = occu_->GetBasis();
float yaw_cos = glm::cos(yaw_);
float yaw_sin = glm::sin(yaw_);
float pitch_cos = glm::cos(pitch_);
@ -101,7 +99,8 @@ void game::Player::Update(float dt)
const glm::mat3& occu_basis = occu_->GetBasis();
cam_forward_ = occu_basis * glm::vec3(yaw_sin * pitch_cos, yaw_cos * pitch_cos, pitch_sin);
cam_up_ = occu_basis[2]; // Up vector is always the Z axis in sector space
cam_right_ = glm::cross(cam_forward_, cam_up_);
float scale = glm::length(cam_forward_);
cam_right_ = glm::normalize(glm::cross(cam_forward_, cam_up_)) * scale;
}
time_ += dt;
@ -109,12 +108,10 @@ void game::Player::Update(float dt)
void game::Player::GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& forward, glm::vec3& up) const
{
float scale = 1.0f;
sector_idx = occu_->GetSector().GetIndex();
glm::vec2 bobbing_offset = GetBobbingOffset(time_, 0.01f * current_speed_);
glm::vec3 offset = ((cam_up_ * (0.7f + bobbing_offset.y)) + cam_right_ * bobbing_offset.x) * scale;
glm::vec3 offset = cam_up_ * (0.7f + bobbing_offset.y) + cam_right_ * bobbing_offset.x;
position = occu_->GetPosition() + offset;

View File

@ -78,6 +78,15 @@ void game::Sector::ComputePortalVertex(Portal& portal, size_t idx, const glm::ve
portal.verts[idx] = glm::vec3(portal.trans * glm::vec4(vert, 1.0f));
}
void game::Sector::AddLight(const glm::vec3& position, const glm::vec3& color, float radius)
{
Light light;
light.position = position;
light.color = color;
light.radius = radius;
lights_.push_back(light);
}
int game::Sector::GetPortalIndex(const std::string& name) const
{
auto it = portal_map_.find(name);
@ -89,9 +98,15 @@ int game::Sector::GetPortalIndex(const std::string& name) const
return -1;
}
static void LinkPortal(game::Portal& p1, game::Portal& p2) {
static void LinkPortal(game::Portal& p1, game::Portal& p2, int flags) {
// Calculate the traverse transformation
glm::mat4 rotation = glm::rotate(glm::mat4(1.0f), glm::radians(180.0f), glm::vec3(0, 0, 1));
if (flags & game::LINK_ROTATE180)
{
rotation = glm::rotate(rotation, glm::radians(180.0f), glm::vec3(0, 1, 0));
}
glm::mat4 p1_to_p2 = p2.trans * rotation * glm::inverse(p1.trans);
p1.link = &p2;
@ -102,14 +117,14 @@ static void LinkPortal(game::Portal& p1, game::Portal& p2) {
// Transforms directions from p1's sector space to p2's sector space
p1.tr_basis = glm::mat3(p1.tr_position);
// get rid of scale
for (size_t i = 0; i < 3 ; i++)
p1.tr_basis[i] = glm::normalize(p1.tr_basis[i]);
//// get rid of scale
//for (size_t i = 0; i < 3 ; i++)
// p1.tr_basis[i] = glm::normalize(p1.tr_basis[i]);
p1.tr_scale = p2.scale / p1.scale;
}
void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2)
void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2, int flags)
{
Portal& p1 = s1.portals_[idx1];
Portal& p2 = s2.portals_[idx2];
@ -119,11 +134,11 @@ void game::Sector::LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2)
throw std::runtime_error("One of the portals is already linked");
}
LinkPortal(p1, p2);
LinkPortal(p1, p2, flags);
if (&p1 != &p2)
{
LinkPortal(p2, p1);
LinkPortal(p2, p1, flags);
}
}
@ -248,3 +263,154 @@ const game::Portal* game::Sector::TestPortalContact(btCapsuleShapeZ& capsule, co
return result_callback.portal;
}
static bool PointIsOnRight(const glm::vec2& p0, const glm::vec2& p1, const glm::vec2& p)
{
// Check if point p is on the right side of the line segment p0 -> p1
return (p1.x - p0.x) * (p.y - p0.y) - (p1.y - p0.y) * (p.x - p0.x) < 0.0f;
}
static float SignedDistanceToLine(const glm::vec2& p0, const glm::vec2& p1, const glm::vec2& p)
{
// Calculate the signed distance from point p to the line segment p0 -> p1
return (p1.x - p0.x) * (p.y - p0.y) - (p1.y - p0.y) * (p.x - p0.x);
}
void game::Sector::BakeLightmap()
{
const size_t lightmap_size = 256;
const glm::vec3 ambient_light(0.2f); // Ambient light color
const float margin = 2.0f;
std::span<const assets::MeshVertex> mesh_verts = mesh_->GetVertices();
std::span<const assets::MeshTriangle> mesh_tris = mesh_->GetTriangles();
struct LightmapTexel
{
bool used = false;
glm::vec3 color = glm::vec3(0.0f);
};
std::vector<LightmapTexel> lightmap(lightmap_size * lightmap_size);
for (const assets::MeshTriangle& tri : mesh_tris)
{
//const assets::MeshVertex* verts[3];
glm::vec2 verts[3];
glm::vec3 vert_pos[3];
glm::vec3 vert_norm[3];
collision::AABB2 tri_aabb;
for (size_t i = 0; i < 3; ++i)
{
const assets::MeshVertex& vert = mesh_verts[tri.vert[i]];
glm::vec2 pos_lightmap = vert.lightmap_uv * (float)lightmap_size;
verts[i] = pos_lightmap;
tri_aabb.AddPoint(pos_lightmap);
vert_pos[i] = vert.pos;
vert_norm[i] = vert.normal;
}
// Clamp to lightmap size
int y0 = glm::max(0, (int)(tri_aabb.min.y - margin));
int y1 = glm::min((int)lightmap_size, (int)(tri_aabb.max.y + 1.0f + margin));
int x0 = glm::max(0, (int)(tri_aabb.min.x - margin));
int x1 = glm::min((int)lightmap_size, (int)(tri_aabb.max.x + 1.0f + margin));
for (int y = y0; y < y1; ++y)
{
for (int x = x0; x < x1; ++x)
{
LightmapTexel& texel = lightmap[y * lightmap_size + x];
if (texel.used)
{
continue;
}
glm::vec2 texel_pos((float)x + 0.5f, (float)y + 0.5f); // Center of the texel
//if (PointIsOnRight(verts[0], verts[1], texel_pos) ||
// PointIsOnRight(verts[1], verts[2], texel_pos) ||
// PointIsOnRight(verts[2], verts[0], texel_pos))
//{
// continue; // Texel is outside the triangle
//}
float sd = SignedDistanceToLine(verts[0], verts[1], texel_pos);
sd = glm::min(sd, SignedDistanceToLine(verts[1], verts[2], texel_pos));
sd = glm::min(sd, SignedDistanceToLine(verts[2], verts[0], texel_pos));
if (sd > margin)
{
continue; // Texel is outside the triangle
}
texel.used = sd < 0.0f;
// Compute barycentric coordinates
glm::vec2 v0 = verts[1] - verts[0];
glm::vec2 v1 = verts[2] - verts[0];
glm::vec2 v2 = texel_pos - verts[0];
float d00 = glm::dot(v0, v0);
float d01 = glm::dot(v0, v1);
float d11 = glm::dot(v1, v1);
float d20 = glm::dot(v2, v0);
float d21 = glm::dot(v2, v1);
float denom = d00 * d11 - d01 * d01;
if (denom == 0.0f)
{
continue; // Degenerate triangle, skip
}
float v = (d11 * d20 - d01 * d21) / denom;
float w = (d00 * d21 - d01 * d20) / denom;
float u = 1.0f - v - w;
glm::vec3 texel_pos_ws = u * vert_pos[0] + v * vert_pos[1] + w * vert_pos[2];
glm::vec3 texel_norm_ws = glm::normalize(u * vert_norm[0] + v * vert_norm[1] + w * vert_norm[2]);
glm::vec3 light_color = ambient_light;
for (const Light& light : lights_)
{
glm::vec3 light_dir = glm::normalize(light.position - texel_pos_ws);
float dot = glm::dot(texel_norm_ws, light_dir);
if (dot > 0.0f)
{
float dist = glm::length(light.position - texel_pos_ws);
float attenuation = glm::clamp(1.0f - (dist / light.radius), 0.0f, 1.0f);
light_color += light.color * dot * attenuation;
}
}
// Store the light color in the lightmap
texel.color = light_color;
}
}
}
std::vector<uint8_t> lightmap_data(lightmap_size * lightmap_size * 3);
for (size_t i = 0; i < lightmap.size(); ++i)
{
const LightmapTexel& texel = lightmap[i];
glm::vec3 color = glm::clamp(texel.color * 0.5f, glm::vec3(0.0f), glm::vec3(1.0f));
lightmap_data[i * 3 + 0] = static_cast<uint8_t>(color.r * 255.0f);
lightmap_data[i * 3 + 1] = static_cast<uint8_t>(color.g * 255.0f);
lightmap_data[i * 3 + 2] = static_cast<uint8_t>(color.b * 255.0f);
}
lightmap_ = std::make_shared<gfx::Texture>(
lightmap_size, lightmap_size,
lightmap_data.data(),
GL_RGB,
GL_RGB,
GL_UNSIGNED_BYTE,
GL_LINEAR
);
}

View File

@ -48,6 +48,18 @@ namespace game
CollisionObjectData col_data;
};
enum LinkPortalFlags
{
LINK_ROTATE180 = 1 << 0,
};
struct Light
{
glm::vec3 position;
glm::vec3 color;
float radius;
};
class Sector
{
World* world_;
@ -56,6 +68,9 @@ namespace game
std::shared_ptr<assets::Mesh> mesh_;
std::vector<Light> lights_;
std::shared_ptr<gfx::Texture> lightmap_;
std::vector<Portal> portals_;
std::map<std::string, size_t> portal_map_; // Maps portal name to index in portals_
@ -73,14 +88,18 @@ namespace game
void ComputePortalVertex(Portal& portal, size_t idx, const glm::vec2& base_vert);
public:
void AddLight(const glm::vec3& position, const glm::vec3& color, float radius);
size_t GetIndex() const { return idx_; }
const std::shared_ptr<assets::Mesh>& GetMesh() const { return mesh_; }
const std::shared_ptr<gfx::Texture>& GetLightmap() const { return lightmap_; }
int GetPortalIndex(const std::string& name) const;
const Portal& GetPortal(size_t idx) const { return portals_[idx]; }
const size_t GetNumPortals() const { return portals_.size(); }
static void LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2);
static void LinkPortals(Sector& s1, size_t idx1, Sector& s2, size_t idx2, int flags);
bool SweepCapsule(
const btCapsuleShapeZ& capsule,
@ -92,6 +111,7 @@ namespace game
const Portal* TestPortalContact(btCapsuleShapeZ& capsule, const glm::mat3& basis, const glm::vec3& pos);
void BakeLightmap();
};

View File

@ -13,7 +13,7 @@ size_t game::World::AddSector(std::shared_ptr<assets::SectorDef> def)
return idx;
}
void game::World::LinkPortals(size_t sector1, const std::string& portal_name1, size_t sector2, const std::string& portal_name2)
void game::World::LinkPortals(size_t sector1, const std::string& portal_name1, size_t sector2, const std::string& portal_name2, int flags)
{
Sector& s1 = *sectors_[sector1];
Sector& s2 = *sectors_[sector2];
@ -25,5 +25,13 @@ void game::World::LinkPortals(size_t sector1, const std::string& portal_name1, s
throw std::runtime_error("Invalid portal names for linking");
}
Sector::LinkPortals(s1, p1, s2, p2);
Sector::LinkPortals(s1, p1, s2, p2, flags);
}
void game::World::Bake()
{
for (auto& sector : sectors_)
{
sector->BakeLightmap();
}
}

View File

@ -20,7 +20,7 @@ namespace game
size_t AddSector(std::shared_ptr<assets::SectorDef> def);
void LinkPortals(size_t sector1, const std::string& portal_name1,
size_t sector2, const std::string& portal_name2);
size_t sector2, const std::string& portal_name2, int flags);
template<class T, class... TArgs>
T* Spawn(TArgs&&... args)
@ -34,6 +34,8 @@ namespace game
Sector& GetSector(size_t idx) { return *sectors_[idx]; }
const Sector& GetSector(size_t idx) const { return *sectors_[idx]; }
void Bake();
};
}

View File

@ -113,6 +113,18 @@ void gfx::Renderer::DrawSector(const DrawSectorParams& params)
glUniformMatrix4fv(sector_shader_->U(gfx::SU_MODEL), 1, GL_FALSE, &model[0][0]);
glUniform4fv(sector_shader_->U(gfx::SU_CLIP_PLANE), 1, &params.clip_plane[0]);
glActiveTexture(GL_TEXTURE1);
glUniform1i(sector_shader_->U(gfx::SU_LIGHTMAP_TEX), 1); // Bind lightmap to sampler
const auto& lightmap = params.sector->GetLightmap();
if (lightmap)
{
glBindTexture(GL_TEXTURE_2D, lightmap->GetId());
}
else
{
glBindTexture(GL_TEXTURE_2D, 0); // No lightmap
}
glBindVertexArray(mesh.GetVA().GetVAOId());
for (auto mesh_materials = mesh.GetMaterials(); const auto& mat : mesh_materials) {
glActiveTexture(GL_TEXTURE0);
@ -242,7 +254,7 @@ bool gfx::Renderer::ComputePortalScreenAABB(const game::Portal& portal, const gl
glm::vec2 ndc = clip;
ndc /= clip.w;
aabb.AddPoint(ndc, 0.0f);
aabb.AddPoint(ndc);
}
if (num_behind == 4)

View File

@ -11,6 +11,7 @@ static const char* const s_uni_names[] = {
"u_clip_plane", // SU_CLIP_PLANE
"u_portal_size", // SU_PORTAL_SIZE
"u_clear_depth", // SU_CLEAR_DEPTH
"u_lightmap_tex", // SU_LIGHTMAP_TEX
};
// Vytvori shader z daneho zdroje

View File

@ -16,6 +16,7 @@ namespace gfx
SU_CLIP_PLANE,
SU_PORTAL_SIZE,
SU_CLEAR_DEPTH,
SU_LIGHTMAP_TEX,
SU_COUNT
};

View File

@ -20,12 +20,14 @@ R"GLSL(
layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec3 a_normal;
layout (location = 3) in vec2 a_uv;
layout (location = 4) in vec2 a_lightmap_uv;
uniform mat4 u_model;
uniform mat4 u_view_proj;
uniform vec4 u_clip_plane;
out vec2 v_uv;
out vec2 v_lightmap_uv;
out float v_clip_distance;
void main() {
@ -37,6 +39,7 @@ void main() {
//v_normal = mat3(u_model) * a_normal;
v_uv = vec2(a_uv.x, 1.0 - a_uv.y);
v_lightmap_uv = a_lightmap_uv;
}
)GLSL",
@ -44,9 +47,11 @@ void main() {
GLSL_VERSION
R"GLSL(
in vec2 v_uv;
in vec2 v_lightmap_uv;
in float v_clip_distance;
uniform sampler2D u_tex;
uniform sampler2D u_lightmap_tex;
layout (location = 0) out vec4 o_color;
@ -56,6 +61,7 @@ void main() {
}
o_color = vec4(texture(u_tex, v_uv));
o_color.rgb *= texture(u_lightmap_tex, v_lightmap_uv).rgb * 2.0; // Apply lightmap
//o_color = vec4(1.0, 0.0, 0.0, 1.0);
}

View File

@ -16,8 +16,10 @@ gfx::Texture::Texture(GLuint width, GLuint height, const void* data, GLint inter
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}
@ -37,7 +39,7 @@ std::shared_ptr<gfx::Texture> gfx::Texture::LoadFromFile(const std::string& file
std::shared_ptr<Texture> texture;
try {
texture = std::make_shared<Texture>(width, height, data, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR);
texture = std::make_shared<Texture>(width, height, data, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, GL_LINEAR_MIPMAP_LINEAR);
} catch (const std::exception& e) {
stbi_image_free(data);
throw; // Rethrow the exception after freeing the data