Prepare for mesh rendering

This commit is contained in:
tovjemam 2025-08-29 22:22:02 +02:00
parent 2b69893841
commit a2bef73191
23 changed files with 542 additions and 108 deletions

View File

@ -12,13 +12,19 @@ add_executable(PortalGame
"src/app.cpp"
"src/gl.hpp"
"src/utils.hpp"
"src/assets/sectordef.hpp"
"src/assets/sectordef.cpp"
"src/assets/animation.hpp"
"src/assets/animation.cpp"
"src/assets/mesh.hpp"
"src/assets/mesh.cpp"
"src/assets/sectordef.hpp"
"src/assets/sectordef.cpp"
"src/assets/skeleton.hpp"
"src/assets/skeleton.cpp"
"src/collision/trianglemesh.cpp"
"src/game/entity.hpp"
"src/game/entity.cpp"
"src/game/meshinstance.hpp"
"src/game/meshinstance.cpp"
"src/game/player_input.hpp"
"src/game/player.hpp"
"src/game/player.cpp"

View File

@ -5,7 +5,7 @@
#include <sstream>
#include <stdexcept>
std::shared_ptr<assets::Animation> assets::Animation::LoadFromFile(const std::string& filename, const Skeleton* skeleton)
std::shared_ptr<const assets::Animation> assets::Animation::LoadFromFile(const std::string& filename, const Skeleton* skeleton)
{
std::ifstream ifs(filename, std::ios::binary);

View File

@ -34,7 +34,7 @@ namespace assets
size_t GetNumChannels() const { return channels_.size(); }
const AnimationChannel& GetChannel(int index) const { return channels_[index]; }
static std::shared_ptr<Animation> LoadFromFile(const std::string& filename, const Skeleton* skeleton);
static std::shared_ptr<const Animation> LoadFromFile(const std::string& filename, const Skeleton* skeleton);
};

View File

@ -5,7 +5,8 @@
#include <vector>
#include <map>
std::map<std::string, std::weak_ptr<gfx::Texture>> assets::Mesh::s_texture_cache;
std::map<std::string, std::weak_ptr<const gfx::Texture>> assets::Mesh::s_texture_cache;
std::map<std::string, std::weak_ptr<const assets::Skeleton>> assets::Mesh::s_skeleton_cache;
static int GetVertexAttrFlags(int mesh_flags)
{
@ -31,8 +32,9 @@ static void BufferPut(std::vector<char>& buffer, const T& val)
buffer.insert(buffer.end(), data, data + sizeof(T));
}
assets::Mesh::Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials, int flags) :
va_(GetVertexAttrFlags(flags), gfx::VF_CREATE_EBO)
assets::Mesh::Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials, int flags, std::shared_ptr<const Skeleton> skeleton) :
va_(GetVertexAttrFlags(flags), gfx::VF_CREATE_EBO),
skeleton_(std::move(skeleton))
{
verts_.assign(verts.begin(), verts.end());
tris_.assign(tris.begin(), tris.end());
@ -72,7 +74,7 @@ assets::Mesh::Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, st
va_.SetIndices(reinterpret_cast<const GLuint*>(tris.data()), tris.size() * 3);
}
std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision, const Skeleton* skeleton)
std::shared_ptr<const assets::Mesh> assets::Mesh::LoadFromFile(const std::string& filename, bool load_collision)
{
std::shared_ptr<collision::TriangleMesh> collision_mesh;
@ -87,6 +89,8 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
}
int flags = 0;
std::shared_ptr<const Skeleton> skeleton;
std::vector<MeshVertex> verts;
std::vector<MeshTriangle> tris;
std::vector<MeshMaterial> materials;
@ -184,6 +188,11 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
}
else if (command == "d")
{
if (!skeleton)
{
throw std::runtime_error("Bone definition found in non-skeletal mesh file: " + filename);
}
std::string bone_name;
iss >> bone_name;
int idx = skeleton->GetBoneIndex(bone_name);
@ -204,19 +213,21 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
throw std::runtime_error("Lightmap UV flag must be specified before vertex definitions in mesh file: " + filename);
}
}
else if (command == "skeletal")
else if (command == "skeleton")
{
flags |= MF_IS_SKELETAL;
if (!skeleton)
{
throw std::runtime_error("Loading a skeletal mesh with no skeleton provided: " + filename);
}
if (verts.size() > 0)
{
throw std::runtime_error("Skeletal flag must be specified before vertex definitions in mesh file: " + filename);
}
flags |= MF_IS_SKELETAL;
std::string skel_filename;
iss >> skel_filename;
// load skeleton
skeleton = LoadSkeleton("data/" + skel_filename + ".skel");
}
else
{
@ -251,7 +262,7 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
}
// Create the mesh object
std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>(verts, tris, materials, flags);
std::shared_ptr<Mesh> mesh = std::make_shared<Mesh>(verts, tris, materials, flags, skeleton);
if (load_collision)
{
@ -261,7 +272,7 @@ std::shared_ptr<assets::Mesh> assets::Mesh::LoadFromFile(const std::string& file
return mesh;
}
std::shared_ptr<gfx::Texture> assets::Mesh::LoadTexture(const std::string& filename)
std::shared_ptr<const gfx::Texture> assets::Mesh::LoadTexture(const std::string& filename)
{
auto it = s_texture_cache.find(filename);
if (it != s_texture_cache.end())
@ -277,3 +288,20 @@ std::shared_ptr<gfx::Texture> assets::Mesh::LoadTexture(const std::string& filen
return texture;
}
std::shared_ptr<const assets::Skeleton> assets::Mesh::LoadSkeleton(const std::string& filename)
{
auto it = s_skeleton_cache.find(filename);
if (it != s_skeleton_cache.end())
{
if (auto skel = it->second.lock())
{
return skel; // Return cached skeleton
}
}
auto skel = Skeleton::LoadFromFile(filename);
s_skeleton_cache[filename] = skel; // Cache the skeleton
return skel;
}

View File

@ -36,7 +36,7 @@ namespace assets
struct MeshMaterial
{
std::string name;
std::shared_ptr<gfx::Texture> texture;
std::shared_ptr<const gfx::Texture> texture;
size_t first_tri = 0;
size_t num_tris = 0;
};
@ -57,8 +57,10 @@ namespace assets
gfx::VertexArray va_;
std::shared_ptr<collision::TriangleMesh> collision_mesh_;
std::shared_ptr<const Skeleton> skeleton_;
public:
Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials, int flags);
Mesh(std::span<MeshVertex> verts, std::span<MeshTriangle> tris, std::span<MeshMaterial> materials, int flags, std::shared_ptr<const Skeleton> skeleton);
void SetCollisionMesh(std::shared_ptr<collision::TriangleMesh> mesh) {
collision_mesh_ = std::move(mesh);
@ -68,16 +70,20 @@ namespace assets
return collision_mesh_;
}
const gfx::VertexArray& GetVA() { return va_; }
const gfx::VertexArray& GetVA() const { return va_; }
const std::shared_ptr<const Skeleton>& GetSkeleton() const { return skeleton_; }
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, const Skeleton* skeleton = nullptr);
static std::shared_ptr<const Mesh> LoadFromFile(const std::string& filename, bool load_collision);
private:
static std::map<std::string, std::weak_ptr<gfx::Texture>> s_texture_cache;
static std::shared_ptr<gfx::Texture> LoadTexture(const std::string& filename);
static std::map<std::string, std::weak_ptr<const gfx::Texture>> s_texture_cache;
static std::shared_ptr<const gfx::Texture> LoadTexture(const std::string& filename);
static std::map<std::string, std::weak_ptr<const Skeleton>> s_skeleton_cache;
static std::shared_ptr<const Skeleton> LoadSkeleton(const std::string& filename);
};
}

