Unityモバイル動的シャドウのまとめ

この記事で、Unityモバイル端末の大量のアートシャドウをまとめています。例えば、Cubemapの動的なソフトシャドウ、地面にある雲のシャドウ、キャラクター足元のシャドウパッチなど。プロジェクトの制作と開発であろうと、日々の学習の蓄積であろうと、それは参考価値のある貴重な資料です。

---
###壱、Cubemapに基づく動的ソフトシャドウ
会社ARMはUnityを使用して2つのテクノロジーDemo（[Ice CaveとChess Room](https://developer.arm.com/solutions/graphics-and-gaming/gaming-engine/unity/unity-demos)）を作成しました。Cubemapの強さを十分に発揮しました。地面の反射、氷の屈折だけでなく、動的なソフトシャドウにも、シンプルなテクノロジーを使用して高品質の画像を作成しました。 Ice Caveの効果は次のとおりです。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/fdb15646-588d-5d97-57ec-087de12f8412.png)

その中で、反射と屈折の部分に関しては、記事[「Reflections Based on Local Cubemaps in Unity」](https://community.arm.com/graphics/b/blog/posts/reflections-based-on-local-cubemaps-in-unity)、[「ARM Guide for Unity Developers」](https://developer.arm.com/solutions/graphics-and-gaming/gaming-engine/unity/arm-guide-for-unity-developers)を参考してください。ここでは主にソフトシャドウ部分の原理を紹介します。 

このチェス部屋を例として、部屋の真ん中に一つのReflect Probeを置いて周りの環境を撮影します。CubemapのRGBチャネルのみが使用されます。周囲環境のAlphaも光が窓を透過するか、または壁に遮蔽されるかを代表しています。この場合、Cubemapの残ったAlphaチャネルを利用して光と周りの環境の遮蔽状況を保存できます。Alphaチャネルの画像は次とおります。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/d90d7ab0-1569-e8b4-3771-141d5d54f0c0.png)
Cubemapを生成する詳細は[「AssetStoreにあるソースコード」](https://assetstore.unity.com/packages/vfx/shaders/dynamic-soft-shadows-based-on-local-cubemaps-61640)を参考できます。

生成されたCubemapを利用してシャドウをレンダリングすることは主に二つステップに分けます。1つはベクトルL（vertex-to-light）からLp（キャリブレーションされたvertex-to-light、Cubemapのサンプリングに使用）への変換で、もう1つはソフトシャドウ処理です。

####1、LからLpへのベクトルキャリブレーション
**入力するパラメータ**
_EnviCubeMapPos >> Cubemap中心座標
_BBoxMax >>キューブマップの生成時に自動的に生成されるバウンディングボックスの最大座標
_BBoxMin >>キューブマップの生成時に自動的に生成されるバウンディングボックスの最小座標
V：>>頂点座標
L：>>vertex-to-lightベクトル、normalizedされました

**出力するパラメータ**
Lp >>キャリブレーションされたvertex-to-lightベクトル、UVとしてCubemapをサンプリングします

**キャリブレーションプロセス**

```ruby:キャリブレーションプロセス
// Working in World Coordinate System.
vec3 intersectMaxPointPlanes = (_BBoxMax - V) / L;
vec3 intersectMinPointPlanes = (_BBoxMin - V) / L;
// Looking only for intersections in the forward direction of the ray.   
vec3 largestRayParams = max(intersectMaxPointPlanes, intersectMinPointPlanes);
// Smallest value of the ray parameters gives us the intersection.
float dist = min(min(largestRayParams.x, largestRayParams.y), largestRayParams.z);
// Find the position of the intersection point.
vec3 intersectPositionWS = V + L * dist;
// Get the local corrected vector.
Lp = intersectPositionWS - _EnviCubeMapPos;
```

最初に線と包囲ボックスを使用して交点を見つけます。包囲ボックスの位置から交点までのベクトルはLpであり、次にLpを使用して色付けのためにCubemapをサンプリングします。
![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/fbf42cf7-0980-6d74-4452-639721f25aae.jpeg)

```ruby:
float shadow = texCUBE(cubemap, Lp).a;
```

さらに、シャドウの透過を防ぐために、裏側は特別に処理する必要があります。

```ruby:
if (dot(L,N) < 0)
  shadow = 0.0;
shadow *= max(dot(L, N), 0.0);
```

####2.ソフトシャドウ
シャドウをスムーズにする過程は面白いです。まず、Cubemap濾過方式tri-linear filteringを選択し、次にvertex-to-intersection-point（頂点から交点）ベクトルの長さを計算し、それを外部の入力係数で乗算します。

```ruby:
float texLod = length(IntersectPositionWS - V);
texLod *= distanceCoefficient;
```

シャドウをスムーズにするために、texCUBElodを使用してCubemapをサンプリングします。UVのXYZはLpから、Wはvertex-to-intersection-point（頂点から交点）の距離から取得されます。

```ruby:
Lp.w = texLod;
shadow = texCUBElod(cubemap, Lp).a;
```

この画像でも、ウィンドウから離れるほど、シャドウがぼやけていることもわかります。
![4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/1f4895b3-16c8-fdfc-3964-50f1588b93d2.jpeg)
この種のシャドウは点光源の位置は変わらず、内部には動く物体のある屋内環境により適しております。

---
###弐、地面にある雲のシャドウ
地面にある雲のシャドウに対して、リアルタイムのライトでシャドウを照らすことは明らかに適当ではありません。地面Shaderの中に一つの移動する雲の画像を直接混合して、同様の効果を得ることができます。
![5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/99b7b7ca-5e1b-2494-3c91-c2c122132208.gif)
Shaderforgeを使用して簡単なバージョンを作成しました。
![6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/b776591f-a456-2f48-1cf8-e4c86d23ad80.jpeg)
さらに、この方法は、地面にある風と雪の効果を作成するためにも使用できます。

---
###参、揺らす植物のシャドウ
木、草、旗など、位置は同じでニメーションが揺れるオブジェクトの場合、事前にシャドウをテクスチャにベイクしてから、シャドウマップを単一テクスチャまたは地面テクスチャとしてAlphaチャネルで地面Shaderに入力します。そして、シャドウに揺れる特徴を与えたら、植物の揺れに伴って揺れ、本物の影の感覚を伴うことがあります。また、影の方向と植物の揺れの方向の同期などの細部にも注意を払ってください。
![7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/05ca6545-0bce-f654-a2be-117cf3c0c1fa.jpeg)
![8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/9f8ee6b2-51ab-47d6-c83e-1e17612d6638.jpeg)

---
###肆、ProjectorとRendertextureを組み合わせたリアルタイムシャドウ
メインカメラにフォローするシャドウカメラを作成し、正射影に変更します。単一のshadow Layerを設置して、投影したいオブジェクトをshadow Layerに設置し、シャドウカメラのレンダリングターゲットをレンダリングテクスチャRTT_Shadowに設定します。さらに、一つのProjectorを作成し、マテリアルMat_Projを設定し、RTT_ShadowをMat_Projのshaderに入力して色付けをします。シャドウカメラの端にザラザラな長い線を防止するために、一つのシャドウ減衰テクスチャを設定する必要があります。ソフトシャドウが必要な場合は、Blurを追加する必要があります。
![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/de54ad3e-4f6d-fb64-f6c5-8baabc100266.png)
これは、近年モバイルゲームで広く採用されている方法です。インターネット上には多くの関連記事があります。例えばあ、[「ProjectorとRendertextureを組み合わせてリアルタイムシャドウを実現する」](http://blog.csdn.net/mobilebbki399/article/details/50240515)、[「ProjectorShadow（モバイルゲームのリアルタイムシャドウスキーム）」](http://blog.csdn.net/aceyan0718/article/details/52279594)など。（中国語注意）

そして、AssetStoreにもは多くのプラグインがあります：[「Fast Shadow Projector」](https://assetstore.unity.com/packages/tools/particles-effects/fast-shadow-projector-14746)
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/d9c6cce0-4109-2a62-e9a9-37b77f370214.png)

---
###伍、キャラクター足元のシャドウパッチ
ゲーム内のNPC、兵士、モンスターなあどの重要ではないキャラクターに対して、シャドウパッチを直接設定してシャドウをシミュレートできます。もちろん、地面が起伏している場合は、インターリーブの問題が発生する可能性があります。
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/2add1b6d-ba68-9e4d-eb3f-913422be214b.png)

---
###陸、Light Probe
具体的な詳細はUnityのマニュアルを参照してください、ここには詳しく説明しません。
[Light Probes](https://docs.unity3d.com/Manual/LightProbes.html)
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/ffc9239a-c41c-8931-1608-37f9703a2200.png)

---
###漆、Shadow Maps
####1.Standard Shadow Mapping
基本的な考え方は、光源の位置に一つのカメラ（Light space Camera）を配置し、深さを描画して深度マップを取得し、シーンをレンダリングする時にpixel座標をLight Spaceに変更して深度を計算します。その深度と深度マップを比較して、深度マップより深い場合、シャドウにあると意味します。それ以外の場合は、照らされています。

シャドウのエイリアスには、透視が導くエイリアス（Perspective alias）と投影が導くエイリアス（Project alias）の2種類があります。
####2.PCF
投影によるギザギザは、光の投影方向とオブジェクトの表面との間の角度が小さすぎる時、複数のpixelがシャドウマップの一つのtexelに対応するためです。これは、シャドウマップのサイズを大きくすることで解決できます。Percentage CloserFilteringを使用してエッジを柔らかくすることでも解決できます。PCFは、現在のポイントを描画するだけでなく、周囲のピクセルを複数回サンプリングし、混ぜてギザギザなところを柔らかくします。よく使うPCFは、[「ランダムサンプリングを使用してソフトシャドウを実現する」](http://blog.csdn.net/candycat1992/article/details/8981370)、[「ポアソンサンプリング」](http://www.ownself.org/blog/2010/percentage-closer-filtering.html)など。（中国語注意）
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/4f782ebd-3aaf-5a3b-d406-faa077acb685.png)

####3.PSM
透視が導くエイリアスは、遠近法の遠小近大によって引き起こされています。ですから、Perspective Shadow Mapがあります。シャドウマップの計算プロセス全体を正規化デバイス空間（NDC）に転送して計算します。遠小近大の問題を解決します。次の図は、Standard Shadow MapとPerspective Shadow Mapで最適化されたシャドウであり、明らかにより詳細です。
![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/f989a429-69a8-a345-91fb-3f109bef96ec.png)
![14-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/cc90dc8f-bedf-3cd6-542b-1577fad6b5e5.png)
ただし、PSM自身には大きな制限があります。たとえば、シャドウの品質は視角角度に大きく依存し、近所にあるシャドウと遠所にあるシャドウのZ分布が大きすぎます。
####4.LISPSM
PSMに基づいて、新しいシャドウテクノロジーであるLight Space Perspective Shadow Mapsがあります。これは、ライト方向と垂直な方向にView Frustrumを構築し、ライトとシーンをこのView FrustrumのPerspective spaceに転移してShadow Mapを計算します。これで、点光、集光、平行光のいずれであっても、すべて平行光に変わります。
![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/45bbbd29-68cf-e68a-0fc0-3f494460912c.png)
左はUniform（近所での精度が不十分）、真ん中のはLISPSM（近所と遠所でも良好）、右はPSM（遠所での精度が不十分）です。

LISPSMの具体的な詳細については、以下を参照してください。
https://www.cg.tuwien.ac.at/research/vr/lispsm/shadows_egsr2004_revised.pdf
####5.VSM
PCFを使用する場合、PCFの計算を不正確になさせやすいから通常、シャドウマップを事前にぼかすことはできません。ただし、Variance Shadow Mapsにはそのような制限がありません。VSMによって保存されるShadow Mapは、深度だけでなく、深度の2乗も含まれます。この時点で、Shadow Mapをフィルタリングし、チェビシェフの不等式を使用して、現在よりも大きい確率の上限を計算します。つまり、シャドウ付け部分の確率。チェビシェフ不等式は下図のように、
![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/73b33adf-7530-8f23-1ca7-6d56aaf1a16b.png)
![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/a7471f97-8ab6-92d6-921e-9cfdd358f234.png)
![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/50396ba4-1b3f-2c86-6f61-f3c321fa3f67.png)
上はStandard Shadow Map、下はVariance Shadow Map、詳細については、こちらを参照してください、[「Variance Shadow Mapping」](http://developer.download.nvidia.com/whitepapers/2007/SDK10/VarianceShadowMapping.pdf)、[「VSMのDemos」](http://www.punkuser.net/vsm/)、[「Matt’s Variance Shadow Maps」](https://graphics.stanford.edu/~mdfisher/Shadows.html)。
####6.CSM / PSSM
これらは別々に研究および公開された2つのシャドウテクノロジーですが、原理はほぼ同じです。UnityはCSMを使用します。 それらの原理は次のとおりです。

a）カメラ視錐台のZに沿ってシャドウマップを近くから遠くまで分割します。セグメンテーションは2つのセグメンテーションルールの混合であり、1つは均一セグメンテーション、もう1つは指数セグメンテーションであり、両方とも特定の比率で混合します。
![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/f6c9b3dd-4be7-4727-4afe-eff1be3b9858.png)
b）各セグメントの光源投影空間内で平行移動とスケーリングの行列cropMatrixを別々に計算します。分割されたセグメントを光源の視錐台に移動してスケーリングできます。このマトリックスは、直交投影マトリックスとよく似ています。

```ruby:
// Build a matrix for cropping light's projection
   // Given vectors are in light's clip space
Matrix Light::CalculateCropMatrix(Frustum splitFrustum)
{
  Matrix lightViewProjMatrix = viewMatrix * projMatrix;
  // Find boundaries in light's clip space
  BoundingBox cropBB = CreateAABB(splitFrustum.AABB,
                                  lightViewProjMatrix);
  // Use default near-plane value
  cropBB.min.z = 0.0f;
  // Create the crop matrix
  float scaleX, scaleY, scaleZ;
  float offsetX, offsetY, offsetZ;
  scaleX = 2.0f / (cropBB.max.x - cropBB.min.x);
  scaleY = 2.0f / (cropBB.max.y - cropBB.min.y);
  offsetX = -0.5f * (cropBB.max.x + cropBB.min.x) * scaleX;
  offsetY = -0.5f * (cropBB.max.y + cropBB.min.y) * scaleY;
  scaleZ = 1.0f / (cropBB.max.z - cropBB.min.z);
  offsetZ = -cropBB.min.z * scaleZ;
  return Matrix( scaleX,     0.0f,     0.0f,  0.0f,
                   0.0f,   scaleY,     0.0f,  0.0f,
                   0.0f,     0.0f,   scaleZ,  0.0f,
                offsetX,  offsetY,  offsetZ,  1.0f);
}
```

c）分割された各セグメントのシャドウマップをレンダリングします。通常、シャドウマップのサイズは同じです（例えば、1024 * 1024などであり）。近所に含まれるシーン範囲は、遠所より小さいから、近所シャドウマップの精度が高くなります。

d）シーンシャドウをレンダリングします。
![20.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/b89d8abe-0f2c-5793-dae8-4195133b4cbe.jpeg)
CSMおよびPSSMの具体的な詳細については、[「Cascaded Shadow Maps」](https://docs.microsoft.com/zh-cn/windows/win32/dxtecharts/cascaded-shadow-maps?redirectedfrom=MSDN)、[「Parallel-split shadow maps for large-scale virtual environments」](https://www.researchgate.net/publication/220805307_Parallel-split_shadow_maps_for_large-scale_virtual_environments)、[「PSSM from GPU Gems 3」](https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-10-parallel-split-shadow-maps-programmable-gpus)を参考してください。

そして、Shadow Mapsには他にも多くのバリエーションがあります、[「既知シャドウマップの名前のまとめ」](http://hyzgame.blogspot.com/2009/04/sm.html)。

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

UWA公式サイト：https://jp.uwa4d.com
UWA公式ブログ：https://blog.jp.uwa4d.com