MRが楽しい

MRやVRについて学習したことを書き残す

Hololensで壁の向こうの世界が見える窓を作る その3

hololensの学習枠の続きです。
bluebirdofoz.hatenablog.com

以下の技術ブログを参考にしています。
tips.hecomi.com

今回は「方法2」プロジェクトの動作を確認します。
HoloLens_Stencil_Window/Scenes にある Pattern2 のシーンです。
f:id:bluebirdofoz:20170804011026j:plain

ビルド設定の方法や、Skyboxのマテリアルの設定方法は「方法1」と変わらないので省略します。
bluebirdofoz.hatenablog.com

hololensにインストールして起動します。
窓の向こう側に空とCubeオブジェクトが見えました。
f:id:bluebirdofoz:20170804011046j:plain

方法1と明確に違うのは、窓が壁に設置していない点です。
f:id:bluebirdofoz:20170804011114j:plain
窓以外の壁を遮蔽する仕組みの方法1と、窓を通した場合のみ描画する仕組みの方法2の違いの一つですね。


では合わせてプロジェクト内のコードを確認して、方法2の理解を深めます。
今回のシェーダは、窓を通したときのみ描画を行う MaskedSky のシェーダと、窓を開ける Window オブジェクトのシェーダです。

まずは、窓を開ける Window オブジェクトのシェーダですが、これは方法1の Window.shader と同じです。
窓オブジェクトの位置のステンシルバッファに 1 を書き込みます。

ですので早速、窓を通したときのみ描画を行う Skybox シェーダについて読み解きます。
・MaskedSky.shader

Shader "HoloLens/MaskedSky"
{
  Properties
  {
    _Mask("Mask", Int) = 1
    _Tint ("Tint Color", Color) = (.5, .5, .5, .5)
    [Gamma] _Exposure ("Exposure", Range(0, 8)) = 1.0
    [NoScaleOffset] _Cube ("Cubemap (HDR)", Cube) = "grey" {}
  }
  SubShader
  {
    // タグ付け
    Tags 
    { 
      // "RenderType(レンダータイプ)"を"Opaque(不透明)"に指定
      "RenderType" = "Opaque" 
      // Background→Geometry→AlphaTest→Transparent→Overlayの順で描画される。
      // つまり通常オブジェクトより背景に近いシェーダとして設定している
      "Queue" = "Geometry-1"
      // マテリアルのインスペクタープレビューの表示形式
      // Skybox:空テクスチャのように表示
      "PreviewType" = "Skybox"
    }
    CGINCLUDE
    // "UnityCG.cginc"をインクルード
    #include "UnityCG.cginc"

    samplerCUBE _Cube;
    half4 _Cube_HDR;
    half4 _Tint;
    half _Exposure;

    // v2f構造体の定義
    // 頂点シェーダからフラグメントシェーダに複数の値を渡す時に利用する
    struct v2f 
    {
      // float4 SV_POSITION  MVP 変換後の座標
      float4 vertex : SV_POSITION;
      // float3 TEXCOORD0    1番目のテクスチャの UV 座標
      float3 texcoord : TEXCOORD0;
      // VRのシングルパスステレオレンダリングのマクロ
      UNITY_VERTEX_OUTPUT_STEREO
    };
    // 頂点シェーダ
    v2f vert(appdata_base v)
    {
      // 返り値として利用するv2f構造体の変数を作成
      v2f o;
      // インスタンス ID がシェーダー関数にアクセス可能になる
      UNITY_SETUP_INSTANCE_ID(v);
      // VR向けの頂点シェーダーへの変換を行う
      UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
      // 同次座標において、オブジェクト空間からカメラのクリップ空間へ点を変換する
      o.vertex = UnityObjectToClipPos(v.vertex);
      // 物体の頂点ごとのワールド座標を取得し、テクスチャの UV 座標に代入している
      o.texcoord = v.vertex.xyz;
      return o;
    }
    // フラグメントシェーダ
    fixed4 frag(v2f i) : SV_Target
    {
      // テクスチャの UV 座標からカラーを取得している
      half4 tex = texCUBE(_Cube, i.texcoord);
      half3 c = DecodeHDR(tex, _Cube_HDR);
      // ガンマ補正やら何やら……
      c = c * _Tint.rgb * unity_ColorSpaceDouble.rgb;
      c *= _Exposure;
      // 最終的なカラーを出力する
      return half4(c, 1);
    }
    ENDCG

    Pass
    { 
      // Cull:ポリゴンのどちら側をカリングする(描画しない)か制御。
      // Front 表側のポリゴンをレンダリングしない。オブジェクトを反転するのに使用する。
      Cull Front
      // ZWrite:デプスバッファに書き込みするか制御
      // 不透明なオブジェクトを描画する場合、On
      ZWrite On

      // Stencil:ステンシルバッファはピクセルマスクごとにピクセルを保存や廃棄することを目的とする
      // ステンシルバッファは、通常、1 ピクセルあたり 8 ビットの整数である
      Stencil 
      {
        // バッファに_Maskの 1 の整数が書き込まれる
        Ref [_Mask]
        // Comp:関数はバッファの現在の内容と基準値の比較に使用される
        // NotEqual:ピクセルのレファレンス値がバッファの値と等しい場合のみレンダリングする
        Comp Equal
      }

      CGPROGRAM
      #pragma fragmentoption ARB_precision_hint_fastest
      #pragma vertex vert
      #pragma fragment frag
      #pragma target 5.0
      #pragma only_renderers d3d11
      ENDCG
    }
  }
}

重要なのは Stencil 内の Comp Equal です。
要は方法1と異なり、ステンシルバッファに 1 が書き込まれている場所のみ、Skybox の描画を行うという仕組みです。
Cube オブジェクトについても MaskedObject という全く同じレンダリング条件を持ったシェーダが割り当てられています。

また、本プロジェクトでは、窓に対して、一つの Skybox と 一つのオブジェクトでしたが。
この方法2ではステンシルバッファへの書き込み値を変えることで、窓ごとに表示オブジェクトを変更することが可能です。

試してみましょう。
プロジェクトに、新しい窓、オブジェクト、Skyboxを用意します。これらのシェーダのマスク値は"2"を設定します。
f:id:bluebirdofoz:20170804011213j:plain

アプリを起動します。一方の窓からは先ほどと同様、空とCubeオブジェクトが確認できます。
f:id:bluebirdofoz:20170804011224j:plain

新しい窓を覗いてみると……。
f:id:bluebirdofoz:20170804011232j:plain
夜空とSphereオブジェクトが確認できました。


さて、これで技術に関する理解は完了です。理解してみると、仕組みとしては思った以上に単純です。
機会があれば活用アイデアを考えて自作アプリへ応用してみます。