Skip to content
Go back

Jimenez's MLAA & SMAA: Subpixel Morphological Anti-Aliasing

· Updated:

slides

拙訳

重要な機能(Key Features)

  • 高品質
    • 16階調(または、それ以上!)
    • 耐ノイズ性noise proof時間的に安定
    • 鮮鋭さsharpnessの保存
  • 高速
    • 720pで0.28ms (GeForce GTX 470)
    • MSAAより約1180%速いbeats MSAA by about a 1180% (GeForce 9800 GTX+)
  • 低メモリフットプリント
    • バックバッファサイズの2倍
  • ポータブル
  • カスタマイズ可能なエッジ検出

テクニックの開発中に、我々はいくつかのトレードオフを決めておかなければならなかった。

まずはじめに、できる限りの最高品質を達成することに最も重点を置いている。

  • オブジェクトの輪郭においてグラデーションを得る。通常、これは16xMSAAによって生み出されるものを凌ぐ。
  • 最新バージョンでは、更に時間的に安定であり、より良いノイズ管理を持つ。
  • 本当に必要な所に触れるのみなので、画像に対してまったく保守的である。これは、鮮鋭さをより上手に保存するようになる。

高品質の次に、我々はできる限りの最高のパフォーマンスを達成しようとした。

ミッドレンジからハイエンドの範囲のGPUで極めて高速である。これはGeForce GTX 470上で0.28msで動作し、我々の古いテストマシンではMSAAより12倍速い。

また、低メモリフットプリントを持ち、容易に持ち運びできる。

Alexanderがすでに言及しているように、エッジ検出ステップは色、深度、インスタンスID、法線、プリミティブID、または、いずれかの組み合わせを用いるようにカスタマイズできる。

これは特定のシナリオに対して最適な手法を選択できるようにする。

我々は、Tobiasにより後で説明されるKillzone 3で使われた手法が最良のエッジ検出アプローチのひとつとなり得るかもしれないと思っている。

重要なアイデア(Key Ideas)

  • 単純なテクスチャを使ってMLAAを解釈translateする。
  • 事前計算したテクスチャを使う。
    • 動的分岐を回避する。
    • オンザフライで面積areaを計算するのを避ける。
  • 限界までバイリニアフィルタリングを活用する。
  • ピクセル間で計算を共有する。(ピクセルはエッジを共有する!)
  • ステンシルバッファを用いることによるマスク処理

我々のテクニックの重要な高レベルのアイデアから始めたいと思う。

このために、この退屈なスライドについては忘れて、オリジナルのCPUベースのアプローチが行っていること、そして、各要素をよりGPUフレンドリーな形式に置き換える方法から始めよう。

MLAAの高レベルアルゴリズム(MLAA High-Level Algorithm)

では、この画像をアンチエイリアスしたいとしよう。

そのためにはこの青い線を理解しなければならない。これは、そのピクセルパターンの再ベクトル化を表している。

この再ベクトル化を用いることで、各ピクセルで反対の色を用いて線の下の領域を埋める。

つまり、左は黒で埋められ、右は白で埋められる。

そして、これはこう解釈される…

…灰色の階調として。これは、実際の形状を近似している。

それでは、話を戻そう。

第1ステップはエッジのある場所を検出することである。これは、緑でマークした線である。

そして、線の左終端と右終端を探さなければならない。

そして、線の各側で交差するエッジを得る。

距離と交差するエッジが使えるなら、再ベクトル化した線の下の面積を計算するための情報としては十分である。

簡単でしょう?

問題(Problems)

  • 終端の検索は低速である。
  • 交差するエッジのフェッチはコストが高い。

ただし、その美しさは問題なしにはやってこない。

検索や交差するエッジのためにエッジをフェッチすることは、大量のメモリ帯域幅を必要とするので、低速である。

  • 再ベクトル化は分岐が多いbranchy
    • 2^4 = 16通り!
  • 面積計算は安価ではない。

交差するエッジが使えても、可能性のあるパターン数が多ければ、再ベクトル化は自明ではない。

  • ピクセルあたり最大4つの線!