View File

@ -5,7 +5,7 @@
std::map<std::string, assets::PortalDef> assets::PortalDef::portal_defs;
assets::SectorDef::SectorDef(std::shared_ptr<Mesh> mesh, std::span<SectorPortalDef> portals)
assets::SectorDef::SectorDef(std::shared_ptr<const Mesh> mesh, std::span<SectorPortalDef> portals)
{
mesh_ = std::move(mesh);
portals_.assign(portals.begin(), portals.end());

View File

@ -28,13 +28,13 @@ namespace assets
class SectorDef
{
std::shared_ptr<Mesh> mesh_;
std::shared_ptr<const Mesh> mesh_;
std::vector<SectorPortalDef> portals_;
public:
SectorDef(std::shared_ptr<Mesh> mesh, std::span<SectorPortalDef> portals);
SectorDef(std::shared_ptr<const Mesh> mesh, std::span<SectorPortalDef> portals);
const std::shared_ptr<Mesh>& GetMesh() const { return mesh_; }
const std::shared_ptr<const Mesh>& GetMesh() const { return mesh_; }
std::span<const SectorPortalDef> GetPortals() const { return portals_; }
static std::shared_ptr<SectorDef> LoadFromFile(const std::string& base_name);

View File

@ -27,7 +27,16 @@ int assets::Skeleton::GetBoneIndex(const std::string& name) const
return -1;
}
std::shared_ptr<assets::Skeleton> assets::Skeleton::LoadFromFile(const std::string& filename)
const assets::Animation* assets::Skeleton::GetAnimation(const std::string& name) const
{
auto it = anims_.find(name);
if (it != anims_.end()) {
return it->second.get();
}
return nullptr;
}
std::shared_ptr<const assets::Skeleton> assets::Skeleton::LoadFromFile(const std::string& filename)
{
std::ifstream ifs(filename, std::ios::binary);
if (!ifs.is_open()) {
@ -48,26 +57,41 @@ std::shared_ptr<assets::Skeleton> assets::Skeleton::LoadFromFile(const std::stri
std::string command;
iss >> command;
if (command != "b")
continue;
Transform t;
glm::vec3 angles;
std::string bone_name, parent_name;
iss >> bone_name >> parent_name;
iss >> t.position.x >> t.position.y >> t.position.z;
iss >> angles.x >> angles.y >> angles.z;
iss >> t.scale;
if (iss.fail())
if (command == "b")
{
throw std::runtime_error("Failed to parse bone definition in file: " + filename);
Transform t;
glm::vec3 angles;
std::string bone_name, parent_name;
iss >> bone_name >> parent_name;
iss >> t.position.x >> t.position.y >> t.position.z;
iss >> angles.x >> angles.y >> angles.z;
iss >> t.scale;
if (iss.fail())
{
throw std::runtime_error("Failed to parse bone definition in file: " + filename);
}
t.SetAngles(angles);
skeleton->AddBone(bone_name, parent_name, t);
}
else if (command == "anim")
{
std::string anim_name, anim_filename;
iss >> anim_name >> anim_filename;
if (iss.fail())
{
throw std::runtime_error("Failed to parse animation definition in file: " + filename);
}
std::shared_ptr<const Animation> anim = Animation::LoadFromFile("data/" + anim_filename + ".anim", skeleton.get());
skeleton->AddAnimation(anim_name, anim);
}
t.SetAngles(angles);
skeleton->AddBone(bone_name, parent_name, t);
}
return skeleton;

View File

@ -23,6 +23,8 @@ namespace assets
std::vector<Bone> bones_;
std::map<std::string, int> bone_map_;
std::map<std::string, std::shared_ptr<const Animation>> anims_;
public:
Skeleton() = default;
@ -30,9 +32,15 @@ namespace assets
int GetBoneIndex(const std::string& name) const;
const Bone& GetBone(int index) const { return bones_[index]; }
size_t GetNumBones() const { return bones_.size(); }
const Bone& GetBone(size_t idx) const { return bones_[idx]; }
static std::shared_ptr<Skeleton> LoadFromFile(const std::string& filename);
const Animation* GetAnimation(const std::string& name) const;
static std::shared_ptr<const Skeleton> LoadFromFile(const std::string& filename);
private:
void AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim) { anims_[name] = anim; }
};

137
src/game/meshinstance.cpp Normal file
View File

@ -0,0 +1,137 @@
#include "meshinstance.hpp"
#include <stdexcept>
game::MeshInstance::MeshInstance(std::shared_ptr<const assets::Mesh> mesh, const TransformNode* root_node, const float* time_ptr) :
mesh_(std::move(mesh)),
root_node_(root_node),
time_ptr_(time_ptr)
{
if (IsSkeletal())
{
SetupBoneNodes();
}
}
void game::MeshInstance::SetupBoneNodes()
{
const auto& skeleton = mesh_->GetSkeleton();
size_t num_bones = skeleton->GetNumBones();
bone_nodes_.resize(num_bones);
for (size_t i = 0; i < num_bones; ++i)
{
const auto& bone = skeleton->GetBone(i);
TransformNode& node = bone_nodes_[i];
node.local_transform = bone.bind_transform;
if (bone.parent_idx >= 0)
{
node.parent = &bone_nodes_[bone.parent_idx];
}
else
{
node.parent = root_node_;
}
}
UpdateBoneMatrices();
}
const game::TransformNode* game::MeshInstance::GetBoneNodeByName(const std::string& name) const
{
int bone_idx = mesh_->GetSkeleton()->GetBoneIndex(name);
if (bone_idx < 0)
{
return nullptr; // Bone not found in skeleton
}
return &bone_nodes_[bone_idx];
}
void game::MeshInstance::UpdateBoneMatrices()
{
for (TransformNode& node : bone_nodes_)
{
node.UpdateMatrix();
}
}
void game::MeshInstance::PlayAnim(const std::string& name)
{
if (!IsSkeletal())
{
throw std::runtime_error("Cannot play animation on non-skeletal mesh");
}
const auto& skeleton = mesh_->GetSkeleton();
current_anim_ = skeleton->GetAnimation(name); // Can be nullptr
anim_time_ = 0.0f;
}
void game::MeshInstance::Update()
{
if (IsSkeletal())
{
ApplyAnimFrame();
UpdateBoneMatrices();
}
}
void game::MeshInstance::ApplyAnimFrame()
{
ApplySkelAnim(current_anim_, anim_time_, 1.0f);
}
void game::MeshInstance::ApplySkelAnim(const assets::Animation* anim, float time, float weight)
{
float anim_frame = (*time_ptr_ - time) * anim->GetTPS();
if (anim_frame < 0.0f)
{
anim_frame = 0.0f;
}
size_t num_anim_frames = anim->GetNumFrames();
size_t frame_i = static_cast<size_t>(anim_frame);
size_t frame0 = frame_i % num_anim_frames;
size_t frame1 = (frame_i + 1) % num_anim_frames;
float t = anim_frame - static_cast<float>(frame_i);
for (size_t i = 0; i < anim->GetNumChannels(); ++i)
{
const assets::AnimationChannel& channel = anim->GetChannel(i);
TransformNode& node = bone_nodes_[channel.bone_index];
const Transform* t0 = channel.frames[frame0];
const Transform* t1 = channel.frames[frame1];
Transform anim_transform;
if (t0 != t1)
{
anim_transform = Transform::Lerp(*t0, *t1, t); // Frames are different, interpolate
}
else
{
anim_transform = *t0; // Frames are the same, no interpolation needed
}
if (weight < 1.0f)
{
// blend with existing transform
node.local_transform = Transform::Lerp(node.local_transform, anim_transform, weight);
}
else
{
node.local_transform = anim_transform;
}
}
}

45
src/game/meshinstance.hpp Normal file
View File

@ -0,0 +1,45 @@
#pragma once
#include "assets/mesh.hpp"
#include "transform_node.hpp"
namespace game
{
class MeshInstance
{
std::shared_ptr<const assets::Mesh> mesh_;
const TransformNode* root_node_ = nullptr;
std::vector<TransformNode> bone_nodes_; // Only used if the mesh is skeletal
const float* time_ptr_ = nullptr; // Pointer to game time variable
const assets::Animation* current_anim_ = nullptr;
float anim_time_ = 0.0f;
public:
MeshInstance(std::shared_ptr<const assets::Mesh> mesh, const TransformNode* root_node, const float* time_ptr);
bool IsSkeletal() const { return (bool)mesh_->GetSkeleton(); }
void PlayAnim(const std::string& name);
void Update();
const std::shared_ptr<const assets::Mesh>& GetMesh() const { return mesh_; }
const TransformNode* GetRootNode() const { return root_node_; }
const TransformNode& GetBoneNode(size_t index) const { return bone_nodes_[index]; }
const TransformNode* GetBoneNodeByName(const std::string& name) const;
private:
void SetupBoneNodes();
void UpdateBoneMatrices();
protected:
virtual void ApplyAnimFrame();
private:
void ApplySkelAnim(const assets::Animation* anim, float time, float weight);
};
}

View File

@ -9,9 +9,17 @@ game::Player::Player(World* world, size_t sector_idx, const glm::vec3& position)
input_(0),
cam_forward_(0.0f),
cam_up_(0.0f),
cam_right_(0.0f)
{
cam_right_(0.0f),
vm_hands_(assets::Mesh::LoadFromFile("data/hands.mesh", false), &vm_node_, world->GetTimePtr())
{
vm_weapon_ = std::make_unique<MeshInstance>(
assets::Mesh::LoadFromFile("data/knife.mesh", false),
vm_hands_.GetBoneNodeByName("weapon"),
world->GetTimePtr()
);
vm_hands_.PlayAnim("knife_attack1");
}
void game::Player::Rotate(float delta_yaw, float delta_pitch)
@ -28,6 +36,15 @@ void game::Player::Rotate(float delta_yaw, float delta_pitch)
void game::Player::Update(float dt)
{
time_ += dt;
vm_hands_.Update();
if (vm_weapon_)
{
vm_weapon_->Update();
}
float yaw_cos = glm::cos(yaw_);
float yaw_sin = glm::sin(yaw_);
float pitch_cos = glm::cos(pitch_);
@ -103,7 +120,6 @@ void game::Player::Update(float dt)
cam_right_ = glm::normalize(glm::cross(cam_forward_, cam_up_)) * scale;
}
time_ += dt;
}
void game::Player::GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& forward, glm::vec3& up) const
@ -134,6 +150,16 @@ void game::Player::GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& fo
}
}
void game::Player::GetViewMeshes(std::vector<const MeshInstance*>& out_meshes) const
{
out_meshes.push_back(&vm_hands_);
if (vm_weapon_)
{
out_meshes.push_back(vm_weapon_.get());
}
}
glm::vec2 game::Player::GetBobbingOffset(float t, float amplitude)
{
// Frequency and amplitude can be adjusted to tweak the effect

View File

@ -2,6 +2,7 @@
#include "entity.hpp"
#include "player_input.hpp"
#include "meshinstance.hpp"
namespace game
{
@ -25,6 +26,11 @@ namespace game
float time_ = 0.0f;
float current_speed_ = 0.0f;
// Viewmodel stuff
TransformNode vm_node_; // Affected by bobbing
MeshInstance vm_hands_;
std::unique_ptr<MeshInstance> vm_weapon_; // Can be null
public:
Player(World* world, size_t sector_idx, const glm::vec3& position);
@ -37,6 +43,8 @@ namespace game
void GetPOV(size_t& sector_idx, glm::vec3& position, glm::vec3& forward, glm::vec3& up) const;
void GetViewMeshes(std::vector<const MeshInstance*>& out_meshes) const;
private:
static glm::vec2 GetBobbingOffset(float t, float amplitude);

View File

@ -68,7 +68,7 @@ namespace game
size_t idx_;
std::shared_ptr<assets::SectorDef> def_;
std::shared_ptr<assets::Mesh> mesh_;
std::shared_ptr<const assets::Mesh> mesh_;
std::vector<Light> lights_; // Light in this sector
std::vector<Light> all_lights_; // Lights in this sector and linked sectors
@ -96,7 +96,7 @@ namespace game
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<const assets::Mesh>& GetMesh() const { return mesh_; }
const std::shared_ptr<gfx::Texture>& GetLightmap() const { return lightmap_; }
btCollisionWorld& GetBtWorld() { return bt_world_; }

View File

@ -0,0 +1,29 @@
#pragma once
#include "utils/transform.hpp"
namespace game
{
struct TransformNode
{
const TransformNode* parent = nullptr;
Transform local_transform;
glm::mat4 matrix; // Global
TransformNode()
{
UpdateMatrix();
}
void UpdateMatrix()
{
matrix = local_transform.ToMatrix();
if (parent)
{
matrix = parent->matrix * matrix;
}
}
};
}

View File

@ -14,6 +14,8 @@ namespace game
std::vector<std::unique_ptr<Sector>> sectors_;
std::vector<std::unique_ptr<Entity>> entities_;
float time_ = 0.0f; // Game time
public:
World();
@ -34,6 +36,8 @@ namespace game
Sector& GetSector(size_t idx) { return *sectors_[idx]; }
const Sector& GetSector(size_t idx) const { return *sectors_[idx]; }
const float* GetTimePtr() const { return &time_; }
void Bake();
};

View File

@ -9,8 +9,9 @@
gfx::Renderer::Renderer()
{
ShaderSources::MakeShader(sector_shader_, SS_SECTOR_MESH_VERT, SS_SECTOR_MESH_FRAG);
ShaderSources::MakeShader(portal_shader_, SS_PORTAL_VERT, SS_PORTAL_FRAG);
ShaderSources::MakeShader(sector_shader_.shader, SS_SECTOR_MESH_VERT, SS_SECTOR_MESH_FRAG);
ShaderSources::MakeShader(portal_shader_.shader, SS_PORTAL_VERT, SS_PORTAL_FRAG);
ShaderSources::MakeShader(mesh_shader_.shader, SS_MESH_VERT, SS_MESH_FRAG);
proj_ = glm::mat4(1.0f); // Initialize projection matrix to identity
SetupPortalVAO();
@ -74,9 +75,13 @@ void gfx::Renderer::DrawWorld(
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilMask(0xFF);
last_sector_id = 0;
UnreadyShaders();
const game::Sector& sector = world.GetSector(sector_idx);
DrawSectorParams params;
params.draw_id = ++last_sector_id;
params.sector = &sector;
params.entry_portal = nullptr; // No entry portal for the first sector
params.clip_plane = glm::vec4(0.0f, 0.0f, 1.0f, 1000.0f); // Default clip plane
@ -94,57 +99,68 @@ void gfx::Renderer::DrawWorld(
}
void gfx::Renderer::UnreadyShaders()
{
portal_shader_.setup_sector = 0;
sector_shader_.setup_sector = 0;
}
void gfx::Renderer::SetupSectorShader(const DrawSectorParams& params, SectorShader& sshader)
{
const Shader& shader = *sshader.shader;
if (current_shader_ != &shader)
{
glUseProgram(shader.GetId());
current_shader_ = &shader;
}
if (sshader.setup_sector == params.draw_id)
{
return; // Shader is already set up for this sector
}
glUniformMatrix4fv(shader.U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, &params.view_proj[0][0]);
glUniform4fv(shader.U(gfx::SU_CLIP_PLANE), 1, &params.clip_plane[0]);
sshader.setup_sector = params.draw_id;
}
static void BindTexture(const gfx::Texture* tex)
{
GLuint id = 0;
if (tex)
{
id = tex->GetId();
}
glBindTexture(GL_TEXTURE_2D, id);
}
void gfx::Renderer::DrawSector(const DrawSectorParams& params)
{
assets::Mesh& mesh = *params.sector->GetMesh();
const assets::Mesh& mesh = *params.sector->GetMesh();
glm::mat4 model = glm::mat4(1.0f); // Identity matrix for model
// #ifndef PG_GLES
// glEnable(GL_CLIP_DISTANCE0);
// #else
// glEnable(GL_CLIP_DISTANCE0_EXT);
// #endif
glUseProgram(sector_shader_->GetId());
glUniformMatrix4fv(sector_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, &params.view_proj[0][0]);
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]);
SetupSectorShader(params, sector_shader_);
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
}
BindTexture(params.sector->GetLightmap().get());
glBindVertexArray(mesh.GetVA().GetVAOId());
glActiveTexture(GL_TEXTURE0);
for (auto mesh_materials = mesh.GetMaterials(); const auto& mat : mesh_materials) {
glActiveTexture(GL_TEXTURE0);
if (mat.texture) {
glBindTexture(GL_TEXTURE_2D, mat.texture->GetId());
glUniform1i(sector_shader_->U(gfx::SU_TEX), 0); // Bind texture to sampler
}
else {
glBindTexture(GL_TEXTURE_2D, 0); // No texture
}
BindTexture(mat.texture.get()); // diffuse texture
glDrawElements(GL_TRIANGLES, mat.num_tris * 3, GL_UNSIGNED_INT, (void*)(mat.first_tri * 3 * sizeof(uint32_t)));
}
glBindVertexArray(0);
// Draw portals
for (size_t i = 0; i < params.sector->GetNumPortals(); ++i)
{
const game::Portal& portal = params.sector->GetPortal(i);
DrawPortal(params, portal);
}
}
void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Portal& portal)
@ -211,9 +227,6 @@ void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Porta
const game::Sector& other_sector = *other_portal.sector;
// Open portal
glStencilFunc(GL_EQUAL, params.recursion, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
@ -227,6 +240,7 @@ void gfx::Renderer::DrawPortal(const DrawSectorParams& params, const game::Porta
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
DrawSectorParams new_params;
new_params.draw_id = ++last_sector_id;
new_params.sector = &other_sector;
new_params.entry_portal = &other_portal;
new_params.clip_plane = new_clip_plane; // Use the portal plane as the clip plane
@ -353,18 +367,17 @@ void gfx::Renderer::DrawPortalPlane(const DrawSectorParams& params, const game::
{
if (!clear_depth) // first draw
{
glUseProgram(portal_shader_->GetId());
glUniformMatrix4fv(portal_shader_->U(gfx::SU_VIEW_PROJ), 1, GL_FALSE, &params.view_proj[0][0]);
glUniformMatrix4fv(portal_shader_->U(gfx::SU_MODEL), 1, GL_FALSE, &trans[0][0]);
glUniform4fv(portal_shader_->U(gfx::SU_CLIP_PLANE), 1, &params.clip_plane[0]);
glUniform2fv(portal_shader_->U(gfx::SU_PORTAL_SIZE), 1, &portal.def->size[0]);
glUniform1i(portal_shader_->U(gfx::SU_CLEAR_DEPTH), 0);
SetupSectorShader(params, portal_shader_);
glUniformMatrix4fv(portal_shader_.shader->U(gfx::SU_MODEL), 1, GL_FALSE, &trans[0][0]);
glUniform2fv(portal_shader_.shader->U(gfx::SU_PORTAL_SIZE), 1, &portal.def->size[0]);
glUniform1i(portal_shader_.shader->U(gfx::SU_CLEAR_DEPTH), 0);
glBindVertexArray(portal_vao_->GetVAOId());
}
else // assume uniforms are already set from previous draw
{
glUniform1i(portal_shader_->U(gfx::SU_CLEAR_DEPTH), 1);
glUniform1i(portal_shader_.shader->U(gfx::SU_CLEAR_DEPTH), 1);
}
glDrawArrays(GL_TRIANGLE_FAN, 0, 4); // Draw the portal as a quad
}

View File

@ -4,11 +4,13 @@
#include "gfx/shader.hpp"
#include "game/world.hpp"
#include "game/sector.hpp"
#include "game/meshinstance.hpp"
namespace gfx
{
struct DrawSectorParams
{
size_t draw_id;
const game::Sector* sector;
const game::Portal* entry_portal;
glm::vec4 clip_plane;
@ -19,6 +21,12 @@ namespace gfx
glm::vec3 eye;
};
struct SectorShader
{
std::unique_ptr<Shader> shader;
size_t setup_sector = 0;
};
class Renderer
{
public:
@ -36,8 +44,9 @@ namespace gfx
float fov);
private:
std::unique_ptr<Shader> sector_shader_;
std::unique_ptr<Shader> portal_shader_;
SectorShader sector_shader_;
SectorShader portal_shader_;
SectorShader mesh_shader_;
std::shared_ptr<VertexArray> portal_vao_;
void SetupPortalVAO();
@ -49,12 +58,20 @@ namespace gfx
float min_portal_distance_;
glm::mat4 proj_;
size_t last_sector_id;
const Shader* current_shader_;
void UnreadyShaders();
void SetupSectorShader(const DrawSectorParams& params, SectorShader& sshader);
void DrawSector(const DrawSectorParams& params);
void DrawPortal(const DrawSectorParams& params, const game::Portal& portal);
static bool ComputeQuadScreenAABB(const glm::vec3* verts, const glm::mat4 view_proj, collision::AABB2& aabb);
void DrawPortalPlane(const DrawSectorParams& params, const game::Portal& portal, const glm::mat4& trans, bool clear_depth);
void DrawMeshInstance(const DrawSectorParams& params, const game::MeshInstance& mesh_instance, const glm::mat4& model);
};
}

View File

@ -80,9 +80,21 @@ gfx::Shader::Shader(const char* vert_src, const char* frag_src) {
// Ziskani lokaci uniformnich promennych
for (size_t i = 0; i < SU_COUNT; i++)
m_uni[i] = glGetUniformLocation(m_id, s_uni_names[i]);
SetupTextureBindings();
}
gfx::Shader::~Shader() {
// Smazani programu
glDeleteProgram(m_id);
}
void gfx::Shader::SetupTextureBindings()
{
glUseProgram(m_id);
glUniform1i(m_uni[SU_TEX], 0);
glUniform1i(m_uni[SU_LIGHTMAP_TEX], 1);
glUseProgram(0);
}

View File

@ -48,5 +48,8 @@ namespace gfx
*/
GLuint GetId() const { return m_id; }
private:
void SetupTextureBindings();
};
}

