ポイント、バイリニア、トライリニア、異方性。名前は聞くけれど、結局どれを選べばよいのか。異方性フィルタリングを16倍にすれば、いちばん綺麗になるのか。そして、フィルタリングを上げると、どのくらい重くなるのか。
この記事では、テクスチャを画面に貼るときのフィルタリング(サンプラーの設定)を、8通りの設定で並べて比べます。「見た目の印象」だけではなく、正解に近い画像との差(RMSE)と、GPUの描画時間を実測して、「どれがどれだけ良いのか、どれだけ重いのか」を数字にしました。ミップマップの作り方そのものは、別記事のミップマップとは?で扱っているので、今回は「サンプラー側でどう読むか」に絞ります。
テクスチャフィルタリングとは?
テクスチャフィルタリングとは、
・画面の1ピクセルが、テクスチャのどこをどう覗くかに応じて、テクスチャの複数の画素を混ぜて1つの色にする方法
です。画面のピクセルとテクスチャの画素(テクセル)は、1対1に対応しません。近くではテクスチャが引き伸ばされ、遠くではぎゅっと縮みます。そのときの色の決め方が、フィルタリングです。
- ポイント:一番近いテクセル1つをそのまま使う。混ぜない
- バイリニア:まわりの4テクセルを、距離に応じて混ぜる
- トライリニア:バイリニアを、隣り合う2つのミップマップの階層でそれぞれ行い、さらに2階層を混ぜる
- 異方性:床のように斜めに見る面では、1ピクセルが覗くテクスチャの範囲は「細長い楕円」になる。その細長い方向に沿って、複数回サンプリングする(最大2〜16回)
ミップマップは、テクスチャを1/2、1/4…と縮めた「縮小版」を用意しておく仕組みです。トライリニアと異方性は、これがないと本来の力を発揮できません。この点は、あとで数字で見ます。
実験:64枚平均の「正解」と比べる
フィルタリングの良し悪しは、見た目では判断しにくいものです。そこで、次の方法で「正解画像」を作りました。
- 一番細かい階層(1024×1024)だけを、ポイントサンプリングで描く
- 射影行列をサブピクセル単位で8×8=64通りにずらして、64枚を描く
- 64枚を平均する。1ピクセルの中を64点でサンプリングした「ほぼ正解」の画像になる
あとは、各方式の画像と正解画像とのRMSE(二乗平均平方根誤差、0〜255)を、床までの距離の帯ごとに測ります。数字が小さいほど、正解に近いことになります。
テストするテクスチャは、模様の細かさが違う4つの領域(32px市松、8px市松、2px市松、1px縞)を持つ1024×1024の画像です。ミップマップはDirectXTexで作りました。
▼FilterDemo.cpp
if (withMips) {
ScratchImage chain;
hr = GenerateMipMaps(*img, TEX_FILTER_DEFAULT, 0, chain);
if (FAILED(hr)) return false;
hr = CreateShaderResourceView(GetDevice(), chain.GetImages(), chain.GetImageCount(),
chain.GetMetadata(), &srv);
} else {
hr = CreateShaderResourceView(GetDevice(), base.GetImages(), 1, base.GetMetadata(), &srv);
}サンプラーは、方式ごとに次のように作り替えています。ここが今回の主役です。
▼FilterDemo.cpp
switch (mode) {
case 0: sd.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; sd.MaxLOD = 0; break;
case 1: sd.Filter = D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT; sd.MaxLOD = 0; break;
case 2: sd.Filter = D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT; break;
case 3: sd.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; break;
default: {
static const UINT levels[4] = { 2, 4, 8, 16 };
sd.Filter = D3D11_FILTER_ANISOTROPIC;
sd.MaxAnisotropy = (aniso > 0) ? (UINT)aniso : levels[(mode - 4) & 3];
break;
}
}注目してほしいのは、ポイント(0)とバイリニア(1)でMaxLOD = 0にしている点です。ミップマップを持っているテクスチャでも、最も細かい階層しか使わせない設定にして、「ミップなし」の状態を再現しています。
カメラの高さ1.2m、見下ろし3度で、床を遠くまで見渡す構図です。テクスチャは4mごとに1枚繰り返します。
結果1:見た目

