feat(render): add model loading and rendering pipeline

This commit is contained in:
2026-07-24 18:35:09 +08:00
parent f4174724c6
commit 94fed9a810
20 changed files with 393 additions and 11 deletions

View File

@@ -92,4 +92,7 @@ target_sources(${PROJECT_NAME}
ui/row_layout.cpp
ui/item_slot.cpp
ui/inventory_ui.cpp
tools/model_loader.cpp
render/model_manager.cpp
render/model_renderer.cpp
)

View File

@@ -20,7 +20,8 @@ App::App()
: m_game_config(ASSETS_PATH "config.toml"), m_window(m_game_config),
m_texture_manager(m_game_config), m_audio(m_game_config),
m_renderer(m_texture_manager, m_game_config), m_scene_manager(*this) {}
m_renderer(m_texture_manager, m_game_config, m_model_manager),
m_scene_manager(*this) {}
App::~App() {
stop_text_input();
@@ -85,6 +86,7 @@ void App::init(int argc, char** argv) {
Logger::info("Renderer Init Success");
// MapTable::init_map();
m_texture_manager.init_texture();
m_model_manager.init();
Logger::info("Texture Load Success");
m_scene_manager.request_push(SceneType::MAIN_MENU);

View File

@@ -0,0 +1,32 @@
#include "Cubed/render/model_manager.hpp"
#include "Cubed/tools/log.hpp"
namespace Cubed {
ModelManager::ModelManager() {}
ModelManager::~ModelManager() {}
void ModelManager::init() {
};
const ModelNode& ModelManager::get_model(const std::string& model_name) {
auto it = m_models.find(model_name);
if (it == m_models.end()) {
return load_model(model_name);
}
return it->second;
}
const ModelNode& ModelManager::load_model(const std::string& model_name) {
std::string path = ASSETS_PATH + model_name;
auto model = m_loader.load(path);
auto [it, instert] = m_models.try_emplace(model_name, std::move(model));
if (!instert) {
Logger::error("Model Key {} already exist!", model_name);
}
return it->second;
}
} // namespace Cubed

View File

@@ -0,0 +1,48 @@
#include "Cubed/render/model_renderer.hpp"
#include "Cubed/camera.hpp"
#include "Cubed/render/model_manager.hpp"
#include "Cubed/render/renderer.hpp"
namespace Cubed {
ModelRender::ModelRender(Renderer& renderer) : m_renderer(renderer) {}
void ModelRender::render_model(const std::string& name, const glm::vec3& pos,
Camera& camera) {
auto& model_manager = m_renderer.model_manager();
auto& root = model_manager.get_model(name);
glm::mat4 transform = glm::translate(glm::mat4(1.0f), pos);
auto& shader = m_renderer.get_shader("model_shader");
glm::mat4 view = camera.get_camera_lookat();
shader.use();
shader.set_loc("proj_matrix", m_renderer.p_mat());
render_node(root, transform, view, shader);
}
void ModelRender::render_node(const ModelNode& node, const glm::mat4& parent,
const glm::mat4& view, const Shader& shader) {
glm::mat4 transform = parent * node.transform;
shader.set_loc("mv_matrix", view * transform);
for (auto& mesh : node.meshes) {
render_mesh(mesh);
}
for (auto& child : node.children) {
render_node(child, transform, view, shader);
}
}
void ModelRender::render_mesh(const Mesh& mesh) {
mesh.vao->bind();
if (mesh.texture) {
mesh.texture->bind(0);
} else {
Logger::error("Model Texture Not Find");
}
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mesh.indices.size()),
GL_UNSIGNED_INT, 0);
}
} // namespace Cubed

View File

@@ -15,9 +15,10 @@
namespace Cubed {
Renderer::Renderer(TextureManager& texture_manager, Config& config)
: m_texture_manager(texture_manager), m_world_renderer(*this),
m_config(config) {}
Renderer::Renderer(TextureManager& texture_manager, Config& config,
ModelManager& model_manager)
: m_texture_manager(texture_manager), m_model_manager(model_manager),
m_world_renderer(*this), m_model_renderer(*this), m_config(config) {}
Renderer::~Renderer() {
if (m_init) {
@@ -249,6 +250,12 @@ void Renderer::render_dev_panel(DevPanel& dev_panel) {
glEnable(GL_DEPTH_TEST);
}
void Renderer::render_model(const std::string& name, const glm::vec3& pos,
Camera& camera) {
glEnable(GL_DEPTH_TEST);
m_model_renderer.render_model(name, pos, camera);
}
bool Renderer::handle_event(const Event& e) {
return std::visit(Overloaded{[](const MouseMoveEvent&) { return false; },
[](const MouseButtonEvent&) { return false; },
@@ -340,4 +347,5 @@ float Renderer::frame_height() const { return m_frame_height; }
float Renderer::frame_width() const { return m_frame_width; }
const glm::mat4& Renderer::p_mat() const { return m_world_proj_matrix; }
const std::vector<VertexArray>& Renderer::vao() const { return m_vao; }
ModelManager& Renderer::model_manager() { return m_model_manager; }
} // namespace Cubed

View File

@@ -35,6 +35,8 @@ void ShaderManager::init() {
"shaders/depth_player_fragment_shader.glsl");
register_shader("rect", "shaders/rect_v_shader.glsl",
"shaders/rect_f_shader.glsl");
register_shader("model_shader", "shaders/model_vert.glsl",
"shaders/model_frag.glsl");
}
void ShaderManager::register_shader(const std::string& name,

View File

@@ -65,6 +65,7 @@ void WorldScene::render(Renderer& renderer) {
return;
}
renderer.render_world(m_client_world);
renderer.render_model("model/creature/pig.glb", {0, 100, 0}, m_camera);
if (m_show_hud) {
m_hud_ui.render(renderer);
}

124
src/tools/model_loader.cpp Normal file
View File

@@ -0,0 +1,124 @@
#include "Cubed/tools/model_loader.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/shader_tools.hpp"
#include <assimp/postprocess.h>
#include <stb_image.h>
namespace Cubed {
ModelLoader::ModelLoader() {}
ModelNode ModelLoader::load(const std::string& path) {
const aiScene* scene = m_importer.ReadFile(
path, aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_FlipUVs |
aiProcess_JoinIdenticalVertices |
aiProcess_ImproveCacheLocality);
if (!scene || !scene->mRootNode) {
Logger::error("Load Model {} Error: {}", path,
m_importer.GetErrorString());
return {};
}
return process_node(scene->mRootNode, scene);
}
ModelNode ModelLoader::process_node(aiNode* node, const aiScene* scene) {
ModelNode result;
result.name = node->mName.C_Str();
result.transform = convert_matrix(node->mTransformation);
for (unsigned i = 0; i < node->mNumMeshes; ++i) {
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
result.meshes.emplace_back(process_mesh(mesh, scene));
}
for (unsigned i = 0; i < node->mNumChildren; ++i) {
result.children.emplace_back(process_node(node->mChildren[i], scene));
}
return result;
}
Mesh ModelLoader::process_mesh(aiMesh* mesh, const aiScene* scene) {
Mesh result;
for (unsigned i = 0; i < mesh->mNumVertices; ++i) {
Vertex3D v{};
v.x = mesh->mVertices[i].x;
v.y = mesh->mVertices[i].y;
v.z = mesh->mVertices[i].z;
if (mesh->HasTextureCoords(0)) {
v.s = mesh->mTextureCoords[0][i].x;
v.t = mesh->mTextureCoords[0][i].y;
}
if (mesh->HasNormals()) {
v.nx = mesh->mNormals[i].x;
v.ny = mesh->mNormals[i].y;
v.nz = mesh->mNormals[i].z;
} else {
Logger::warn("Model Does not have Normals!");
}
result.vertices.emplace_back(v);
}
for (unsigned i = 0; i < mesh->mNumFaces; ++i) {
aiFace face = mesh->mFaces[i];
for (unsigned j = 0; j < face.mNumIndices; ++j) {
result.indices.emplace_back(face.mIndices[j]);
}
}
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
if (!process_texture(result, material, scene, aiTextureType_BASE_COLOR)) {
if (!process_texture(result, material, scene, aiTextureType_DIFFUSE)) {
if (!process_texture(result, material, scene,
aiTextureType_UNKNOWN)) {
Logger::error("Can't Load model texture");
ASSERT(false);
}
}
}
result.upload();
return result;
}
bool ModelLoader::process_texture(Mesh& mesh, aiMaterial* material,
const aiScene* scene, aiTextureType type) {
aiString texture_path;
if (material->GetTexture(type, 0, &texture_path) != AI_SUCCESS) {
return false;
}
mesh.texture = std::make_unique<Texture>(TextureType::TEXTURE_2D);
if (texture_path.C_Str()[0] == '*') {
int index = std::stoi(texture_path.C_Str() + 1);
aiTexture* texture = scene->mTextures[index];
if (texture->mHeight == 0) {
int width, height, channels;
unsigned char* data = stbi_load_from_memory(
reinterpret_cast<unsigned char*>(texture->pcData),
texture->mWidth, &width, &height, &channels, STBI_rgb_alpha);
mesh.texture->tex_image_2d(RGBA, RGBA, GL_UNSIGNED_BYTE, data,
width, height);
stbi_image_free(data);
} else {
mesh.texture->tex_image_2d(RGBA, BGRA, GL_UNSIGNED_BYTE,
reinterpret_cast<char*>(texture->pcData),
texture->mWidth, texture->mHeight);
}
} else {
auto image_data =
Tools::load_image_data(texture_path.C_Str(), true, true);
mesh.texture->tex_image_2d(RGBA, RGBA, GL_UNSIGNED_BYTE,
image_data.data, image_data.width,
image_data.height);
}
mesh.texture->set_nearest();
return true;
}
glm::mat4 ModelLoader::convert_matrix(const aiMatrix4x4& m) {
return glm::mat4(m.a1, m.b1, m.c1, m.d1, m.a2, m.b2, m.c2, m.d2, m.a3, m.b3,
m.c3, m.d3, m.a4, m.b4, m.c4, m.d4);
}
} // namespace Cubed

View File

@@ -175,8 +175,14 @@ void delete_image_data(unsigned char* data) {
stbi_image_free(data);
}
ImageData load_image_data(const std::string& tex_image_path, bool check_exist) {
fs::path path = ASSETS_PATH + tex_image_path;
ImageData load_image_data(const std::string& tex_image_path, bool check_exist,
bool full_path) {
fs::path path;
if (full_path) {
path = tex_image_path;
} else {
path = ASSETS_PATH + tex_image_path;
}
if (check_exist) {
ASSERT_MSG(fs::is_regular_file(path), path.c_str());
}