窓シェーダーを使った仕掛けの作り方

この記事では、「窓越しに見たときだけオブジェクトが現れる」ギミックを作れる窓シェーダーの使い方を紹介します。
サンプルプロジェクトでは窓シェーダーを使ったいくつかのギミックを紹介していますので、これを参考にオリジナルのギミックを考案してみてください。

サンプル

STYLY GALLERYからサンプル空間を体験できます。
9 Windows

記事で紹介しているUnityプロジェクトのダウンロードはこちらからできます。
STYLY-Unity-Examples

窓シェーダーとは?

窓シェーダーは「窓越しに見たときだけオブジェクトが現れる」ギミックを作れるシェーダーで、凹さんのブログで紹介されているものです。
使い方次第でいろいろな表現が可能なシェーダーで、空間の中で自由に視点を動かせるVRと特に相性が良いと言えます。
あっと驚く仕掛けをシェーダーだけで簡単に作ることができるので、初心者にもおすすめのテクニックです。

「窓」と「窓の向こうに置くオブジェクト」のそれぞれに専用のシェーダーをアタッチすることでギミックが成立します。

技術的な解説は凹さんのブログで詳しく紹介されていますので、気になる方はチェックしてみてください。

窓シェーダーのコード

窓シェーダー/Window Shader

Shader "Window"
{

Properties
{
   _Mask ("Mask", Int) = 1
}

SubShader
{

Tags 
{ 
    "RenderType" = "Opaque" 
    "Queue" = "Geometry-2"
}

CGINCLUDE

#include "UnityCG.cginc"

struct v2f
{
    float4 vertex : SV_POSITION;
    UNITY_VERTEX_OUTPUT_STEREO
};

v2f vert(appdata_base v)
{
    v2f o;
    UNITY_SETUP_INSTANCE_ID(v);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
}

fixed4 frag (v2f i) : SV_Target
{
    return 0;
}

ENDCG

Pass
{
    ColorMask 0
    ZWrite Off
    Stencil 
    {
        Ref [_Mask]
        Comp Always
        Pass Replace
    }

    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    ENDCG
}

}

}

窓の向こうに置くオブジェクト用のシェーダー/Masked Object Shader

Shader "MaskedObject" 
{

Properties 
{
    _Mask("Mask", Int) = 1
    _Color ("Color", Color) = (1,1,1,1)
    _MainTex ("Albedo (RGB)", 2D) = "white" {}
    _Glossiness ("Smoothness", Range(0,1)) = 0.5
    _Metallic ("Metallic", Range(0,1)) = 0.0
}

SubShader 
{

Tags 
{ 
    "RenderType" = "Opaque" 
}

Stencil 
{
    Ref [_Mask]
    Comp Equal
}
    
CGPROGRAM

#pragma surface surf Standard fullforwardshadows
#pragma target 3.0

sampler2D _MainTex;

struct Input 
{
    float2 uv_MainTex;
};

half _Glossiness;
half _Metallic;
fixed4 _Color;

void surf(Input IN, inout SurfaceOutputStandard o) 
{
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;
    o.Metallic = _Metallic;
    o.Smoothness = _Glossiness;
    o.Alpha = c.a;
}

ENDCG

}

}

窓の向こうに置くオブジェクト用のシェーダー(Sprite用)/Masked Object Shader (Sprite)

