【Unity】【シェーダ】ノーマルマッピング+キューブマッピング

ノーマルマッピングとキューブマッピングを組み合わせたシェーダの実装方法です。

シェーダ

早速ですがシェーダです。

Shader "NormalAndCubeMapping"
{
    Properties
    {
        [Normal]
        _Normalmap ("Normal map", 2D) = "bump" {}
        [Toggle]
        _UseSkyColorOnly ("Use sky color only", Float) = 0
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
           #pragma vertex vert
           #pragma fragment frag
           #pragma shader_feature _ _USESKYCOLORONLY_ON

           #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex       : POSITION;
                half2 texcoord      : TEXCOORD0;
                half3 normal        : NORMAL;
                half4 tangent       : TANGENT;
            };

            struct v2f
            {
                float4 pos          : SV_POSITION;
                half2 uv            : TEXCOORD0;
                half3 viewDir       : TEXCOORD1;
                half3 normal        : TEXCOORD2;
                half4 tangent       : TEXCOORD3;
                half3 binormal      : TEXCOORD4;
            };


            sampler2D _Normalmap;
            float4 _Normalmap_ST;

            v2f vert (appdata v)
            {
                v2f o           = (v2f)0;
                o.pos           = UnityObjectToClipPos(v.vertex);
                o.uv            = TRANSFORM_TEX(v.texcoord, _Normalmap);

                // 各ベクトルをワールド空間で求める
                o.viewDir       = normalize(mul(unity_ObjectToWorld, ObjSpaceViewDir(v.vertex)));
                // binormalはtangentのwとunity_WorldTransformParams.wを掛ける(Unityの決まり)
                o.binormal      = normalize(cross(v.normal.xyz, v.tangent.xyz) * v.tangent.w * unity_WorldTransformParams.w);
                o.normal        = UnityObjectToWorldNormal(v.normal);
                o.tangent       = mul(unity_ObjectToWorld, v.tangent.xyz);
                o.binormal      = mul(unity_ObjectToWorld, o.binormal);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // ノーマルマップから法線情報を取得する
                half3 normal                    = UnpackNormal(tex2D(_Normalmap, i.uv));
                // ワールド空間に変換
                normal                          = (i.tangent * normal.x) + (i.binormal * normal.y) + (i.normal * normal.z);
                
                half3 reflDir                   = reflect(-i.viewDir, normal);
                // 地面や水面に使うときなど空の色だけ適用したいとき用
               #ifdef _USESKYCOLORONLY_ON
                    reflDir.y *= reflDir.y < 0 ? -1 : 1;
               #endif
                half3 reflCol                   = UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, reflDir, 0);

                return half4(reflCol, 1);
            }
            ENDCG
        }
    }
}

大枠の説明はコメントの通りです。

少し変わったところでいうと、_USESKYCOLORONLY_ONというキーワードでバリアントを作っています。
これを使用すると必ずキューブマップの上半分だけから色が取得されます。

地面にノーマルマッピング+キューブマッピングをすると反射するはずのないキューブマップの下側をサンプリングしてしまうので、
そのような箇所に使うときにはUse sky color onlyにチェックを入れます。

また、ワールド空間と接空間の座標変換については下記の記事にまとめています。

light11.hatenadiary.com

結果

結果は次のようになります。

f:id:halya_11:20181202185554p:plain

正常に処理できていることが確認できました。

接空間への変換だけならTANGENT_SPACE_ROTATION

ちなみにですが、単純にノーマルマッピングをするだけならTANGENT_SPACE_ROTATIONマクロを使ったほうがいいです。

これをつかうことで頂点シェーダで接空間への変換ができます。
実装は次の記事を参照してください。

light11.hatenadiary.com

関連

light11.hatenadiary.com

light11.hatenadiary.com