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でシェーダーを説明してくれるのでわかりやすい
    • コードベースのシェーダーいじりの入門としても良いかも

https://docs.unity3d.com/ja/2022.3/Manual/SL-SurfaceShaderExamples.html

  • シェーダーコードの読み方など
    • こちらも非常に丁寧にコードを解説してくれている

https://docs.unity3d.com/ja/2022.3/Manual/SL-VertexFragmentShaderExamples.html

  • NormalMapの基礎知識
    • 原理から解説していて非常にわかりやすい
    • NormalMapが青紫の理由など

https://docs.unity3d.com/ja/2022.3/Manual/StandardShaderMaterialParameterNormalMap.html

  • シェーダー内での法線情報の取り出し方など
    • _CameraDepthNormalsTextureの取り扱い

https://shiomusubi290.hatenablog.com/entry/2022/05/10/015240

https://light11.hatenadiary.com/entry/2020/02/03/210422

次点の記事

  • シェーダーコードの読み方
    • こちらもわかりやすかった

https://qiita.com/Yumuru/items/77a642004d015b83e0ce#texcoordについて

  • カメラの深度情報からNormalとワイヤーフレーム生成する例
    • ゴリゴリと計算して求めている作例
      • 今回の課題とちょっと異なるのでそのまま使えなかった
    • シェーダーコード全文は最終行にあり

https://qiita.com/HhotateA/items/247cf49dbcda9fd2fd71

  • Boothのワイヤーフレーム表示シェーダー
    • 法線の取り出し方の作例になるかなと思ってカスタムシェーダーを覗く
      • ちと古いのでunityfileが開けなかったのでカスタムシェーダーファイルのみインポート

https://booth.pm/ja/items/1069758

モンティノモンティノ

コード

時間がないので実験用そのまま。
整えたら直すかも。

シェーダーコード

  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);
    }
}