NTTドコモR&Dの技術ブログです。

Unity Shader勉強したので仮想世界から出てくるキャラARを追実装してみた

はじめに

NTTコノキューの新井です。

この度、全能の神でも持ち上がらない腰がようやく持ち上がったのでUnityのShaderについて勉強しました。 せっかくなのでコンテンツを作って、見られるかたちで公開したいと思います。

※この記事はNTTドコモR&D Advent Chalendar 2022の6日目記事です。

デモ

今回作ったものの動作です。

※ 本コンテンツはXRCity(TOP|XR City)で動作させています。

実装

見て分かった人もいるかもしれないですが、本実装の元ネタは下記のコンテンツです。

めちゃくちゃかわいい...... ということで、こちらを追実装することをモチベーションにしてShaderの勉強をします。

調べてみたところ、Moguraさんがインタビュー記事をまとめていました。

www.moguravr.com

どうやら構成は大まかに下記のように分けられるようです。

  1. 3Dモデル
  2. AR演出
  3. 等身大パネル

今回はShaderの就学という目的があるので、「3Dモデル」はVroidのサンプルモデルを、「等身大パネル」は適当な画像を代替し、「AR演出」に注目して実装方法を書きます。

「AR演出」の要素

AR演出をさらに分解していくと、下記のような要素に分けることができます。

① キャラクターがパネル内で動く演出

② パネルからキャラクターが出てくる演出

③ パネル周りのパーティクルライブっぽい演出

前述の通り今回はShaderに関して書きたいので、①と②を実装します。

YORIMIYAさんの動画を観察してみると、

①: パネル型にくりぬいた動画を再生する

②: キャラクターの描画範囲を絞る

といった内容で”それっぽく”見せることができそうです。 そのため、以降は①と②についての手順を詳しく書いていきます。

環境

  • Windows 11
  • Unity 2021.1.22f1
  • UnityChan

①: パネル型にくりぬいた動画を再生する

順序としては、動画を作る → パネル型にくりぬく。という手順で進めようと思います。 そして動画ですが、下記要素を満たす必要があります。

  • キャラクターがパネルの中にいる時は動画内で普通に見える
  • キャラクターがパネルから出てきている時は動画内にいなくなっている

そのため、パネルの位置を境界にして描画の有無を決定するShaderを作ります。

頂点フラグメントShaderをつくる

作りました。 Shaderです。

Shader "Custom/DrawRange"
{
  Properties
  {
    [NoScaleOffset] _MainTex ("SurfaceTexture", 2D) = "white" {}
    [NoScaleOffset] _MainTex2 ("LiningTexture", 2D) = "white" {}
    _Length ("Length", float) = 1
  }
  SubShader
  {
    Tags { "RenderType"="Opaque"}
    LOD 100
    Cull off

    Pass
    {
      CGPROGRAM
      #pragma vertex vert
      #pragma fragment frag

      #include "UnityCG.cginc"

      sampler2D _MainTex;
      sampler2D _MainTex2;

      float _Length;
 
      struct appdata
      {
        fixed2 uv : TEXCOORD0;
        half4 vertex : POSITION;
      };
 
      struct v2f
      {
        fixed2 uv : TEXCOORD0;
        half4 vertex : SV_POSITION;
        half4 wpos : TEXCOORD1;
      };
  
      v2f vert(appdata v)
      {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.wpos = mul(unity_ObjectToWorld, v.vertex);
        o.uv = v.uv;
        return o;
      }
 
      fixed4 frag (v2f i, fixed facing : VFACE) : SV_Target
      {
        fixed4 color = tex2D(_MainTex, i.uv);
        float v = abs(i.wpos.x) <= _Length/2 ? (abs(i.wpos.y) <= _Length/2 ? (abs(i.wpos.z) <= _Length/2 ? 1 : -1) : -1) : -1;
        clip(v);
        return (facing > 0)? color : tex2D(_MainTex2, i.uv);
      }
      ENDCG
    }
  }
}

処理を解説していきます。 が、その前に動作を見てください。 直観的に理解できると思います。

では重要な部分を抜粋して解説をします。

Cull off

まず初めにCullの設定です。 こちらを記述することで、ポリゴンの裏側を描画するようになります。 デフォルトではCull backになっているみたいです。 今回はパネルから出る瞬間のキャラクター断面をきれいにするために設定しています。

fixed4 frag (v2f i, fixed facing : VFACE) : SV_Target
{
  fixed4 color = tex2D(_MainTex, i.uv);
  float v = abs(i.wpos.x) <= _Length/2 ? (abs(i.wpos.y) <= _Length/2 ? (abs(i.wpos.z) <= _Length/2 ? 1 : -1) : -1) : -1;
  clip(v);
  return (facing > 0)? color : tex2D(_MainTex2, i.uv);
}

