C++幼女先輩

プログラミング成分多め

UNITYで影にテクスチャをはる(スクリーンスペース)

やること

影にテクスチャを張っていこうと思います
具体的には、戦場のヴァルキュリアのような影部分にスクリーンスペースで ハッチングかけます

f:id:murasame-labo:20180928223528p:plain
わかりいくいけど、影部分にハッチングいれてます

ソースはこちら
github.com

考えられる手法

マルチレンダーターゲットを使いポストプロセスで処理

MRTを使い影部分を別で書き出し、ポスプロでコンポーズする

何度か試したが、UNITYではレンダーターゲットを別のものを設定すると
デフォルトのレンダーターゲットが外れ、EarlyDepth等の値が失われ
Zバッファがうまくとれなかった
もう少し研究すれば うまくいくかもしれないが、挫折

ステンシルバッファを使いポスプロで処理

MRTと似たかんじではあるが、レンダーターゲットを変更する必要がない

VPOSを使い通常パスで行う

ShaderModel 3.0からある VPOSセマンティクスを使えば
フラグメントシェーダーで、スクリーンでのポジションを取得することが出来る
その座標を使えば、ポスプロを使わず 通常パスにてスクリーン座標系でのテクスチャマッピングが可能になる
ので この手法で行ってみる

VPOSでの影テクスチャマッピング

テクスチャの指定

このあたりは普通のUNITYなのでサラッと

properties{
        _ShadowTex ("ShadowTexture", 2D) = "white" {}
}

uniform sampler2D _ShadowTex; uniform float4 _ShadowTex_ST;

VPOSの設定

VPOSを使いたいが制限が色々ある
SV_POSITIONと同時に使うことが出来ない
そのため 通常は 頂点シェーダの返却値と フラグメントシェーダの入力値に同じ構造体を使うが
それを別にしなければならない
なるべく構造体増やしたくないので、同じ構造体を使う方法もある
ただし今回は問題があり それが使えないが記しておく

struct v2f{
    float4 vertexW: TEXCOORD0;
    float2 uv     : TEXCOORD1;
    float3 normalWorld : TEXCOORD2;
    SHADOW_COORDS(3)
};

v2f vert(appdata_full v, out float4 vertex: SV_POSITION){


float4 frag(v2f i, float4 pos: VPOS) : SV_Target{

同時に使えないので、構造体には SV_POSITIONもVPOSも使わない
代わりに 頂点シェーダの引数に out属性でSV_POSITION を指定し
フラグメントシェーダの引数に VPOSを指定する
この方法であれば構造体は1つでよく メンテナンスがしやすいが
UNITYでは TRANSFER_SHADOW(o); などのマクロにて
構造体のposメンバを必要とするため、問題が生じる

そのため、不本意だが構造体を2個作る方法にする

         struct v2f
            {
                float4 pos : SV_POSITION;
                float4 vertexW: TEXCOORD0;
                float2 uv     : TEXCOORD1;
                float3 normalWorld : TEXCOORD2;
                SHADOW_COORDS(3)
            };


            struct v2f_in
            {
                UNITY_VPOS_TYPE vpos : VPOS;
                float4 vertexW: TEXCOORD0;
                float2 uv     : TEXCOORD1;
                float3 normalWorld : TEXCOORD2;
                SHADOW_COORDS(3)
            };

単純に、頂点シェーダの返却値は SV_POSITION、フラグメントシェーダの入力値は VPOS
にすればいい

影の計算

まず、通常の影では濃いので通常影を薄くするため LIGHT_ATTENUATIONの影響を減らし
先程のテクスチャを乗算してみる
SHADOW_ATTENUATIONは影が0で光が1になっているので そのあたりを考慮
また、VPOSはスクリーン座標なのでそれをuv座標 0~1にするには _ScreenParamsで割らなければならない

//LightColor
float3 lightCol = _LightColor0.rgb * lerp( 0.5, 1.0,  LIGHT_ATTENUATION(i));

i.vpos.xy /= _ScreenParams.xy;
float3 shadowTex = tex2D(_ShadowTex, i.vpos.xy) * lerp( 0.5, 0.0,  SHADOW_ATTENUATION(i));
return float4( (ambient + diffuse) * tex * (1-shadowTex) + specular, 1.0);

これで 影の部分のみハッチングがかかるようになった

影のパラメータを追加

影や影テクスチャの強さを設定できるように パラメータを2つ追加する

        _ShadowPower ("ShadowPower", Range(0.0, 1.0)) = 0.5
        _ShadowTexPower ("ShadowTexPower", Range(0.0, 1.0)) = 0.5


//LightColor
float3 lightCol = _LightColor0.rgb * lerp( (1-_ShadowPower), 1.0,  LIGHT_ATTENUATION(i));

i.vpos.xy /= _ScreenParams.xy;
float3 shadowTex = tex2D(_ShadowTex, i.vpos.xy) * lerp( _ShadowTexPower, 0.0,  SHADOW_ATTENUATION(i));
return float4( (ambient + diffuse) * tex * (1-shadowTex) + specular, 1.0);

これで影の強さや影テクスチャの強さを変更できるようになった!