質問: ### 前提 Unity の Universal RP を用いてARアプリケーションを制作しようとしていて、次の2つの処理を実装したいのですが、シェーダについての理解が追いついておらず、うまくやる方法が見つかりません。知恵をお貸しいただけると幸いです。 ### 実現したいこと #### 1. 受けた影だけを描画するシェーダ ARなので、影だけを描画したいのですが、URPに対応したシェーダでのやり方がわかりません。 このページで紹介されているカスタムノードを用いてシャドウのマップを取得することはできており、PBR Graph を用いて影の落ちた平面の影部分を透過することはできるのですが、透過を逆転させると影の落ちている部分と落ちていない部分の区別ができなくなります。(透過してしまった部分は影を受けられないから、という理解で合っているでしょうか...?) ![影だけ透過するシェーダ](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/514b70387c822277902a5d40aed9209e.png) ![影だけ透過するシェーダ](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/faf41932d469b2d1e131eb774a2dfcbb.gif) #### 2. 透明マント ハリーポッターの透明マントの要領で、自身が透明かつ自身よりも奥のオブジェクトをすべて透過する処理を実装したいです。このページを参考にいろいろいじってみたところ、一応はできました。(なぜか記事の通りにやっても同じことができなかったのですが、透明マントレイヤーと通常オブジェクトでレイヤーを分けてどちらもDefault Layer Maskから外したうえで、マント→オブジェクトの順でRender Features を用いて追加の描画をしています。ここでマントに透過オブジェクトを用いれば図のように処理できました。) ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/b386f7da28a00a8f437f342000a1c158.gif) しかし、Default Layer Maskから対象を外してしまった時点でオブジェクト同士のシャドウが描画されなくなってしまうのが気に入りません。先ほどの記事のように遮蔽部分の描画色を変えるだけならDefault Layer Maskから外さずにシャドウを維持して行えたのですが、この処理で遮蔽部分のシェーダに透過シェーダを適用すると下にもともとのオブジェクトが描画されているため思ったような表示になりません。
回答: 「受けた影だけを描画するシェーダ」について ===================== 下図のように、マスターノードの設定を乗算モードにしてやるというのはどうでしょうか? ついでに、影の色を調節できた方がいいかと思ってプロパティを用意し、そのRGBは影にスクリーン合成、さらにAに応じて白色を乗せてやり影を薄めるようにしてみました。 !図1 マスターノードですが、すみませんが勉強不足につきPBRマスターが内部で何をしているか分からず、余計な色を乗せてしまうかもしれないと不安になりまして、PBRよりも素直な色を描いてくれるだろうと期待してUnlitマスターを使用しました。 ですがこれだとシェーダーグラフのカスタムライティング:Unity 2019 でグラフを拡張する - Unity Technologies Blogのやり方そのままでは正しく`ShadowAtten`を得ることができず、Unity2019.3 URP ShaderGraph Unlit マスターで影を受けるの方法に基づいてライト情報取得ノードのコードを下記のように変更しました。 ``` #if SHADERGRAPH_PREVIEW Direction = half3(0.5, 0.5, 0); Color = 1; DistanceAtten = 1; ShadowAtten = 1; #else #if SHADOWS_SCREEN half4 clipPos = TransformWorldToHClip(WorldPos); half4 shadowCoord = ComputeScreenPos(clipPos); #else half4 shadowCoord = TransformWorldToShadowCoord(WorldPos); #endif Light mainLight = GetMainLight(); Direction = mainLight.direction; Color = mainLight.color; DistanceAtten = mainLight.distanceAttenuation; ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData(); half4 shadowParams = GetMainLightShadowParams(); ShadowAtten = SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture), TransformWorldToShadowCoord(WorldPos), shadowSamplingData, shadowParams, false); #endif ``` 「透明マント」について =========== 当初はレンダリングパイプラインのカスタマイズで何とかしようと思ったものの、ご質問者さんのおっしゃる影問題をどうにも解決できずあきらめました...もしかするとパイプラインの途中にちょこっと追加処理を挟むだけでは困難で、レンダリングパイプラインを完全に自作してやる必要があるかもしれません。 代替案として、カメラをもう一つ用意して背景だけを描かせ、メインのカメラのレンダリング時にはそれを透明マントの表面に貼り付ける方針を検討してみました。 まずシーン上にあるオブジェクトのうち、透明マントを着たオブジェクト(ご質問者さんの想定における薄紫色のキューブ)と透明マントを通すと見えなくなるオブジェクト(ご質問者さんの想定における不透明キューブ)のレイヤーをともに「InvisibilityCloakEffect」と名前を付けたレイヤーに設定しました。これ以外のレイヤーにあるものは透明マントを通しても見ることができるという想定です(背景オブジェクトなど)。 メインカメラの子としてもう一つカメラを作り(メインカメラを動かした際に追従して動かしたかったため)、下図のようにPriorityを-2、Output Targetを適当なレンダーテクスチャとしました。Culling MaskからはInvisibilityCloakEffectレイヤーを除外しています。 なお、もしおっしゃるように本当に自身よりも奥のオブジェクトをすべて透過する...つまり地面すら貫通して背景が見えるようにしたい場合はレイヤー分けすら不要で、Culling MaskをNothingにしてしまえばいいでしょう(ARとのことですので、背景は実写映像であって地面は不要ということでしょうか)。 Audio Listenerもメインカメラのものがあるので外しました。 !図2 メインカメラ側は特に設定はいじっていません。 !図3 透明マント用マテリアルは下図のような構成にしました。テクスチャプロパティとして「Albedo」と「Background」、色プロパティとして「Color」を作り、Albedoからサンプリングした色とColorを掛け、RGBにはAを乗算して暗くしてからアルベド色として出力しました。 Backgroundからはスクリーンスペース座標を基に色を取ってきて、アルファに応じて強度を落としてからエミッションに接続しました。 !図4 それを使ったマテリアルを作り、Backgroundにはサブカメラのレンダリング先となるレンダーテクスチャをセットしておきます。ColorやAlbedoを調整すると色を乗せることができ、それらのアルファが小さいほど背景色が優位となって透けていきます。 !図5 この結果、下図のような見た目になりました。 奥にある青玉、半透明の赤玉、市松模様の床はサブカメラのレンダリング対象になっているので透明マントを通して見えるのに対し、白と灰色のキューブは透明マントによって隠されます。透明マントを着た中央の紫キューブは実際のところ不透明オブジェクトとして扱われていますので、影を落としたり影を受けたりすることが可能です。 !図6 透過部分の映像はサブカメラに依存していますので、シーンビューでは背景がずれて表示されてしまう欠点がありますが、ゲームビュー上ではまともに見えるかと思います。この方式の利点としては、透明部分の見え方を透明マントマテリアル側でコントロールしやすいというのが挙げられるかもしれません。たとえばBackgroundテクスチャをサンプリングする際にサンプリング座標を適当に変調させてやれば、プレデターみたいに背景が歪んでかすかに存在を察知できる...みたいな表現もできそうです。