diff --git a/data/shaders/fragment.glsl b/data/shaders/fragment.glsl index abd6d17..396a596 100644 --- a/data/shaders/fragment.glsl +++ b/data/shaders/fragment.glsl @@ -1,5 +1,12 @@ +uniform int useTexture; +uniform sampler2D texSampler; varying vec3 fcolor; +varying vec2 ftexcoord; void main() { - gl_FragColor = vec4(fcolor, 1); + if (useTexture == 1) { + gl_FragColor = texture2D(texSampler, ftexcoord); + } else { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + } } diff --git a/data/shaders/vertex.glsl b/data/shaders/vertex.glsl index b273de7..6ca3e2b 100644 --- a/data/shaders/vertex.glsl +++ b/data/shaders/vertex.glsl @@ -1,9 +1,12 @@ uniform mat4 mvp; attribute vec3 vpos; attribute vec3 vcolor; +attribute vec2 vtexcoord; varying vec3 fcolor; +varying vec2 ftexcoord; void main() { gl_Position = mvp * vec4(vpos, 1); fcolor = vcolor; + ftexcoord = vtexcoord; } \ No newline at end of file diff --git a/src/engine.cpp b/src/engine.cpp index 753e7fd..209c4a4 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -10,10 +10,12 @@ #include "buffer.h" #include "camera.h" #include "logger.h" +#include "material.h" #include "mesh.h" #include "model.h" #include "shader.h" #include "state.h" +#include "texture.h" #include "vertex.h" #include "world.h" @@ -83,8 +85,8 @@ void Engine::initialize() // Initialize default shader Logger::info("Loading default shaders..."); - state::default_shader = - std::make_shared("data/shaders/vertex.glsl", "data/shaders/fragment.glsl"); + state::default_shader = std::make_shared( + "data/shaders/vertex.glsl", "data/shaders/fragment.glsl"); if (std::strlen(state::default_shader->error()) > 0) { Logger::error(sstr("Failed to initialize shaders: ", state::default_shader->error())); @@ -140,9 +142,10 @@ void Engine::setup() world_ = std::make_unique(); Logger::info("World created"); - // Create camera + // Create camera at position (0, 1, 3) with -20 degrees X rotation camera_ = std::make_shared(); - camera_->set_position(glm::vec3(0.0f, 0.0f, 6.0f)); + camera_->set_position(glm::vec3(0.0f, 1.0f, 3.0f)); + camera_->set_rotation(glm::vec3(glm::radians(-20.0f), 0.0f, 0.0f)); camera_->set_projection( glm::perspective(glm::radians(45.0f), static_cast(screen_width_) @@ -153,31 +156,79 @@ void Engine::setup() world_->add_entity(camera_); Logger::info("Camera created and added to world"); - // Create triangle mesh - std::vector vertices = { - {{0.0f, 0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}}, - {{-0.5f, -0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}}, - {{0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}}}; - std::vector indices = {0, 1, 2}; + // Load textures + auto top_texture = Texture::load("data/textures/top.png"); + auto front_texture = Texture::load("data/textures/front.png"); - auto buffer = std::make_shared(vertices, indices); - mesh_ = std::make_shared(); - mesh_->add_buffer(buffer); - Logger::info(sstr("Mesh created with ", vertices.size(), - " vertices and ", indices.size(), " indices")); + // Create cube mesh with two buffers + mesh_ = std::make_shared(); + + // Buffer 1: Top and bottom faces + std::vector top_bottom_vertices = { + // Top face (y = 0.5) + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + // Bottom face (y = -0.5) + {{-0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, + {{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}}; + std::vector top_bottom_indices = {// Top face + 0, 1, 2, 0, 2, 3, + // Bottom face + 4, 6, 5, 4, 7, 6}; + + auto top_bottom_buffer = + std::make_shared(top_bottom_vertices, top_bottom_indices); + Material top_material(top_texture); + mesh_->add_buffer(top_bottom_buffer, top_material); + + // Buffer 2: Front, back, left, right faces + std::vector side_vertices = { + // Front face (z = 0.5) + {{-0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, + {{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + // Back face (z = -0.5) + {{-0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, + {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + {{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, + // Left face (x = -0.5) + {{-0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}, + {{-0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, + {{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, + {{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + // Right face (x = 0.5) + {{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 0.0f}}, + {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}, + {{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}}, + {{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 0.0f}}}; + std::vector side_indices = {// Front face + 0, 1, 2, 0, 2, 3, + // Back face + 4, 5, 6, 4, 6, 7, + // Left face + 8, 9, 10, 8, 10, 11, + // Right face + 12, 13, 14, 12, 14, 15}; + + auto side_buffer = + std::make_shared(side_vertices, side_indices); + Material side_material(front_texture); + mesh_->add_buffer(side_buffer, side_material); + + Logger::info("Cube mesh created with two buffers"); + + // Create model at origin + auto model = std::make_shared(mesh_); + model->set_position(glm::vec3(0.0f, 0.0f, 0.0f)); + models_.push_back(model); + world_->add_entity(model); - // Create 9 models in a 3x3 grid - for (int row = 0; row < 3; ++row) { - for (int col = 0; col < 3; ++col) { - auto model = std::make_shared(mesh_); - model->set_position( - glm::vec3(-3.0f + static_cast(col) * 3.0f, - 0.0f, static_cast(-row) * 3.0f)); - models_.push_back(model); - world_->add_entity(model); - } - } - Logger::info(sstr("Created ", models_.size(), " models in scene")); Logger::info("Scene setup complete"); } diff --git a/src/material.cpp b/src/material.cpp new file mode 100644 index 0000000..cc25dbc --- /dev/null +++ b/src/material.cpp @@ -0,0 +1,61 @@ +#include "material.h" + +#include "../lib/glm/glm.hpp" + +#include "shader.h" +#include "state.h" +#include "texture.h" + +Material::Material(const std::shared_ptr& tex, + const std::shared_ptr& shader) + : shader_(shader), texture_(tex) +{ +} + +const std::shared_ptr& Material::shader() const +{ + return shader_ ? shader_ : state::default_shader; +} + +std::shared_ptr& Material::shader() +{ + return shader_ ? shader_ : state::default_shader; +} + +void Material::set_shader(const std::shared_ptr& shader) +{ + shader_ = shader; +} + +void Material::prepare() +{ + // Activate shader + std::shared_ptr active_shader = shader(); + if (!active_shader) + return; + + active_shader->use(); + + // Set uniform variables + const glm::mat4 mvp = + state::projection_matrix * state::view_matrix * state::model_matrix; + const int mvp_loc = active_shader->uniform_location("mvp"); + Shader::set_mat4(mvp_loc, mvp); + + // Set texture-related uniforms + const int use_texture_loc = + active_shader->uniform_location("useTexture"); + if (texture_) { + Shader::set_int(use_texture_loc, 1); + const int sampler_loc = + active_shader->uniform_location("texSampler"); + Shader::set_int(sampler_loc, 0); // Texture unit 0 + } else { + Shader::set_int(use_texture_loc, 0); + } + + // Bind texture if available + if (texture_) { + texture_->bind(); + } +} diff --git a/src/material.h b/src/material.h new file mode 100644 index 0000000..b7bf9d4 --- /dev/null +++ b/src/material.h @@ -0,0 +1,35 @@ +#ifndef MATERIAL_H_ +#define MATERIAL_H_ + +#include + +class Shader; +class Texture; + +class Material { +public: + Material(const std::shared_ptr& tex = nullptr, + const std::shared_ptr& shader = nullptr); + ~Material() = default; + + [[nodiscard]] const std::shared_ptr& shader() const; + [[nodiscard]] std::shared_ptr& shader(); + void set_shader(const std::shared_ptr& shader); + + [[nodiscard]] const std::shared_ptr& texture() const + { + return texture_; + } + void set_texture(const std::shared_ptr& tex) + { + texture_ = tex; + } + + void prepare(); + +private: + std::shared_ptr shader_; + std::shared_ptr texture_; +}; + +#endif // MATERIAL_H_ diff --git a/src/mesh.cpp b/src/mesh.cpp index 24aa05e..fdf0253 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1,33 +1,20 @@ #include "mesh.h" + #include "buffer.h" -#include "shader.h" -#include "state.h" +#include "material.h" void Mesh::add_buffer(const std::shared_ptr& buffer, - const std::shared_ptr& shader) + const Material& material) { buffers_.push_back(buffer); - shaders_.push_back(shader); + materials_.push_back(material); } void Mesh::draw() { - // Calculate MVP matrix - glm::mat4 mvp = state::projection_matrix * state::view_matrix - * state::model_matrix; - - // Draw each buffer with its shader + // Draw each buffer with its material for (size_t i = 0; i < buffers_.size(); ++i) { - // Use buffer's shader if available, otherwise use default - // shader - std::shared_ptr shader = - shaders_[i] ? shaders_[i] : state::default_shader; - - if (shader) { - shader->use(); - int mvpLoc = shader->uniform_location("mvp"); - Shader::set_mat4(mvpLoc, mvp); - buffers_[i]->draw(*shader); - } + materials_[i].prepare(); + buffers_[i]->draw(*materials_[i].shader()); } } diff --git a/src/mesh.h b/src/mesh.h index 6f8ba6c..3e529cf 100644 --- a/src/mesh.h +++ b/src/mesh.h @@ -4,8 +4,9 @@ #include #include +#include "material.h" + class Buffer; -class Shader; class Mesh { public: @@ -13,7 +14,7 @@ public: ~Mesh() = default; void add_buffer(const std::shared_ptr& buffer, - const std::shared_ptr& shader = nullptr); + const Material& material); [[nodiscard]] size_t num_buffers() const { @@ -28,11 +29,20 @@ public: return buffers_[index]; } + [[nodiscard]] const Material& material(size_t index) const + { + return materials_[index]; + } + [[nodiscard]] Material& material(size_t index) + { + return materials_[index]; + } + void draw(); private: std::vector> buffers_; - std::vector> shaders_; + std::vector materials_; }; #endif // MESH_H_ diff --git a/src/shader.cpp b/src/shader.cpp index 2345660..e2023e0 100644 --- a/src/shader.cpp +++ b/src/shader.cpp @@ -10,11 +10,11 @@ #include "logger.h" #include "vertex.h" -Shader::Shader(const std::string& vertexPath, const std::string& fragmentPath) +Shader::Shader(const std::string& vertex_path, const std::string& fragment_path) { - const uint32_t vshader = compile_shader(GL_VERTEX_SHADER, vertexPath); + const uint32_t vshader = compile_shader(GL_VERTEX_SHADER, vertex_path); const uint32_t fshader = - compile_shader(GL_FRAGMENT_SHADER, fragmentPath); + compile_shader(GL_FRAGMENT_SHADER, fragment_path); if (vshader == 0 || fshader == 0) { program_id_ = 0; @@ -65,6 +65,14 @@ void Shader::setup_attribs() const loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(offsetof(Vertex, color))); } + + loc = glGetAttribLocation(program_id_, "vtexcoord"); + if (loc != -1) { + glEnableVertexAttribArray(loc); + glVertexAttribPointer( + loc, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), + reinterpret_cast(offsetof(Vertex, tex_coord))); + } } int Shader::uniform_location(const char* key) const @@ -141,9 +149,8 @@ uint32_t Shader::compile_shader(uint32_t type, const std::string& source_path) char buffer[1024]; glGetShaderInfoLog(shader, sizeof(buffer), nullptr, buffer); error_ = buffer; - Logger::error( - sstr("Shader compilation failed (", shader_type_name, "): ", - buffer)); + Logger::error(sstr("Shader compilation failed (", + shader_type_name, "): ", buffer)); glDeleteShader(shader); return 0; } diff --git a/src/texture.cpp b/src/texture.cpp new file mode 100644 index 0000000..b63ca09 --- /dev/null +++ b/src/texture.cpp @@ -0,0 +1,83 @@ +#include "texture.h" + +#include + +#include "../lib/glew/GL/glew.h" +#include "../lib/glfw/glfw3.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "../lib/stb/stb_image.h" + +#include "logger.h" + +Texture::Texture(uint32_t id, const glm::ivec2& size) + : id_(id), size_(size) +{ +} + +Texture::~Texture() +{ + if (id_ != 0) { + glDeleteTextures(1, &id_); + Logger::info(sstr("Texture ", id_, " destroyed")); + } +} + +std::shared_ptr Texture::load(const char* filename) +{ + Logger::info(sstr("Loading texture: ", filename)); + + // Load image + int width, height, channels; + stbi_uc* pixels = + stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha); + + if (!pixels) { + Logger::error(sstr("Failed to load texture: ", filename, " - ", + stbi_failure_reason())); + return nullptr; + } + + // Flip image vertically (OpenGL expects bottom row first) + const int row_size = width * 4; // 4 channels (RGBA) + auto* row_buffer = new stbi_uc[row_size]; + for (int y = 0; y < height / 2; ++y) { + stbi_uc* row1 = pixels + y * row_size; + stbi_uc* row2 = pixels + (height - 1 - y) * row_size; + std::copy(row1, row1 + row_size, row_buffer); + std::copy(row2, row2 + row_size, row1); + std::copy(row_buffer, row_buffer + row_size, row2); + } + delete[] row_buffer; + + // Generate OpenGL texture + GLuint texture_id; + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + + // Upload texture data + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, pixels); + + // Set trilinear filtering + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Generate mipmaps + glGenerateMipmap(GL_TEXTURE_2D); + + // Free image data + stbi_image_free(pixels); + + Logger::info(sstr("Texture loaded successfully: ", filename, " (", + width, "x", height, ", ", channels, " channels)")); + + return std::shared_ptr( + new Texture(texture_id, glm::ivec2(width, height))); +} + +void Texture::bind() const +{ + glBindTexture(GL_TEXTURE_2D, id_); +} diff --git a/src/texture.h b/src/texture.h new file mode 100644 index 0000000..e291dfd --- /dev/null +++ b/src/texture.h @@ -0,0 +1,35 @@ +#ifndef TEXTURE_H_ +#define TEXTURE_H_ + +#include +#include +#include + +#include "../lib/glm/glm.hpp" + +class Texture { +public: + Texture() = delete; + ~Texture(); + + static std::shared_ptr load(const char* filename); + + [[nodiscard]] uint32_t id() const + { + return id_; + } + [[nodiscard]] const glm::ivec2& size() const + { + return size_; + } + + void bind() const; + +private: + Texture(uint32_t id, const glm::ivec2& size); + + uint32_t id_; + glm::ivec2 size_; +}; + +#endif // TEXTURE_H_ diff --git a/src/vertex.h b/src/vertex.h index c6be704..eaa268a 100644 --- a/src/vertex.h +++ b/src/vertex.h @@ -6,6 +6,7 @@ struct Vertex { glm::vec3 position{0.0f, 0.0f, 0.0f}; glm::vec3 color{0.0f, 0.0f, 0.0f}; + glm::vec2 tex_coord{0.0f, 0.0f}; }; #endif // VERTEX_H_ diff --git a/ugine3d.vcxproj b/ugine3d.vcxproj index b5a60a2..b5d25bc 100644 --- a/ugine3d.vcxproj +++ b/ugine3d.vcxproj @@ -159,15 +159,18 @@ + + + @@ -179,15 +182,17 @@ + + - - + + diff --git a/ugine3d.vcxproj.filters b/ugine3d.vcxproj.filters index 9da0674..7d8a737 100644 --- a/ugine3d.vcxproj.filters +++ b/ugine3d.vcxproj.filters @@ -19,6 +19,9 @@ {296e1ba9-28bc-4e0b-8d4e-413551edca96} + + {e3c5a7f2-1d4b-4a9c-8f2e-9b5c8d7a6e4f} + {0628083b-a31c-4825-822c-11b6f933e7bd} @@ -33,6 +36,9 @@ glew + + stb + Source Files @@ -66,6 +72,12 @@ Source Files + + Source Files + + + Source Files + @@ -104,12 +116,18 @@ Source Files + + Source Files + + + Source Files + - + Shaders - + Shaders