UE4のレンダリングのチリチリとディザの裏事情

この記事は[裏 Unreal Engine 4 (UE4) Advent Calendar 2016](http://qiita.com/advent-calendar/2016/ue4_inside)の17日目の記事です。

UE4でレンダリングされたシーンってパッと見ではわからないんですけど、実はカメラやオブジェクトが静止している間も凄く微妙にチリチリしている部分があるんですよね。

オブジェクトのエッジや色の差が大きい部分で拡大して比較したら気が付かれるかもしれませんが、UE4に実装されているTemporalAAの影響によるものです。
（UE4のTemporalAAの実装自体に興味がある方は[こちらの「High Quality Temporal Supersampling」というpdf](https://de45xmedrsdbp.cloudfront.net/Resources/files/TemporalAA_small-59732822.pdf)をご参照ください）
ざっくりいうと、プロジェクションマトリクスでスクリーンスペースで1ピクセル未満のジッタリングさせ、1ピクセル未満の微妙なブレを生じさせてそれを時間で合成することでサブピクセルの描画効果を得るような処理をしています。

TemporalAA自体はなかなか良いものなのですが、この微妙な差が好ましくない場合があります。例えば映像分野等で書き出して外部でコンポジットしたい場合に静止しているハズなのに微妙に差が出ては困るという場合や、VR等でHMD向けに歪めた描画とTemporalAAとの処理がマッチしない場合等があります。

その場合、VR等リアルタイム系のものであればFXAA等を使ったり、映像書き出しであれば高解像度でAAなし（外部ツール等で縮小させてAA効果を得る）等の選択肢があるのですが・・・UE4の描画の中にTemporalAAでのサブピクセルや時系列処理がなされる事を想定して、ディザやサブピクセル単位でランダムにジッタリングするような処理をしている部分があります。

TemporalAAなしだとこれが結構目立つんですよ、特にトゥーンシェーディングやセルシェーディングと呼ばれているアニメ調の輪郭線や色の塗り分けをカッチリした表現をしようとすると・・・。

リアル系のレンダリングでも拡大すると・・TemporalAAありでもSSR（スクリーンスペースリフレクション）のイスの映り込みにドット感が少し感じられると思いますが・・・
![image](https://qiita-image-store.s3.amazonaws.com/0/58485/b6ed9db1-7ac5-51cf-d78c-3759b84d1509.png)
AAなしの下のスクリーンショットだとドット感がさらにわかりやすいかと思います。
![image](https://qiita-image-store.s3.amazonaws.com/0/58485/94f1199f-f6f1-d06d-470f-1d9a4681d364.png)

トゥーンシェーディングで目立つものとしてはSSAO（スクリーンスペースアンビエントオクルージョン）があります。
![HighresScreenshot00004.png](https://qiita-image-store.s3.amazonaws.com/0/58485/6f91d0ac-c7e7-869c-6ead-fce6df39383c.png)
イスの上のムラはノーマルマップがスムーズでない事による影響が出ている感じなのですが、イスとテーブルの間の球体はノーマルマップ無しのマテリアルなのですが影との境がジャギって見えるのがわかるかと思います。
ここにもっと近寄って見ると・・・。
![HighresScreenshot00005.png](https://qiita-image-store.s3.amazonaws.com/0/58485/f6951c3b-f0b8-a184-5440-bd115f7cec80.png)
イスに近い部分の影の縁がジャギってますね。これSSAOのシェーダーコードの影響なんです。

ということでこのジャギを削減してみたいと思います。なのですが、いきなりシェーダーコード弄ります。裏ですからね。
（きちんと作り直したいのですが、ちょっと時間がないので今回は現行のコードを元に改造します）

SSRの方ですが、/Engine/Shaders/ScreenSpaceReflections.usf

129行目付近の
	float FrameRandom = SSRParams.a;
をコメントアウトして
	const float FrameRandom = 0;
に変更。

180行目近辺の
	uint Morton = MortonCode( PixelPos.x & 3 ) | ( MortonCode( PixelPos.y & 3 ) * 2 );
	uint PixelIndex = ReverseUIntBits( Morton );
をコメントアウトして
	const uint PixelIndex = 0;
に変更。

136行目近辺の
		const float2 E = float2(PseudoRandom(PixelPos + float2(FrameRandom, 0)), PseudoRandom(PixelPos + float2(0, FrameRandom)));
		const float StepOffset = PseudoRandom(PixelPos + FrameRandom);
をコメントアウトして
		const float2 E = float2(0,0);
		const float StepOffset = 0
に変更。

191行目近辺の
		uint2 Random = PseudoRandom(SvPosition.xy + View.StateFrameIndexMod8 * float2(97, 71)) * uint2(0x3127352, 0x11229256);		// doesn't compile with external HLSL with obscure error message but the line above does
		uint2 Random = ScrambleTEA( uint2( PixelPos ) ^ (uint)FrameRandom, 3 );	// reference, SSR_QUALITY=3 instructions=296 
をコメントアウトして
		uint2 Random = uint2(0,0);
に変更。

ここで、ScreenSpaceReflections.usf を保存して「Shift+Ctrl+.（ピリオド）」を押してシェーダーのコンパイル。

で、PostProcessVolumeのScreen Space Reflections 項目の Quality を100に設定するとディザやチラチラがなくなると思います。
![image](https://qiita-image-store.s3.amazonaws.com/0/58485/9fc0990c-5296-975a-c603-93ae1ad3384d.png)
まだ少しディザっぽいパターンが一部見られるのと、できればQuality 0でもチラチラしないようにしたかったんですが、ちょっと間に合いませんでした。


SSAOの方ですが、/Engine/Shaders/PostProcessAmbientOcclusion.usf

610行目近辺の
	float2 ViewportUVToRandomUV = ScreenSpaceAOParams[1].xy;
をコメントアウトして
	float2 ViewportUVToRandomUV = float2(0,0);
に変更。

ここで、ScreenSpaceReflections.usf を保存して「Shift+Ctrl+.（ピリオド）」を押してシェーダーのコンパイル。

![ScreenShot00022.png](https://qiita-image-store.s3.amazonaws.com/0/58485/17cef2f9-9152-e28f-a92b-708610c9b18e.png)
するとSSAOのエッジに見られたモヤモヤを消す事ができます。

とはいえ、いろいろ試して見たところ一部意図しない模様が出るようなので調整を試みているんですが、17日を越えてしまうのもナニなので一旦公開させていただきます。
（とりあえず元のコードを無理やり改変しているだけなので、ちゃんとコードをカスタマイズすべきかなと思っています）

17日は過ぎてしまうと思いますが、もう少し継続してアップデートしていきたいと思います。

明日はPaperSlothさんの「[MaterialのCustomNodeの小話](http://papersloth.hatenablog.com/entry/UE4AC2016)」です。お楽しみに！