🌈

そろそろShaderをやるパート74 Phong鏡面反射

2022/07/03に公開

そろそろShaderをやります

そろそろShaderをやります。そろそろShaderをやりたいからです。
パート100までダラダラ頑張ります。10年かかってもいいのでやります。
100記事分くらい学べば私レベルの初心者でもまあまあ理解できるかなと思っています。

という感じでやってます。

※初心者がメモレベルで記録するので
 技術記事としてはお力になれないかもしれません。

下準備

下記参考
そろそろShaderをやるパート1 Unite 2017の動画を見る(基礎知識~フラグメントシェーダーで色を変える)

Phong鏡面反射とは

Phong(フォン)鏡面反射モデルとは、反射ベクトルと視線ベクトルが一致しているほど、ライトが強く照りかえっているとみなすシェーダー技法

【引用元】:UnityでPhong鏡面反射シェーダーを作る

この手法単体ではあまりきれいな表現はできませんが、組み合わせるとより自然なライティングが表現できそうです。

デモ

物体の一部の箇所にのみ反射されるような表現で光源が反映されています。

Shaderサンプル

Shader "Custom/Phong"
{
      Properties
    {
        //ここに書いたものがInspectorに表示される
        _MainColor("MainColor",Color) = (1,1,1,1)
        _Reflection("Reflection", Range(0, 10)) = 1
    }
    SubShader
    {
        Tags
        {
            "RenderType"="Opaque"
            "LightMode"="ForwardBase"
        }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float4 normal: NORMAL;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
                float3 worldPos : WORLD_POS;
            };

            //変数の宣言 Propertiesで定義した名前と一致させる
            float4 _MainColor;
            float _Reflection;

            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }

            float4 frag(v2f i) : SV_Target
            {
                //ライトの方向
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
                //ライトベクトルと法線ベクトルから反射ベクトルを計算
                float3 refVec = reflect(-lightDir, i.normal);
                //視線ベクトル
                float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
                //視線ベクトルと反射ベクトルの内積を計算
                float dotVR = dot(refVec, viewDir);
                //0以下は利用しないように内積の値を再計算
                dotVR = max(0,dotVR);
                dotVR = pow(dotVR, _Reflection);
                float3 specular = _LightColor0.xyz * dotVR;
                //内積を補間値として塗分け
                float4 finalColor =  _MainColor + float4(specular,1);
                return finalColor;
            }
            ENDCG
        }
    }
}

reflect

反射ベクトルを求める際に利用します。
reflect(I, N)は以下のPhong鏡面反射モデルの計算を行ってくれます。
I - 2 * dot(N, I) * N

実際の利用時には以下のようにlightDirを負の値にして計算しています。

//ライトの方向
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//ライトベクトルと法線ベクトルから反射ベクトルを計算
float3 refVec = reflect(-lightDir, i.normal);

これは_WorldSpaceLightPos0の逆ベクトルを利用しているわけですが、
これにより、光の入射光ベクトルが表せるというわけです。

参考リンク

【シェーダー】GLSLで書く!Unityシェーダー - ライティング編① -
Unity UnlitシェーダーでのPhong反射モデルの実装
UnityでPhong鏡面反射シェーダーを作る

Discussion