さらには、これらの計算をピクセルあたり最大4回、境界あたり1回繰り返さなければならない。これは非常に大きなパフォーマンス的なペナルティーを引き起こす。

では、我々はどのようにしてMLAAの品質を落とさずにこれらの問題を解決できるだろうか?

解決法(Solutions)

  • 終端の検索は低速である。
  • 交差するエッジのフェッチはコストが高い。
  • 解決法: ポストプロセッシングアンチエイリアシングにバイリニアフィルタリングを導入する。
    • これは一回のアクセスで複数の値をフェッチすることができる!

MLAAの最も高価な要素である、線の終端の検索とと交差するエッジのフェッチを改善するため、バイリニアフィルタリングを導入してポストプロセッシングアンチエイリアシングを高速化する。

これは一回のアクセスで複数の値をフェッチすることができる。

  • 再ベクトル化の計算は簡単でも高速でもない。
  • 正確な面積計算は安価ではない。
  • 解決法: 事前計算したテクスチャを用いることで分岐の多いコードを回避する。

その後、再ベクトル化と面積計算を容易にするため、距離と交差するエッジを入力に線の下の面積を出力するテクスチャを作った。

これは分岐の多いコードを16通りの識別に、面積計算を単一のテクスチャアクセスに変換する。

  • 最大4つの線がひとつのピクセルを通過する可能性がある。
  • 解決法: つまらない指摘stupid observationで恐縮だが、ピクセルはエッジを共有するのだから、計算を繰り返さなくて良いんだ!

確かに4つの線はひとつのピクセルを通る可能性があるが、一方で、これらはその近傍と共有される。

なので、4つの線を探す代わりに、上と左の線だけを探す。

そして、対応する面積を計算し、それらを一時バッファに格納する。

これは、もうひとつのパスを導入することを代償にして、この情報をその近傍と共有することを可能にする。

新たな問題!(New Problem!)

  • 今や3つのフルスクリーンバスを必要とする。
  • 解決法: ステンシルバッファを使う!

しかし、いま、3つのフルスクリーンパスを必要とする、という新たな問題が生まれた。

しかしながら、その解決法は簡単である。

ステンシルバッファを用いて第1パスで処理が必要なピクセルをマスクする。

ワークフロー(Workflow)

では、高レベルのアイデアは終わりにして、我々の実装の詳細に入っていこう。

ここには、我々のテクニックの全体像big pictureがある。

これは3パスから構成される。

第1パスでは、エッジ検出を行い、エッジテクスチャを得る。

第2パスでは、各エッジを処理し、再ベクトル化を計算し、対応する面積を得る。

第3およぴ最終パスでは、第2パスからの面積を用いて4近傍で各ピクセルをブレンドする。

エッジ検出ステップをスキップして、第2パスに話を進めようと思う。そこには、より興味深い実装がある。

エッジ検出 第1パス(Edge Detection 1st Pass)

  • : (ITU-R勧告 BT.709)
    • Y=0.2126R+0.7152G+0.0722BY' = 0.2126 \cdot R' + 0.7152 \cdot G' + 0.0722 \cdot B'
  • 深度: より安価でより明確なエッジを持つが、すべてのオブジェクト尺度では動作しない可能性がある。
  • インスタンスID/深度+法線: この情報が使える場合に最適。

では、第1パスから始めよう。

エッジ検出は最終画像のクオリティに対して決定的criticalな工程てある。

各未検出エッジは最終画像でエイリアスしたままになるので、すべての知覚できるエッジを検出することは重要である。

良好なエッジ検出が時間的な安定性を高めるとするなら、このステップでのロバスト性もまた望ましい。

複数の選択肢がある。どれが最適かは特定のシナリオに強く依存するだろう。

色は、常に利用可能であるため、最も普遍的でより簡単な解決法と見なされる可能性がある。

色による処理はシェーディングエイリアシングをシームレスにハンドリングする方法を追加で提供する。これは、いくつかのシナリオでクオリティを改善するかもしれない。

