diff --git a/assets/model/creature/pig.glb b/assets/model/creature/pig.glb new file mode 100644 index 0000000..b94f8d9 Binary files /dev/null and b/assets/model/creature/pig.glb differ diff --git a/assets/shaders/model_frag.glsl b/assets/shaders/model_frag.glsl new file mode 100644 index 0000000..1fe8f33 --- /dev/null +++ b/assets/shaders/model_frag.glsl @@ -0,0 +1,15 @@ +#version 460 + +in vec2 tc; +in vec3 normal; +in vec3 vert_pos; + +out vec4 color; + +layout (binding = 0) uniform sampler2D samp; + + +void main() { + color = texture(samp, tc); + +} diff --git a/assets/shaders/model_vert.glsl b/assets/shaders/model_vert.glsl new file mode 100644 index 0000000..7681fb8 --- /dev/null +++ b/assets/shaders/model_vert.glsl @@ -0,0 +1,22 @@ +#version 460 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texCoord; +layout (location = 2) in vec3 aNormal; + +uniform mat4 mv_matrix; +uniform mat4 proj_matrix; + +out vec3 normal; +out vec2 tc; +out vec3 vert_pos; + + +void main() { + //vec4 worldPos = modelMatrix * vec4(pos, 1.0); + vec4 viewPos = mv_matrix * vec4(pos, 1.0); + tc = texCoord; + vert_pos = pos; + //normal = normalize(mat3(norm_matrix) * aNormal); + gl_Position = proj_matrix * viewPos; +} \ No newline at end of file diff --git a/include/Cubed/app.hpp b/include/Cubed/app.hpp index 4d9ee45..2d509aa 100644 --- a/include/Cubed/app.hpp +++ b/include/Cubed/app.hpp @@ -3,6 +3,7 @@ #include "Cubed/audio/audio_engine.hpp" #include "Cubed/config.hpp" #include "Cubed/dev_panel.hpp" +#include "Cubed/render/model_manager.hpp" #include "Cubed/render/renderer.hpp" #include "Cubed/scene/scene_manager.hpp" #include "Cubed/texture_manager.hpp" @@ -49,7 +50,7 @@ private: Window m_window; TextureManager m_texture_manager; - + ModelManager m_model_manager; AudioEngine m_audio; Renderer m_renderer; diff --git a/include/Cubed/render/model_manager.hpp b/include/Cubed/render/model_manager.hpp new file mode 100644 index 0000000..18af757 --- /dev/null +++ b/include/Cubed/render/model_manager.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "Cubed/render/model_node.hpp" +#include "Cubed/tools/model_loader.hpp" +namespace Cubed { +class ModelManager { +public: + ModelManager(); + ModelManager(const ModelManager&) = delete; + ModelManager(ModelManager&&) = delete; + ModelManager& operator=(const ModelManager&) = delete; + ModelManager& operator=(ModelManager&&) = delete; + ~ModelManager(); + const ModelNode& get_model(const std::string& model_name); + void init(); + +private: + ModelLoader m_loader; + std::unordered_map m_models; + const ModelNode& load_model(const std::string& model_name); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/render/model_node.hpp b/include/Cubed/render/model_node.hpp new file mode 100644 index 0000000..b3e7414 --- /dev/null +++ b/include/Cubed/render/model_node.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "Cubed/primitive_data.hpp" +#include "Cubed/render/texture.hpp" +#include "Cubed/render/vertex_array.hpp" +#include "Cubed/render/vertex_buffer.hpp" + +#include +#include +#include +#include +namespace Cubed { + +struct Mesh { + std::vector vertices; + std::vector indices; + std::unique_ptr vbo; + std::unique_ptr ebo; + std::unique_ptr vao; + std::unique_ptr texture; + void upload() { + vao = std::make_unique(); + vao->bind(); + vbo = std::make_unique(); + vbo->buffer_data(vertices.data(), vertices.size() * sizeof(Vertex3D)); + ebo = std::make_unique(BufferType::ELEMENT_ARRAY_BUFFER); + ebo->buffer_data(indices.data(), indices.size() * sizeof(uint32_t)); + vao->attribute(0, 3, GL_FLOAT, sizeof(Vertex3D), (void*)0); + vao->attribute(1, 2, GL_FLOAT, sizeof(Vertex3D), + (void*)offsetof(Vertex3D, s)); + vao->attribute(2, 3, GL_FLOAT, sizeof(Vertex3D), + (void*)offsetof(Vertex3D, nx)); + }; +}; +struct ModelNode { + std::string name; + glm::mat4 transform{1.0f}; + std::vector meshes; + std::vector children; +}; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/render/model_renderer.hpp b/include/Cubed/render/model_renderer.hpp new file mode 100644 index 0000000..64df2a4 --- /dev/null +++ b/include/Cubed/render/model_renderer.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "Cubed/render/model_node.hpp" +#include "Cubed/shader.hpp" + +#include +namespace Cubed { +class Renderer; +class Camera; +class ModelRender { +public: + ModelRender(Renderer& renderer); + void render_model(const std::string& name, const glm::vec3& pos, + Camera& camera); + +private: + Renderer& m_renderer; + void render_node(const ModelNode& node, const glm::mat4& parent, + const glm::mat4& view, const Shader& shader); + void render_mesh(const Mesh& mesh); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/render/renderer.hpp b/include/Cubed/render/renderer.hpp index 00edc28..15a57d1 100644 --- a/include/Cubed/render/renderer.hpp +++ b/include/Cubed/render/renderer.hpp @@ -4,6 +4,7 @@ #include "Cubed/constants.hpp" #include "Cubed/input/event.hpp" #include "Cubed/primitive_data.hpp" +#include "Cubed/render/model_renderer.hpp" #include "Cubed/render/player_renderer.hpp" #include "Cubed/render/shader_manager.hpp" #include "Cubed/render/vertex_array.hpp" @@ -19,12 +20,14 @@ namespace Cubed { class TextureManager; class ClientWorld; +class ModelManager; class DevPanel; class Renderer { public: constexpr static int NUM_VAO = 7; - Renderer(TextureManager& texture_manager, Config& config); + Renderer(TextureManager& texture_manager, Config& config, + ModelManager& model_manager); ~Renderer(); void reload_config(); void init(); @@ -79,9 +82,14 @@ public: bool handle_event(const Event& e); + void render_model(const std::string& name, const glm::vec3& pos, + Camera& camera); + + ModelManager& model_manager(); + private: TextureManager& m_texture_manager; - + ModelManager& m_model_manager; bool m_init = false; float m_aspect = 0.0f; @@ -118,6 +126,7 @@ private: std::vector m_ui; WorldRenderer m_world_renderer; + ModelRender m_model_renderer; Config& m_config; bool handle_window_resize_event(const WindowResizeEvent& e); diff --git a/include/Cubed/render/texture.hpp b/include/Cubed/render/texture.hpp index f058ef5..129a400 100644 --- a/include/Cubed/render/texture.hpp +++ b/include/Cubed/render/texture.hpp @@ -38,9 +38,11 @@ enum TextureFormat : GLenum { R8 = GL_R8, RGB = GL_RGB, RGBA8 = GL_RGBA8, + BGRA = GL_BGRA }; - +// You need to set the texture scaling method, otherwise it will render as +// black. class Texture { public: explicit Texture(TextureType type); diff --git a/include/Cubed/tools/model_loader.hpp b/include/Cubed/tools/model_loader.hpp new file mode 100644 index 0000000..da301da --- /dev/null +++ b/include/Cubed/tools/model_loader.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "Cubed/render/model_node.hpp" + +#include +#include +namespace Cubed { + +class ModelLoader { +public: + ModelLoader(); + ModelNode load(const std::string& path); + +private: + Assimp::Importer m_importer; + ModelNode process_node(aiNode* node, const aiScene* scene); + Mesh process_mesh(aiMesh* mesh, const aiScene* scene); + bool process_texture(Mesh& mesh, aiMaterial* material, const aiScene* scene, + aiTextureType type); + glm::mat4 convert_matrix(const aiMatrix4x4& matrix); +}; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/tools/shader_tools.hpp b/include/Cubed/tools/shader_tools.hpp index 443328a..d9eb81e 100644 --- a/include/Cubed/tools/shader_tools.hpp +++ b/include/Cubed/tools/shader_tools.hpp @@ -47,7 +47,7 @@ bool check_opengl_error(); std::string read_shader_source(const std::string& file_path); ImageData load_image_data(const std::string& tex_image_path, - bool check_exist = true); + bool check_exist = true, bool full_path = false); } // namespace Tools diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f16a3f8..589c8be 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 ) \ No newline at end of file diff --git a/src/app.cpp b/src/app.cpp index 1abc51a..2ae7ddc 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -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); diff --git a/src/render/model_manager.cpp b/src/render/model_manager.cpp new file mode 100644 index 0000000..fc568fa --- /dev/null +++ b/src/render/model_manager.cpp @@ -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 \ No newline at end of file diff --git a/src/render/model_renderer.cpp b/src/render/model_renderer.cpp new file mode 100644 index 0000000..c6b2ba3 --- /dev/null +++ b/src/render/model_renderer.cpp @@ -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(mesh.indices.size()), + GL_UNSIGNED_INT, 0); +} + +} // namespace Cubed \ No newline at end of file diff --git a/src/render/renderer.cpp b/src/render/renderer.cpp index d26c525..80ffe37 100644 --- a/src/render/renderer.cpp +++ b/src/render/renderer.cpp @@ -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& Renderer::vao() const { return m_vao; } +ModelManager& Renderer::model_manager() { return m_model_manager; } } // namespace Cubed \ No newline at end of file diff --git a/src/render/shader_manager.cpp b/src/render/shader_manager.cpp index 3669b7e..96d66ac 100644 --- a/src/render/shader_manager.cpp +++ b/src/render/shader_manager.cpp @@ -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, diff --git a/src/scene/world_scene.cpp b/src/scene/world_scene.cpp index 827ab4b..33c15b5 100644 --- a/src/scene/world_scene.cpp +++ b/src/scene/world_scene.cpp @@ -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); } diff --git a/src/tools/model_loader.cpp b/src/tools/model_loader.cpp new file mode 100644 index 0000000..cd86be3 --- /dev/null +++ b/src/tools/model_loader.cpp @@ -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 +#include +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(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(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(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 \ No newline at end of file diff --git a/src/tools/shader_tools.cpp b/src/tools/shader_tools.cpp index 7a4f489..e24f1aa 100644 --- a/src/tools/shader_tools.cpp +++ b/src/tools/shader_tools.cpp @@ -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()); }