ポイントとバイリニア(ミップなし)は、遠くがノイズの塊です。動かすとチラチラ光ります。ミップを使う3つ(最寄り、トライリニア、異方性)は、遠くが自然なグレーに落ち着きます。
異方性フィルタリングは、ミップだけの方式より奥まで模様が残ります。トライリニアでは、少し遠くの2pxの市松が、早めにグレーに霞んでしまいます。異方性ではそれが、細長い方向のサンプルで粘り強く保たれています。
もう一つ、地味な違いも見つけました。「ミップは最寄り」(左段の2番目)では、ミップの階層の切り替わりに、くっきりした境界線が見えます。トライリニアは、この境目を2階層の混ぜ合わせでなだらかにしています。
結果2:正解との差

| 方式 | 2〜5m | 5〜12m | 12〜30m | 30〜80m | 80〜200m |
|---|---|---|---|---|---|
| ポイント | 64.8 | 81.6 | 97.6 | 97.6 | 87.0 |
| バイリニア(ミップなし) | 37.4 | 64.2 | 80.5 | 78.9 | 62.5 |
| ミップは最寄り | 18.5 | 29.5 | 25.9 | 20.0 | 14.7 |
| トライリニア | 17.5 | 28.7 | 25.9 | 20.0 | 14.7 |
| 異方性 x2 | 13.3 | 19.9 | 24.9 | 20.0 | 14.7 |
| 異方性 x4 | 10.0 | 14.9 | 22.6 | 20.0 | 14.7 |
| 異方性 x8 | 9.6 | 13.3 | 20.6 | 19.9 | 14.7 |
| 異方性 x16 | 9.6 | 13.5 | 20.0 | 19.6 | 14.7 |
- ミップの有無が、圧倒的に大きい。ミップなしのバイリニアは、12〜30mで80.5と、ミップありのトライリニア(25.9)の約3倍ズレている
- 異方性は、近〜中距離で効く。5〜12mでは、トライリニアの28.7が、x4で14.9、x16で13.5まで減った
- x8とx16の差は、ほとんどない。2〜5mは9.6と9.6、5〜12mは13.3と13.5
- 30m以遠は、ミップを使う方式はどれも同じ(20前後と14.7)。ここは細かい縞が平均されて、灰色になるためと考えている
30m以遠でどの方式も同じ値になるのは、異方性が効かなくなったからというより、この距離では模様が完全にグレーに潰れていて、差の付けようがないからだと考えています。なお、正解画像も64サンプルの有限個の平均なので、RMSEにはその分のノイズが含まれています(この点は確かめていません)。
結果3:GPU時間

