JKになりたい

何か書きたいことを書きます。主にWeb方面の技術系記事が多いかも。

Shaderやっていく〜異方性の鏡面反射〜

ちょっとVRまわりで遊びすぎてて更新が止まってしまいました。
前回に引き続き、シェーダーやっていきますーー

完成図

f:id:sakata_harumi:20190524131950p:plain:w200

使用テクスチャ

f:id:sakata_harumi:20190524133103p:plain:w200

異方性の鏡面反射

前回は等方性鏡面反射のシェーダーを実装してみました。これは滑らかな面を想定しており、どの角度から光があたったとしてもその分布は一様になります。

一方、異方性の鏡面反射では傷が入ったような金属面のように光の当たり方によっては特定方向の反射が強くなったりします。 例えば、CDやDVDの裏面の反射や、ステンレス素材の表面仕上げ方法の「ヘアライン加工」が施された面などが異方性の反射になります。

ただ、ちゃんと実装すると難しそうなので今回の実装では、表面方向の反射特性を再現するのではなく、ハイライトの方向を示す法線マップを用意し、それに従い反射成分を増強させていく、といった方法をとっていきます。

正確にはBRDF(双方向反射率分布関数)に基づく異方性反射のモデルが幾つかある(Ward、Ashikhmin等)ようなのですが、今の自分には難しいのでそのうち機会があれば見ていきたいと思います。

実装

実装はほぼ http://wiki.unity3d.com/index.php?title=Anisotropic_Highlight_Shader の引用です

Shader "Custom/Anisotropic"
{
    Properties 
    { 
        _MainTint ("Diffuse Tint", Color) = (1,1,1,1) 
        _MainTex ("Base (RGB)", 2D) = "white" {} 
        _SpecularColor ("Specular Color", Color) = (1,1,1,1) 
        _Specular ("Specular Amount", Range(0,1)) = 0.5 
        _SpecPower ("Specular Power", Range(0,1)) = 0.5 
        _AnisoDir ("Anisotropic Direction", 2D) = "" {} 
        _AnisoOffset ("Anisotropic Offset", Range(-1,1)) = -0.2 
    } 
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Anisotropic 

        #pragma target 3.0

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_AnisoDir;
        };
        struct SurfaceAnisoOutput 
        { 
            fixed3 Albedo; 
            fixed3 Normal; 
            fixed3 Emission; 
            fixed3 AnisoDirection; 
            half Specular; 
            fixed Gloss; 
            fixed Alpha; 
        }; 

        sampler2D _MainTex; 
        sampler2D _AnisoDir; 
        float4 _MainTint; 
        float4 _SpecularColor; 
        float _AnisoOffset; 
        float _Specular; 
        float _SpecPower; 

        void surf(Input IN, inout SurfaceAnisoOutput o) 
        { 
            half4 c = tex2D(_MainTex, IN.uv_MainTex) * _MainTint; 
            float3 anisoTex = UnpackNormal(tex2D(_AnisoDir, IN.uv_AnisoDir)); 
            
            o.AnisoDirection = anisoTex; 
            o.Specular = _Specular; 
            o.Gloss = _SpecPower; 
            o.Albedo = c.rgb; 
            o.Alpha = c.a; 
        } 
        fixed4 LightingAnisotropic(SurfaceAnisoOutput s, fixed3 lightDir, half3 viewDir, fixed atten) 
        { 
            fixed3 halfVector = normalize(normalize(lightDir) + normalize(viewDir)); 
            float NdotL = saturate(dot(s.Normal, lightDir)); 
            
            fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection), halfVector); 
            float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180)));  
            float spec = saturate(pow(aniso, s.Gloss * 128) * s.Specular); 
            
            fixed4 c; 
            c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.rgb * spec)) * atten; 
            c.a = s.Alpha; 
            return c; 
        } 
        ENDCG
    }
    FallBack "Diffuse"
}

カスタムライティングモデルの定義であるLightingAnisotropicの実装をみていきます。

fixed4 LightingAnisotropic(SurfaceAnisoOutput s, fixed3 lightDir, half3 viewDir, fixed atten) 
{ 
    fixed3 halfVector = normalize(normalize(lightDir) + normalize(viewDir)); 
    float NdotL = saturate(dot(s.Normal, lightDir)); 
    
    fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection), halfVector); 
    float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180)));  
    float spec = saturate(pow(aniso, s.Gloss * 128) * s.Specular); 
    
    fixed4 c; 
    c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * _SpecularColor.rgb * spec)) * atten; 
    c.a = s.Alpha; 
    return c; 
} 

実装のベース自体はBlinnPhongとなっていますね。 ちなみにsaturate(x)はxを0.0~1.0の間にクランプする処理です。具体的にはmin(max(x, 0.0), 1.0))です。

fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection), halfVector); 

HdotAですが、BlinnPhongのときは面の法線ベクトルとハーフベクトルの内積を取っていました。
今回は、法線マップで示されているハイライトの方向に従って法線方向をずらします。

float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180)));  

_AnisoOffsetを加えることで、光の反射の仕方をコントロールしています。
単純に加えるだけだと鏡面反射成分がただ強くなるだけですので、このようにsin噛ませることで_AnisoOffsetを加えるほど光があたっている周囲の部分が負になり、aniso=0で返ります。
つまり、光があたっている中心部から離れたところに鏡面成分が0以外の値を返すことになるので、輪状の反射になります。

_AnisoOffsetを0 ~ 1.0へ変異させていくgifを貼っておきます。(初期状態が_AnisoOffset=0です)

f:id:sakata_harumi:20190609012334g:plain:w200

あとの部分は、BlinnPhongとほぼ同様ですね。s.Glossにかけている128の数字は主観的にセットした数字で根拠は特にないという認識ですが、もしかしたらソースがあるのかもしれません・・??

さいごに

原始的なライティングは一旦ここまで!で次はPBR・・じゃなくて頂点関数を活用したシェーダーを作っていこうと思います!