欠点として、モデルに現れるテキストやその他の高周波な特徴で若干のブラーをもたらすかもしれない。

深度、法線、または、オブジェクトIDも使うことができる。

これは、幾何的なエッジに対してより良い推定器であるためであり、

画像の最大鮮鋭度を維持することができる。

Peteが後に示すように、すべてのオブジェクトの尺度を適切に管理することが本当に難しいので、深度のみを使うことはトリッキーである。

深度とインスタンスIDを法線に組み合わせることは、一般にとても良い結果をもたらす。

これは、それらが本当に明確で完璧なエッジを生み出すためであり、

画像鮮鋭度のほとんどを保存するために管理する。

しかし、エッジ検出パスは、追加の処理が必要になるとすれば、更に高価になる。

ときどき、これらもまた色では起こらないアーティファクトを引き起こすが、これの説明はこのトークの範疇ではない。

  • 色バージョン
float4 ColorEdgeDetectionPS(float4 position : SV_POSITION, float2 texcoord : TEXCOORD0) : SV_TARGET {
    float3 weights = float3(0.2126, 0.7152, 0.0722);

    // 輝度計算はガンマ補正済みの色を必要とするので、'colorTex'はsRGBでないテクスチャとすべき。
    float L = dot(colorTex.SampleLevel(PointSampler, texcoord, 0).rgb, weights);
    float Lleft   = dot(colorTex.SampleLevel(PointSampler, texcoord, 0, -int2(1, 0)).rgb, weights);
    float Ltop    = dot(colorTex.SampleLevel(PointSampler, texcoord, 0, -int2(0, 1)).rgb, weights);
    float Lright  = dot(colorTex.SampleLevel(PointSampler, texcoord, 0,  int2(1, 0)).rgb, weights);
    float Lbottom = dot(colorTex.SampleLevel(PointSampler, texcoord, 0,  int2(0, 1)).rgb, weights);

    float4 delta = abs(L.xxxx - float4(Lleft, Ltop, Lright, Lbottom));
    float4 edges = step(threshold.xxxx, delta);

    if (dot(edges, 1.0) == 0.0)
        discard;

    return edges;
}

ここでは、色の入力データを用いる、エッジ検出の最も単純な形式を確認できる。

これは5回のメモリアクセスと多少の算術命令を用いている。

Gather4が使えるプラットフォームでは…

float4 ColorEdgeDetectionPS(float4 position : SV_POSITION, float2 texcoord : TEXCOORD0) : SV_TARGET {
    float3 weights = float3(0.2126, 0.7152, 0.0722);

    // 輝度計算はガンマ補正済みの色を必要とするので、'colorTex'はsRGBでないテクスチャとすべき。
    float topLeft     = lumaTex.Gather(LinearSampler, texcoord + PIXEL_SIZE * float2(-0.5, -0.5), 0);
    float bottomRight = lumaTex.Gather(LinearSampler, texcoord + PIXEL_SIZE * float2( 0.5,  0.5), 0);
    float L = topLeft.g;
    float Lleft = topLeft.r;
    float Ltop = topLeft.b;
    float Lright = bottomRight.b;
    float Lbottom = bottomRight.r;

    float4 delta = abs(L.xxxx - float4(Lleft, Ltop, Lright, Lbottom));
    float4 edges = step(threshold.xxxx, delta);

    if (dot(edges, 1.0) == 0.0)
        discard;

    return edges;
}

…輝度が事前計算されるとすれば、アクセス数を3回に減らし、すべての内積を取り除くことができる。

ブレンディング重み計算 第2パス(Blending Weights Calculation 2nd Pass)

  • 3つの部分で構成される。
    • 現在の線の終端への距離dleftd_{left}drightd_{right}を探す。
    • 交差するエッジelefte_{left}erighte_{right}をフェッチする。
    • ddeeを用いて、このピクセルのカパレッジaaを計算する。

この第2パスでは、再ベクトル化した線の下の面積を計算したい。