| 方式 | 1回の描画(マイクロ秒) | トライリニアとの比 |
|---|---|---|
| ポイント(ミップなし) | 19.7 | 1.8倍 |
| バイリニア(ミップなし) | 25.7 | 2.4倍 |
| ミップは最寄り | 10.7 | 1.0倍 |
| トライリニア | 10.7 | 1.0倍 |
| 異方性 x2 | 13.4 | 1.3倍 |
| 異方性 x4 | 26.2 | 2.4倍 |
| 異方性 x8 | 42.8 | 4.0倍 |
| 異方性 x16 | 55.9 | 5.2倍 |
意外だったのは、ミップありのほうが、ミップなしより速いことです。ミップなしのバイリニアは、遠くでテクスチャの離れた場所を読みに行くため、キャッシュが効きにくいのだと考えています(原因までは調べていません)が、トライリニアの2.4倍かかりました。「ミップを使うと重くなる」というのは誤解で、速くて綺麗が実測の結果です。
異方性は、倍率に応じて時間が増えます。x16はトライリニアの5.2倍。ただ、上の表のとおり、x8とx16で見た目の差はほぼなく、時間はx8→x16で約30%増えています。「とりあえずx16」にせず、x4かx8で十分なことが多そうです。
【重要】実際に踏んだ罠:異方性を指定したのに、ぜんぜん効かない
ここからは、私が実際に踏んだ罠を、数字と一緒に紹介します。どれも、エラーが出ずに「なんとなく綺麗にならない」で済んでしまう失敗です。
罠①:MaxAnisotropyが1のまま
サンプラーのフィルターをD3D11_FILTER_ANISOTROPICにしても、MaxAnisotropyを1のままにすると、異方性フィルタになりません。実測すると、5つの帯すべてでトライリニアと完全に同じ値(17.5、28.7、25.9、20.0、14.7)になりました。フィルターの種類だけ変えて安心してしまうと、この失敗に気づけません。
罠②:ミップがないテクスチャに、異方性を指定した
こちらが一番怖い罠でした。テクスチャを読み込む関数が、初期設定ではミップマップを作らない実装だったため、サンプラーだけ異方性16倍にしても、遠景がチラつき続けました。数字にすると、こうなります。
| 条件 | 5〜12m | 12〜30m | 30〜80m | 80〜200m |
|---|---|---|---|---|
| 異方性x16 + ミップあり | 13.5 | 20.0 | 19.6 | 14.7 |
| 異方性x16 + ミップなし | 30.5 | 44.7 | 47.0 | 35.7 |
| トライリニア + ミップなし | 64.2 | 80.5 | 78.9 | 62.5 |
| バイリニア(ミップなし) | 64.2 | 80.5 | 78.9 | 62.5 |
ミップがないと、異方性x16でも遠くのズレが2倍以上(12〜30mで20.0→44.7)になりました。また、トライリニアの指定は、ミップなしだとバイリニアと完全に同じ値になります。「ミップを混ぜる」フィルターを指定しても、混ぜるミップがなければ何も起きません。サンプラー(読む側)とテクスチャ(作る側)の両方が必要です。
罠③:MaxLODを0にしてしまった
サンプラーのMaxLODを0にすると、ミップマップを持っていても、最も細かい階層しか使えません。異方性x16でMaxLODを0にした画像は、ミップなしの異方性x16と、5つの帯すべてで同じ値でした(11.3、30.5、44.7、47.0、35.7)。D3D11_FLOAT32_MAXにしておくのが基本です。
罠④:LODバイアスで、暗黙のうちに霞ませていた
MipLODBiasは、ミップの階層を選ぶ計算にずらしを足す設定です。+1にすると、階層が1つ粗い側に寄ります。トライリニアで試すと、2〜5mの正解とのズレが17.5から27.3に悪化しました(ぼやけた)。逆に-1にすると16.7と少しだけ良くなりましたが、これは正解画像に近づいただけで、動かしたときのチラつきは測っていません。バイアスを負にするのは、シャープに見える反面、チラつきが増えるトレードオフがあると言われているので、動かして確認してください。なお、12m以遠では±1でも値が変わりませんでした。この帯は、ミップが最も小さい階層に達しているためだと考えています。
テクスチャフィルタリングのよくある失敗例と対処法
①フィルターの種類だけを変えて満足する
ANISOTROPICにしたのに、MaxAnisotropyが1のまま。あるいは、ミップがない。どちらも「エラーなし、効果なし」になります。サンプラーの設定と、テクスチャのミップ数を、セットで確認します。
②とりあえず異方性16倍にする
x8とx16の差は、ほぼありませんでした。GPU時間はx16のほうが約30%かかります。まずx4かx8で試して、床や道路のような「斜めに長く見える面」にだけ効くことを確認します。
③UIやピクセルアートにも同じサンプラーを使う
ポイントサンプリングは、遠景では汚くなりますが、ドット絵やUIをくっきり見せたいときは、逆に正解です。3D用と2D用で、サンプラーを分けます。
注意点
- ミップマップがないと、トライリニアも異方性も意味がない(サンプラーとテクスチャの両方が必要)
MaxAnisotropyは1にしない。x4かx8が現実的MaxLODはD3D11_FLOAT32_MAXのまま- ミップは「重くなる」とは限らない。今回はミップなしのほうが遅かった(原因は未調査)
- 異方性の効果は、床や壁のように斜めに見える面で出る。正面を向いた面では差が出にくいはず(今回は床しか測っていません)
まとめ
- 64枚平均の正解と比べると、ミップの有無が最大の差。ミップなしのバイリニアは、トライリニアの約3倍ズレる(12〜30m)
- 異方性は近〜中距離で効く(5〜12mで28.7→13.5)が、x8とx16の差はほぼない
- GPU時間は、ミップありのほうが速く、異方性x16はトライリニアの5.2倍
- 「異方性を指定したのに効かない」の多くは、MaxAnisotropyが1、ミップなし、MaxLODが0のどれか
サンプラーの設定は、書き換えても見た目の変化が分かりにくく、間違えたまま気づかないことの多い項目です。正解画像との差と、GPU時間を数字で見る、という見方を持っておくと、「どの設定が効いているのか」が、はっきりします。


