ゲームエフェクトデザイナーのブログ (新)

レポート記事とかUE4のマテリアルとか。C#とかも触ったり。

UE4 ComputeMipLevelとDDX・DDYについて

先日、極座標マテリアルについて記事にしました。

この時はマテリアル関数の「ComputeMipLevel」の中身についてちゃんと理解してなかったので、分かったことを書いておきたいと思います。

上記の記事をすでに読まれていること前提で話を進めます。

Texture Sampleノードの MipValueMode を MipLevel にすることで「Level」入力ピンが表示され、そこに数値を与えることでテクスチャの MipLevel を直接指定できます。

f:id:moko_03_25:20180617175637j:plain

そしてマテリアル関数「ComputeMipLevel」は「DDXDDYを使ってMipLevelを決める」構成になっている‥という感じかと思います。

f:id:moko_03_25:20180617175634j:plain

このたまに耳にするDDX・DDYが一体何なのか?については、ずし (@shiba_zushi) さんが教えてくださいました!ブログで解説されているもんしょ (@monsho1977) さんともども感謝です!

せっかくなので新しい方のブログのURLも。

つまるところ、以下のような感じのようです。

・スクリーンスペースで2x2ピクセルごとに処理する
DDXは入力された値に対し、左上のピクセルとその右のピクセルを比較して差を出す
・DDYは入力された値に対し、左上のピクセルとその下のピクセルを比較して差を出す
・出した結果は2x2ピクセル全てに同じ値を返す


試しにUVを繋いでDebugノードで数値を可視化してみました。
カメラから離すと隣接ピクセルのUV値の変化が大きくなるので、結果の値も大きくなっているのが分かります(カメラから離すだけでなくスケールで小さくしたりUVタイリング数を増やして小さくしても同様の結果になるかと思います)。

値がとても小さいのは板ポリの端〜端でUV座標の値が0〜1だからですね。つまりこの板ポリ全体がぴったり2ピクセル幅で表示されるくらい小さくなればやっと1が返ってくる理屈になるのかなと。

f:id:moko_03_25:20180617175615g:plain

DDX・DDYは4チャンネルの値を入れたら4チャンネルそれぞれ比較して返すようです。
例えば下図は2チャンネルの値を渡した場合。双方の計算を返していますね。

f:id:moko_03_25:20180617175720j:plain

こちらは、黒白で二値化した情報を入力した場合。
黒と白の境界で1の変化があるため、DDXでは縦に白いラインが入りました。
なるほど、これは色々利用できそうですね。。

f:id:moko_03_25:20180617175717j:plain

という訳で、DDXとDDYについては分かりました。では次。

DDXとDDYの計算結果に対して、入力してきたテクスチャサイズを掛けて、DotProduct>If>Squart>Log2といった流れになっています。順に見ていきます。

f:id:moko_03_25:20180617175634j:plain

DDXとDDYどちらにも、UとVの両方の情報を渡しています。それはポリゴンを回転させるとUとVのどちらの値もスクリーンスペース上で横方向の差に影響するためです。例えば45度ロール方向で回転させるとスクリーンスペース上ではUとVのどちらも同じ変化量になりますね(最初は勘違いしてDDXにVの値を渡す必要ないと思って構成を削ってしまっていました。こちらについて教えてくださった けだまじごく (@kedamazigoku) さん、ありがとうございます!)

f:id:moko_03_25:20180618201539j:plain

そして結果にテクスチャサイズを掛けているのは、恐らくUV座標が0〜1なのを0〜テクスチャサイズに広げてやってるのだと思います。これで隣接ピクセルのUV座標の差がテクスチャのピクセルの差になります。テクスチャのピクセル数を反映しないと実際にどれだけ荒くなってるか判断できないからですね。例えばテクスチャが2*2ピクセルなら板ポリも2*2ピクセルで表示されれば適正サイズとなるイメージで。

次に DotProduct が挟まっていますが、こちらは後の工程の Sqrt ( Square Root / 平方根) と併せて、DDXやDDYの結果である2チャンネルの値を「1チャンネルの大きさ情報」にまとめるためのようです。

こちら、トリニ (@to_ri2) さんが教えてくださいました!感謝です!

f:id:moko_03_25:20180618201547j:plain

そして If では、DDXの結果はAに・DDYの結果はBに入力してAとBを比較、Aの方が大きいならDDXの結果を出力し、Bの方が大きければDDYの結果を出力します。

つまりわざわざ If を使わなくても「Max」でいいじゃん、ということですね。

ちなみにAとBが同じ場合 (A==B) に指定が無いですが、その場合は A>B が返るようです。

f:id:moko_03_25:20180617175714j:plain

結局「Ifで何をしているのか?」というと、隣のピクセルのU座標の差とV座標の差の大きい方をミップレベルに利用する‥ということですかね。つまり、UとVで差の大きさが違う場合に差が大きい方を選び、結果「MipLevelを高くする=粗くする」選択をしているということになるかと思います。

つまり、極座標の記事でも引用させていただいた、けだまじごく (@kedamazigoku) さんのこちらのツイートは、ここをMaxではなくMinにすることで「解像度を高めの方を選択した方が綺麗な結果になる (=アーティファクトを抑えられる) 」ということでしょうか。

という訳で、If を Min に入れ替えるとこんな感じ。

f:id:moko_03_25:20180618195411j:plain

最後の Castumノードによる対数 (Log2) で、具体的なミップマップレベルを算出しているようです。「Log2(x)」は「2を底とする対数」の関数で「xは2の何乗か?」になります。

Mipmap が効くテクスチャのサイズは「2 の累乗」になるので、Log2を使って「何段階目の MipLevel なのか?」が求められる訳ですね。

ちなみに対数についてはこちらの解説がとても分かりやすかったです。