このパスでは、線の終端を検索し、交差するエッジをフェッチし、この情報を使ってこのピクセルのカバレッジ面積を計算する必要がある。

距離の検索(Searching for Distances)

  • バイリニアフィルタリングを活用することで行われる。

終端の検索はメモリ負荷の高いmemory-intensiveタスクである。

帯域幅の使用率を改善するため、バイリニアフィルタリングがほとんどのプラットフォームでタダであるという事実を活用する。

この画像には、エッジバッファの値を表す点の色とともに、青色でマークしたエッジがある。

では、ここからスタートして、2ピクセルずつ、ちょうどこれらの間をジャンプしていきます。

このひし形rhombusは最初のフェッチを表す。

両ピクセルにエッジがあるので、バイリニアフィルタリングは1を返す。

ここも同じ…

しかし、このフェッチでは、エッジの片方が有効でないことを意味する0.5を得るので、検索が終了した。

この方法でバイリニアフィルタリングを用いることにより、検索を2倍に高速化することができ、パフォーマンスを低下させずにとても長い距離の検索に到達することが可能になる。

  • 検索のコード例
float SearchXLeft(float2 texcoord) {
    texcoord -= float2(1.5, 0.0) * PIXEL_SIZE;
    float e = 0.0;

    // エッジ間をサンプルするために0.5だけオフセットする。すなわち、横一列の2つをフェッチする。
    for (int i = 0; i < maxSearchSteps; i++) {
        e = edgesTex.SampleLevel(LinearSampler, texcoord, 0).g;

        // バイリニアのアクセス精度の問題を軽減するために0.9と比較する。
        [flatten] if (e < 0.9) break;
        texcoord -= float2(2.0, 0.0) * PIXEL_SIZE;
    }

    // 終端を見つけられずにループを脱出した場合、-2 * maxSearchStepsを返したい。
    return max(-2.0 * i - 2.0 * e, -2.0 * maxSearchSteps);
}

ここには、左への検索を扱う単純なコードがある。

どのように一度に2つのピクセルを飛び越えるか、

そして、どのようにフェッチが1でない値を返すときにストップするか、を確認できる。

交差するエッジのフェッチ(Fetching crossing edges)

線の終端への距離が分かったら、それを使って交差するエッジを得る。

交差するエッジをフェッチするナイーブなアプローチは4つのエッジのクエリをおそらく暗に示すだろう。

  • 再び、バイリニアフィルタリングを利用することで行われる。

代わりに、より効率的なアプローチは、距離の検索と同様に、一度に両方のエッジをフェッチするのにバイリニアフィルタリングを使うことである。

だが、少し問題がある。

戻り値が0.5のとき、この場合を扱うのか、または、もう一方の場合を扱うのか?

  • 解決法: 座標をオフセットする!

その解決法はクエリを0.25だけオフセットすることである。これは、それぞれの場合で戻り値が異なるので、これらを区別することが可能となる。

カバレッジの計算(Calculating the coverage)

  • 以下を回避するために事前計算したテクスチャを使う。
    • 動的分岐
    • 高価な面積計算

カバレッジ計算での重要な貢献は、16の異なるパターンを扱う代わりに、分岐コードを回避する4Dテクスチャにこれらを事前計算することである。

このテクスキャへのアクセスは次のようになる。

まず、パターンのタイプまたはブロックが選択される…

…交差するエッジ情報を用いることで。

そして、線の終端への距離を用いることで適切な面積が選択される。

なぜ事前計算した面積テクスチャが2チャンネルを持つか、を考えているかもしれない。

答えはかなり簡単で、2チャンネルあると、ブレンド方向の曖昧さをなくすことが可能になるためである。

赤の値は下のピクセルを上のものとブレンドすることを意味する。

一方、緑の値はその逆である。

面積テクスチャの利点(Area Texture Advantages)

  • 対称的なパターンの扱い
  • 非対称な再ベクトル化 → パターンの微調整

事前計算した面積も用いることには複数の利点がある。

そこには、すべてのパターンが同じ方法で扱われる、すなわち、単純なテクスチャアクセスであるという事実を含む。

