読者です 読者をやめる 読者になる 読者になる

しゅみぷろ

プログラミングとか

クック・トランスの金属反射モデル

Graphics Shader Shading Unity

はじめに

この記事は

 

esprog.hatenablog.com

 

の延長です。

なるべくパラメーターを渡す部分等はスクリプトから渡していますが、Propertiesから設定した方がいい部分もあります。

 

esprog.hatenablog.com

 


上記の記事で金属の反射モデルを紹介しています。この金属反射モデルを実際にUnityで実装してみたのでメモを残しておきます。

今回、NDFにはクックとトランスの提案した分布関数を使用しています。

実装

f:id:es_program:20160426005833p:plain

f:id:es_program:20160426005845p:plain

以前紹介したスペキュラー反射と比べると、つやの出方が随分と違っているのがわかると思います。

まず、シェーダーにパラメーターを渡す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
        }
    }
}

シェーダーのコードは、素直にシェーディングまとめ - しゅみぷろで紹介した式をそのまま書いているだけです。あとは以前紹介したスペキュラー反射のコードと変わりません。