VPOSを使ってスクリーンに対してテクスチャをマッピングする方法です。
VPOS?
以前、スクリーンにテクスチャをマッピングする方法を紹介しました。
今回のVPOSはこれとは別の方法で、もう少しお手軽です。
VPOSというセマンティクスを付けた変数をフラグメントシェーダに定義しておくだけで
スクリーンスペースのピクセル位置を得ることができます。
実装
実装はこのようにします。
Shader "Sample" { Properties { [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // シェーダモデルは3.0以上 #pragma target 3.0 #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; // 頂点シェーダの出力構造体 struct v2f_vert { float4 vertex : SV_POSITION; }; // フラグメントシェーダの入力構造体 struct v2f_frag { // UNITY_VPOS_TYPE型の変数をVPOSセマンティクスで定義する UNITY_VPOS_TYPE vpos : VPOS; }; sampler2D _MainTex; v2f_vert vert (appdata v) { v2f_vert o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f_frag i) : SV_Target { i.vpos.xy /= _ScreenParams.xy; return tex2D(_MainTex, i.vpos.xy); } ENDCG } } }
フラグメントシェーダの入力構造体にUNITY_VPOS_TYPE型の変数をVPOSセマンティクスで定義しています。
頂点シェーダの出力構造体を分けている理由は次節で説明します。
SV_POSITIONとVPOSは一緒に定義できない
さて前節では頂点シェーダの出力とフラグメントシェーダの入力に別の構造体を使いました。
これは、SV_POSITIONとVPOSを一緒に定義するとエラーが出るためです。
例えば以下のように構造体を共通化してみます。
Shader "Sample" { Properties { [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // シェーダモデルは3.0以上 #pragma target 3.0 #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; }; // 頂点シェーダの出力構造体 かつ フラグメントシェーダの入力構造体 struct v2f { float4 vertex : SV_POSITION; UNITY_VPOS_TYPE vpos : VPOS; }; sampler2D _MainTex; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); return o; } fixed4 frag (v2f i) : SV_Target { i.vpos.xy /= _ScreenParams.xy; return tex2D(_MainTex, i.vpos.xy); } ENDCG } } }
すると次のようなエラーがでます。
Shader error in 'Sample': Duplicate system value semantic definition: input semantic 'SV_POSITION' and input semantic 'VPOS' at line 31
これはGPUの内部構造に起因するもののようですが、幸いにもSV_POSITIONはフラグメントシェーダでは使用せず、
VPOSは頂点シェーダでは使用しないため、前節のように別の構造体を定義すれば解決します。
構造体を2つ定義したくない場合
上述のように構造体を2つ定義すると、頂点シェーダからフラグメントシェーダに受け渡したい値は両方の構造体に定義しなければなりません。
// 頂点シェーダの出力構造体 struct v2f_vert { float4 vertex : SV_POSITION; // 両方に定義する必要がある float4 color : COLOR; }; // フラグメントシェーダの入力構造体 struct v2f_frag { UNITY_VPOS_TYPE vpos : VPOS; // 両方に定義する必要がある float4 color : COLOR; };
これはメンテナンス性が低下しそうです。
マクロを使う方法なども考えられますが、ここではマニュアルに載っている方法で実装してみます。
Shader "Sample" { Properties { [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma target 3.0 #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float4 color : COLOR; }; // 頂点シェーダ -> フラグメントシェーダへの構造体 struct v2f { // SV_POSITIONとVPOSはこの構造体には定義しない float4 color : COLOR; }; sampler2D _MainTex; // 入力構造体のほかにout修飾子をつけたSV_POSITIONの変数を定義 v2f vert (appdata v, out float4 vertex : SV_POSITION) { v2f o; vertex = UnityObjectToClipPos(v.vertex); o.color = v.color; return o; } // 入力構造体のほかにVPOSを定義 fixed4 frag (v2f i, UNITY_VPOS_TYPE vpos : VPOS) : SV_Target { vpos.xy /= _ScreenParams.xy; return tex2D(_MainTex, vpos.xy) * i.color; } ENDCG } } }
上記のように、頂点シェーダとフラグメントシェーダの引数に、
入力構造体のほかにSV_POSITIONとVPOSを渡すようにするだけです。
これでv2f構造体が共通化されました。
結果
キューブに対して上記のシェーダを用いてテクスチャを貼ってみます。
スクリーン座標に対してマッピングされていることがわかります。