diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 799397c..f89bc3c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,5 +4,5 @@ PROJECT(renderer) aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} CPP_FILES) add_executable(${CMAKE_PROJECT_NAME} ${CPP_FILES}) -target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${OpenCV_INCLUDE_DIRS}) +target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${OpenCV_INCLUDE_DIRS}) target_link_libraries(${CMAKE_PROJECT_NAME} LINK_PUBLIC OBJ_Loader spdlog imgui eigen ${OpenCV_LIBS}) diff --git a/src/common.h b/src/common.h index 1b43053..6bc3868 100644 --- a/src/common.h +++ b/src/common.h @@ -11,5 +11,13 @@ using Vector3d = Eigen::Vector3d; using Point2d = Eigen::Vector2d; using Point3d = Eigen::Vector3d; using BBox = cv::Rect2i; -using PixelProperty = Eigen::Vector4d; using TransformMatrix = Eigen::Matrix; + +typedef struct Vertex { + Vertex(const Point3d &p, const Vector3d &n, const Point2d &tex_coor) + : position(p), normal(n), texture_coordinate(tex_coor) {} + + Point3d position; + Vector3d normal; + Point2d texture_coordinate; +} Vertex; diff --git a/src/main.cc b/src/main.cc index e7af66b..5bfe33c 100644 --- a/src/main.cc +++ b/src/main.cc @@ -84,7 +84,7 @@ int main(int argc, char *argv[]) { // Setup Dear ImGui style ImGui::StyleColorsDark(); - // Setup Platform/Renderer backends + // Setup Platform backends ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init(glsl_version); @@ -93,7 +93,7 @@ int main(int argc, char *argv[]) { Camera camera{Vector3d{0, 1, 0}}; MinusRenderer renderer{near, far, fov, aspect_ratio}; - assert(renderer.load_mesh(obj_path)); + renderer.load_mesh(obj_path); TransformMatrix translate{ {1., 0., 0., 3.}, {0., 1., 0., 3.}, {0., 0., 1., -4.}, {0., 0., 0., 1.}}; renderer.model_transform(translate); diff --git a/src/material.cc b/src/material.cc new file mode 100644 index 0000000..c316a7e --- /dev/null +++ b/src/material.cc @@ -0,0 +1,2 @@ +// Copyright 2024 Bytedance Inc. All Rights Reserved. +// Author: tianlei.richard@qq.com (tianlei.richard) diff --git a/src/material.h b/src/material.h new file mode 100644 index 0000000..e2f14fc --- /dev/null +++ b/src/material.h @@ -0,0 +1,4 @@ +// Copyright 2024 Bytedance Inc. All Rights Reserved. +// Author: tianlei.richard@qq.com (tianlei.richard) + +#pragma once \ No newline at end of file diff --git a/src/mesh.cc b/src/mesh.cc new file mode 100644 index 0000000..73fe542 --- /dev/null +++ b/src/mesh.cc @@ -0,0 +1,51 @@ +// Copyright 2024 Bytedance Inc. All Rights Reserved. +// Author: tianlei.richard@qq.com (tianlei.richard) + +#include "mesh.h" + +#include "OBJ_Loader.h" +#include "spdlog/spdlog.h" + +#define OBJL_VEC2_TO_EIGEN_VECTOR(objl_v) \ + Eigen::Vector2d { objl_v.X, objl_v.Y } +#define OBJL_VEC3_TO_EIGEN_VECTOR(objl_v) \ + Eigen::Vector3d { objl_v.X, objl_v.Y, objl_v.Z } + +Mesh::Mesh(const std::vector> &vertices, + const std::vector &primitives) + : vertices_(vertices), primitives_(primitives) {} + +Point2d Mesh::get_texture_coordinate(const unsigned int index) const { + return (vertices_[index])->texture_coordinate; +} + +Point3d Mesh::get_normal_vector(const unsigned int index) const { + return (vertices_[index])->normal; +} + +std::vector Mesh::load_mesh(const std::string &file_path) { + objl::Loader loader{}; + assert(loader.LoadFile(file_path.c_str())); + + std::vector res; + for (const auto &mesh : loader.LoadedMeshes) { + spdlog::info("Current mesh has {} vertices.", mesh.Vertices.size()); + + std::vector> vertices; + for (int i = 0; i < mesh.Vertices.size(); i++) { + vertices.push_back(std::make_shared( + OBJL_VEC3_TO_EIGEN_VECTOR(mesh.Vertices[i].Position), + OBJL_VEC3_TO_EIGEN_VECTOR(mesh.Vertices[i].Normal), + OBJL_VEC2_TO_EIGEN_VECTOR(mesh.Vertices[i].TextureCoordinate))); + } + + std::vector primitives; + for (int i = 0; i < mesh.Indices.size(); i += 3) { + primitives.push_back(Triangle(vertices, mesh.Indices[i], + mesh.Indices[i + 1], mesh.Indices[i + 2])); + } + + res.push_back(Mesh(vertices, primitives)); + } + return res; +} diff --git a/src/mesh.h b/src/mesh.h new file mode 100644 index 0000000..e7e4e25 --- /dev/null +++ b/src/mesh.h @@ -0,0 +1,27 @@ +// Copyright 2024 Bytedance Inc. All Rights Reserved. +// Author: tianlei.richard@qq.com (tianlei.richard) + +#pragma once + +#include +#include + +#include "common.h" +#include "triangle.h" + +class Mesh { +public: + Mesh(const std::vector> &vertices, + const std::vector &primitives); + + static std::vector load_mesh(const std::string &file_path); + +public: + Point2d get_texture_coordinate(const unsigned int index) const; + Point3d get_normal_vector(const unsigned int index) const; + std::vector &get_primitives() { return primitives_; }; + +private: + std::vector> vertices_; + std::vector primitives_; +}; diff --git a/src/minus_renderer.cc b/src/minus_renderer.cc index 604cec7..cb8edf5 100644 --- a/src/minus_renderer.cc +++ b/src/minus_renderer.cc @@ -3,7 +3,6 @@ #include -#include "OBJ_Loader.h" #include "spdlog/spdlog.h" #include "minus_renderer.h" @@ -12,7 +11,7 @@ MinusRenderer::MinusRenderer(float near, float far, float fov, float aspect_ratio) : near_(near), far_(far), fov_(fov), aspect_ratio_(aspect_ratio), - projection_matrix_(orthographic_tranform() * squish_tranform()) { + projection_matrix_(orthographic_transform() * squish_transform()) { spdlog::info("near: {}, far: {}, fov: {}, aspect ratio: {}", near_, far_, fov_, aspect_ratio_); } @@ -25,7 +24,7 @@ float MinusRenderer::calculate_width(const float height, const float ratio) { return height * ratio; } -TransformMatrix MinusRenderer::squish_tranform() { +TransformMatrix MinusRenderer::squish_transform() { // Frustum to Cuboid return TransformMatrix{{near_, 0, 0, 0}, {0, near_, 0, 0}, @@ -33,7 +32,7 @@ TransformMatrix MinusRenderer::squish_tranform() { {0, 0, 1, 0}}; } -TransformMatrix MinusRenderer::orthographic_tranform() { +TransformMatrix MinusRenderer::orthographic_transform() { const float height = calculate_height(fov_, near_); const float width = calculate_width(height, aspect_ratio_); const float right = width * 0.5; @@ -63,18 +62,34 @@ TransformMatrix MinusRenderer::view_port_transform(const float width, {0, 0, 0, 1}}; } +Point3d MinusRenderer::calculate_barycentric_coordinate(const Triangle &t, + const Point2d &p) { + const auto &points = t.get_vertex_position(); + const auto &A = (points[0]).head(2); + const auto &B = (points[1]).head(2); + const auto &C = (points[2]).head(2); + double alpha = + ((p.x() - B.x()) * (C.y() - B.y()) + (p.y() - B.y()) * (C.x() - B.x())) / + (-(A.x() - B.x()) * (C.y() - B.y()) + (A.y() - B.y()) * (C.x() - B.x())); + double beta = + (-(p.x() - C.x()) * (A.y() - C.y()) + (p.y() - C.y()) * (A.x() - C.x())) / + (-(B.x() - C.x()) * (A.y() - C.y()) + (B.y() - C.y()) * (A.x() - C.x())); + double gamma = 1. - alpha - beta; + return Point3d{alpha, beta, gamma}; +} + void MinusRenderer::model_transform(const TransformMatrix &mtx) { for (auto &m : meshes_) { - for (auto &p : m) { - p.affine_transform(mtx); + for (auto &t : m.get_primitives()) { + t.set_points(apply_transform(mtx, t.get_vertex_position())); } } } void MinusRenderer::view_transform(const TransformMatrix &mtx) { for (auto &m : meshes_) { - for (auto &p : m) { - p.affine_transform(mtx); + for (auto &t : m.get_primitives()) { + t.set_points(apply_transform(mtx, t.get_vertex_position())); } } } @@ -83,64 +98,51 @@ cv::Mat MinusRenderer::render(const int resolution_width, const int resolution_height) { std::vector meshes = meshes_; Rasterizer rasterizer{resolution_width, resolution_height}; - for (auto &primitives : meshes) { + for (auto &m : meshes) { + auto &primitives = m.get_primitives(); for (int i = 0; i < primitives.size(); ++i) { auto &t = primitives[i]; - t.projective_transform(projection_matrix_); - t.affine_transform( - view_port_transform(resolution_width, resolution_height)); + const auto &triangle_vertices = t.get_vertex_position(); + auto tmp = apply_transform(projection_matrix_, triangle_vertices); + + const auto &triangle_indices = t.get_vertex_index(); + std::vector triangle_uv; + for (int j = 0; j < triangle_indices.size(); ++j) { + const auto &uv = m.get_texture_coordinate(triangle_indices[j]); + triangle_uv.push_back(Point3d{uv.x(), uv.y(), 1}); + } + apply_transform(projection_matrix_, triangle_uv); + + t.set_points(apply_transform( + view_port_transform(resolution_width, resolution_height), tmp)); } rasterizer.rasterize(primitives); } - const auto &pixels = rasterizer.get_picture(); - assert(!pixels.empty()); - cv::Mat color_image(pixels.size(), (pixels[0]).size(), CV_8UC3); - cv::Mat depth_image(pixels.size(), (pixels[0]).size(), CV_8UC1); + const auto &shading_points = rasterizer.get_picture(); + assert(!shading_points.empty()); + cv::Mat depth_image(shading_points.size(), (shading_points[0]).size(), + CV_8UC1); - for (int i = 0; i < pixels.size(); ++i) { - const auto &row = pixels[i]; + for (int i = 0; i < shading_points.size(); ++i) { + const auto &row = shading_points[i]; for (int j = 0; j < row.size(); ++j) { - auto &pixel_color = color_image.at(pixels.size() - i - 1, j); - pixel_color = cv::Vec3b{ - static_cast(row[j].x() * - std::numeric_limits::max()), - static_cast(row[j].y() * - std::numeric_limits::max()), - static_cast( - row[j].z() * std::numeric_limits::max())}; auto &pixel_depth = - depth_image.at(pixels.size() - i - 1, j); - if (std::isinf(std::fabs(row[j].w()))) { + depth_image.at(shading_points.size() - i - 1, j); + if (std::isinf(std::fabs(row[j].depth))) { pixel_depth = 0; } else { const float k = static_cast(std::numeric_limits::max()) * 0.5; const float b = k; - pixel_depth = static_cast(k * row[j].w() + b); + pixel_depth = static_cast(k * row[j].depth + b); } } } return depth_image; } -bool MinusRenderer::load_mesh(const std::string &file_path) { - objl::Loader loader{}; - bool load_status = loader.LoadFile(file_path.c_str()); - if (load_status) { - for (const auto &mesh : loader.LoadedMeshes) { - spdlog::info("Current mesh has {} vertices.", mesh.Vertices.size()); - Mesh m{}; - for (int i = 0; i < mesh.Vertices.size(); i += 3) { - const auto objl_v0 = mesh.Vertices[mesh.Indices[i]].Position; - Point3d v0(objl_v0.X, objl_v0.Y, objl_v0.Z); - const auto objl_v1 = mesh.Vertices[mesh.Indices[i + 1]].Position; - Point3d v1(objl_v1.X, objl_v1.Y, objl_v1.Z); - const auto objl_v2 = mesh.Vertices[mesh.Indices[i + 2]].Position; - Point3d v2(objl_v2.X, objl_v2.Y, objl_v2.Z); - m.push_back(Triangle(v0, v1, v2)); - } - meshes_.push_back(m); - } - } - return load_status; +void MinusRenderer::load_mesh(const std::string &file_path) { + meshes_ = Mesh::load_mesh(file_path); + spdlog::info("Mesh size: {}, the primitives size of the first mesh: {}", + meshes_.size(), meshes_.front().get_primitives().size()); } diff --git a/src/minus_renderer.h b/src/minus_renderer.h index bb0ff90..e600a73 100644 --- a/src/minus_renderer.h +++ b/src/minus_renderer.h @@ -3,35 +3,33 @@ #pragma once -#include "triangle.h" +#include "mesh.h" +#include "util/math_util.h" #include class MinusRenderer { public: MinusRenderer(float near, float far, float fov, float aspect_ratio); + void load_mesh(const std::string &file_path); public: cv::Mat render(const int resolution_width, const int resolution_height); -public: - bool load_mesh(const std::string &file_path); - public: void model_transform(const TransformMatrix &mtx); void view_transform(const TransformMatrix &mtx); -private: - using Mesh = std::vector; - private: static float calculate_height(const float fov, const float near); static float calculate_width(const float height, const float ratio); static TransformMatrix view_port_transform(const float width, const float height); + static Point3d calculate_barycentric_coordinate(const Triangle &t, + const Point2d &p); private: - TransformMatrix squish_tranform(); - TransformMatrix orthographic_tranform(); + TransformMatrix squish_transform(); + TransformMatrix orthographic_transform(); private: float near_; diff --git a/src/rasterizer.cc b/src/rasterizer.cc index 7fa582c..b6dad1e 100644 --- a/src/rasterizer.cc +++ b/src/rasterizer.cc @@ -10,74 +10,67 @@ Rasterizer::Rasterizer(const int width, const int height) : width_(width), height_(height), - pixels_(std::vector>( - height, std::vector( - width, PixelProperty{ - 0., 0., 0., - -std::numeric_limits::infinity()}))) {} + shading_points_(std::vector>( + height, std::vector(width, RasterizerResult{}))) {} -std::vector> +std::vector> Rasterizer::rasterize(const std::vector &primitives) { - for (const auto &t : primitives) { - const auto &triangle_points = t.get_points<3>(); + auto partial_rasterize = [&primitives, this](const int begin, const int end) { + for (int idx = begin; idx < end; ++idx) { + const auto &t = primitives[idx]; + const auto &triangle_points = t.get_vertex_position(); - const auto &aabb = t.axis_align_bbox(); - const int x_min = std::min(std::max(0, aabb.x), width_); - const int x_max = std::min(std::max(0, aabb.x + aabb.width), width_); - const int y_min = std::min(std::max(0, aabb.y), height_); - const int y_max = std::min(std::max(0, aabb.y + aabb.height), height_); + const auto &aabb = t.axis_align_bbox(); + const int x_min = std::min(std::max(0, aabb.x), width_); + const int x_max = std::min(std::max(0, aabb.x + aabb.width), width_); + const int y_min = std::min(std::max(0, aabb.y), height_); + const int y_max = std::min(std::max(0, aabb.y + aabb.height), height_); - auto partial_rasterize = [x_min, x_max, &t, this](const int start, - const int end) { - for (int i = start; i < end; ++i) { + for (int i = y_min; i < y_max; ++i) { for (int j = x_min; j <= x_max; ++j) { - auto &property = (this->pixels_)[i][j]; + auto &property = (this->shading_points_)[i][j]; const auto &[is_inside, z_screen] = inside(Point2d{j + 0.5, i + 0.5}, t); - if (is_inside && z_screen > property[3]) { - property[0] = 0; - property[1] = 1; - property[2] = 0; - property[3] = z_screen; + if (is_inside && z_screen > property.depth) { + property.depth = z_screen; + property.triangle_index = idx; } } } - }; - const int thread_num = 6; - std::vector rasterize_threads; - for (int start = y_min, offset = (y_max - y_min) / thread_num, - remainer = (y_max - y_min) % thread_num; - start < y_max;) { - int end = start + offset; - if (remainer > 0) { - end += 1; - remainer -= 1; - } - rasterize_threads.push_back(std::thread(partial_rasterize, start, end)); - start = end; } - for (auto &t : rasterize_threads) { - t.join(); + }; + + const int thread_num = 6; + std::vector rasterize_threads; + for (int begin = 0, offset = primitives.size() / thread_num, + remainer = (primitives.size() % thread_num); + begin < primitives.size();) { + int end = begin + offset; + if (remainer > 0) { + end += 1; + remainer -= 1; } + rasterize_threads.push_back(std::thread(partial_rasterize, begin, end)); + begin = end; } - return pixels_; + for (auto &t : rasterize_threads) { + t.join(); + } + return shading_points_; } -std::vector> Rasterizer::get_picture() const { - return pixels_; +std::vector> Rasterizer::get_picture() const { + return shading_points_; } void Rasterizer::reset() { - pixels_ = std::vector>( - height_, - std::vector( - width_, - PixelProperty{0., 0., 0., -std::numeric_limits::infinity()})); + shading_points_ = std::vector>( + height_, std::vector(width_, RasterizerResult{})); } std::pair Rasterizer::inside(const Point2d &p_screen, const Triangle &t) { - const auto points = t.get_points<3>(); + const auto points = t.get_vertex_position(); const auto plane_normal = t.normal_vector(); const auto plane_point = points[0]; diff --git a/src/rasterizer.h b/src/rasterizer.h index 36d172d..1883d20 100644 --- a/src/rasterizer.h +++ b/src/rasterizer.h @@ -7,15 +7,20 @@ #include #include +typedef struct RasterizerResult { + double depth{-std::numeric_limits::infinity()}; + int64_t triangle_index{-1}; +} RasterizerResult; + class Rasterizer { public: Rasterizer(const int width, const int height); public: - std::vector> + std::vector> rasterize(const std::vector &primitives); - std::vector> get_picture() const; + std::vector> get_picture() const; void reset(); private: @@ -25,5 +30,5 @@ private: private: int width_; int height_; - std::vector> pixels_; + std::vector> shading_points_; }; diff --git a/src/triangle.cc b/src/triangle.cc index 053be1c..7c43329 100644 --- a/src/triangle.cc +++ b/src/triangle.cc @@ -5,37 +5,48 @@ #include #include -Triangle::Triangle(const Point3d &a, const Point3d &b, const Point3d &c) - : points_({a, b, c}) {} +Triangle::Triangle(const Point3d &a, unsigned int idx_a, const Point3d &b, + unsigned int idx_b, const Point3d &c, unsigned int idx_c) + : indices_({idx_a, idx_b, idx_c}), vertices_({a, b, c}) {} -Triangle::Triangle(const HomoPointType &a, const HomoPointType &b, - const HomoPointType &c) - : points_({(a / a.z()).head(3), (b / b.z()).head(3), (c / c.z()).head(3)}) { +Triangle::Triangle(const std::vector> &vertices, + unsigned int idx_a, unsigned int idx_b, unsigned int idx_c) + : indices_({idx_a, idx_b, idx_c}), + vertices_({(vertices[idx_a])->position, (vertices[idx_b])->position, + (vertices[idx_c])->position}) {} + +std::vector Triangle::get_vertex_index() const { + return {indices_[0], indices_[1], indices_[2]}; +} + +std::vector Triangle::get_vertex_position() const { + return std::vector{ + vertices_[0], + vertices_[1], + vertices_[2], + }; +} + +void Triangle::set_points(const std::vector &points) { + vertices_[0] = points[0]; + vertices_[1] = points[1]; + vertices_[2] = points[2]; } BBox Triangle::axis_align_bbox() const { - const int x_min = std::min({points_[0].x(), points_[1].x(), points_[2].x()}); - const int y_min = std::min({points_[0].y(), points_[1].y(), points_[2].y()}); - const int x_max = std::max({points_[0].x(), points_[1].x(), points_[2].x()}); - const int y_max = std::max({points_[0].y(), points_[1].y(), points_[2].y()}); + const int x_min = + std::min({vertices_[0].x(), vertices_[1].x(), vertices_[2].x()}); + const int y_min = + std::min({vertices_[0].y(), vertices_[1].y(), vertices_[2].y()}); + const int x_max = + std::max({vertices_[0].x(), vertices_[1].x(), vertices_[2].x()}); + const int y_max = + std::max({vertices_[0].y(), vertices_[1].y(), vertices_[2].y()}); return BBox{x_min, y_min, (x_max - x_min + 1), (y_max - y_min + 1)}; } Vector3d Triangle::normal_vector() const { - const auto v1 = points_[1] - points_[0]; - const auto v2 = points_[2] - points_[1]; + const auto v1 = vertices_[1] - vertices_[0]; + const auto v2 = vertices_[2] - vertices_[1]; return v1.cross(v2).normalized(); } - -void Triangle::affine_transform(const TransformMatrix &m) { - for (auto &p : points_) { - p = (m * Eigen::Vector4d(p.x(), p.y(), p.z(), 1.)).head<3>(); - } -} - -void Triangle::projective_transform(const TransformMatrix &m) { - for (auto &p : points_) { - auto p_homo = m * Eigen::Vector4d(p.x(), p.y(), p.z(), 1.); - p = (p_homo / p_homo.w()).head<3>(); - } -} diff --git a/src/triangle.h b/src/triangle.h index 4863dba..b9d553a 100644 --- a/src/triangle.h +++ b/src/triangle.h @@ -4,34 +4,26 @@ #pragma once #include "common.h" -#include #include +#include class Triangle { -private: - using HomoPointType = Eigen::Vector4d; +public: + Triangle(const Point3d &a, unsigned int idx_a, const Point3d &b, + unsigned int idx_b, const Point3d &c, unsigned int idx_c); + Triangle(const std::vector> &vertices, + unsigned int idx_a, unsigned int idx_b, unsigned int idx_c); public: - Triangle(const Point3d &a, const Point3d &b, const Point3d &c); - Triangle(const HomoPointType &a, const HomoPointType &b, - const HomoPointType &c); - - template - std::array, 3> get_points() const { - return std::array, 3>{ - (points_[0]).head(size), - (points_[1]).head(size), - (points_[2]).head(size), - }; - } + std::vector get_vertex_index() const; + std::vector get_vertex_position() const; + void set_points(const std::vector &points); public: BBox axis_align_bbox() const; Vector3d normal_vector() const; - void affine_transform(const TransformMatrix &m); - void projective_transform(const TransformMatrix &m); - private: - std::array points_; + std::array indices_; + std::array vertices_; }; diff --git a/src/util/math_util.h b/src/util/math_util.h new file mode 100644 index 0000000..06e2756 --- /dev/null +++ b/src/util/math_util.h @@ -0,0 +1,28 @@ +// Copyright 2024 Bytedance Inc. All Rights Reserved. +// Author: tianlei.richard@bytedance.com (tianlei.richard) + +#include "common.h" +#include +#include + +template +bool fequal(const FloatType a, const FloatType b) { + return std::fabs(a - b) <= std::numeric_limits::epsilon(); +} + +template +std::vector> +apply_transform(const TransformMatrix &mtx, + const std::vector> &points) { + std::vector> res; + for (const auto &p : points) { + auto tmp_p = Eigen::Vector(); + tmp_p << p, 1.; + tmp_p = mtx * tmp_p; + if (!fequal(tmp_p.w(), 1.)) { + tmp_p /= tmp_p.w(); + } + res.push_back(tmp_p.template head()); + } + return res; +}