壁の向こうの異世界
8時間で終わらなかったので追加作業
— 1pp0 (@1pp0qq1) June 23, 2019
ARFoundationとステンシルバッファ何となく理解した #hachitai pic.twitter.com/9mTfRCuDJQ
はじめに
本記事はUnityのARFoundationを使用して、現実空間で認識された平面越しに異世界(AR空間)を表示する仕組みの備忘録で、Unityで認識した平面をポータル化して異世界を覗く【ARFoundation編】の続きになります。
前記事では、現実空間にAR空間を重畳して表示する所まで説明しました。本記事では、重畳されたAR空間を平面を通してのみ表示する、ステンシルバッファを用いた平面のポータル化について説明していきます。
ステンシルバッファによるポータル表現
ステンシルバッファは、一般的にピクセルマスクごとにピクセルの保存や廃棄を行うために使用されます。ステンシルバッファは、通常、1 ピクセルあたり 8 ビットの整数です。値はインクリメントまたはデクリメントで書き込みできます。後続の描画呼び出しは値をテストでき、ピクセルシェーダーを実行する前にピクセルを廃棄する必要があるか判断します。
参考:ShaderLab: ステンシル
ステンシルバッファではピクセル毎に表示非表示を決められるため、下記の手順により平面として認識されたピクセルのみAR空間のオブジェクトを表示することで、平面をポータル化する事ができます。
- AR空間の映像をテクスチャに書き込んで画面全体に表示
- 平面領域のピクセルをステンシルバッファのレファレンス値に書き込む
- 1のテクスチャからレファレンス値を参照して平面領域のピクセルのみ表示
実装
AR空間のカメラ映像をテクスチャに表示
Unityでカメラ映像をテクスチャに書き込むには、RenderTexture
を使用します。
RenderTexture
をPlane
に適用する事で、AR空間を写した映像をAR Cameraで表示できるようにします。
Projectを右クリックして
Create > RenderTexture
を作成World CameraのTarget Textureに作成したARWorldCameraRenderTextureを設定
この状態でエディタを再生すると、World Cameraに映るAR空間の映像がARWorldCameraRenderTextureに反映されている事を確認できます。
ARWorldCameraRenderTextureの映像表示用
Plane
を作成
ARWorldCameraRenderTextureに書き込んだAR空間の映像をAR Cameraに表示できるよう、AR Cameraの子としてPlane
を作成し、CameraPreviewの全体を占めるよう位置とサイズを調整します。
このPlane
にRenderTexture
を反映できるよう、Shader
及びMaterial
を作成して適用します。Projectを右クリックして
Create > Shader > Unlit Shader
を作成Material
を作成してARWorldCameraRenderTextureを適用
5.で作成したシェーダーのファイル名をRenderTexturePlaneShaderに変更し、そのファイルの上で右クリックしてCreate > Material
を選択するとUnlit_RenderTexturePlaneShaderが作成されます。
作成されたUnlit_RenderTexturePlaneShaderのTextureにARWorldCameraRenderTextureを設定します。
PlaneにUnlit_RenderTexturePlaneShaderを適用
これでAR空間のカメラ映像を、AR Cameraに表示できるようになりました!
この段階では、AR Cameraに本来表示される現実空間のカメラ映像の上全体に、AR空間の映像が重畳されている状態です。ここからステンシルバッファにより、平面として検出された領域のAR空間の映像のみを表示することで、冒頭の動画のような平面領域のポータル化が実現できます。
ステンシルバッファによる平面領域のAR空間描画
最初に平面領域として検出されたピクセルにステンシルバッファのレファレンス値を書き込みます。次に、AR空間の映像を表示しているPlaneからそのレファレンス値を確認し、平面領域として書き込まれているピクセルのみを描画することで、平面領域として検出された領域のみAR空間が表示されるようになります。
ステンシルバッファのシェーダーは下記記事を参考に改変しています。
【Unity】ステンシルバッファの復習#シェーダーコード
平面領域にレファレンス値を書き込み
Projectを右クリックして Create > Shader > Unlit Shader
を作成し、ファイル名をPlaneAreaTransparentShaderに変更して下記コードで上書きします。
Shader "Custom/PlaneAreaTransparentShader" {
SubShader {
Tags { "RenderType"="Transparent" "Queue"="Geometry" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Stencil {
Ref 1
Comp Always
Pass Replace
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct appdata {
float4 vertex : POSITION;
};
struct v2f {
float4 pos : SV_POSITION;
};
v2f vert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
return o;
}
half4 frag(v2f i) : SV_Target {
return half4(0,0,0,0);
}
ENDCG
}
}
}
参考元からの変更点としては、平面領域はレファレンス値を書き込むだけで何も表示しないようにするため、"RenderType"="Transparent"
、ZWrite Off
、Blend SrcAlpha OneMinusSrcAlpha
、return half4(0,0,0,0);
として透明にしています。
このPlaneAreaTransparentShaderからCustom_PlaneAreaTransparentShaderを作成し、AR Default Planeに設定されている、Mesh Renderer > Materials > Element0 > DebugPlane
及び、Line Renderer > Materials > Element0 > Default-Line
と置き換えます。
以上の処理により、平面領域として検出された領域は透明で表示され、そのピクセルのステンシルバッファには、レファレンス値1が書き込まれるようになります。
AR空間の映像を平面領域箇所のみ表示
AR空間の映像を表示しているPlaneからレファレンス値を確認し、平面領域として書き込まれているピクセルのみ表示するようにRenderTexturePlaneShaderを修正します。
Shader "Unlit/RenderTexturePlaneShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry+1"}
Stencil {
Ref 1
Comp Equal
}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
全コードを掲載していますが、Unlit Shaderからの変更点は、"Queue"="Geometry+1"
及び、Stencil {Ref 1 Comp Equal}
の追加のみです。
AR空間の映像を表示しているPlaneは、レファレンス値として1が書き込まれている平面領域のみが表示されるようになり、平面領域のポータル化が実現します!
この修正の適用によりエディタのシーン上ではPlaneが表示されなくなりますが、これはPlaneの後ろにAR空間を表示するべき平面領域が存在しないためなので正しい挙動となります。
ビルドして実機で動作を確認すると、冒頭の動画のように平面領域として認識された領域のみAR空間が表示されます!
おわりに
「Unityで認識した平面をポータル化して異世界を覗く」の説明は以上となります。
ARFoundationの導入方法や、平面領域の可視化方法については、前記事Unityで認識した平面をポータル化して異世界を覗く【ARFoundation編】にて説明してますので、併せてご覧ください!