Shader "MaskedObject/Sprite"
{
	Properties
	{
		[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
		_Mask ("Mask", Int) = 1
		_Color ("Tint", Color) = (1,1,1,1)
		[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
	}

	SubShader
	{
		Tags
		{ 
			"Queue"="Transparent" 
			"IgnoreProjector"="True" 
			"RenderType"="Opaque" 
			"PreviewType"="Plane"
			"CanUseSpriteAtlas"="True"
		}

		Cull Off
		Lighting Off
		ZWrite Off
		Blend One OneMinusSrcAlpha

		Stencil {
		Ref [_Mask]
		Comp Equal
		}

		Pass
		{
		CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile _ PIXELSNAP_ON
			#include "UnityCG.cginc"
			
			struct appdata_t
			{
				float4 vertex   : POSITION;
				float4 color    : COLOR;
				float2 texcoord : TEXCOORD0;
			};

			struct v2f
			{
				float4 vertex   : SV_POSITION;
				fixed4 color    : COLOR;
				float2 texcoord  : TEXCOORD0;
			};
			
			fixed4 _Color;

			v2f vert(appdata_t IN)
			{
				v2f OUT;
				OUT.vertex = UnityObjectToClipPos(IN.vertex);
				OUT.texcoord = IN.texcoord;
				OUT.color = IN.color * _Color;
				#ifdef PIXELSNAP_ON
				OUT.vertex = UnityPixelSnap (OUT.vertex);
				#endif

				return OUT;
			}

			sampler2D _MainTex;
			sampler2D _AlphaTex;
			float _AlphaSplitEnabled;

			fixed4 SampleSpriteTexture (float2 uv)
			{
				fixed4 color = tex2D (_MainTex, uv);

#if UNITY_TEXTURE_ALPHASPLIT_ALLOWED
				if (_AlphaSplitEnabled)
					color.a = tex2D (_AlphaTex, uv).r;
#endif //UNITY_TEXTURE_ALPHASPLIT_ALLOWED

				return color;
			}

			fixed4 frag(v2f IN) : SV_Target
			{
				fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;
				c.rgb *= c.a;
				return c;
			}
		ENDCG
		}
	}
}

Unityで窓シェーダーを使用する

それでは実際に窓シェーダーを利用してみましょう。

シェーダーを作成する

まずはシェーダーの作成を行います。

①プロジェクトウィンドウにて右クリックし、「Create」から「Sandard Surface Shader」を選択して新規シェーダーを2つ作ります。

シェーダーの作成

シェーダーの作成

②一つは「Window Shader」と名前を付け、もう一つは「Masked Object Shader」と名付けましょう。
③作成したシェーダーをダブルクリックし、コードの編集を行います。元のコードをすべて削除したら、上の項で紹介したシェーダーのコードをコピー&ペーストします。
「Window Shader」には「窓シェーダー」のコードをコピペし、「Masked Object Shader」のほうは「窓の向こうに置くオブジェクト用のシェーダー」のコードをコピペします。

シェーダーの編集

シェーダーの編集

シェーダーの編集が完了したら保存を行い、Unityの画面に戻りましょう。

これでシェーダーの作成が終わりました。

マテリアルを作成する

次に、マテリアルの作成を行います。

①プロジェクトウィンドウにて右クリックし、「Create」から「Material」を選択して新規マテリアルを2つ作ります。

マテリアルの作成

マテリアルの作成

②一つは「Window Material」と名前を付け、もう一つは「Masked Object Material」と名付けましょう。
②作成したマテリアルのインスペクタを開き、インスペクタの何も入力されていない部分に先ほど作成したシェーダーをドラッグ&ドロップします。
「Window Material」には「Window Shader」をドラッグ&ドロップし、「Masked Object Material」には「Masked Object Shader」をドラッグ&ドロップします。

(あるいは、Shaderのプルダウンメニューから該当のShaderを選択することでも設定可能です。)

マテリアルにシェーダーをアタッチ

マテリアルにシェーダーをアタッチ

これでマテリアルが完成しました。

窓を作成する

次に「窓にするオブジェクト」と「窓越しで見るオブジェクト」を用意します。
これらはどんなものを使用しても問題ありません。

今回はシンプルに、「Quad」を利用してオーソドックスな窓を作成します。

ヒエラルキーウィンドウにて右クリックし、「3D Object」から「Quad」を作成して「Window」と名前を付けましょう。
ここで注意点があります。「Quad」には表と裏があり、表から見ると面が見えますが裏から見ると面が見えません。窓シェーダーをアタッチした際も、表面から覗く必要があります。

「Window Material」をアタッチするとメッシュは見えなくなってしまいますので、必要であればフレームを作りましょう。
今回は「Box」を変形して簡易的にフレームを作成します。

まずはヒエラルキーウィンドウにて右クリックし、「3D Object」から「Cube」を作成して「frame_01」と名付けましょう。
作成した「frame_01」を「Window」の上にドラッグ&ドロップし、「frame_01」を「Window」の子オブジェクトにします。

「frame_01」のインスペクタを開き、Scaleの値を変更します。X「1」Y「0.01」Z「0.01」と入力しましょう。

フレームの作成

フレームの作成

フレームの色を変えたい場合は、新しいマテリアルを作成し、フレームにアタッチします。
プロジェクトウィンドウにて右クリックし、「Create」から「Material」を作成して「Frame」と名付けましょう。
「Frame」のインスペクタを開き、「Albedo」の隣のカラー選択ツールをクリックしましょう。
カラー選択ツールが出てくるので好きな色を選択します。今回は黒を選択しました。

作成したマテリアルを「frame_01」にアタッチすると、マテリアルが適用されます。

色の選択

色の選択

あとはこれを4つにコピーし、それぞれ正しい位置に配置するだけです。「frame_01」にカーソルを合わせて右クリックすると「Copy」「Paste」が選択できますので、これを利用してコピーが可能です。
以下の図を参考に、4つのフレームのPositionとRotationを変更してください。

フレームの位置を変更

フレームの位置を変更

次に窓を覗いた先に「窓越しで見るオブジェクト」を配置しましょう。
今回はシンプルに、「Cube」を配置します。

ヒエラルキーウィンドウを右クリックし、「3D Object」から「Cube」を作成して「Masked_object」と名前を付けましょう。
「Masked_object」のインスペクタを開き、Scaleの値をX「0.25」Y「0.25」Z「0.25」と変更したら、「Window」の少し後ろに配置しましょう。

マテリアルをアタッチする

作成したマテリアルをアタッチすれば、窓シェーダーのギミックは完成します。

マテリアルのアタッチ

マテリアルのアタッチ

「Window」に「Window Material」をアタッチし、「窓越しで見るオブジェクト」に「Masked Object Material」をアタッチしましょう。
これでギミックの完成です。

窓シェーダーの完成

窓シェーダーの完成

【上級】スプライトに窓シェーダーを適用する

スプライトは2Dグラフィック用のオブジェクトで、VR空間に文字や画像を配置するときに大変便利です。
スプライトを窓で覗いた時だけ現れるようにしたい場合は、スプライト用のシェーダー「Masked Object Shader(Sprite)」を利用する必要があります。
「Window Shader」は共通です

今回は下の画像のように、空中に浮いている文字を作成します。

Spriteに窓シェーダーのギミックを適用する

Spriteに窓シェーダーのギミックを適用する

まずはSpriteとして使用する画像を用意します。今回のように文字を切り抜いて表示したい場合は、背景が透過している画像(.png)を用意しましょう。
透過画像の例を用意しましたので、こちらをコピーして使用すれば同じものを作成できます。

透過画像の例

透過画像の例

画像を用意したら、Unityのプロジェクトウィンドウにドラッグ&ドロップし、画像を読み込みます。

画像をUnityに読み込む

画像をUnityに読み込む

画像のインスペクタを開くと、Texture Typeの項が「Default」に設定されていますので、プルタブから「Sprite(2D and UI)」に変更します。
その後、インスペクタの下のほうにあるApplyボタンを押し、変更を適用します。

Texture Typeの変更

Texture Typeの変更

これで画像をSpriteとして使う準備が整いました。プロジェクトウィンドウの画像をヒエラルキーウィンドウにドラッグ&ドロップすることで、Spriteがシーンに現れます。
適当な大きさに変更し、「Window」の少し後ろに配置します。

あとは、「Masked Object Material」を作成したときと同様の手順でマテリアルを作成してアタッチすることで機能します。
Sprite用のシェーダー「Masked Object Shader (Sprite)」は上の項で紹介しています。

【上級】マスク値を変更する

窓シェーダーにはマスク値が設定されており、「Window Shader Material」のマスク値と「Masked Object Material」のマスク値が一致する場合のみ、オブジェクトが見えるようになっています。
これを利用することで「複数の窓を覗いた時、それぞれ見えるものが異なる」というギミックを作成することができます。

マスク値を使い分けた作品例

マスク値を使い分けた作品例

Mask値の変更方法はとても簡単です。
マテリアルのインスペクタを開き、「Mask」の値を任意の値に変更することでマスク値を変更することができます。

Mask値の変更

Mask値の変更

マスク値の異なるマテリアルを複数用意し、窓とオブジェクトのマスク値を使いわけることで上記画像のようなギミックを作成することができます。

マスク値の使い分け例

マスク値の使い分け例

STYLYにインポート

STYLYに窓シェーダーを利用したギミックをインポートする方法はとても簡単です。
作成したオブジェクトをプレハブ化し、STYLYにアップロードするだけです。

UnityからSTYLYにアセットをアップロードする方法は以下の記事で詳しく解説しています。
記事を読む

 

窓シェーダーを使えばアイデア次第で様々なギミックを作成できます。
この記事を参考にいろいろな表現を模索してみてください。