View File

@ -22,7 +22,6 @@ 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;
@ -31,7 +30,7 @@ out vec2 v_lightmap_uv;
out float v_clip_distance;
void main() {
vec4 sector_pos = u_model * vec4(a_pos, 1.0);
vec4 sector_pos = vec4(a_pos, 1.0);
gl_Position = u_view_proj * sector_pos;
// Clip against the plane
@ -113,6 +112,61 @@ void main() {
)GLSL",
// SS_MESH_VERT
GLSL_VERSION
R"GLSL(
layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec3 a_normal;
layout (location = 3) in vec2 a_uv;
uniform mat4 u_view_proj;
uniform vec4 u_clip_plane;
uniform mat4 u_model; // Transform matrix
out vec2 v_uv;
out float v_clip_distance;
out vec3 v_color;
void main() {
vec4 sector_pos = u_model * vec4(a_pos, 1.0);
gl_Position = u_view_proj * sector_pos;
// Clip against the plane
v_clip_distance = dot(sector_pos, u_clip_plane);
//v_normal = mat3(u_model) * a_normal;
v_uv = vec2(a_uv.x, 1.0 - a_uv.y);
v_color = vec3(1.0, 1.0, 1.0);
}
)GLSL",
// SS_MESH_FRAG
GLSL_VERSION
R"GLSL(
in vec2 v_uv;
in float v_clip_distance;
in vec3 v_color;
uniform sampler2D u_tex;
layout (location = 0) out vec4 o_color;
void main() {
if (v_clip_distance < 0.0) {
discard; // Discard fragment if it is outside the clip plane
}
o_color = vec4(texture(u_tex, v_uv));
o_color.rgb *= v_color; // Apply vertex color
//o_color = vec4(1.0, 0.0, 0.0, 1.0);
}
)GLSL",
};
// Vrati zdrojovy kod shaderu

