回顧games101中的SSAA和MSAA


回顧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,不需要擴展額外的深度緩存空間,但是效果不是特別好,可能需要后續的其他改進方法吧

參考博文

antialiasing 抗鋸齒

深入剖析MSAA


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM