ミップマップ

Mipmap別名:MIP マップ、ミップマッピング

概要

テクスチャを縦横半分ずつ縮めた画像を、あらかじめ何段も用意しておく仕組み。遠くで小さく映る面には縮めた画像を使い、細かい模様のちらつきやモアレを防ぐ。

デモ

デモを読み込んでいます
白い線を左右にドラッグすると、ミップマップなしとありを見比べられます。奥へ行くほど、左は模様がざらついてちらつき、右は灰色になじみます。「ミップレベルを色で表示」をオンにすると、右側でどの段の縮小画像が使われているかが色で分かります。

使われる場面

  • 3D の地面や壁

    床のタイル、芝生、道路のように奥まで続く面に貼るテクスチャ。ミップマップがないと、遠くの模様がざらつき、カメラが動くたびにちらつく。

  • 3D のテクスチャ全般

    画像を読み込むときにミップマップを作るのが標準的な設定である。メモリは約 3 分の 1 増えるが、遠くを描くときに読むデータが減るので、かえって速くなることが多い。

  • 2D の画像の縮小表示

    大きな画像やスプライトを小さく表示するときに、縮小画像を使って細い線や文字が途切れるのを防ぐ。

  • 距離に応じた細かさの切り替え

    遠くでは細かい情報が見えないので、小さな画像で足りる。同じ考え方を形状に当てはめ、距離に応じてポリゴン数を切り替えるのが LOD である。

判断基準

こういうときこうする
3D 空間に置く物体や地面のテクスチャミップマップを作る。距離や角度で映る大きさが変わる面では、ほぼ必須である。
地面のように浅い角度で見る面が、奥でぼやけすぎる異方性フィルタリングを併用する。縦と横で縮み方が違うときに、つぶれた方向に沿って複数回読み、ぼけを抑える。
常に等倍で表示する UI の画像やドット絵ミップマップは不要。等倍より少しでも小さく表示されると縮小画像が混ざり、輪郭が甘くなる。
1 枚の画像に複数の絵を詰めたテクスチャ(アトラス)縮小画像では隣の絵の色がにじむので、絵のまわりに余白を取るか、縮小の段数を制限する。
ノーマルマップのように数値を持つテクスチャミップマップは作ってよいが、縮小すると細かい凹凸が平均されて平らになる。遠くでつやが強く見えるときは、ラフネスで補う手法がある。

こう見えたら

指示の例

  • 遠くの床の模様がざらついて、カメラを動かすとちらつく。テクスチャのミップマップを有効にして

  • 斜めに見た地面の奥がぼやけて、灰色一色になる。異方性フィルタリングを 16 倍にして

  • 等倍で表示する UI の画像がにじむ。UI のテクスチャはミップマップを切って

  • 複数の絵をまとめたテクスチャで、遠くに行くと隣の絵の色がにじむ。絵のまわりに余白を足して

詳細

遠くの模様がちらつく理由

テクスチャを貼った面を描くとき、GPU は画面の 1 ピクセルごとに、テクスチャの対応する 1 点の色を読む(その点を囲む 4 画素を混ぜる)。面が近くにあれば、1 ピクセルに対応するのはテクスチャの 1 画素(テクセル)程度なので問題はない。面が遠くなると、1 ピクセルの範囲に何十ものテクセルが入るのに、読むのはその中の 1 点だけになる。市松模様なら白と黒のどちらを拾うかがわずかな位置の違いで変わり、ざらつきや、元にはない縞(モアレ)が現れる。カメラが少し動くだけで拾う点が変わるので、動きのある場面ではちらつく。これは「アンチエイリアス」の項で扱ったエイリアシングと同じ現象である。

縮めた画像を先に用意する

ミップマップは、元の画像(レベル 0)から、縦横を半分にした画像(レベル 1)、さらに半分(レベル 2)と、1×1 画素まで縮小画像を並べたものである。各段の 1 画素は、一つ上の段の 2×2 画素の平均なので、広い範囲の色がすでに混ぜてある。描画時は、1 ピクセルに入るテクセルの数に見合う段を選んで読む。段の数は画面上の隣のピクセルとのテクスチャ座標の差から見積もり、テクセル単位の差の大きさを ρ\rho として次のように決める。

λ=log⁡2ρ,ρ=max⁡(∥∂t∂x∥, ∥∂t∂y∥)\lambda = \log_2 \rho, \qquad \rho = \max\left( \left\| \frac{\partial \mathbf{t}}{\partial x} \right\|,\ \left\| \frac{\partial \mathbf{t}}{\partial y} \right\| \right)

t\mathbf{t} はテクセル単位のテクスチャ座標、xx と yy は画面上の位置である。1 ピクセルに 4 テクセルが並ぶなら λ=2\lambda = 2 で、レベル 2 を使う。λ\lambda は小数になるので、前後二つの段を読んで混ぜ、段の切り替わりを目立たなくする(トライリニアフィルタリング)。追加のメモリは 1/4+1/16+⋯=1/31/4 + 1/16 + \cdots = 1/3 で、元の画像の約 33% である。

vec4 c  = texture(tex, uv);           // 段を自動で選んで読む(通常はこれ)
vec4 c0 = textureLod(tex, uv, 0.0);   // 常にレベル 0 から読む(ミップマップなしと同じ)

見分け方と落とし穴

デモの「ミップレベルを色で表示」のように、段ごとに色を付けて使われ方を確かめる方法は、実際の調整でも使われる。手前はレベル 0 のまま、奥ほど小さい段に切り替わり、その境目がトライリニアフィルタリングでなめらかにつながっているのが分かる。

地面のように浅い角度で見る面では、1 ピクセルの範囲が奥行き方向に細長くなる。段は長い方向に合わせて選ばれるので、横方向までぼけてしまい、奥が必要以上にのっぺりする。デモの右側で、奥の模様が早々に灰色一色になるのはこのためである。異方性フィルタリングを有効にすると、細長い方向に沿って何回かに分けて読むので、奥まで模様が残る。

関連