ちょっとVRまわりで遊びすぎてて更新が止まってしまいました。
前回に引き続き、シェーダーやっていきますーー
完成図
使用テクスチャ
異方性の鏡面反射
前回は等方性鏡面反射のシェーダーを実装してみました。これは滑らかな面を想定しており、どの角度から光があたったとしてもその分布は一様になります。
一方、異方性の鏡面反射では傷が入ったような金属面のように光の当たり方によっては特定方向の反射が強くなったりします。 例えば、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です)
あとの部分は、BlinnPhongとほぼ同様ですね。s.Gloss
にかけている128の数字は主観的にセットした数字で根拠は特にないという認識ですが、もしかしたらソースがあるのかもしれません・・??
さいごに
原始的なライティングは一旦ここまで!で次はPBR・・じゃなくて頂点関数を活用したシェーダーを作っていこうと思います!