一方で、テクスチャそれ自体をカスタマイズでき、ある種のパターンを微調整することができる。

これらの再ベクトル化は通常アーティファクトをもたらすので、これらのパターンをフィルタリングするのを回避する。

単純なZパターンとして扱うことで、その他の再ベクトル化もカスタマイズした。

この微調整は、必要に応じて高品質なアンチエイリアシングを提供しつつ、できる限り綺麗なままの画像を維持するのに役立つ。

ブレンディング重み計算 第2パス(Blending Weights Calculation 2nd Pass)

  • シェーダコード(I)
float4 BlendingWeightCalculationPS(float4 position : SV_POSITION, float2 texcoord : TEXCOORD0) : SV_TARGET {
    float4 weights = 0.0;

    float2 e = edgesTex.SmapleLevel(PointSampler, texcoord, 0).rg;

    [branch]
    if (e.g) {  // 北のエッジ
        // 左と右への距離を検索する。
        float2 d = float2(SearchXLeft(texcoord), SearchXRight(texcoord));

        // 交差するエッジをフェッチする。edgel^《edge + pixel?》の間をサンプルする代わりに、どの値がそれぞれのedgelを持つかを見分けられるように、-0.25の地点をサンプルする。
        float4 coords = mad(float4(d.x, -0.25, d.y + 1.0, -0.25), PIXEL_SIZE.xyxy, texcoord.xyxy);
        float e1 = edgesTex.SampleLevel(LinearSampler, coords.xy, 0).r;
        float e2 = edgesTex.SampleLevel(LinearSampler, coords.zw, 0).r;

        // このパターンがどう見えているかが分かったので、今や実際の面積を得る時である。
        weights.rg = Area(abs(d), e1, e2);
    }

ここには、パス全体のコードがある。

まず、線の終端を検索して、

そして、交差するエッジをフェッチして、

最後に、カバレッジ面積を求める。

  • シェーダコード(II)
    [branch]
    if (e.r) {  // 西のエッジ
        // 上と下への距離を検索する。
        float2 d = float2(SearchYUp(texcoord), SearchYDown(texcoord));

        // (またまた)交差するエッジをフェッチする。
        float4 coords = mad(float4(-0.25, d.x, -0.25, d.y + 1.0), PIXEL_SIZE.xyxy, texcoord.xyxy);
        float e1 = edgesTex.SampleLevel(LinearSampler, coords.xy, 0).g;
        float e2 = edgesTex.SampleLevel(LinearSampler, coords.zw, 0).g;

        // この方向の面積を得る。
        weights.ba = Area(abs(d), e1, e2);
    }

    return wieghts;
}

これは垂直の場合のコードを示しており、ご覧の通り、かなり似通っている。

近傍ブレンディング 第3パス(Neighborhood Blending 3rd Pass)

  • 前のパスで計算した面積を用いて近傍をブレンドする。
cnew=(1a)cold+acoppc_{new} = (1 - a) \cdot c_{old} + a \cdot c_{opp}

最後に、前フレームで計算した面積を用いてエッジにあるピクセルをブレンドしたい。

例えば、ピクセルcoldc_{old}の場合では、下のエッジからの面積を取得した。

必要とされるブレンディングは1Dバイリニアフィルタリングに似ている。

  • バイリニアフィルタリングを活用する(またもや)。

バイリニアフィルタリングを再び活用する。これを使ってブレンディング式を実装する。

  • シェーダコード(I)
float4 NeighborhoodBlendingPS(float4 position : SV_POSITION, float2 texcoord : TEXCOORD0) : SV_TARGET {
    // 現在のピクセルのブレンディング重みをフェッチする。
    float4 topLeft = blendTex.SampleLevel(PointSampler, texcoord, 0);
    float bottom = blendTex.SampleLevel(PointSampler, texcoord, 0, int2(0, 1)).g;
    float right = blendTex.SampleLevel(PointSampler, texcoord, 0, int2(1, 0)).a;
    float4 a = float4(topLeft.r, bottom, topLeft.b, right);

    // 最大4つの線がピクセルと交差できる(各エッジにつき1つ)。各線の重みがaの3乗となる重み付き平均を計算する。これはブレンディングに有利に働き、実践でうまく動作する。
    float4 w = a * a * a;
}

