クック・トランスの金属反射モデル
はじめに
この記事は
の延長です。
なるべくパラメーターを渡す部分等はスクリプトから渡していますが、Propertiesから設定した方がいい部分もあります。
上記の記事で金属の反射モデルを紹介しています。この金属反射モデルを実際にUnityで実装してみたのでメモを残しておきます。
今回、NDFにはクックとトランスの提案した分布関数を使用しています。
実装
以前紹介したスペキュラー反射と比べると、つやの出方が随分と違っているのがわかると思います。
まず、シェーダーにパラメーターを渡すC#スクリプトは以下のようになりました。
using System.Collections; using UnityEngine; [ExecuteInEditMode] public class PerPixelCookTorranceShader : MonoBehaviour { [SerializeField] private Material specular; [SerializeField] private Transform lightTransform; [SerializeField] private float k_diffuse; [SerializeField] private float k_ambient; [SerializeField] private float k_specular; [SerializeField] private float roughness; [SerializeField] private float complexRefractiveIndex; public void OnWillRenderObject() { Vector4 lightPos = lightTransform.position; specular.SetVector("light_pos", lightPos); specular.SetFloat("k_diffuse", k_diffuse); specular.SetFloat("k_ambient", k_ambient); specular.SetFloat("k_specular", k_specular); specular.SetFloat("roughness", roughness); specular.SetFloat("complex_refractive_index", complexRefractiveIndex); } }
これはUnityでなるべくシェーディング処理を自作してみる - しゅみぷろで紹介したPhongのスペキュラー反射モデルのスクリプトに少々修正、加筆しただけです。 roughnessは面の荒さを決める定数で、小さいほどシャープなハイライトになります。complex_refractive_indexは材質ごとに異なる定数「複素屈折率」です。シェーディングまとめ - しゅみぷろを参照して下さい。
続いて、シェーダーです。
Shader "Custom/PerPixelCookTorranceShader" { Properties{ _DiffuseColor("Diffuse Color", COLOR) = (1,1,1,1) _SpecularColor("Specular Color", COLOR) = (1,1,1,1) } SubShader{ Pass{ CGPROGRAM #pragma vertex vert #pragma fragment frag struct app_data { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; float4 local_pos : TEXCOORD0; float4 normal : TEXCOORD1; }; uniform float4 light_pos; uniform float4 camera_pos; uniform float k_diffuse; uniform float k_ambient; uniform float k_specular; uniform float roughness; uniform float complex_refractive_index; float4 _DiffuseColor; float4 _SpecularColor; float normal_distribution_function(float d) { return 1 / roughness + roughness * pow(cos(d), 4) * exp(-pow((tan(d) / roughness), 2)); } float geometric_attenuation_coefficient(float3 n, float3 v, float3 h, float3 l) { float g_out = 2 * dot(n, h) * dot(n, v) / dot(v, h); float g_in = 2 * dot(n, h) * dot(n, l) / dot(v, h); return min(min(g_in, g_out), 1); } float fresnel_reflection_coefficient(float phi, float eta) { float c = cos(phi); float zeta = sqrt(eta * eta + c * c - 1); float zpc = zeta + c; float zmc = zeta - c; return zmc*zmc / zpc*zpc * (1 - (c*zpc - 1)*(c*zpc - 1) / (c*zmc + 1)*(c*zmc + 1)); } v2f vert(app_data i) { v2f o; o.pos = mul(UNITY_MATRIX_MVP, i.vertex); o.local_pos = i.vertex; o.normal = mul(UNITY_MATRIX_IT_MV, float4(i.normal, 1)); return o; } float4 frag(v2f i) : SV_TARGET{ float3 vs_light_pos = mul(UNITY_MATRIX_V, light_pos).xyz; float3 vs_pos = mul(UNITY_MATRIX_MV, i.local_pos).xyz; float3 vs_camera_pos = mul(UNITY_MATRIX_V, camera_pos).xyz; float3 vs_n = normalize(i.normal.xyz); float3 vs_l = normalize(vs_light_pos - vs_pos); float3 vs_v = normalize(vs_camera_pos - vs_pos); float3 vs_h = normalize(vs_l + vs_v); float r_diffuse = k_diffuse * max(dot(vs_n, vs_l), 0); float D = normal_distribution_function(dot(vs_n, vs_h)); float G = geometric_attenuation_coefficient(vs_n, vs_v, vs_h, vs_l); float F = fresnel_reflection_coefficient(dot(vs_n, vs_l), complex_refractive_index); float r_specular = k_specular * D * F * G / dot(vs_n, vs_v); return r_diffuse * _DiffuseColor + r_specular * _SpecularColor + k_ambient; } ENDCG } } }
シェーダーのコードは、素直にシェーディングまとめ - しゅみぷろで紹介した式をそのまま書いているだけです。あとは以前紹介したスペキュラー反射のコードと変わりません。