Open5
RenderTextureでNormalMap生成

課題
VRChat上のギミック付きオブジェクトを作成するケース。
動的にテクスチャを作成する仕組みとしてサブカメラ + RenderTextureの組み合わせが考えられる。
この動的に作成したテクスチャに、法線情報(NormalMap)を使えるようにして質感を上げたい。

解決策(概要)
- Unityのカメラは深度と法線情報を取得するモードに設定できる
- スクリプトからモードを切り替える
- サーフェイスシェーダーをカスタムしてRenderTextureと法線情報を利用できるようにする
- RenderTextureをalbedo
- カメラから法線情報はシェーダーで処理を書いて内部で取り出す
ハマりポイント
シェーダーコード内でカメラの法線情報は簡単に取り出せる。
そいつを一度NormalMapのテクスチャ化しようとして大いにハマった。
通常、サーフェイスシェーダーは外部から与えられたNormalMapテクスチャを解凍して法線情報にして取り扱う。
今回はカメラからほぼ解凍後のデータとして取得できているため、テクスチャ化せずにそのままサーフェイスシェーダーに突っ込めば良かったのだ。
テスト用中間成果物
- 左:普通のRenderTexture
- 右:法線情報を付加したRenderTexture(surfaceシェーダーカスタム)
本来のアバターのShaderはToon調のため、ワールドの光源の影響をあまり受けないが、サーフェイスシェーダーで法線情報を付加したRenderTexuteではわーる井戸の光源の影響を受けている。

知識
データ構成について
カメラ画像をQuadに表示するケースの構成
通常のRenderTexture構成
- カメラ : 出力先をRenderTextureへ設定
- RenderTexture : カメラで撮影したデータが描画される
- Material : Unlitシェーダーに上記RenderTextureを設定
- Quad : Materialを適用し、カメラ画像を表示するオブジェクト
解決案の構成
- カメラ : 出力先をRenderTextureへ設定
- スクリプト追加:Start()にてDepthNormalを有効化する
- RenderTexture : カメラで撮影したデータが描画される
- Material : カスタムサーフェイスシェーダーを作成
- 内部でカメラのNormalを取得
- Z方向の修正
- サーフェイスの出力に修正したNormalを設定
- Quad : Materialを適用し、カメラ画像を表示するオブジェクト

シェーダー基礎知識
- サーフェイスシェーダーのカスタム方法:法線の追加
- StepByStepでシェーダーを説明してくれるのでわかりやすい
- コードベースのシェーダーいじりの入門としても良いかも
- シェーダーコードの読み方など
- こちらも非常に丁寧にコードを解説してくれている
- NormalMapの基礎知識
- 原理から解説していて非常にわかりやすい
- NormalMapが青紫の理由など
- シェーダー内での法線情報の取り出し方など
- _CameraDepthNormalsTextureの取り扱い
次点の記事
- シェーダーコードの読み方
- こちらもわかりやすかった
- カメラの深度情報からNormalとワイヤーフレーム生成する例
- ゴリゴリと計算して求めている作例
- 今回の課題とちょっと異なるのでそのまま使えなかった
- シェーダーコード全文は最終行にあり
- ゴリゴリと計算して求めている作例
- Boothのワイヤーフレーム表示シェーダー
- 法線の取り出し方の作例になるかなと思ってカスタムシェーダーを覗く
- ちと古いのでunityfileが開けなかったのでカスタムシェーダーファイルのみインポート
- 法線の取り出し方の作例になるかなと思ってカスタムシェーダーを覗く

コード
時間がないので実験用そのまま。
整えたら直すかも。
シェーダーコード
Shader "Example/Diffuse Bump" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_BumpMap ("Bumpmap", 2D) = "bump" {}
}
SubShader {
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma surface surf Lambert
struct Input {
float2 uv_MainTex;
float2 uv_BumpMap;
};
sampler2D _CameraDepthNormalsTexture;
sampler2D _MainTex;
sampler2D _BumpMap;
void surf (Input IN, inout SurfaceOutput o) {
o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
// o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
float4 cdn = tex2D(_CameraDepthNormalsTexture, IN.uv_BumpMap);
o.Normal = DecodeViewNormalStereo(cdn) * float3(1.0, 1.0, -1.0);
}
ENDCG
}
Fallback "Diffuse"
}
カメラ用スクリプト(UdonSharp)
冒頭の構成にしたので、OnRenderImageいらない。
Start()だけあればよいはず。こちらも整えたら差し替えるかも。
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
public class DepthNormal : UdonSharpBehaviour
{
[SerializeField]
private Material _material;
void Start () {
// たとえばライトのShadow TypeがNo Shadowsのときなどに
// これが設定されていないとデプステクスチャが生成されない
GetComponent<Camera>().depthTextureMode = DepthTextureMode.DepthNormals;
}
private void OnRenderImage(RenderTexture source, RenderTexture dest)
{
//こいつは禁止されているようだ
// Graphics.Blit(source, dest, _material);
VRCGraphics.Blit(source, dest, _material);
}
}