ここでは、4つの可能性のある線の面積をフェッチする方法が確認できる。

  • シェーダコード(II)
    // 0.0より大きい値を持つブレンディング重みがあるか?
    float sum = dot(w, 1.0);
    [branch]
    if(sum > 0.0) {
        float4 o = a * PIXEL_SIZE.yyxx;
        float4 color = 0.0;

        // このピクセルと交差する可能性がある4つの線の寄与を追加する。
        color = mad(colorTex.SampleLevel(LinearSampler, texcoord + float2( 0.0, -o.r), 0), w.r, color);
        color = mad(colorTex.SampleLevel(LinearSampler, texcoord + float2( 0.0,  o.g), 0), w.g, color);
        color = mad(colorTex.SampleLevel(LinearSampler, texcoord + float2(-o.b, 0.0), 0), w.b, color);
        color = mad(colorTex.SampleLevel(LinearSampler, texcoord + float2( o.a, 0.0), 0), w.a, color);

        // 結果の色を正規化して、終わり!
        return color / sum;
    } else {
        return colorTex.SampleLevel(LinearSampler, texcorrd, 0);
    }
}

そして、ここでは、近傍とブレンドして、ピクセルと交差する可能性がある4つの線の結果を平均化する。

sRGBと線形ブレンディング(SRGB and linear blending)

  • ブレンディングは線形空間で行われるべき。

最も正確な結果のため、このブレンディングは線形空間で行われるべきである。

ガンマと線形の間の差異が微妙だがはっきりと確認できる。

sRGBバッファとDirectX10を使うと、ブレンディングが線形空間で行われることを保証するだろう。

この結果を見るには我々のページを訪問するのをおすすめするが、とはいえ、その一部を示そうと思う。

ここには、最初のものがある。

ここでは、色エッジ検出の正確さを見て取れる。

そしてここには、Unigineによる興味深い画像がある。

見ての通り、色エッジ検出を用いるときにいくつかのテクスチャの詳細が失われる。

一方で、深度ベースのエッジ検出を用いるときは…

テクスチャの詳細は完全に保たれている。

しかし、小さな深度差depth deltasを持ついくつかの区域ではアンチエイリアスされないだろう。

両方の最適な世界を組み合わせる方法は後にTobiasがカバーするだろう。

パフォーマンス(Performance)

GeForce GTX 470において、0.28ms @ 720

Gather4を用いない場合、0.37ms @ 720

Assasin’s Creed0.3680.10
Bioshock0.3520.12
Crysis0.3480.12
Dead Space0.3120.12
Devil May Cry 40.2560.05
GTA IV0.2340.01
Modern Warfare 20.2480.02
NFS Shift0.260.04
Split / Second0.2760.04
S.T.A.L.K.E.R.0.290.04
Grand Average0.29440.08

: スクリーンキャプチャによる計測

これはすべて良く、本当に高速で、正常に動作している。そして、このテクニックはすでにいくつかのゲームで使われている。

しかし、更に良いニュースがある。我々はあるものについての技術報告書を公開したばかりである。

それは我々がサブピクセルモーフォロジカルアンチエイリアシングと呼ぶものである。

SMAA: サブピクセルモーフォロジカルアンチエイリアシング(SMAA: Subpixel Morphological Antialiasing)

数カ月前、我々は考えていた。

モーフォロジカルアンチエイリアシングは良い。

テンポラルアンチエイリアシングもまた良いものだ。

そして、マルチサンプリングはとても良い。

これらを組み合わせてひとつのテクニックにしてみたらどうだろう?

ただ、まあ…いうほど簡単ではないけど。

解決前にモーフォロジカルアンチエイリアシングを適用しようとする場合、エッジが滑らかになって検出し辛くなることを考えると、適切なグラデーションを生成できなくなるだろう。

