普通作业:
// 判断点是否在三角形内的辅助函数
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和y
const 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;
else
return 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和y
const 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;
else
return 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]之间的Vector3f
color[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;
}