回顧games101中的AA(抗鋸齒)
前言
善於進行課後總結,可以更加鞏固自己的知識和具體細節
鋸齒(走樣)產生的原因
本質上,在光柵化階段中,用有限離散的資料想表示連續的(類似三角形的某一邊),就可能存在取樣點不夠的問題,也就引申出了鋸齒(走樣 Aliasing)的這個概念,在訊號處理以及相關領域中,走樣(混疊)在對不同的訊號進行取樣時,導致得出的訊號相同的現象。它也可以指訊號從取樣點重新訊號導致的跟原始訊號不匹配的瑕疵
具體到實時渲染領域中,走樣有以下三種:[3]
- 幾何體走樣(幾何物體的邊緣有鋸齒),幾何走樣由於對幾何邊緣取樣不足導致。

- 著色走樣,由於對著色器中著色公式(渲染方程)取樣不足導致。比較明顯的現象就是高光閃爍。


上面一張圖顯示了由於對使用了高頻法線貼圖的高頻高光BRDF取樣不足時產生的著色走樣。下面這張圖顯示了使用4倍超取樣產生的效果。
- 時間走樣,主要是對高速運動的物體取樣不足導致。比如遊戲中播放的動畫發生跳變等。
SSAA(超取樣反走樣)
產生鋸齒的原因本質上是因為取樣點個數不夠,少了,那我給你多一倍的取樣點不就可以彌補了嗎,比如一張800x600解析度的圖,我先長寬都加倍取樣,變成1600x1200,那我在把它縮放回800x600不就可以了嗎
過程:
對每個畫素取n個子取樣點,然後針對每個子畫素點進行著色計算。最後根據每個子畫素的值來合成最終的影象
MSAA(多重取樣反走樣)
在前面提到的SSAA中,每個子取樣點都要進行單獨的著色,這樣在片斷(畫素)著色器比較複雜的情況下還是很費的。那麼能不能只計算每個畫素的顏色,而對於那些子取樣點只計算一個覆蓋資訊(coverage)和遮擋資訊(occlusion)來把畫素的顏色資訊寫到每個子取樣點裡面呢?最終根據子取樣點裡面的顏色值來通過某個重建過濾器來降取樣生成目標影象。這就是MSAA的原理。注意這裡有一個很重要的點,就是每個子畫素都有自己的顏色、深度模板資訊,並且每一個子取樣點都是需要經過深度和模板測試才能決定最終是不是把畫素的顏色得到到這個子取樣點所在的位置,而不是簡單的作一個覆蓋測試就寫入顏色
程式碼實現
沒有SSAA和MSAA之前,可以看到邊緣鋸齒化特別明顯

SSAA



#define ssaa_sample 2
float sampling_period = 1.0f / ssaa_sample;
// 2x2SSAA
for (int x = xmin; x <= xmax; x++) {
for (int y = ymin; y <= ymax; y++) {
int in_num = 0;
Eigen::Vector3f color_sum;
for (int i = 0; i < ssaa_sample; ++i) {
for (int j = 0; j < ssaa_sample; ++j) {
// 中心點
float new_x = x + (i + 0.5) * sampling_period;
float new_y = y + (j + 0.5) * sampling_period;
if (insideTriangle(new_x, new_y, t.v)) {
auto [alpha, beta, gamma] = computeBarycentric2D(new_x, new_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 depth_buf_x, depth_buf_y;
depth_buf_x = x * ssaa_sample + i;
depth_buf_y = y * ssaa_sample + j;
if (z_interpolated < depth_buf[get_index(depth_buf_x, depth_buf_y)]) {
depth_buf[get_index(depth_buf_x, depth_buf_y)] = z_interpolated;
Vector3f temp_point = { depth_buf_x * 1.0f,depth_buf_y * 1.0f,0.0f };
Vector3f color = t.getColor();
set_temp_pixel(temp_point, color);
}
}
}
}
}
}
// Down Sample
for (int x = xmin; x <= xmax; x++)
{
for (int y = ymin; y <= ymax; y++)
{
Eigen::Vector3f color{ 0,0,0 };
Eigen::Vector3f point{ x * 1.0f, y * 1.0f, 0 };
for (int i = 0; i < ssaa_sample; ++i)
{
for (int j = 0; j < ssaa_sample; ++j)
{
int depth_buf_x, depth_buf_y;
depth_buf_x = x * ssaa_sample + i;
depth_buf_y = y * ssaa_sample + j;
color += temp_frame_buf[get_index(depth_buf_x, depth_buf_y)];
}
}
color /= (ssaa_sample * ssaa_sample);
set_pixel(point, color);
}
}
通過放大對比,其實可以看到SSAA的效果比MSAA好很多,解決了出現黑邊的問題,整體也是接近於完美的效果
MSAA
以下例子為8x8的MSAA,MSAA其實就是求出一個面積的覆蓋率,然後通過覆蓋率乘以rgb,然後更新深度緩衝區和顏色緩衝區
auto v = t.toVector4();
int xmin = MIN(MIN(floor(v[0].x()), floor(v[0].x())), floor(v[2].x()));
int xmax = MAX(MAX(floor(v[0].x()), floor(v[1].x())), floor(v[2].x()));
int ymin = MIN(MIN(floor(v[0].y()), floor(v[1].y())), floor(v[2].y()));
int ymax = MAX(MAX(floor(v[0].y()), floor(v[1].y())), floor(v[2].y()));
int sample_num = 8;
std::vector<float> offset;
for (int i = 0; i < sample_num; ++i)
{
offset.push_back((0.5 + i) * 1.0 / static_cast<float>(sample_num));
}
int index;
// MSAA
for (int x = xmin; x <= xmax; x++) {
for (int y = ymin; y <= ymax; y++) {
int in_num = 0;
for (int i = 0; i < sample_num; ++i) {
for (int j = 0; j < sample_num; ++j) {
if (insideTriangle(x + offset[i], y + offset[j], t.v)) {
++in_num;
}
}
}
if (in_num > 0 &&insideTriangle(x + 0.5, y + 0.5, t.v)) {
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;
index = get_index(x, y);
if (index < frame_buf.size() && depth_buf[index] > z_interpolated) {
depth_buf[index] = z_interpolated;
Eigen::Vector3f point;
point << static_cast<float>(x), static_cast<float>(y), z_interpolated;
set_pixel(point, t.getColor() * in_num / (sample_num * sample_num));
}
}
}
}
其中會遇到幾個問題,一是三角形的邊可能會呈現黑色,這是因為當覆蓋率過低的時候,乘上rgb基於接近於0,也就是黑色,然後藍色三角形在綠色三角形的後面,計算深度的時候大於綠色三角形的深度,所以無法寫入,就會呈現黑邊的情況。


不過總體上效果也還算看得過去
SSAA和MSAA的優缺點
通過實踐中可以看出,SSAA需要額外用到擴大的緩衝空間,以及在計算所有畫素點後,還會經過downSample的過程,可以說從時間還是空間上消耗都比MSAA要大,但是他的效果也是顯著的好
MSAA效能上優於SSAA,不需要擴充套件額外的深度快取空間,但是效果不是特別好,可能需要後續的其他改進方法吧