目錄
- 普通作業:
- 提高作業:
- 參考博客
- 博客一
- 博客二
- 博客三
- 附代碼框架的個人一些注釋和理解:
- rasterizer.cpp
- Triangle.cpp
普通作業:
// 判斷點是否在三角形內的輔助函數
static bool insideTriangle(float x, float y, const Vector3f* _v)
{//Vector3f* _v這個參數是一個Vector3f指針,實際上代表的是一個數組。數組中的每個元素是一個Vector3f類型,//也就是代表了一個頂點的坐標。每個點用_v[0],_v[1],_v[2]表示。而每個_v[0]就是一個Vector3f類型的點。// //整個數組代表了多個點。這里一般認為傳入的是三個點,即一個三角形。const Eigen::Vector2f P(x, y);// 提取x和yconst Eigen::Vector2f A = _v[0].head(2), B = _v[1].head(2), C = _v[2].head(2);// 計算需求的向量const Eigen::Vector2f AP = P - A;const Eigen::Vector2f BP = P - B;const Eigen::Vector2f CP = P - C;const Eigen::Vector2f AB = B - A;const Eigen::Vector2f BC = C - B;const Eigen::Vector2f CA = A - C;//計算叉積float eq1 = AB[0] * AP[1] - AB[1] * AP[0];float eq2 = BC[0] * BP[1] - BC[1] * BP[0];float eq3 = CA[0] * CP[1] - CA[1] * CP[0];//判斷是否同向if (eq1 > 0 && eq2 > 0 && eq3 > 0)return true;else if (eq1 < 0 && eq2 < 0 && eq3 < 0)return true;elsereturn false;// TODO: 實現此函數,檢查點 (x, y) 是否在由 _v[0]、_v[1]、_v[2] 表示的三角形內
}
// 屏幕空間光柵化
void rst::rasterizer::rasterize_triangle(const Triangle& t)
{auto v = t.toVector4();int min_x = INT_MAX;int max_x = INT_MIN;int min_y = INT_MAX;int max_y = INT_MIN;//找出包圍盒的邊界for (auto point : v){if (point[0] < min_x) min_x = point[0];if (point[0] > max_x) max_x = point[0];if (point[1] < min_y) min_y = point[1];if (point[1] > max_y) max_y = point[1];}for (int y = min_y; y <= max_y; y++){for (int x = min_x; x <= max_x; x++){if (insideTriangle((float)x + 0.5, (float)y + 0.5, t.v)){// 如果是這樣,使用以下代碼獲取插值的 z 值。// auto [alpha, beta, gamma] = computeBarycentric2D(x, y, t.v); C++17以上的版本才可以用此句,否則修改成下面的倆句float alpha, beta, gamma;std::tie(alpha, beta, gamma) = computeBarycentric2D(x, y, t.v);float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();z_interpolated *= w_reciprocal;if (z_interpolated < depth_buf[get_index(x, y)]){Eigen::Vector3f p = { (float)x,(float)y, z_interpolated }; //當前坐標set_pixel(p, t.getColor());depth_buf[get_index(x, y)] = z_interpolated; //更新z值}}}}// TODO: 找出當前三角形的邊界框。// 遍歷像素并查找當前像素是否在三角形內// 如果是這樣,使用以下代碼獲取插值的 z 值。// auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);// float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());// float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();// z_interpolated *= w_reciprocal;// TODO: 設置當前像素(使用 set_pixel 函數)為三角形的顏色(使用 getColor 函數),如果應該繪制的話。
}
提高作業:
MSAA多重采樣抗鋸齒
這個算法很簡單,只要把判斷像素中心點在不在三角形內改成判斷像素塊中的四個采樣點在不在三角形內,然后按點在三角形內的比例去改變顏色就行,話不多說看代碼更清楚:
void rst::rasterizer::rasterize_triangle(const Triangle& t) {auto v = t.toVector4();int min_x = INT_MAX;int max_x = INT_MIN;int min_y = INT_MAX;int max_y = INT_MIN;for (auto point : v){if (point[0] < min_x) min_x = point[0];if (point[0] > max_x) max_x = point[0];if (point[1] < min_y) min_y = point[1];if (point[1] > max_y) max_y = point[1];}//MSAA:for (int y = min_y; y <= max_y; y++){for (int x = min_x; x <= max_x; x++){float fineness = 0; //創建一個純度值if (insideTriangle((float)x + 0.25, (float)y + 0.25, t.v)) fineness += 0.25; //改為判斷四個采樣塊的中心點在不在三角形內if (insideTriangle((float)x + 0.25, (float)y + 0.75, t.v)) fineness += 0.25;if (insideTriangle((float)x + 0.75, (float)y + 0.25, t.v)) fineness += 0.25;if (insideTriangle((float)x + 0.75, (float)y + 0.75, t.v)) fineness += 0.25;if (fineness != 0) {auto abg = computeBarycentric2D((float)x + 0.5, (float)y + 0.5, t.v);float alpha = std::get<0>(abg);float beta = std::get<1>(abg);float gamma = std::get<2>(abg);float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();z_interpolated *= w_reciprocal;if (z_interpolated < depth_buf[get_index(x, y)]){Eigen::Vector3f p = { (float)x,(float)y, z_interpolated };set_pixel(p, fineness*t.getColor()); //將比例乘給顏色depth_buf[get_index(x, y)] = z_interpolated;}}}}
}
參考博客
博客一
博客二
博客三
附代碼框架的個人一些注釋和理解:
rasterizer.cpp
// 防止代碼格式化
//
// 由 goksu 于 4/6/19 創建#include <algorithm>
#include <vector>
#include "rasterizer.h"
#include <opencv2/opencv.hpp>
#include <math.h>// 將三維向量加載到頂點緩沖區的函數
rst::pos_buf_id rst::rasterizer::load_positions(const std::vector<Eigen::Vector3f>& positions)
{auto id = get_next_id();pos_buf.emplace(id, positions);return { id };
}// 將三維向量加載到索引緩沖區的函數
rst::ind_buf_id rst::rasterizer::load_indices(const std::vector<Eigen::Vector3i>& indices)
{auto id = get_next_id();ind_buf.emplace(id, indices);return { id };
}// 將三維向量加載到顏色緩沖區的函數
rst::col_buf_id rst::rasterizer::load_colors(const std::vector<Eigen::Vector3f>& cols)
{auto id = get_next_id();col_buf.emplace(id, cols);return { id };
}// 將三維向量轉換為四維向量的輔助函數
auto to_vec4(const Eigen::Vector3f& v3, float w = 1.0f)
{return Vector4f(v3.x(), v3.y(), v3.z(), w);
}
//------------------補充——————————————————————————
// 判斷點是否在三角形內的輔助函數
static bool insideTriangle(float x, float y, const Vector3f* _v)
{//Vector3f* _v這個參數是一個Vector3f指針,實際上代表的是一個數組。數組中的每個元素是一個Vector3f類型,//也就是代表了一個頂點的坐標。每個點用_v[0],_v[1],_v[2]表示。而每個_v[0]就是一個Vector3f類型的點。// //整個數組代表了多個點。這里一般認為傳入的是三個點,即一個三角形。const Eigen::Vector2f P(x, y);// 提取x和yconst Eigen::Vector2f A = _v[0].head(2), B = _v[1].head(2), C = _v[2].head(2);// 計算需求的向量const Eigen::Vector2f AP = P - A;const Eigen::Vector2f BP = P - B;const Eigen::Vector2f CP = P - C;const Eigen::Vector2f AB = B - A;const Eigen::Vector2f BC = C - B;const Eigen::Vector2f CA = A - C;//計算叉積float eq1 = AB[0] * AP[1] - AB[1] * AP[0];float eq2 = BC[0] * BP[1] - BC[1] * BP[0];float eq3 = CA[0] * CP[1] - CA[1] * CP[0];//判斷是否同向if (eq1 > 0 && eq2 > 0 && eq3 > 0)return true;else if (eq1 < 0 && eq2 < 0 && eq3 < 0)return true;elsereturn false;// TODO: 實現此函數,檢查點 (x, y) 是否在由 _v[0]、_v[1]、_v[2] 表示的三角形內
}// 計算二維重心坐標的輔助函數
static std::tuple<float, float, float> computeBarycentric2D(float x, float y, const Vector3f* v)
{// 計算重心坐標float c1 = (x * (v[1].y() - v[2].y()) + (v[2].x() - v[1].x()) * y + v[1].x() * v[2].y() - v[2].x() * v[1].y()) /(v[0].x() * (v[1].y() - v[2].y()) + (v[2].x() - v[1].x()) * v[0].y() + v[1].x() * v[2].y() - v[2].x() * v[1].y());float c2 = (x * (v[2].y() - v[0].y()) + (v[0].x() - v[2].x()) * y + v[2].x() * v[0].y() - v[0].x() * v[2].y()) /(v[1].x() * (v[2].y() - v[0].y()) + (v[0].x() - v[2].x()) * v[1].y() + v[2].x() * v[0].y() - v[0].x() * v[2].y());float c3 = (x * (v[0].y() - v[1].y()) + (v[1].x() - v[0].x()) * y + v[0].x() * v[1].y() - v[1].x() * v[0].y()) /(v[2].x() * (v[0].y() - v[1].y()) + (v[1].x() - v[0].x()) * v[2].y() + v[0].x() * v[1].y() - v[1].x() * v[0].y());return { c1, c2, c3 };
}// 繪制函數,根據指定類型繪制三角形
void rst::rasterizer::draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, col_buf_id col_buffer, Primitive type)
{auto& buf = pos_buf[pos_buffer.pos_id];auto& ind = ind_buf[ind_buffer.ind_id];auto& col = col_buf[col_buffer.col_id];// 計算透視變換的范圍float f1 = (50 - 0.1) / 2.0;float f2 = (50 + 0.1) / 2.0;Eigen::Matrix4f mvp = projection * view * model;for (auto& i : ind){Triangle t;Eigen::Vector4f v[] = {mvp * to_vec4(buf[i[0]], 1.0f),mvp * to_vec4(buf[i[1]], 1.0f),mvp * to_vec4(buf[i[2]], 1.0f)};// 齊次除法for (auto& vec : v){vec /= vec.w();}// 視口變換for (auto& vert : v){vert.x() = 0.5 * width * (vert.x() + 1.0);vert.y() = 0.5 * height * (vert.y() + 1.0);vert.z() = vert.z() * f1 + f2;}for (int i = 0; i < 3; ++i){t.setVertex(i, v[i].head<3>());t.setVertex(i, v[i].head<3>());t.setVertex(i, v[i].head<3>());}auto col_x = col[i[0]];auto col_y = col[i[1]];auto col_z = col[i[2]];t.setColor(0, col_x[0], col_x[1], col_x[2]);t.setColor(1, col_y[0], col_y[1], col_y[2]);t.setColor(2, col_z[0], col_z[1], col_z[2]);rasterize_triangle(t);}
}//------------------補充——————————————————————————
// 屏幕空間光柵化
void rst::rasterizer::rasterize_triangle(const Triangle& t)
{auto v = t.toVector4();int min_x = INT_MAX;int max_x = INT_MIN;int min_y = INT_MAX;int max_y = INT_MIN;//找出包圍盒的邊界for (auto point : v){if (point[0] < min_x) min_x = point[0];if (point[0] > max_x) max_x = point[0];if (point[1] < min_y) min_y = point[1];if (point[1] > max_y) max_y = point[1];}for (int y = min_y; y <= max_y; y++){for (int x = min_x; x <= max_x; x++){if (insideTriangle((float)x + 0.5, (float)y + 0.5, t.v)){// 如果是這樣,使用以下代碼獲取插值的 z 值。// auto [alpha, beta, gamma] = computeBarycentric2D(x, y, t.v); C++17以上的版本才可以用此句,否則修改成下面的倆句float alpha, beta, gamma;std::tie(alpha, beta, gamma) = computeBarycentric2D(x, y, t.v);float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();z_interpolated *= w_reciprocal;if (z_interpolated < depth_buf[get_index(x, y)]){Eigen::Vector3f p = { (float)x,(float)y, z_interpolated }; //當前坐標set_pixel(p, t.getColor());depth_buf[get_index(x, y)] = z_interpolated; //更新z值}}}}// TODO: 找出當前三角形的邊界框。// 遍歷像素并查找當前像素是否在三角形內// 如果是這樣,使用以下代碼獲取插值的 z 值。// auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);// float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());// float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();// z_interpolated *= w_reciprocal;// TODO: 設置當前像素(使用 set_pixel 函數)為三角形的顏色(使用 getColor 函數),如果應該繪制的話。
}// 設置模型矩陣的函數
void rst::rasterizer::set_model(const Eigen::Matrix4f& m)
{model = m;
}// 設置視圖矩陣的函數
void rst::rasterizer::set_view(const Eigen::Matrix4f& v)
{view = v;
}// 設置投影矩陣的函數
void rst::rasterizer::set_projection(const Eigen::Matrix4f& p)
{projection = p;
}// 清除顏色和深度緩沖區的函數
void rst::rasterizer::clear(rst::Buffers buff)
{if ((buff & rst::Buffers::Color) == rst::Buffers::Color){std::fill(frame_buf.begin(), frame_buf.end(), Eigen::Vector3f{ 0, 0, 0 });}if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth){std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());}
}// 構造函數,初始化寬度和高度
rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
{frame_buf.resize(w * h);depth_buf.resize(w * h);
}// 獲取索引的函數
int rst::rasterizer::get_index(int x, int y)
{return (height - 1 - y) * width + x;
}// 設置像素顏色的函數
void rst::rasterizer::set_pixel(const Eigen::Vector3f& point, const Eigen::Vector3f& color)
{// 舊的索引:auto ind = point.y() + point.x() * width;auto ind = (height - 1 - point.y()) * width + point.x();frame_buf[ind] = color;
}
Triangle.cpp
//
// Created by LEI XU on 4/11/19.
//#include "Triangle.h"
#include <algorithm>
#include <array>// 初始化三角形的頂點坐標、顏色和紋理坐標為零向量或零值
Triangle::Triangle() {v[0] << 0, 0, 0;v[1] << 0, 0, 0;v[2] << 0, 0, 0;color[0] << 0.0, 0.0, 0.0;color[1] << 0.0, 0.0, 0.0;color[2] << 0.0, 0.0, 0.0;tex_coords[0] << 0.0, 0.0;tex_coords[1] << 0.0, 0.0;tex_coords[2] << 0.0, 0.0;
}
// 設置三角形的頂點坐標
void Triangle::setVertex(int ind, Vector3f ver) {v[ind] = ver;
}
// 設置三角形的法向量
void Triangle::setNormal(int ind, Vector3f n) {normal[ind] = n;
}
// 設置三角形的顏色
void Triangle::setColor(int ind, float r, float g, float b) {if ((r < 0.0) || (r > 255.) ||(g < 0.0) || (g > 255.) ||(b < 0.0) || (b > 255.)) {fprintf(stderr, "ERROR! Invalid color values");fflush(stderr);exit(-1);}// 將顏色值存儲為范圍在[0, 1]之間的Vector3fcolor[ind] = Vector3f((float)r / 255., (float)g / 255., (float)b / 255.);return;
}
// 設置三角形的紋理坐標
void Triangle::setTexCoord(int ind, float s, float t) {tex_coords[ind] = Vector2f(s, t);
}
// 將三角形的頂點坐標轉換為四維向量
//這里transform()是stl算法,作用是將第一個迭代器的元素做op變換之后放到第二個迭代器中,
//最后一個位置的參數代表重載() {}運算符的操作。這里的transform的意思就是將v中vec的xyz存給res
std::array<Vector4f, 3> Triangle::toVector4() const
{std::array<Eigen::Vector4f, 3> res;std::transform(std::begin(v), std::end(v), res.begin(), [](auto& vec) { return Eigen::Vector4f(vec.x(), vec.y(), vec.z(), 1.f); });return res;
}