support texture mapping.

This commit is contained in:
tianlei.richard 2024-03-22 20:12:09 +08:00
parent 974848ad96
commit 18bb7e4cd7
21 changed files with 278 additions and 113 deletions

View File

@ -3,7 +3,7 @@ project(minus_renderer)
set(CMAKE_CXX_STANDARD 17)
find_package(OpenCV REQUIRED core)
find_package(OpenCV REQUIRED core imgproc highgui)
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)

View File

@ -1,7 +1,14 @@
cmake_minimum_required(VERSION 3.14)
PROJECT(renderer)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} CPP_FILES)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
file(GLOB_RECURSE CPP_FILES ${CMAKE_CURRENT_SOURCE_DIR} *.cc)
add_executable(${CMAKE_PROJECT_NAME} ${CPP_FILES})
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${OpenCV_INCLUDE_DIRS})

View File

@ -1,4 +1,4 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include "camera.h"

View File

@ -1,4 +1,4 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include "common.h"

View File

@ -1,4 +1,4 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#pragma once

View File

@ -1,4 +1,4 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include <opencv2/core/core.hpp>
@ -152,15 +152,15 @@ int main(int argc, char *argv[]) {
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Texture must be bound first
GLint swizzle[4] = {
GL_RED, // Shader Red channel source = Texture Red
GL_RED, // Shader Green channel source = Texture Red
GL_RED, // Shader Blue channel source = Texture Red
GL_ONE // Shader Alpha channel source = One
};
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, image.cols, image.rows, 0, GL_RED,
// // Texture must be bound first
// GLint swizzle[4] = {
// GL_RED, // Shader Red channel source = Texture Red
// GL_RED, // Shader Green channel source = Texture Red
// GL_RED, // Shader Blue channel source = Texture Red
// GL_ONE // Shader Alpha channel source = One
// };
// glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzle);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows, 0, GL_BGR,
GL_UNSIGNED_BYTE, image.data);
ImGui::Image(reinterpret_cast<void *>(static_cast<intptr_t>(texture)),
ImVec2(image.cols, image.rows));

View File

@ -1,2 +0,0 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)

View File

@ -1,4 +0,0 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#pragma once

View File

