diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ca556e..896c2c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ # It sets global settings and includes the sub-projects. # =================================================================== cmake_minimum_required(VERSION 3.22) -project(Prism VERSION 0.1.0 LANGUAGES CXX) +project(Prism VERSION 0.2.0 LANGUAGES CXX) # Set the policy for FetchContent to use modern, safer behavior. cmake_policy(SET CMP0135 NEW) @@ -21,16 +21,30 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(BUILD_SHARED_LIBS ON CACHE BOOL "Build shared libraries" FORCE) option(PRISM_BUILD_DEMO "Build the Prism demo application" ON) +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + message(STATUS "Building in Debug mode.") + set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Suffix for debug binaries" FORCE) +else() + set(CMAKE_DEBUG_POSTFIX "" CACHE STRING "Suffix for non-debug binaries" FORCE) +endif() + # Set a common output directory for all executables. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # --- Sub-projects --- add_subdirectory(src) # The Prism library -add_subdirectory(demo) # The demo application + +if (PRISM_BUILD_DEMO) + message(STATUS "Building the Prism demo application.") + add_subdirectory(demo) # The demo application +else() + message(STATUS "Skipping the Prism demo application build.") +endif() # --- Testing --- enable_testing() if(BUILD_TESTING) + message(STATUS "Building tests for the Prism library.") # Fetch GoogleTest only when tests are enabled. include(FetchContent) FetchContent_Declare( diff --git a/demo/data/input/scene.yml b/demo/data/input/scene.yml index 1572623..0d40a8c 100644 --- a/demo/data/input/scene.yml +++ b/demo/data/input/scene.yml @@ -1,93 +1,143 @@ # ----------------------------------------------------------------- -# Exemplo de Arquivo de Cena para o Renderizador Prism -# Este arquivo demonstra o uso de múltiplos objetos, materiais -# e transformações para criar uma cena complexa. +# Cena da Cornell Box Final - Com Esferas em Destaque (Avançadas) # ----------------------------------------------------------------- -# Definições reutilizáveis da cena, como materiais. -# Usamos âncoras (&) para definir um material e aliases (*) para reutilizá-lo. +ambient_light: [0.1, 0.1, 0.1] + +# --- Definições de Materiais --- definitions: materials: - # Material fosco para o chão - chao_cinza: &material_chao - color: [0.8, 0.8, 0.8] - ka: [0.1, 0.1, 0.1] # Pouca reflexão ambiente - ks: [0.1, 0.1, 0.1] # Pouco brilho especular + # Materiais das paredes + parede_branca: &branco + color: [0.73, 0.73, 0.73] + ka: [0.73, 0.73, 0.73] + ks: [0.0, 0.0, 0.0] ns: 10 - # Material de plástico vermelho, com brilho moderado - plastico_vermelho: &material_esfera_vermelha - color: [1.0, 0.2, 0.2] - ka: [0.2, 0.05, 0.05] - ks: [0.7, 0.7, 0.7] # Brilho especular forte - ns: 128 # Expoente de brilho alto para um highlight pequeno e intenso + parede_vermelha: &vermelho + color: [0.65, 0.05, 0.05] + ka: [0.65, 0.05, 0.05] + ks: [0.0, 0.0, 0.0] + ns: 10 - # Material metálico/refletivo para o cubo - metal_azul: &material_cubo_metalico - color: [0.2, 0.3, 1.0] - ka: [0.1, 0.1, 0.2] - ks: [0.9, 0.9, 0.9] # Reflexão especular muito alta - ns: 256 + parede_verde: &verde + color: [0.12, 0.45, 0.15] + ka: [0.12, 0.45, 0.15] + ks: [0.0, 0.0, 0.0] + ns: 10 + + # Materiais para os objetos internos + bloco_azul: &azul + color: [0.1, 0.2, 0.8] + ka: [0.1, 0.2, 0.8] + ks: [0.3, 0.3, 0.3] + ns: 64 - # Material de vidro para o triângulo - vidro_transparente: &material_vidro - color: [0.9, 0.9, 1.0] # Cor levemente azulada - ka: [0.1, 0.1, 0.1] - ks: [0.8, 0.8, 0.8] - ns: 200 - ni: 1.5 # Índice de refração (típico para vidro) - d: 0.1 # Opacidade (valor 'd' baixo significa mais transparente) + bloco_laranja: &laranja + color: [0.9, 0.5, 0.1] + ka: [0.9, 0.5, 0.1] + ks: [0.2, 0.2, 0.2] + ns: 32 + + esfera_espelho: &espelho + color: [0.0, 0.0, 0.0] + ka: [0.0, 0.0, 0.0] + ks: [0.98, 0.98, 0.98] + ns: 2048 -# ----------------------------------------------------------------- -# Configurações da Câmera -# ----------------------------------------------------------------- + esfera_vidro: &vidro + color: [1.0, 1.0, 1.0] + ka: [1.0, 1.0, 1.0] + ks: [0.1, 0.1, 0.1] + ns: 256 + ni: 1.52 + d: 0.05 + +# --- Configurações da Câmera --- camera: - image_width: 960 - image_height: 540 + image_width: 800 + image_height: 800 screen_distance: 1.5 viewport_width: 2.0 - viewport_height: 1.125 - lookfrom: [0, 2, 8] # Posição da câmera - lookat: [0, 0, 0] # Ponto para onde a câmera olha - vup: [0, 1, 0] # Vetor "para cima" + viewport_height: 2.0 + lookfrom: [278, 278, -800] + lookat: [278, 278, 0] + vup: [0, 1, 0] -# ----------------------------------------------------------------- -# Lista de Objetos na Cena -# ----------------------------------------------------------------- +# --- Fontes de Luz --- +lights: + - name: Luz do Teto + position: [278, 548, 278] + color: [1.0, 1.0, 1.0] + - name: Flash da Câmera + position: [278, 278, -800] + color: [0.5, 0.5, 0.5] + +# --- Objetos na Cena --- objects: + # As 5 paredes da caixa - name: Chão type: plane - point_on_plane: [0, -1, 0] # Ponto que define a altura do plano - normal: [0, 1, 0] # Vetor normal aponta para cima - material: *material_chao # Reutiliza o material do chão definido acima - - - name: Esfera Vermelha Principal - type: sphere - center: [-1.5, 0, 0] - radius: 1.0 - material: *material_esfera_vermelha # Reutiliza o material de plástico + point_on_plane: [0, 0, 0] + normal: [0, 1, 0] + material: *branco + - name: Teto + type: plane + point_on_plane: [0, 555, 0] + normal: [0, -1, 0] + material: *branco + - name: Parede do Fundo + type: plane + point_on_plane: [0, 0, 555] + normal: [0, 0, -1] + material: *branco + - name: Parede Direita (Verde) + type: plane + point_on_plane: [555, 0, 0] + normal: [-1, 0, 0] + material: *verde + - name: Parede Esquerda (Vermelha) + type: plane + point_on_plane: [0, 0, 0] + normal: [1, 0, 0] + material: *vermelho - - name: Malha de Cubo Metálico + # Blocos com tamanho e posição originais + - name: Bloco Alto (Laranja) type: mesh - path: "./cubo.obj" # Caminho para o arquivo .obj - material: *material_cubo_metalico # Reutiliza o material metálico - # Múltiplas transformações são aplicadas em ordem + path: "cubo.obj" + material: *laranja transform: - type: scaling - factors: [0.7, 0.7, 0.7] # Primeiro, diminui a escala do cubo + factors: [82.5, 165, 82.5] - type: rotation - angle: 45 # Em graus (o parser converterá para radianos) - axis: [0, 1, 0] # Rotaciona em torno do eixo Y + angle: 18 + axis: [0, 1, 0] - type: translation - vector: [1.5, 0, -1] # Por último, move o cubo para sua posição final + vector: [180, 165, 250] - - name: Triângulo de Vidro - type: triangle - # Vértices definidos diretamente no arquivo de cena - p1: [-3, -1, -5] - p2: [3, -1, -5] - p3: [0, 4, -5] - material: *material_vidro # Reutiliza o material de vidro + - name: Bloco Baixo (Azul) + type: mesh + path: "cubo.obj" + material: *azul transform: + - type: scaling + factors: [82.5, 82.5, 82.5] + - type: rotation + angle: -20 + axis: [0, 1, 0] - type: translation - vector: [0, 0, 2] # Move o triângulo um pouco para frente \ No newline at end of file + vector: [370, 82.5, 150] + + # ESFERAS COM POSIÇÃO AJUSTADA (AINDA MAIS PERTO) + - name: Esfera de Vidro + type: sphere + center: [380, 90, -140] # Z foi de 120 para 80 + radius: 90 + material: *vidro + + - name: Esfera de Espelho + type: sphere + center: [180, 90, -200] # Z foi de 180 para 100 + radius: 90 + material: *espelho \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fba2e22..ea93b20 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,8 @@ # manages its dependencies, and sets up installation rules. # =================================================================== +message(STATUS "Configuring Prism Library (v${PROJECT_VERSION}).") + # --- Dependencies --- include(FetchContent) @@ -16,6 +18,8 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(yaml-cpp) +message(STATUS "Configuring yaml-cpp dependency: building SHARED library, tests/tools DISABLED.") + set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "Disable building yaml-cpp contrib tools" FORCE) set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "Disable building yaml-cpp parse tools" FORCE) set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "Disable building yaml-cpp tests" FORCE) @@ -31,11 +35,23 @@ option(PRISM_BUILD_CORE "Build the Core module (math, etc.)" ON) option(PRISM_BUILD_OBJECTS "Build the Objects module (Sphere, Plane, Mesh, etc.)" ON) option(PRISM_BUILD_SCENE "Build the Scene module (Scene, Camera)" ON) +# --- Dependecy Management --- + # The CORE module is essential for all other parts of the library. if(NOT PRISM_BUILD_CORE) message(FATAL_ERROR "PRISM_BUILD_CORE is a required module and cannot be disabled.") endif() +# The OBJECTS module depends on the CORE module. +if(PRISM_BUILD_OBJECTS AND NOT PRISM_BUILD_CORE) + message(FATAL_ERROR "PRISM_BUILD_OBJECTS requires PRISM_BUILD_CORE to be enabled. Please enable PRISM_BUILD_CORE.") +endif() + +# The SCENE module depends on the OBJECTS module. +if(PRISM_BUILD_SCENE AND NOT PRISM_BUILD_OBJECTS) + message(FATAL_ERROR "PRISM_BUILD_SCENE requires PRISM_BUILD_OBJECTS to be enabled. Please enable PRISM_BUILD_OBJECTS.") +endif() + # --- Build Source File Collection --- @@ -44,19 +60,26 @@ set(PRISM_SOURCES "") # Always add the core source files. if(PRISM_BUILD_CORE) + message(STATUS "Prism Library: Core module ENABLED.") file(GLOB CORE_SOURCES CONFIGURE_DEPENDS "src/core/*.cpp") list(APPEND PRISM_SOURCES ${CORE_SOURCES}) endif() # Conditionally add sources for the other modules. if(PRISM_BUILD_OBJECTS) + message(STATUS "Prism Library: Objects module ENABLED.") file(GLOB GEOMETRY_SOURCES CONFIGURE_DEPENDS "src/objects/*.cpp") list(APPEND PRISM_SOURCES ${GEOMETRY_SOURCES}) +else() + message(STATUS "Prism Library: Objects module DISABLED.") endif() if(PRISM_BUILD_SCENE) + message(STATUS "Prism Library: Scene module ENABLED.") file(GLOB SCENE_SOURCES CONFIGURE_DEPENDS "src/scene/*.cpp") list(APPEND PRISM_SOURCES ${SCENE_SOURCES}) +else() + message(STATUS "Prism Library: Scene module DISABLED.") endif() # Add any remaining top-level source files (e.g., init.cpp) diff --git a/src/include/Prism/core/color.hpp b/src/include/Prism/core/color.hpp index 72d7e21..8726ac7 100644 --- a/src/include/Prism/core/color.hpp +++ b/src/include/Prism/core/color.hpp @@ -42,6 +42,18 @@ class PRISM_EXPORT Color { */ Color(const Color& other); + Color operator* (Color other) const; + + Color operator* (double scalar) const; + + Color operator+ (Color other) const; + + Color operator+= (Color other); + + Color& clamp(); + + + double r; ///< Red component of the color (0.0 to 1.0) double g; ///< Green component of the color (0.0 to 1.0) double b; ///< Blue component of the color (0.0 to 1.0) diff --git a/src/include/Prism/core/material.hpp b/src/include/Prism/core/material.hpp index 6908211..fdb3b29 100644 --- a/src/include/Prism/core/material.hpp +++ b/src/include/Prism/core/material.hpp @@ -22,25 +22,26 @@ class PRISM_EXPORT Material { * @brief * Default constructor that initializes the material with default values. * The default values are: - * - Color: Black (0, 0, 0) - * - Ambient reflectivity (ka): (0, 0, 0) + * - Color: white (1,1,1) + * - Ambient reflectivity (ka): (0.1, 0.1, 0.1) * - Specular reflectivity (ks): (0, 0, 0) * - Emissive color (ke): (0, 0, 0) - * - Shininess (ns): 0 - * - Index of refraction (ni): 0 - * - Transparency (d): 0 + * - Shininess (ns): 1 + * - Index of refraction (ni): 1 + * - Transparency (d): 1 */ - Material(Color color = Color(), Vector3 ka = Vector3(), Vector3 ks = Vector3(), - Vector3 ke = Vector3(), double ns = 0, double ni = 0, double d = 0) + Material(Color color = Color(1, 1, 1), Color ka = Color(0.1, 0.1, 0.1), + Color ks = Color(0, 0, 0), Color ke = Color(0, 0, 0), double ns = 1.0, double ni = 1.0, + double d = 1.0) : color(color), ka(ka), ks(ks), ke(ke), ns(ns), ni(ni), d(d) { } Color color; ///< The color of the material, typically used for diffuse reflection. - Vector3 ka; ///< Ambient reflectivity of the material, representing how much ambient light it + Color ka; ///< Ambient reflectivity of the material, representing how much ambient light it ///< reflects. - Vector3 ks; ///< Specular reflectivity of the material, representing how much specular light it + Color ks; ///< Specular reflectivity of the material, representing how much specular light it ///< reflects. - Vector3 + Color ke; ///< Emissive color of the material, representing light emitted by the material itself. double ns; ///< Shininess factor of the material, affecting the size and intensity of specular ///< highlights. diff --git a/src/include/Prism/core/point.hpp b/src/include/Prism/core/point.hpp index 77e148c..cf76e7d 100644 --- a/src/include/Prism/core/point.hpp +++ b/src/include/Prism/core/point.hpp @@ -57,6 +57,12 @@ class PRISM_EXPORT Point3 { */ bool operator==(const Point3& p) const; + /** + * @brief Negates the point. + * @return A new point that is the negation of this point. + */ + Point3 operator-() const; + /** * @brief Gets a vector from this point to another point. * @param p The point to get the vector to. diff --git a/src/include/Prism/core/style.hpp b/src/include/Prism/core/style.hpp index 4b53364..e2457b1 100644 --- a/src/include/Prism/core/style.hpp +++ b/src/include/Prism/core/style.hpp @@ -36,7 +36,7 @@ const std::string BOLD_YELLOW = "\033[1;33m"; * @param message The message to display. */ inline void logInfo(const std::string& message) { - std::clog << YELLOW << "[INFO] " << RESET << message << std::endl; + std::clog << YELLOW << "[INFO] " << RESET << message << RESET << std::endl; } /** @@ -44,7 +44,7 @@ inline void logInfo(const std::string& message) { * @param message The message to display. */ inline void logDone(const std::string& message) { - std::clog << GREEN << "[DONE] " << RESET << message << std::endl; + std::clog << GREEN << "[DONE] " << RESET << message << RESET << std::endl; } /** diff --git a/src/include/Prism/core/utils.hpp b/src/include/Prism/core/utils.hpp index 53bd7f7..ea7260a 100644 --- a/src/include/Prism/core/utils.hpp +++ b/src/include/Prism/core/utils.hpp @@ -42,6 +42,30 @@ template T sqr(const T& value) { return value * value; } +/** + * @brief Computes the refracted vector based on Snell's law. + * This function calculates the refracted vector given an incident vector, a normal vector, + * and the ratio of indices of refraction. + * @param uv The incident vector (unit vector). + * @param n The normal vector at the point of incidence (unit vector). + * @param etai_over_etat The ratio of indices of refraction (etai/etat). + * @return The refracted vector as a Vector3. + */ +Vector3 PRISM_EXPORT refract(const Vector3& uv, const Vector3& n, double etai_over_etat); + +/** + * @brief Calcs the reflectance using Schlick's approximation. + * This function computes the reflectance based on the angle of incidence and the index of refraction + * using Schlick's approximation. + * @param cosine The cosine of the angle between the incident vector and the normal. + * @param ref_idx The index of refraction of the material. + * @return The reflectance value as a double, which is the probability of reflection at the + * interface. + * This value is used in ray tracing to determine how much light is reflected versus refracted. + */ +double PRISM_EXPORT schlick(double cosine, double ref_idx); + + } // namespace Prism #endif // PRISM_UTILS_HPP_ \ No newline at end of file diff --git a/src/include/Prism/core/vector.hpp b/src/include/Prism/core/vector.hpp index b88fb08..a3faad0 100644 --- a/src/include/Prism/core/vector.hpp +++ b/src/include/Prism/core/vector.hpp @@ -67,6 +67,12 @@ class PRISM_EXPORT Vector3 { */ Vector3& operator=(const Vector3& v); + /** + * @brief Negates the vector. + * @return A new vector that is the negation of this vector. + */ + Vector3 operator-() const; + /** * @brief Adds two vectors. * @param v The vector to add. diff --git a/src/include/Prism/objects/Colormap.hpp b/src/include/Prism/objects/Colormap.hpp index 2938691..1007365 100644 --- a/src/include/Prism/objects/Colormap.hpp +++ b/src/include/Prism/objects/Colormap.hpp @@ -72,19 +72,19 @@ class colormap { double ksR, ksG, ksB; iss >> ksR >> ksG >> ksB; if (!currentMaterial.empty()) { - mp[currentMaterial].ks = Vector3(ksR, ksG, ksB); + mp[currentMaterial].ks = Color(ksR, ksG, ksB); } } else if (keyword == "Ke") { double keR, keG, keB; iss >> keR >> keG >> keB; if (!currentMaterial.empty()) { - mp[currentMaterial].ke = Vector3(keR, keG, keB); + mp[currentMaterial].ke = Color(keR, keG, keB); } } else if (keyword == "Ka") { double kaR, kaG, kaB; iss >> kaR >> kaG >> kaB; if (!currentMaterial.empty()) { - mp[currentMaterial].ka = Vector3(kaR, kaG, kaB); + mp[currentMaterial].ka = Color(kaR, kaG, kaB); } } else if (keyword == "Ns") { iss >> mp[currentMaterial].ns; diff --git a/src/include/Prism/objects/ObjReader.hpp b/src/include/Prism/objects/ObjReader.hpp index 929f725..5b102b8 100644 --- a/src/include/Prism/objects/ObjReader.hpp +++ b/src/include/Prism/objects/ObjReader.hpp @@ -12,16 +12,25 @@ #include #include #include +#include namespace Prism { class ObjReader { public: + struct FaceIndices { + std::array vertex_indices; + std::array normal_indices; + }; + std::shared_ptr curMaterial; std::vector> vertices; - std::vector> triangles; - + std::vector> normals; + std::vector faces; + ObjReader(const std::string& filename) { + curMaterial = std::make_shared(); + file.open(filename); if (!file.is_open()) { Style::logError("Erro ao abrir o arquivo: " + filename); @@ -49,16 +58,23 @@ class ObjReader { double x, y, z; iss >> x >> y >> z; vertices.push_back({x, y, z}); + } else if (prefix == "vn") { + double nx, ny, nz; + iss >> nx >> ny >> nz; + normals.push_back({nx, ny, nz}); } else if (prefix == "f") { - unsigned int vi[3], ni[3]; - char slash; - int _; // for skipping texture indices + FaceIndices face; + std::string vertex_info; for (int i = 0; i < 3; ++i) { - iss >> vi[i] >> slash >> _ >> slash >> ni[i]; - --vi[i]; - --ni[i]; + iss >> vertex_info; + std::replace(vertex_info.begin(), vertex_info.end(), '/', ' '); + std::istringstream v_iss(vertex_info); + unsigned int t_idx; + v_iss >> face.vertex_indices[i] >> t_idx >> face.normal_indices[i]; + --face.vertex_indices[i]; // Convert to 0-based index + --face.normal_indices[i]; // Convert to 0-based index } - triangles.push_back({vi[0], vi[1], vi[2]}); + faces.push_back(face); } } diff --git a/src/include/Prism/objects/mesh.hpp b/src/include/Prism/objects/mesh.hpp index b400f8a..0ca1fe9 100644 --- a/src/include/Prism/objects/mesh.hpp +++ b/src/include/Prism/objects/mesh.hpp @@ -56,8 +56,11 @@ class PRISM_EXPORT Mesh : public Object { */ virtual bool hit(const Ray& ray, double t_min, double t_max, HitRecord& rec) const override; + void setMaterial(std::shared_ptr new_material); + private: std::vector> points; ///< Points that define the vertices of the mesh + std::vector> normals; ///< Normals for each vertex in the mesh std::vector mesh; ///< Triangles that make up the mesh, each defined by three points std::shared_ptr diff --git a/src/include/Prism/objects/triangle.hpp b/src/include/Prism/objects/triangle.hpp index 38ffc6b..fad0a68 100644 --- a/src/include/Prism/objects/triangle.hpp +++ b/src/include/Prism/objects/triangle.hpp @@ -92,10 +92,14 @@ class PRISM_EXPORT MeshTriangle { * @param p1 The first point of the triangle. * @param p2 The second point of the triangle. * @param p3 The third point of the triangle. + * @param n1 The normal vector at the first point. + * @param n2 The normal vector at the second point. + * @param n3 The normal vector at the third point. * This constructor initializes the MeshTriangle with the specified points. */ MeshTriangle(std::shared_ptr p1, std::shared_ptr p2, - std::shared_ptr p3); + std::shared_ptr p3, std::shared_ptr n1, + std::shared_ptr n2, std::shared_ptr n3); /** * @brief Constructs a MeshTriangle given three Point3 objects. @@ -148,6 +152,10 @@ class PRISM_EXPORT MeshTriangle { std::shared_ptr point1; ///< The first point of the triangle std::shared_ptr point2; ///< The second point of the triangle std::shared_ptr point3; ///< The third point of the triangle + + std::shared_ptr normal1; ///< The normal vector at the first point + std::shared_ptr normal2; ///< The normal vector at the second point + std::shared_ptr normal3; ///< The normal vector at the third point }; } // namespace Prism diff --git a/src/include/Prism/scene/light.hpp b/src/include/Prism/scene/light.hpp new file mode 100644 index 0000000..09a2eab --- /dev/null +++ b/src/include/Prism/scene/light.hpp @@ -0,0 +1,32 @@ +#ifndef PRISM_LIGHT_HPP_ +#define PRISM_LIGHT_HPP_ + +#include "prism_export.h" +#include "Prism/core/point.hpp" +#include "Prism/core/color.hpp" + +namespace Prism { + +/** + * @class Light + * @brief Represents a point light source in the scene. + */ +class PRISM_EXPORT Light { +public: + Point3 position; ///< The position of the light in 3D space. + Color color; ///< The color of the light, typically used to determine how it illuminates objects. + + /** + * @brief Default constructor that initializes the light with default values. + * The default values are: + * - Position: (0, 0, 0) + * - Color: White (1, 1, 1) + * - Intensity: 1.0 + */ + Light(const Point3& pos, const Color& col) + : position(pos), color(col) {} +}; + +} // namespace Prism + +#endif // PRISM_LIGHT_HPP_ \ No newline at end of file diff --git a/src/include/Prism/scene/scene.hpp b/src/include/Prism/scene/scene.hpp index 97f767d..beb8d5f 100644 --- a/src/include/Prism/scene/scene.hpp +++ b/src/include/Prism/scene/scene.hpp @@ -5,6 +5,8 @@ #include "Prism/objects/objects.hpp" #include "Prism/scene/camera.hpp" +#include "Prism/scene/light.hpp" +#include "Prism/core/color.hpp" #include #include @@ -28,7 +30,7 @@ class PRISM_EXPORT Scene { * @param camera The Camera object that defines the viewpoint and projection parameters for * rendering the scene. */ - explicit Scene(Camera camera); + explicit Scene(Camera camera, Color ambient_light = Color(0.1, 0.1, 0.1)); Scene(const Scene&) = delete; Scene& operator=(const Scene&) = delete; @@ -43,6 +45,14 @@ class PRISM_EXPORT Scene { */ void addObject(std::unique_ptr object); + /** + * @brief Adds a light source to the scene. + * @param light The Light object to be added to the scene. + * This method stores the light in the scene's collection, allowing it to be used during + * rendering for illumination calculations. + */ + void addLight(std::unique_ptr light); + /** * @brief Renders the scene from the camera's perspective. * This method iterates over all objects in the scene, checks for ray-object intersections, and @@ -53,7 +63,11 @@ class PRISM_EXPORT Scene { void render() const; private: + Color trace(const Ray& ray, int depth) const; + std::vector> objects_; ///< Collection of objects in the scene + std::vector> lights_; ///< Collection of light sources in the scene + Color ambient_color_ = Color(0.1, 0.1, 0.1); ///< Ambient color for the scene Camera camera_; ///< The camera used to view the scene }; } // namespace Prism diff --git a/src/src/core/color.cpp b/src/src/core/color.cpp index b8e69f2..76729e0 100644 --- a/src/src/core/color.cpp +++ b/src/src/core/color.cpp @@ -1,4 +1,5 @@ #include "Prism/core/color.hpp" +#include namespace Prism { @@ -13,4 +14,31 @@ Color::Color(int red, int green, int blue) Color::Color(const Color& other) : r(other.r), g(other.g), b(other.b) { } +Color Color::operator*(Color other) const { + return Color(r * other.r, g * other.g, b * other.b); +} + +Color Color::operator*(double scalar) const { + return Color(r * scalar, g * scalar, b * scalar); +} + +Color Color::operator+(Color other) const { + return Color(r + other.r, g + other.g, b + other.b); +} + +Color Color::operator+=(Color other) { + r += other.r; + g += other.g; + b += other.b; + return *this; +} + +Color& Color::clamp() { + r = std::max(0.0,std::min(r, 1.0)); + g = std::max(0.0,std::min(g, 1.0)); + b = std::max(0.0,std::min(b, 1.0)); + + return *this; +} + } // namespace Prism \ No newline at end of file diff --git a/src/src/core/point.cpp b/src/src/core/point.cpp index adf5a46..386c9e3 100644 --- a/src/src/core/point.cpp +++ b/src/src/core/point.cpp @@ -38,6 +38,10 @@ bool Point3::operator==(const Point3& p) const { return (x == p.x && y == p.y && z == p.z); } +Point3 Point3::operator-() const { + return Point3(-x, -y, -z); +} + Vector3 Point3::operator-(const Point3& p) const { return Vector3(x - p.x, y - p.y, z - p.z); } diff --git a/src/src/core/utils.cpp b/src/src/core/utils.cpp index 81c1976..90c6087 100644 --- a/src/src/core/utils.cpp +++ b/src/src/core/utils.cpp @@ -48,4 +48,22 @@ Matrix orthonormalBasisContaining(const Vector3& v) { return basis; } +Vector3 refract(const Vector3& uv, const Vector3& n, double etai_over_etat) { + auto cos_theta = std::fmin((-uv).dot(n), 1.0); + Vector3 r_out_perp = (uv + n * cos_theta) * etai_over_etat; + Vector3 r_out_parallel = n * -sqrt(fabs(1.0 - sqr(r_out_perp.magnitude()))); + + if (sqr(r_out_perp.magnitude()) > 1.0) { + return Vector3(0, 0, 0); + } + + return r_out_perp + r_out_parallel; +} + +double schlick(double cos, double ref_idx) { + auto r0 = (1.0 - ref_idx) / (1.0 + ref_idx); + r0 = r0 * r0; + return r0 + (1.0 - r0) * pow((1.0 - cos), 5); +} + } // namespace Prism \ No newline at end of file diff --git a/src/src/core/vector.cpp b/src/src/core/vector.cpp index e078ffd..0fefece 100644 --- a/src/src/core/vector.cpp +++ b/src/src/core/vector.cpp @@ -43,6 +43,10 @@ Vector3& Vector3::operator=(const Vector3& v) { return *this; } +Vector3 Vector3::operator-() const { + return Vector3(-x, -y, -z); +} + Vector3 Vector3::operator+(const Vector3& v) const { return Vector3(x + v.x, y + v.y, z + v.z); } diff --git a/src/src/objects/mesh.cpp b/src/src/objects/mesh.cpp index 9cb88ee..9ba07c4 100644 --- a/src/src/objects/mesh.cpp +++ b/src/src/objects/mesh.cpp @@ -13,8 +13,20 @@ Mesh::Mesh(std::filesystem::path& path) { for (auto& point : reader.vertices) { points.emplace_back(std::make_shared(point[0], point[1], point[2])); } - for (auto& triangle : reader.triangles) { - mesh.push_back({points[triangle[0]], points[triangle[1]], points[triangle[2]]}); + + for (auto& normal : reader.normals) { + normals.emplace_back(std::make_shared(normal[0], normal[1], normal[2])); + } + + for (auto& face : reader.faces) { + mesh.emplace_back( + points[face.vertex_indices[0]], + points[face.vertex_indices[1]], + points[face.vertex_indices[2]], + normals[face.normal_indices[0]], + normals[face.normal_indices[1]], + normals[face.normal_indices[2]] + ); } }; @@ -23,8 +35,20 @@ Mesh::Mesh(ObjReader& reader) : material(std::move(reader.curMaterial)) { for (auto& point : reader.vertices) { points.emplace_back(std::make_shared(point[0], point[1], point[2])); } - for (auto& triangle : reader.triangles) { - mesh.push_back({points[triangle[0]], points[triangle[1]], points[triangle[2]]}); + + for (auto& normal : reader.normals) { + normals.emplace_back(std::make_shared(normal[0], normal[1], normal[2])); + } + + for (auto& face : reader.faces) { + mesh.emplace_back( + points[face.vertex_indices[0]], + points[face.vertex_indices[1]], + points[face.vertex_indices[2]], + normals[face.normal_indices[0]], + normals[face.normal_indices[1]], + normals[face.normal_indices[2]] + ); } }; @@ -33,11 +57,20 @@ bool Mesh::hit(const Ray& ray, double t_min, double t_max, HitRecord& rec) const rec.t = t_max; for (const auto& triangle : mesh) { - triangle.hit(transformed_ray, 0.001, rec.t, rec); + triangle.hit(transformed_ray, t_min, rec.t, rec); } + Point3 world_p; + double world_t = INFINITY; + if (rec.t < t_max) { - rec.p = transform * transformed_ray.at(rec.t); + world_p = transform * transformed_ray.at(rec.t); + world_t = (world_p - ray.origin()).dot(ray.direction().normalize()); + } + + if (world_t < t_max) { + rec.p = world_p; + rec.t = world_t; Vector3 world_normal = (inverseTransposeTransform * rec.normal).normalize(); rec.set_face_normal(ray, world_normal); rec.material = material; @@ -47,4 +80,8 @@ bool Mesh::hit(const Ray& ray, double t_min, double t_max, HitRecord& rec) const return false; }; +void Mesh::setMaterial(std::shared_ptr new_material) { + material = std::move(new_material); +} + }; // namespace Prism diff --git a/src/src/objects/triangle.cpp b/src/src/objects/triangle.cpp index 03f8de1..450a8d7 100644 --- a/src/src/objects/triangle.cpp +++ b/src/src/objects/triangle.cpp @@ -67,8 +67,10 @@ bool Triangle::hit(const Ray& ray, double t_min, double t_max, HitRecord& rec) c } MeshTriangle::MeshTriangle(std::shared_ptr p1, std::shared_ptr p2, - std::shared_ptr p3) - : point1(std::move(p1)), point2(std::move(p2)), point3(std::move(p3)) { + std::shared_ptr p3, std::shared_ptr n1, + std::shared_ptr n2, std::shared_ptr n3) + : point1(std::move(p1)), point2(std::move(p2)), point3(std::move(p3)), + normal1(std::move(n1)), normal2(std::move(n2)), normal3(std::move(n3)) { } MeshTriangle::MeshTriangle(Point3 p1, Point3 p2, Point3 p3) @@ -124,8 +126,10 @@ bool MeshTriangle::hit(const Ray& ray, double t_min, double t_max, HitRecord& re const double t = f * (edge2 * q); if (t > t_min && t < t_max) { + + const double w = 1.0 - u - v;; rec.t = t; - rec.normal = edge1 ^ edge2; + rec.normal = ((*normal1 * w) + (*normal2 * u) + (*normal3 * v)).normalize(); return true; } return false; diff --git a/src/src/scene/camera.cpp b/src/src/scene/camera.cpp index 04bff16..0335874 100644 --- a/src/src/scene/camera.cpp +++ b/src/src/scene/camera.cpp @@ -9,15 +9,13 @@ namespace Prism { Camera::Camera(const Point3& position, const Point3& target, const Vector3& upvec, double distance, double viewport_height, double viewport_width, int image_height, int image_width) - : pos(position), aim(target), up(upvec), coordinate_basis(), screen_distance(distance), + : pos(position), aim(target), up(upvec), screen_distance(distance), screen_height(viewport_height), screen_width(viewport_width), pixel_height(image_height), pixel_width(image_width) { - coordinate_basis = orthonormalBasisContaining(pos - aim); - const auto& basis = coordinate_basis; - Vector3 w = Vector3{basis[0][0], basis[1][0], basis[2][0]}; - Vector3 u = Vector3{basis[0][1], basis[1][1], basis[2][1]}; - Vector3 v = Vector3{basis[0][2], basis[1][2], basis[2][2]}; + Vector3 w = (pos - aim).normalize(); + Vector3 u = (up.cross(w)).normalize(); + Vector3 v = w.cross(u); Point3 screen_center = pos - (w * screen_distance); Point3 top_left_corner = diff --git a/src/src/scene/scene.cpp b/src/src/scene/scene.cpp index 8e6dd2f..1a5ca95 100644 --- a/src/src/scene/scene.cpp +++ b/src/src/scene/scene.cpp @@ -3,6 +3,7 @@ #include "Prism/core/color.hpp" #include "Prism/core/material.hpp" #include "Prism/core/style.hpp" +#include "Prism/core/utils.hpp" #include #include @@ -25,13 +26,18 @@ PRISM_EXPORT std::ostream& operator<<(std::ostream& os, const Color& color) { return os; } -Scene::Scene(Camera camera) : camera_(std::move(camera)) { +Scene::Scene(Camera camera, Color ambient_light) + : camera_(std::move(camera)), ambient_color_(ambient_light) { } void Scene::addObject(std::unique_ptr object) { objects_.push_back(std::move(object)); } +void Scene::addLight(std::unique_ptr light) { + lights_.push_back(std::move(light)); +} + bool get_local_time(std::tm* tm_out, const std::time_t* time_in) { #if defined(_WIN32) || defined(_MSC_VER) // Usa a versão segura do Windows (MSVC) @@ -59,6 +65,100 @@ std::filesystem::path generate_filename() { return "render_fallback.ppm"; } +Color Scene::trace(const Ray& ray, int depth) const { + if (depth <= 0) { + return Color(0, 0, 0); // Base case for recursion, return black color + } + + HitRecord rec; + bool hit_anything = false; + double closest_t = INFINITY; + + for (const auto& object_ptr : objects_) { + HitRecord temp_rec; + if (object_ptr->hit(ray, 0.001, closest_t, temp_rec)) { + hit_anything = true; + closest_t = temp_rec.t; + rec = temp_rec; + } + } + + if (!hit_anything) { + return ambient_color_; // Return ambient color if no hit + } + + auto mat = rec.material; + + Vector3 view_dir = (ray.origin() - rec.p).normalize(); + + Color surface_color = mat->ka * ambient_color_; + + for (const auto& light_ptr : lights_) { + Vector3 light_dir = (light_ptr->position - rec.p).normalize(); + double light_distance = (light_ptr->position - rec.p).magnitude(); + + Ray shadow_ray(rec.p, light_dir); + bool in_shadow = false; + for (const auto& obj_ptr : objects_) { + HitRecord shadow_rec; + if (obj_ptr->hit(shadow_ray, 0.001, light_distance, shadow_rec)) { + in_shadow = true; + break; + } + } + + if (!in_shadow) { + double diff_factor = std::max(rec.normal.dot(light_dir), 0.0); + surface_color += mat->color * diff_factor * light_ptr->color; + + Vector3 reflect_dir = (-light_dir) - rec.normal * 2 * (-light_dir).dot(rec.normal); + double spec_factor = std::pow(std::max(view_dir.dot(reflect_dir), 0.0), mat->ns); + surface_color += mat->ks * spec_factor * light_ptr->color; + } + } + + Color final_color = mat->ke + surface_color; + + double opacity = mat->d; + if (opacity < 1.0) { + double reflectance; + double refraction_ratio = rec.front_face ? (1.0 / mat->ni) : mat->ni; + Vector3 unit_direction = ray.direction().normalize(); + + double cos_theta = fmin((-unit_direction).dot(rec.normal), 1.0); + double sin_theta = sqrt(1.0 - cos_theta * cos_theta); + + if (refraction_ratio * sin_theta > 1.0) { + reflectance = 1.0; + } else { + reflectance = schlick(cos_theta, refraction_ratio); + } + + Color reflection_color = Color(0, 0, 0); + Color refraction_color = Color(0, 0, 0); + + Vector3 reflect_dir = ray.direction() - rec.normal * 2 * ray.direction().dot(rec.normal); + Ray reflection_ray(rec.p, reflect_dir); + reflection_color = trace(reflection_ray, depth - 1); + + if (reflectance < 1.0) { + Vector3 refracted_dir = refract(unit_direction, rec.normal, refraction_ratio); + Ray refracted_ray(rec.p, refracted_dir); + refraction_color = trace(refracted_ray, depth - 1); + } + + Color trasmited_color = reflection_color * reflectance + refraction_color * (1.0 - reflectance); + final_color = final_color * opacity + trasmited_color * (1.0 - opacity); + } + else if (mat->ks.r > 0 || mat->ks.g > 0 || mat->ks.b > 0) { + Vector3 reflect_dir = ray.direction() - rec.normal * 2 * ray.direction().dot(rec.normal); + Ray reflection_ray(rec.p, reflect_dir); + final_color += mat->ks * trace(reflection_ray, depth - 1); + } + + return final_color.clamp(); +} + void Scene::render() const { std::filesystem::path output_dir = "./data/output"; std::filesystem::create_directories(output_dir); @@ -84,28 +184,7 @@ void Scene::render() const { int last_progress_percent = -1; for (Ray const& ray : camera_) { - HitRecord closest_hit_rec; - bool hit_anything = false; - double closest_t = INFINITY; - - for (const auto& object_ptr : objects_) { - HitRecord temp_rec; - if (object_ptr->hit(ray, 0.001, closest_t, temp_rec)) { - hit_anything = true; - closest_t = temp_rec.t; - closest_hit_rec = temp_rec; - } - } - - Color pixel_color; - if (hit_anything) { - pixel_color = closest_hit_rec.material->color; - } else { - Vector3 unit_direction = ray.direction().normalize(); - pixel_color = Color(0, 0, 0); - } - - image_file << pixel_color << '\n'; + image_file << trace(ray, 5) << '\n'; pixels_done++; int current_progress_percent = @@ -120,7 +199,7 @@ void Scene::render() const { image_file.close(); Style::logDone("Rendering complete."); - Style::logDone("Image saved as: " + Prism::Style::CYAN + generate_filename().string()); + Style::logDone("Image saved as: " + Prism::Style::CYAN + full_path.string()); } } // namespace Prism \ No newline at end of file diff --git a/src/src/scene/scene_parser.cpp b/src/src/scene/scene_parser.cpp index 4ee4210..9c257c6 100644 --- a/src/src/scene/scene_parser.cpp +++ b/src/src/scene/scene_parser.cpp @@ -45,6 +45,13 @@ Vector3 parseVector(const YAML::Node& node) { return Vector3(node[0].as(), node[1].as(), node[2].as()); } +Color parseColor(const YAML::Node& node) { + if (!node.IsSequence() || node.size() != 3) { + throw std::runtime_error("Parsing error: Malformed color."); + } + return Color(node[0].as(), node[1].as(), node[2].as()); +} + // Converts a YAML node with material properties to a Material std::shared_ptr parseMaterial(const YAML::Node& node) { auto mat = std::make_shared(); @@ -54,11 +61,11 @@ std::shared_ptr parseMaterial(const YAML::Node& node) { mat->color = Color(v.x, v.y, v.z); } if (node["ka"]) - mat->ka = parseVector(node["ka"]); + mat->ka = parseColor(node["ka"]); if (node["ks"]) - mat->ks = parseVector(node["ks"]); + mat->ks = parseColor(node["ks"]); if (node["ke"]) - mat->ke = parseVector(node["ke"]); + mat->ke = parseColor(node["ke"]); if (node["ns"]) mat->ns = node["ns"].as(); if (node["ni"]) @@ -125,7 +132,15 @@ Scene SceneParser::parse() { cam_node["viewport_height"].as(), cam_node["viewport_width"].as(), cam_node["image_height"].as(), cam_node["image_width"].as()); - Scene scene(std::move(camera)); + Color ambient_light(0.1, 0.1, 0.1); // Valor padrão + if (root["ambient_light"]) { + Vector3 v = parseVector(root["ambient_light"]); + ambient_light = Color(v.x, v.y, v.z); + } else { + Style::logWarning("Ambient light not defined. Using default (0.1, 0.1, 0.1)."); + } + + Scene scene(std::move(camera), ambient_light); // Parse Material Definitions (for reuse) std::map> materials; @@ -136,6 +151,18 @@ Scene SceneParser::parse() { } } + if (root["lights"] && root["lights"].IsSequence()) { + for (const auto& light_node : root["lights"]) { + Point3 pos = parsePoint(light_node["position"]); + Vector3 color_vec = parseVector(light_node["color"]); + Color color(color_vec.x, color_vec.y, color_vec.z); + scene.addLight(std::make_unique(pos, color)); + } + } + else { + Style::logWarning("'lights' node not found or is not a list. No lights will be added."); + } + // Parse Objects if (!root["objects"] || !root["objects"].IsSequence()) { throw std::runtime_error("'objects' node not found or is not a list."); @@ -180,7 +207,7 @@ Scene SceneParser::parse() { // Overrides the .obj material with the one from the .yml, if specified if (obj_node["material"]) { auto mesh_ptr = static_cast(object.get()); - // (A material setter would be needed in the Mesh class here) + mesh_ptr->setMaterial(material); } } else { Style::logWarning("Unknown object type: " + type + ". Skipping this object."); diff --git a/tests/src/CameraTest.cpp b/tests/src/CameraTest.cpp index 9b5ce5a..d91dc13 100644 --- a/tests/src/CameraTest.cpp +++ b/tests/src/CameraTest.cpp @@ -33,47 +33,6 @@ TEST(CameraTest, Instantiation) { EXPECT_DOUBLE_EQ(cam.screen_width, width); } -TEST(CameraTest, CoordinateBasisOrthonormal) { - Point3 position(1.0, 2.0, 3.0); - Point3 target(4.0, 5.0, 6.0); - Vector3 upvec(0.0, 1.0, 0.0); - double distance = 10.0, height = 5.0, width = 8.0; - - Camera cam(position, target, upvec, distance, height, width, 10, 20); - - const Matrix& basis = cam.coordinate_basis; - auto getCol = [&](int c) { return Vector3(basis[0][c], basis[1][c], basis[2][c]); }; - - Vector3 dir = (position - target).normalize(); - Vector3 b1 = getCol(0); - Vector3 b2 = getCol(1); - Vector3 b3 = getCol(2); - - AssertVectorAlmostEqual(b1, dir); - ASSERT_NEAR(b1.magnitude(), 1.0, 1e-9); - ASSERT_NEAR(b2.magnitude(), 1.0, 1e-9); - ASSERT_NEAR(b3.magnitude(), 1.0, 1e-9); - ASSERT_NEAR(b1.dot(b2), 0.0, 1e-9); - ASSERT_NEAR(b1.dot(b3), 0.0, 1e-9); - ASSERT_NEAR(b2.dot(b3), 0.0, 1e-9); -} - -TEST(CameraTest, BasisHasOpposite) { - Point3 position(1.0, 2.0, 3.0); - Point3 target(4.0, 5.0, 6.0); - Vector3 upvec(0.0, 1.0, 0.0); - double distance = 10.0, height = 5.0, width = 8.0; - - Camera cam(position, target, upvec, distance, height, width, 10, 20); - - const Matrix& basis = cam.coordinate_basis; - auto getCol = [&](int c) { return Vector3{basis[0][c], basis[1][c], basis[2][c]}; }; - - Vector3 b1 = getCol(0); - - AssertVectorAlmostEqual(b1, ((target - position) * -1).normalize()); -} - TEST(CameraTest, IteratorGeneratesCorrectNumberOfRays) { Point3 position(0, 0, 0); Point3 target(0, 0, -1);