作業任務:
填寫並呼叫函式 rasterize_triangle(const Triangle& t)。
即實現光柵化
該函式的內部工作流程如下:
- 建立三角形的 2 維 bounding box。
- 遍歷此 bounding box 內的所有畫素(使用其整數索引)。然後,使用畫素中
心的螢幕空間座標來檢查中心點是否在三角形內。 - 如果在內部,則將其位置處的插值深度值 (interpolated depth value) 與深度
緩衝區 (depth buffer) 中的相應值進行比較。 - 如果當前點更靠近相機,請設定畫素顏色並更新深度緩衝區 (depth buffer)。
你需要修改的函式如下:
• rasterize_triangle(): 執行三角形柵格化演算法
• static bool insideTriangle(): 測試點是否在三角形內。你可以修改此函
數的定義,這意味著,你可以按照自己的方式更新返回型別或函式引數。
開始實現:
在進行光柵化的時候,我們需要判斷某個點是否在triangle內,來決定是否著色。
所以我們第一步實現
static bool insideTriangle()
x:表示觀測點的x值
y:表示觀測點的y值
_v:是一個vector<Vector3f>,內含三角形的三個點,每個點存有x、y、z值
static bool insideTriangle(double x, double y, const Vector3f* _v)
{
Eigen::Vector2f p; //檢測目標
p << x, y;
//.head(2)指這個點的前兩個數值,即x,y
Eigen::Vector2f a, b, c; //被檢測的三角形三邊向量
a = _v[0].head(2) - _v[1].head(2); //a = A - B 即B->A
b = _v[1].head(2) - _v[2].head(2); //b = B - C 即C->B
c = _v[2].head(2) - _v[0].head(2); //c = C - A 即A->C
Eigen::Vector2f AP, BP, CP;
AP = p - _v[0].head(2);
BP = p - _v[1].head(2);
CP = p - _v[2].head(2);
//由於我這裡的向量方向都是逆時針的,所以如果點p在內,那麼所有邊向量叉乘對應的XP都應該為正值,指向z的正半軸。
return AP[0] * c[1] - AP[1] * c[0] > 0 && BP[0] * a[1] - BP[1] * a[0] > 0 && CP[0] * b[1] - CP[1] * b[0] > 0;
}
現在來實現
rasterize_triangle(const Triangle& t)
分析:
-
第一步 確定bounding box
給定一個三角形,我們先要掃描出這個三角佔據了哪些格子(畫素單元),即定義bounding box,取得這個三角形的x和y軸上的邊界值,使得這個bounding box儘可能的小,但又必須包裹住這個三角形。 -
第二步 掃描bounding box
逐一掃描這個bounding box內的所有單元,以每個單元的中心位置代替此單元,將座標傳入insideTriangle
函式中,如果不在,則掃描下一個,如果在,則以三角形三點做插值得到這個中心點的z值,進一步判斷,這個z值是否比深度快取中對應位置的值要小,不小則不處理,小則替換深度快取中的值為當前值。隨後設定這個單元的顏色值。
這裡很容易就能想到,如果這個單元尺寸越小,就能更加精確的確定邊界。也就是MSAA,多重取樣抗鋸齒,這個並不是從物理上將單元切割成更小的,而是將一個單元看成多個單元組合,然後對這個更小單元進行取樣。每個單元的尺寸越小,成像的邊界鋸齒效果就越虛,即成像效果越好,但這樣會使效率降低,將一個單元切割成2x2的四個小單元,會使原來一個單位的工作量升至四個單位的工作量。
//Screen space rasterization
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
auto v = t.toVector4();//這裡是把三角形結構體換成三個頂點如(x1,y1,z1,1)
//還記得嘛?最後的1表示它是一個點,用於齊次座標
//bounding box
float min_x = std::min(v[0][0], std::min(v[1][0], v[2][0]));
float max_x = std::max(v[0][0], std::max(v[1][0], v[2][0]));
float min_y = std::min(v[0][1], std::min(v[1][1], v[2][1]));
float max_y = std::max(v[0][1], std::max(v[1][1], v[2][1]));
min_x = static_cast<int>(std::floor(min_x));
min_y = static_cast<int>(std::floor(min_y));
max_x = static_cast<int>(std::ceil(max_x));
max_y = static_cast<int>(std::ceil(max_y));
//左邊界小數部分全部直接舍,右邊界小數部分直接入,確保單元邊界座標都是整數,三角形一定在bounding box內。
bool MSAA = true;//MSAA是否啟用
if (MSAA)
{
std::vector<Eigen::Vector2f> pos
{ //對一個畫素分割四份 當然你還可以分成4x4 8x8等等甚至你還可以為了某種特殊情況設計成不規則的圖形來分割單元
{0.25,0.25}, //左下
{0.75,0.25}, //右下
{0.25,0.75}, //左上
{0.75,0.75} //右上
};
for (int i = min_x; i <= max_x; ++i)
{
for (int j = min_y; j <= max_y; ++j)
{
int count = 0;
float minDepth = FLT_MAX;
for (int MSAA_4 = 0; MSAA_4 < 4; ++MSAA_4)
{
if (insideTriangle(static_cast<float>(i+pos[MSAA_4][0]), static_cast<float>(j+pos[MSAA_4][1]),t.v))
{
auto[alpha, beta, gamma] = computeBarycentric2D(static_cast<float>(i + pos[MSAA_4][0]), static_cast<float>(j + pos[MSAA_4][1]), 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;
minDepth = std::min(minDepth, z_interpolated);
++count;
}
}
if (count)
{
if (depth_buf[get_index(i, j)] > minDepth)
{
depth_buf[get_index(i, j)] = minDepth;//更新深度
Eigen::Vector3f color = t.getColor() * (count / 4.0);//對顏色進行平均,使得邊界更平滑,也是一種模糊的手段
Eigen::Vector3f point;
point << static_cast<float>(i), static_cast<float>(j), minDepth;
set_pixel(point, color);//設定顏色
}
}
}
}
}
else
{
for (int i = min_x; i <= max_x; ++i)
{
for (int j = min_y; j <= max_y; ++j)
{
if (insideTriangle(static_cast<float>(i+0.5), static_cast<float>(j+0.5),t.v))
{
auto [alpha, beta, gamma] = computeBarycentric2D(static_cast<float>(i + 0.5), static_cast<float>(j + 0.5), 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 (depth_buf[get_index(i, j)] > z_interpolated)
{
depth_buf[get_index(i, j)] = z_interpolated;//更新深度
Eigen::Vector3f color = t.getColor();
Eigen::Vector3f point;
point << static_cast<float>(i), static_cast<float>(j), z_interpolated;
set_pixel(point, color);//設定顏色
}
}
}
}
}
}
根據三角形三點求得觀測點的z插值,此時視訊還沒有講解,是直接使用的框架中的程式碼。
MSAA = true
MSAA = false
細節對比
還是比較明顯的吧!