Fragment Shader部分です。 まずはセマンティクスにfixed facing : VFACEを追加しています。 これによって、Fragment Shaderに渡されたメッシュがカメラから見てどちらの方向を向いているかとることができます。

また4行目では、渡された頂点が立方体の範囲内なのか判定を行っています。

最後にfacingの値を確認し、ポリゴンの表裏に対応してテクスチャを変えて描画します。

ちなみにVRMモデルやFBXモデルを利用していて、テクスチャのCutOffなどを操作したい場合は下記のように修正/追加しましょう。

Properties
{
  _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5 // 追加する
}
fixed _Cutoff; // Pass内に追加する

fixed4 frag (v2f i, fixed facing : VFACE) : SV_Target
{
  fixed4 color = tex2D(_MainTex, i.uv);
  float v = abs(i.wpos.x) <= _Length/2 ? (abs(i.wpos.y) <= _Length/2 ? (abs(i.wpos.z) <= _Length/2 ? 1 : -1) : -1) : -1;
  clip(v == 1 ? color.a - _Cutoff : -1); // 修正する
  return (facing > 0)? color : tex2D(_MainTex2, i.uv);
}

ここまでのShaderによって、「パネルから出ていっているキャラクター」っぽく見せる動作が実現できると思います。

動画を撮る

先ほどのShaderを使って動画を撮りましょう。

まずは下記を参考にしてUnityにUnity Recorderを導入しましょう。

dkrevel.com

もろもろうまいことやって動画を撮ります。

できました。

動画をくりぬいて再生する

パネル型に動画をくりぬきます。 動画編集技術ではなくShaderで作ります。

実装に関してぱっと思いつくのがマスク処理です。 Unityでどうやって実装するかというと、Stencil Shaderを利用します。

  • MaskオブジェクトとMaskedオブジェクトをつくる
  • MaskオブジェクトではStencilバッファに値を書き込む
  • MaskedオブジェクトではStencilバッファの値を読み取って描画を実行する

これによって、パネルと同じStencilバッファ形状のマスク処理が実行され、動画をパネル型に切り抜くことができそうです。

ステンシルShaderをつくる

作りました。 Shaderです。

まずはMask用Shaderです。

Shader "Custom/UnityChanPanelMask"
{
  Properties
  {
    [NoScaleOffset] _MainTex("Texture", 2D) = "white"{}
  }

  SubShader
  {
    Tags {"Queue"="Geometry"}
    Pass
    {
      Stencil
      {
        Ref 2
        Comp always
        Pass replace
      }

      CGPROGRAM
      #pragma vertex vert_img
      #pragma fragment frag

      #include "UnityCG.cginc"

      sampler2D _MainTex;

      fixed4 frag (v2f_img i) : SV_Target
      {
        fixed4 color = tex2D(_MainTex, i.uv);  
        return color;
      }
      ENDCG
    }
  }
}

内容としては、下記のStencil内でバッファを2に置き換えてます。

Stencil
{
  Ref 2
  Comp always
  Pass replace
}

次にMasked用Shaderです。

Shader "Custom/UnityChanPanelMasked"
{
  Properties
  {
    [NoScaleOffset] _MainTex("Texture", 2D) = "white"{}
  }

  SubShader
  {
      Tags {"Queue"="Geometry+1"}
      Pass
      {
        Stencil
        {
          Ref 2
          Comp equal
        }
        ZTest Always

        CGPROGRAM
        #pragma vertex vert_img
        #pragma fragment frag

        #include "UnityCG.cginc"

        sampler2D _MainTex;

        fixed4 frag (v2f_img i) : SV_Target
      {
        return tex2D(_MainTex, i.uv);
      }
      ENDCG
    }
  }
}

こちらの内容としては、下記の記述でStencilバッファが2の場合に描画する対象として選択しています。

Stencil
{
  Ref 2
  Comp equal
}

ここまでのShaderを組み合わせていい感じの画像に適応すると下記のようになります。

Shape

ここまでの全部を組み合わせてみる

成果物がこちらになります。 かなりやりたいことに近づいてきました。

②: キャラクターの描画範囲を絞る

次にキャラクターがボードから出てくる処理をShaderでつくります。

...と思いましたが、ここまででもうできていました。 描画範囲を限定していたDrawRange Shaderを少しいじれば、キャラクターがボードから出てくる処理っぽいものを実装出来そうです。

Shaderをコピペするなどして実装しましょう。

以上で実装については終了です。

実行結果

ではここまでのすべてを組み込んでコンテンツにしましょう。

しました。

これで冒頭のARコンテンツが完成しました。

おわりに

今回はUnityのShaderを使って3Dキャラクターが転送されてくるコンテンツを実装してみました。 Shaderは完全に初心者の状態から書いたため、内容に間違いなどありましたら本記事へコメントをつけていただけると幸いです。

参考

ライセンス表記

  • © UTJ/UCL