UnityでのオープンワールドにはZ-Fightingという壁が立ちはだかっていた

はじめに

エンジニアの小池(DBK)です。

VRでオープンワールドに入りたいと思った人はたくさんいると思います。最近はSwitchなどでオープンワールドのゲームが出ているので特に多いのではないでしょうか。
弊社にもそんな人たちがたくさんいて、でっかいシーンを作りVRで入って色々なことがしたい!となりまして、いわゆるオープンワールドのような非常に広大なシーンの作成に取り掛かりました。

成果物

結局、弊社提供中のサービスであるNEUTRANS BIZ上では3km四方のLargeFieldという名前のシーンになりました。

建物や橋を実物大で見ることができるので、なかなか好評です。

訪れた悲しみ

シーン上のオブジェクトが原点から遠ざかっていくほど、Z-Fightingが激しくなっていきます。弊社サービスではuGUIを利用して様々なUIを構成しているので、この現象が頻発してしまい、シーンの大きさを小さくせざるを得ませんでした。

ちなみにZ-Fightingとは、メッシュがジラジラするアレです。ちゃんとWikipediaにも項目があります。
簡単に言うと、同じ位置に面が2つ以上ある場合、描画したいピクセルが複数存在するため、このような現象が発生します。特に、UnityではTransformがfloatなので、浮動小数点の計算精度がZ-Fightingに大きく影響しています。

Z-fighting - Wikipedia

開発環境

一応検証環境を記載しておきますが、Unityのバージョンは関係ないかと思います。

  • CPU:i7 8700
  • GPU:GTX1080
  • メモリ:16GB
  • OS:Windows 10 pro
  • Unity:2018.4.6f

検証

  • 準備

Z-Fightingを故意に発生させるために、uGUIのImageを利用してメッシュの重なりを作成しました。0.0001のオフセットがされていて、緑のImageが手前に配置されています。

右上に表示されているのは原点からのx方向の距離です。

また、メッシュの法線方向のオフセットを0.0001単位で広げることができるボタンも設置しました。

f:id:hdkkikdbk:20200627235817j:plain
Test UI

検証に利用したUnityのシーンはNEUTRANS BIZのLargeFieldで行いました。簡単な部屋のモデルと、グラウンドとしてのPlaneがオブジェクトとして配置されています。ちなみにこのシーンの容量は以下です。

  • LargeField:44KB
  • 空のシーン:16KB

部屋の写真
Room

  • やり方

作成したUIをx方向に原点から移動していき、どの位置でZ-Fightingが発生するか検証しました。

  • 結果

f:id:hdkkikdbk:20200630162655g:plain

原点から離れるほどにZ-Fightingが激しくなりました。こちらのシーンでは約9,000mからZ-Fightingが確認されました。オフセットを+0.1000すると約10,000mで再度Z-Fightingが発生しました。
また、Editorで実行した方が発生しやすいこともわかりました。Editorの処理によるオーバーヘッドが入ってくるためだと思われます。
そのほか、シーン内に配置されているオブジェクトが多いほど、原点からの距離が短くても発生しやすくなりました。

結論と対策

シーンに配置されているオブジェクト数、原点からの距離、表示するメッシュサイズなど、さまざまなパラメータが関係していることが容易に想像できます。定量的に検証するためにはこのようなパラメータで比較する必要がありますが、すくなくともZ-Fightingが発生しないように、メッシュの法線方向の重なりに注意する必要があることがわかりました。

今回の検証では、3Dオブジェクトの検証は行っていませんが、メッシュが重なっている部分では同じ現象が確認されました。

対策としては以下があげられます。

  1. Transformをいじって、Z-Fightingが発生しているメッシュの法線方向に差をつける
  2. シェーダーでオフセットを指定してずらす
  3. 原点からの距離ができるだけ短くなるように、原点を中心として円状にオブジェクトを配置する
  4. Playerが原点から離れないように背景オブジェクトを回転させて移動する

今回の対策としてはTransformで様々なUIにオフセットを持たせることで解決しました。もともと、uGUIのImageが重なっていた部分もあり、すでに多少のオフセットがあったのそれを広げた形になります。

また、初めはシーンに存在する部屋のモデルを原点に置いてシーンを作成していたのですが、端に行くほど原点からの距離が長くなってしまいます。そこで、中央を原点として端に部屋のモデルを配置することで、原点からの距離がシーンサイズの1辺の長さの半分になりました。

Transformを変更する方法だと、Staticなオブジェクトは問題ないですが、オブジェクトが動く場合は原点からの距離に合わせてメッシュの差を制御する必要がありそうです。

また、面のメッシュにだけ対応するだけであれば問題ないですが、立体的なオブジェクトはシェーダーで対策を行う必要があります。シェーダーについての詳しい方法はこちらを参考にしてください。

Unity でのZファイティング(Z-Fighting)の対処法 - 強火で進め

一番確実な方法としては、4つ目にあげたようにPlayerを中心として世界を回すように移動させるのがよいのでしょうが、オブジェクトを動かすときの負荷やVRなので3次元移動での方法を考える必要があるなど、なかなか考えることが多いです。本当にオープンワールドが必要になった時には再度検証してみようと思います。

最後に

Synamonでは、XRで市場を切り開いていきたい、スタートアップで挑戦していきたいエンジニアを募集しています。

気になった方は下記のWantedlyや、自分の(Twitter)でも何でもいいので、気軽にご連絡ください!