View File

@ -13,6 +13,9 @@ namespace gfx
SS_PORTAL_VERT,
SS_PORTAL_FRAG,
SS_MESH_VERT,
SS_MESH_FRAG,
};
class ShaderSources

View File

@ -5,11 +5,12 @@
struct Transform
{
glm::vec3 position;
glm::quat rotation;
float scale;
glm::vec3 position = glm::vec3(0.0f);
glm::quat rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); // Identity quaternion
float scale = 1.0f;
glm::mat4 ToMatrix() const {
glm::mat4 ToMatrix() const
{
// TODO: Optimize matrix construction
glm::mat4 translation_mat = glm::translate(glm::mat4(1.0f), position);
glm::mat4 rotation_mat = glm::mat4_cast(rotation);
@ -17,8 +18,18 @@ struct Transform
return translation_mat * rotation_mat * scaling_mat;
}
void SetAngles(const glm::vec3& angles_deg) {
void SetAngles(const glm::vec3& angles_deg)
{
glm::vec3 angles_rad = glm::radians(angles_deg);
rotation = glm::quat(angles_rad);
}
static Transform Lerp(const Transform& a, const Transform& b, float t)
{
Transform result;
result.position = glm::mix(a.position, b.position, t);
result.rotation = glm::slerp(a.rotation, b.rotation, t);
result.scale = glm::mix(a.scale, b.scale, t);
return result;
}
};