そして、解決前に適用しようとする場合、MLAA以上の改善を見せるが、ブラーが強すぎて依然としてあまり良くない。

詳細は省かせていただくが、我々は、サブピクセル位置を合わせるように再ベクトル化をオフセットすることで、さらに良い結果を何とかして得ている。

非常に網羅的な詳細のために技術文書をダウンロードすることをおすすめする。

  • より良い代替品fallback: すべてが処理される。
    • 低コントラストの区域はマルチ/スーパーサンプリングでアンチエイリアシングされる。

3つの要素を合成すると、それらのひとつが失敗したとき、他の2つは予備として役立つ。

この例では、通常モーフォロジカルなアプローチによって無視される低コントラスト区域が空間的なマルチサンプリング要素によってどのように扱われるかを確認できる。

  • パターンの扱いを改善する。
    • 対角線

より良い対角線ハンドリングも達成する。

  • パターンの扱いを改善する。
    • 鮮鋭なジオメトリ特徴の検出(テキストで有用!)

それと、鮮鋭なジオメトリ特徴も達成する。これは、MLAAによって導入される一般的な丸くなる現象roundnessを回避することを可能にする。

たった2つの追加のメモリアクセスのコストで、より良いテキストハンドリングが可能になる。

  • 正確な検索

我々の単純化した検索スキームは、とても高速である一方、ディザリングの形でアーティファクトをときどき引き起こす。

これは、ピクセル間の中間でサンプルすることで最終段が曖昧になる、という事実によるものである。

X方向とY方向に異なるオフセットでサンプルすることで、追加のオーバーヘッドを受けずに適切な瞬間にストップする。

  • 近傍の輝度を計算に入れる。

導入したもうひとつの機能は我々が局所的コントラスト認識local contrast awarenessと呼ぶものである。

MLAAアプローチの大きな欠点はエッジをオンかオフの二値と通常見なすことである。

エッジの実際の強さが重要であるので、これは問題になることが時々ある。

例えば、この画像では、このオブジェクトの輪郭にグラデーションが確認できる…

…望ましいところよりも前で止めてしまう交差するエッジがあるので、これはMLAAの検索スキームを混乱させる。

しかし、白の背景とのコントラストはとてもとても大きいので、我々は知覚的にこれらの交差するエッジを無視する。

だから、我々が行ったことは、我々のビジュアルシステムを真似して、近傍に関して低コントラストを持つエッジを無視することである。

中身(What’s under the hood)

  • 高品質
    • 16階調(または、それ以上!)
    • 耐ノイズ性 → 時間的に安定
    • 鮮鋭さの保存
  • 中身
    • 高速で正確な距離検索
    • 局所的コントラスト認識
    • 固有のパターン調整
    • ピクセルと交差する可能性のある4つの線を計算して、賢く平均する。

我々のテクニックの重要な要素は、どれがピクセルに対する最適なパターンの再ベクトル化かを決定し、それを継続して維持するための経験則であると思う。

SMAA: サブピクセルモーフォロジカルアンチエイリアシング(SMAA: Subpixel Morphological Antialiasing)

  • モジュール式
    • SMAA 1x 改善したパターンハンドリング
    • SMAA T2x 1x+時間的スーパーサンプリング
    • SMAA S2x 1x+空間的マルチサンプリング
    • SMAA 4x 1x+2x時間的スーパーサンプリング+2x空間的マルチサンプリング
  • SMAA 4xのパフォーマンス(GTX 580上で)
    • 1.06ms @ 1080p
    • 0.5ms @ 720p 両方とも2倍のレンダリングオーバーヘッドを計算に入れていない。

このテクニックはモジュール式であり、利用可能な予算に合わせて容易に、選択的に機能をオンオフできる。

実際に、改善のほとんどは現在のMLAAシェーダに数行加えるだけである。

SMAAへのアップグレードはかなり簡単な仕事になるだろう。

そして最後に、高品質プロファイルのパフォーマンスは720pバッファに対して1msで動作する。