
记录一下作业2的实现
我感觉这次作业的主要用到的知识点有2个:
用向量叉乘判断点与三角形的位置关系
super-sampling 的反走样算法

作业2 基本要求
第一次实现的时候出现了黑边问题,其中的黑边其实是很浅的绿色,出现的原因是我没有保留每个子像素的全部信息(深度和颜色信息)而只是算出一个每个像素的系数直接乘到颜色上,所以到后面蓝色黑边处时,由于区分不出子像素的深度和颜色,蓝色三角形的这部分子像素就被判断为被前面的浅绿色挡住了,所以本来应该混合进去的浅蓝色就被丢弃了。将每个子像素的深度信息和色彩信息保留并将颜色合并就可以解决这个问题。
需要注意的是,只保留子像素的深度信息是不够的,如果不保留子像素的颜色信息的话,在更新蓝色三角形时,黑边处的像素只有一个被系数乘后的绿色像素,这时候就无法正确混合蓝色和绿色。

MSAA 带黑边错误

正确的 MSAA

效果对比图
本文主要目的是个人记录,也希望尽量能为大家提供一些参考,小白一枚,水平有限,错误的地方希望大家交流指正!
话不多说,附上代码
main.cpp
// clang-format off
#include <iostream>
#include <opencv2/opencv.hpp>
#include "rasterizer.hpp"
#include "global.hpp"
#include "Triangle.hpp"
constexpr double MY_PI = 3.1415926;
inline double DEG2RAD(double deg) { return deg * MY_PI / 180; }
Eigen::Matrix4f get_view_matrix(Eigen::Vector3f eye_pos)
{
Eigen::Matrix4f view = Eigen::Matrix4f::Identity();
Eigen::Matrix4f translate;
translate << 1, 0, 0, -eye_pos[0],
0, 1, 0, -eye_pos[1],
0, 0, 1, -eye_pos[2],
0, 0, 0, 1;
view = translate * view;
return view;
}
Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
// TODO: Implement this function
// Create the model matrix for rotating the triangle around the Z axis.
Eigen::Matrix4f trans;
float radian = DEG2RAD(rotation_angle);
trans << std::cos(radian), -std::sin(radian), 0, 0,
std::sin(radian), std::cos(radian), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
// Then return it.
model = trans * model;
return model;
}
Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
float zNear, float zFar)
{
// Students will implement this function
Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
// TODO: Implement this function
// Create the projection matrix for the given parameters.
// Then return it.
float eye_fov_radian = DEG2RAD(eye_fov);
float t = -std::tan(eye_fov_radian / 2) * std::abs(zNear);
float r = t * aspect_ratio;
float l = -r;
float b = -t;
projection << 2 * zNear / (r - l), 0, (l + r) / (l - r), 0,
0, 2 * zNear / (t - b), (b + t) / (b - t), 0,
0, 0, (zNear + zFar) / (zNear - zFar), -2 * zNear * zFar / (zNear - zFar),
0, 0, 1, 0;
return projection;
}
Eigen::Matrix4f get_rotation(Vector3f axis, float angle) {
// Use Rodrigues rotation formula
float radian = DEG2RAD(angle);
Eigen::Matrix4f model = Eigen::Matrix4f::Identity();
Eigen::Matrix3f I = Eigen::Matrix3f::Identity();
Eigen::Matrix3f M;
Eigen::Matrix3f Rk;
Rk << 0, -axis[2], axis[1],
axis[2], 0, -axis[0],
-axis[1], axis[0], 0;
M = I + (1 - cos(radian)) * Rk * Rk + sin(radian) * Rk;
model << M(0, 0), M(0, 1), M(0, 2), 0,
M(1, 0), M(1, 1), M(1, 2), 0,
M(2, 0), M(2, 1), M(2, 2), 0,
0, 0, 0, 1;
return model;
}
int main(int argc, const char** argv)
{
float angle = 0;
Vector3f axis = { 0.0, 0.0, 1.0 };
bool command_line = false;
std::string filename = "output.png";
if (argc == 2)
{
command_line = true;
filename = std::string(argv[1]);
}
rst::rasterizer r(700, 700);
Eigen::Vector3f eye_pos = { 0,0,5 };
std::vector<Eigen::Vector3f> pos
{
{2, 0, -2},
{0, 2, -2},
{-2, 0, -2},
{3.5, -1, -5},
{2.5, 1.5, -5},
{-1, 0.5, -5}
};
std::vector<Eigen::Vector3i> ind
{
{0, 1, 2},
{3, 4, 5}
};
std::vector<Eigen::Vector3f> cols
{
{217.0, 238.0, 185.0},
{217.0, 238.0, 185.0},
{217.0, 238.0, 185.0},
{185.0, 217.0, 238.0},
{185.0, 217.0, 238.0},
{185.0, 217.0, 238.0}
};
auto pos_id = r.load_positions(pos);
auto ind_id = r.load_indices(ind);
auto col_id = r.load_colors(cols);
int key = 0;
int frame_count = 0;
if (command_line)
{
r.clear(rst::Buffers::Color | rst::Buffers::Depth);
r.set_model(get_model_matrix(angle));
r.set_view(get_view_matrix(eye_pos));
r.set_projection(get_projection_matrix(45, 1, 0.1, 50));
r.draw(pos_id, ind_id, col_id, rst::Primitive::Triangle);
cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
image.convertTo(image, CV_8UC3, 1.0f);
cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
cv::imwrite(filename, image);
return 0;
}
while (key != 27)
{
r.clear(rst::Buffers::Color | rst::Buffers::Depth);
//r.set_model(get_model_matrix(angle));
r.set_model(get_rotation(axis, angle));
r.set_view(get_view_matrix(eye_pos));
r.set_projection(get_projection_matrix(45, 1, 0.1, 50));
r.draw(pos_id, ind_id, col_id, rst::Primitive::Triangle);
cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
image.convertTo(image, CV_8UC3, 1.0f);
cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
cv::imshow("image", image);
key = cv::waitKey(10);
std::cout << "frame count: " << frame_count++ << '\n';
if (key == 'a') {
angle += 15;
}
else if (key == 'd') {
angle -= 15;
}
else if (key == 'x') {
axis = { 1.0, 0.0, 0.0 };
}
else if (key == 'y') {
axis = { 0.0, 1.0, 0.0 };
}
else if (key == 'z') {
axis = { 0.0, 0.0, 1.0 };
}
}
return 0;
}
// clang-format on rasterizer.cpp
// clang-format off
//
// Created by goksu on 4/6/19.
//
#include <algorithm>
#include <vector>
#include "rasterizer.hpp"
#include <opencv2/opencv.hpp>
#include <math.h>
#include<numeric>
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)
{
// TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]
//Vector3f P(x + 0.5f, y + 0.5f, 1.0f);
Vector3f P(x, y, 1.0f);
const Vector3f& A = _v[0];
const Vector3f& B = _v[1];
const Vector3f& C = _v[2];
Vector3f AB = B - A;
Vector3f BC = C - B;
Vector3f CA = A - C;
Vector3f AP = P - A;
Vector3f BP = P - B;
Vector3f CP = P - C;
float z1 = AB.cross(AP).z();
float z2 = BC.cross(BP).z();
float z3 = CA.cross(CP).z();
return (z1 > 0 && z2 > 0 && z3 > 0) || (z1 < 0 && z2 < 0 && z3 < 0);
}
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)
};
//Homogeneous division
for (auto& vec : v) {
vec /= vec.w();
}
//Viewport transformation
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);
}
}
//Screen space rasterization
//void rst::rasterizer::rasterize_triangle(const Triangle& t) {
// auto v = t.toVector4();
//
// // TODO : Find out the bounding box of current triangle.
// float minx = std::min({ v[0].x(), v[1].x(), v[2].x() });
// float miny = std::min({ v[0].y(), v[1].y(), v[2].y() });
// float maxx = std::max({ v[0].x(), v[1].x(), v[2].x() });
// float maxy = std::max({ v[0].y(), v[1].y(), v[2].y() });
// // iterate through the pixel and find if the current pixel is inside the triangle
//
// for (int x = (int)minx; x < maxx; x++)
// {
// for (int y = (int)miny; y < maxy; y++)
// {
//
// if (!insideTriangle(x, y, t.v)) continue;
// // If so, use the following code to get the interpolated z value.
// 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;
//
// int buf_index = get_index(x, y);
//
// if (z_interpolated >= depth_buf[buf_index]) continue;
//
// depth_buf[buf_index] = z_interpolated;
//
// // TODO : set the current pixel (use the set_pixel function) to the color of the triangle (use getColor function) if it should be painted.
// set_pixel(Vector3f(x, y, 1), t.getColor());
// }
// }
//}
//Screen space rasterization
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
// with msaa
auto v = t.toVector4();
// TODO : Find out the bounding box of current triangle.
float minx = std::min({ v[0].x(), v[1].x(), v[2].x() });
float miny = std::min({ v[0].y(), v[1].y(), v[2].y() });
float maxx = std::max({ v[0].x(), v[1].x(), v[2].x() });
float maxy = std::max({ v[0].y(), v[1].y(), v[2].y() });
// iterate through the pixel and find if the current pixel is inside the triangle
for (int x = (int)minx; x < maxx; x++)
{
for (int y = (int)miny; y < maxy; y++)
{
bool isSetPixel = false;
for (float i = 0.; i < 1.; i += 1. / msaa_w) {
for (float j = 0.; j < 1; j += 1. / msaa_h) {
Vector3f subP(x + i, y + j, 0);
if (!insideTriangle(subP.x(), subP.y(), t.v)) {
continue;
}
// If so, use the following code to get the interpolated z value.
auto [alpha, beta, gamma] = computeBarycentric2D(subP.x(), subP.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;
int msaa_index = get_msaa_index(x + i, y + j);
if (z_interpolated >= depth_buf[msaa_index]) {
continue;
}
depth_buf[msaa_index] = z_interpolated;
color_buf[msaa_index] = t.getColor();
isSetPixel = true;
}
}
if (!isSetPixel){
continue;
}
int buf_index = get_index(x, y);
Vector3f comb_color = Vector3f::Zero();
for (float i = 0.; i < 1.; i += 1. / msaa_w) {
for (float j = 0.; j < 1; j += 1. / msaa_h) {
int msaa_index = get_msaa_index(x + i, y + j);
comb_color += (1. / (msaa_w * msaa_h)) * color_buf[msaa_index];
}
}
Vector3f cur_point = Vector3f(x, y, 0);
set_pixel(cur_point, comb_color);
}
}
}
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 });
std::fill(color_buf.begin(), color_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);
depth_buf.resize(w * h * msaa_w * msaa_h);
color_buf.resize(w * h * msaa_w * msaa_h);
}
int rst::rasterizer::get_index(int x, int y)
{
return (height - 1 - y) * width + x;
}
int rst::rasterizer::get_msaa_index(float x, float y)
{
return (height * msaa_h - 1 - y * msaa_h) * width * msaa_w + x * msaa_w;
}
void rst::rasterizer::set_pixel(const Eigen::Vector3f& point, const Eigen::Vector3f& color)
{
//old index: auto ind = point.y() + point.x() * width;
//auto ind = (height - 1 - point.y()) * width + point.x();
auto ind = get_index(point.x(), point.y());
frame_buf[ind] = color;
}
// clang-format on rasterizer.hpp
#pragma once
//
// Created by goksu on 4/6/19.
//
#pragma once
#include <eigen3/Eigen/Eigen>
#include <algorithm>
#include "global.hpp"
#include "Triangle.hpp"
using namespace Eigen;
namespace rst
{
enum class Buffers
{
Color = 1,
Depth = 2
};
inline Buffers operator|(Buffers a, Buffers b)
{
return Buffers((int)a | (int)b);
}
inline Buffers operator&(Buffers a, Buffers b)
{
return Buffers((int)a & (int)b);
}
enum class Primitive
{
Line,
Triangle
};
/*
* For the curious : The draw function takes two buffer id's as its arguments. These two structs
* make sure that if you mix up with their orders, the compiler won't compile it.
* Aka : Type safety
* */
struct pos_buf_id
{
int pos_id = 0;
};
struct ind_buf_id
{
int ind_id = 0;
};
struct col_buf_id
{
int col_id = 0;
};
class rasterizer
{
public:
rasterizer(int w, int h);
pos_buf_id load_positions(const std::vector<Eigen::Vector3f>& positions);
ind_buf_id load_indices(const std::vector<Eigen::Vector3i>& indices);
col_buf_id load_colors(const std::vector<Eigen::Vector3f>& colors);
void set_model(const Eigen::Matrix4f& m);
void set_view(const Eigen::Matrix4f& v);
void set_projection(const Eigen::Matrix4f& p);
void set_pixel(const Eigen::Vector3f& point, const Eigen::Vector3f& color);
void clear(Buffers buff);
void draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, col_buf_id col_buffer, Primitive type);
std::vector<Eigen::Vector3f>& frame_buffer() { return frame_buf; }
std::vector<int> msaaCalcSubPoints(int x, int y, const Vector3f* _v);
private:
void draw_line(Eigen::Vector3f begin, Eigen::Vector3f end);
void rasterize_triangle(const Triangle& t);
// VERTEX SHADER -> MVP -> Clipping -> /.W -> VIEWPORT -> DRAWLINE/DRAWTRI -> FRAGSHADER
private:
Eigen::Matrix4f model;
Eigen::Matrix4f view;
Eigen::Matrix4f projection;
std::map<int, std::vector<Eigen::Vector3f>> pos_buf;
std::map<int, std::vector<Eigen::Vector3i>> ind_buf;
std::map<int, std::vector<Eigen::Vector3f>> col_buf;
std::vector<Eigen::Vector3f> frame_buf;
std::vector<Eigen::Vector3f> color_buf;
std::vector<float> depth_buf;
int get_index(int x, int y);
int get_msaa_index(float x, float y);
int width, height;
int next_id = 0;
int get_next_id() { return next_id++; }
const int msaa_w = 2;
const int msaa_h = 2;
std::vector<float> msaa_depth_buf;
};
}
参考材料:
https://github.com/Quanwei1992/GAMES101