@ -1,10 +1,10 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include "mesh.h"
#include "OBJ_Loader.h"
#include "spdlog/spdlog.h"
#include <filesystem>
#define OBJL_VEC2_TO_EIGEN_VECTOR(objl_v) \
Eigen::Vector2d { objl_v.X, objl_v.Y }
@ -12,8 +12,9 @@
Eigen::Vector3d { objl_v.X, objl_v.Y, objl_v.Z }
Mesh::Mesh(const std::vector<std::shared_ptr<Vertex>> &vertices,
const std::vector<Triangle> &primitives)
: vertices_(vertices), primitives_(primitives) {}
const std::vector<Triangle> &primitives,
const std::shared_ptr<PhoneMaterial> &phone_material)
: vertices_(vertices), primitives_(primitives), material_(phone_material) {}
Point2d Mesh::get_texture_coordinate(const unsigned int index) const {
return (vertices_[index])->texture_coordinate;
@ -24,6 +25,9 @@ Point3d Mesh::get_normal_vector(const unsigned int index) const {
}
std::vector<Mesh> Mesh::load_mesh(const std::string &file_path) {
const auto &obj_path = std::filesystem::path(file_path);
const auto &material_directory = obj_path.parent_path();
objl::Loader loader{};
assert(loader.LoadFile(file_path.c_str()));
@ -32,20 +36,28 @@ std::vector<Mesh> Mesh::load_mesh(const std::string &file_path) {
spdlog::info("Current mesh has {} vertices.", mesh.Vertices.size());
std::vector<std::shared_ptr<Vertex>> vertices;
for (int i = 0; i < mesh.Vertices.size(); i++) {
for (decltype(mesh.Vertices)::size_type i = 0; i < mesh.Vertices.size();
i++) {
vertices.push_back(std::make_shared<Vertex>(
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<Triangle> primitives;
for (int i = 0; i < mesh.Indices.size(); i += 3) {
for (decltype(mesh.Indices)::size_type 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));
std::vector<std::filesystem::path> texture_path;
if (!mesh.MeshMaterial.map_Kd.empty()) {
texture_path.push_back(material_directory / mesh.MeshMaterial.map_Kd);
}
auto material =
std::shared_ptr<PhoneMaterial>(new PhoneMaterial(texture_path));
res.push_back(Mesh(vertices, primitives, material));
}
return res;
}

View File

@ -1,4 +1,4 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#pragma once
@ -7,21 +7,32 @@
#include <memory>
#include "common.h"
#include "phone_material.h"
#include "triangle.h"
class Mesh {
public:
Mesh(const std::vector<std::shared_ptr<Vertex>> &vertices,
const std::vector<Triangle> &primitives);
const std::vector<Triangle> &primitives,
const std::shared_ptr<PhoneMaterial> &phone_material);
static std::vector<Mesh> 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;
const std::vector<Triangle> &get_primitives() const { return primitives_; };
std::vector<Triangle> &get_primitives() { return primitives_; };
std::shared_ptr<PhoneMaterial> &get_material() { return material_; }
const std::shared_ptr<PhoneMaterial> &get_material() const {
return material_;
}
private:
std::vector<std::shared_ptr<Vertex>> vertices_;
std::vector<Triangle> primitives_;
std::shared_ptr<PhoneMaterial> material_;
};

View File

@ -1,9 +1,10 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include <cmath>
#include "spdlog/spdlog.h"
#include <opencv2/highgui/highgui.hpp>
#include "minus_renderer.h"
#include "rasterizer.h"
@ -62,20 +63,21 @@ TransformMatrix MinusRenderer::view_port_transform(const float width,
{0, 0, 0, 1}};
}
Point3d MinusRenderer::calculate_barycentric_coordinate(const Triangle &t,
std::tuple<double, double, double>
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())) /
(-(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};
return {alpha, beta, gamma};
}
void MinusRenderer::model_transform(const TransformMatrix &mtx) {
@ -104,41 +106,56 @@ cv::Mat MinusRenderer::render(const int resolution_width,
auto &t = primitives[i];
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<Point3d> 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 &shading_points = rasterizer.get_picture();
const auto &shading_points = rasterizer.rasterize(meshes);
assert(!shading_points.empty());
cv::Mat depth_image(shading_points.size(), (shading_points[0]).size(),
CV_8UC1);
cv::Mat color_image(shading_points.size(), (shading_points[0]).size(),
CV_8UC3);
for (int y = 0; y < shading_points.size(); ++y) {
for (int x = 0; x < shading_points[y].size(); ++x) {
auto &pixel_color =
color_image.at<cv::Vec3b>(shading_points.size() - y - 1, x);
pixel_color = cv::Vec3b(0, 0, 0);
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_depth =
depth_image.at<unsigned char>(shading_points.size() - i - 1, j);
if (std::isinf(std::fabs(row[j].depth))) {
pixel_depth = 0;
} else {
const float k =
static_cast<float>(std::numeric_limits<unsigned char>::max()) * 0.5;
const float b = k;
pixel_depth = static_cast<unsigned char>(k * row[j].depth + b);
const auto &p = shading_points[y][x];
if (p.triangle.mesh_index >= 0 && p.triangle.mesh_index < meshes.size()) {
const auto &mesh = meshes[p.triangle.mesh_index];
const auto &primitives = mesh.get_primitives();
if (p.triangle.triangle_index >= 0 &&
p.triangle.triangle_index < primitives.size()) {
const auto &triangle = primitives[p.triangle.triangle_index];
const auto &[alpha, beta, gamma] = calculate_barycentric_coordinate(
triangle, Point2d{x + 0.5, y + 0.5});
const auto &triangle_indices = triangle.get_vertex_index();
std::vector<Point3d> triangle_uv;
for (int j = 0; j < triangle_indices.size(); ++j) {
const auto &uv = mesh.get_texture_coordinate(triangle_indices[j]);
triangle_uv.push_back(Point3d{uv.x(), uv.y(), 1});
}
auto projective_uv = apply_transform(projection_matrix_, triangle_uv);
// TODO(tianlei): 推导一下为什么需要除以下面这个
auto w_reciprocal = alpha * (projective_uv[0].z()) +
beta * (projective_uv[1].z()) +
gamma * (2 * projective_uv[2].z());
Point2d pixel_uv{
(alpha * projective_uv[0].x() + beta * projective_uv[1].x() +
gamma * projective_uv[2].x()) /
w_reciprocal,
(alpha * projective_uv[0].y() + beta * projective_uv[1].y() +
gamma * projective_uv[2].y()) /
w_reciprocal};
auto &material = mesh.get_material();
pixel_color = material->sample_texture(0, pixel_uv);
}
}
}
return depth_image;
}
return color_image;
}
void MinusRenderer::load_mesh(const std::string &file_path) {

View File

@ -1,4 +1,4 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#pragma once
@ -24,8 +24,8 @@ private:
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);
static std::tuple<double, double, double>
calculate_barycentric_coordinate(const Triangle &t, const Point2d &p);
private:
TransformMatrix squish_transform();

49
src/phone_material.cc Normal file
View File

@ -0,0 +1,49 @@
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include "phone_material.h"
#include "spdlog/spdlog.h"
#include "util/interpolation.h"
#include <opencv2/highgui/highgui.hpp>
PhoneMaterial::PhoneMaterial(
const std::vector<std::filesystem::path> &texture_path) {
for (const auto &path : texture_path) {
if (!std::filesystem::exists(path)) {
spdlog::warn("Texture {} is not existed!", path.c_str());
continue;
}
const auto &img(cv::imread(path));
spdlog::debug("Texture at {}, shape: ({},{})", path.c_str(), img.cols,
img.rows);
textures_.push_back(img);
}
}
cv::Vec3b PhoneMaterial::sample_texture(const TextureId texture_id,
const Point2d &texture_coordinate) {
cv::Vec3b res{0, 0, 0};
if (texture_id >= 0 && texture_id < textures_.size()) {
const auto &texture = textures_[texture_id];
const double x = texture_coordinate.x() * texture.cols - 0.5;
const double y = texture_coordinate.y() * texture.rows - 0.5;
const int x_f = x;
const int x_c = x + 1.;
const float t_x = x - x_f;
const int y_f = y;
const int y_c = y + 1.;
const float t_y = y - y_f;
const cv::Vec3b &a = texture.at<cv::Vec3b>(x_f, y_f);
const cv::Vec3b &b = texture.at<cv::Vec3b>(x_f, y_c);
const cv::Vec3b &c = texture.at<cv::Vec3b>(x_c, y_f);
const cv::Vec3b &d = texture.at<cv::Vec3b>(x_c, y_c);
res = bilerp(t_x, t_y, a, b, c, d);
} else {
spdlog::warn("Sample texture {} at ({}, {}) failed!", texture_id,
texture_coordinate.x(), texture_coordinate.y());
}
return res;
}

27
src/phone_material.h Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#pragma once
#include <filesystem>
#include <opencv2/core/core.hpp>
#include <vector>
#include "common.h"
// Bling-Phone Matrerial
class PhoneMaterial {
public:
using TextureId = uint32_t;
PhoneMaterial(const std::vector<std::filesystem::path> &texture_path);
public:
cv::Vec3b sample_texture(const TextureId texture_id,
const Point2d &texture_coordinate);
private:
std::vector<cv::Mat> textures_;
uint32_t illumination_intensity_;
};

View File

@ -1,4 +1,4 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include <limits>
@ -14,10 +14,40 @@ Rasterizer::Rasterizer(const int width, const int height)
height, std::vector<RasterizerResult>(width, RasterizerResult{}))) {}
std::vector<std::vector<RasterizerResult>>
Rasterizer::rasterize(const std::vector<Triangle> &primitives) {
auto partial_rasterize = [&primitives, this](const int begin, const int end) {
for (int idx = begin; idx < end; ++idx) {
const auto &t = primitives[idx];
Rasterizer::rasterize(const std::vector<Mesh> &meshes) {
for (int m_id = 0; m_id < meshes.size(); ++m_id) {
const auto &primitives = (meshes[m_id]).get_primitives();
const int thread_num = 8;
std::vector<std::thread> 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(
[this, m_id](const std::vector<Triangle> &primitives, const int begin,
const int end) {
this->rasterize(m_id, primitives, begin, end);
},
(meshes[m_id]).get_primitives(), begin, end));
begin = end;
}
for (auto &t : rasterize_threads) {
t.join();
}
}
return shading_points_;
}
void Rasterizer::rasterize(const int mesh_idx,
const std::vector<Triangle> &primitives,
const int begin, const int end) {
for (int t_id = begin; t_id < end; ++t_id) {
const auto &t = primitives[t_id];
const auto &triangle_points = t.get_vertex_position();
const auto &aabb = t.axis_align_bbox();
@ -28,39 +58,17 @@ Rasterizer::rasterize(const std::vector<Triangle> &primitives) {
for (int i = y_min; i < y_max; ++i) {
for (int j = x_min; j <= x_max; ++j) {
auto &property = (this->shading_points_)[i][j];
auto &property = 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.depth) {
property.depth = z_screen;
property.triangle_index = idx;
property.triangle.mesh_index = mesh_idx;
property.triangle.triangle_index = t_id;
}
}
}
}
};
const int thread_num = 6;
std::vector<std::thread> 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;
}
for (auto &t : rasterize_threads) {
t.join();
}
return shading_points_;
}
std::vector<std::vector<RasterizerResult>> Rasterizer::get_picture() const {
return shading_points_;
}
void Rasterizer::reset() {

View File

@ -1,15 +1,19 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#pragma once
#include "mesh.h"
#include "triangle.h"
#include <opencv2/core/core.hpp>
#include <vector>
typedef struct RasterizerResult {
double depth{-std::numeric_limits<double>::infinity()};
struct {
int32_t mesh_index{-1};
int64_t triangle_index{-1};
} triangle;
} RasterizerResult;
class Rasterizer {
@ -18,15 +22,18 @@ public:
public:
std::vector<std::vector<RasterizerResult>>
rasterize(const std::vector<Triangle> &primitives);
rasterize(const std::vector<Mesh> &meshes);
std::vector<std::vector<RasterizerResult>> get_picture() const;
void reset();
private:
static std::pair<bool, double> inside(const Point2d &p_screen,
const Triangle &t);
private:
void rasterize(const int mesh_idx, const std::vector<Triangle> &primitives,
const int begin, const int end);
private:
int width_;
int height_;

View File

@ -1,4 +1,4 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include "triangle.h"

View File

@ -1,4 +1,4 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#pragma once

10
src/util/interpolation.cc Normal file
View File

@ -0,0 +1,10 @@
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include "interpolation.h"
template <>
cv::Vec3b lerp(const float t, const cv::Vec3b &a, const cv::Vec3b &b) {
return cv::Vec3b{lerp(t, a[0], b[0]), lerp(t, a[1], b[1]),
lerp(t, a[2], b[2])};
}

23
src/util/interpolation.h Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#pragma once
#include <opencv2/core/core.hpp>
template <typename T> T lerp(const float t, const T &a, const T &b) {
return t * static_cast<float>(b) + (1 - t) * static_cast<float>(a);
}
template <typename T>
T bilerp(const float t_h, const float t_v, const T &a, const T &b, const T &c,
const T &d) {
return lerp(t_v, lerp(t_h, a, b), lerp(t_h, c, d));
}
template <>
cv::Vec3b lerp(const float t, const cv::Vec3b &a, const cv::Vec3b &b);
// template <>
// cv::Vec3b bilerp(const float t_h, const float t_v, const cv::Vec3b &a,
// const cv::Vec3b &b, const cv::Vec3b &c, const cv::Vec3b &d);

View File

@ -1,5 +1,5 @@
// Copyright 2024 Bytedance Inc. All Rights Reserved.
// Author: tianlei.richard@bytedance.com (tianlei.richard)
// Copyright 2024 SquareBlock Inc. All Rights Reserved.
// Author: tianlei.richard@qq.com (tianlei.richard)
#include "common.h"
#include <cmath>