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

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

SurfaceShaderを利用した発光シェーダ

Shader Unity

今回のエンジニアブログを担当する、廣田です。
今回はUnityのSurfaceShaderを使用して、発光シェーダを作ってみたいと思います。


Unityのシェーダには、

・Fixed Function Shaders(固定関数シェーダ)
・Surface Shaders(サーフェイスシェーダ)
・Vertex and Fragment Shaders(頂点/フラグメントシェーダ)

の3種類があります。(http://docs-jp.unity3d.com/Documentation/Manual/Shaders.html)
今回はライトの計算等をUnityが行ってくれる、SurfaceShaderを使用して発光をさせてみたいと思います。

struct SurfaceOutput {  
 half3 Albedo;      // 反射光の比  
 half3 Normal;      // 法線  
 half3 Emission;    // 発光  
 half Specular;     // 鏡面反射  
 half Gloss;        // 光沢  
 half Alpha;        // 透明度  
};  

SurfaceShaderのアウトプットには、上記の構造体が使用されます。
今回は発光シェーダなので、Emissionに値を設定して発光を表現します。

EmissionShader.shader
SS1
上記がEmissionを使用したシェーダコードです。
発光の流れとしては、以下のようになります。

EmissionColor --- 発光色
EmissionTex   --- 発光マップテクスチャ
CosTime --- コサインタイム
SinTime --- サインタイム

◇ 2倍角の公式を使い、時間係数を計算(t)

◇ surf関数内で発光マップテクスチャから値を取得し、時間係数をかける(e)

◇ アウトプットのEmission(ここではo.Emission)に、発光色×発光係数をセットする

実際にこのシェーダを適応したモデルの動画です。
[youtube width="500" height="375" video_id="r_GwMr2My8w"]

この様に、サーフェイスシェーダ内のアウトプットのEmissionを使うことで発光を簡単に表現できます。
試しに、1/fゆらぎを使った発光シェーダを作成してみました。

OOFNoiseEmissionShader.shader
SS2
先ほどのシェーダとの違いは、発光係数を計算する際の時間係数に_EmissionOOFNという自前で計算させた1/fゆらぎの値を指定しているところです。
この値を計算して、シェーダに設定するコードが以下になります。

OneOverFNoise.cs

using UnityEngine;  
using System.Collections;  
  
public class OneOverFNoise : MonoBehaviour  
{  
    // 発光値  
    private float emissionValue = 0.0f;  
  
    // 次の発光値  
    private float nextEmissionValue = 0.0f;  
  
    // 最低発光値  
    [SerializeField]  
    private float minEmissionValue = 0.0f;  
  
    // 更新係数  
    [SerializeField]  
    private float updateTimeFactor = 1.0f;  
  
    // 更新時間  
    private float updateTime;  
  
    /// <summary>  
    /// 初期化処理  
    /// </summary>  
    void Awake()  
    {  
        // 次の発光値を計算  
        CalcNextEmissionValue ();  
    }  
  
    /// <summary>  
    /// 更新前処理  
    /// </summary>  
    void Start ()  
    {  
    }  
  
     /// <summary>  
     /// 更新処理  
     /// </summary>  
    void FixedUpdate ()  
    {  
        // 更新時間を加算  
        updateTime += Time.fixedDeltaTime;  
  
        // 時間係数を計算  
        float factor = Mathf.Min ((updateTime / updateTimeFactor), 1.0f);  
  
        // 補間した発光値を計算  
        float v = Mathf.Lerp (emissionValue, nextEmissionValue, factor);  
  
        if (renderer != null) {  
            // シェーダーに発光値をセット  
            renderer.material.SetFloat ("_EmissionOOFN", v);  
        }  
  
        if (factor >= 1.0f) {  
            updateTime = 0.0f;  
            emissionValue = nextEmissionValue;  
            CalcNextEmissionValue();  
        }  
    }  
  
    /// <summary>  
    /// 次の発光値を計算  
    /// </summary>  
    void CalcNextEmissionValue()  
    {  
        // ランダム値を取得  
        float r = Random.Range (0.0f, 1.0f);  
  
        // 次の発光値を間欠カオス法で計算  
        if(r <= 0.01f) {  
            nextEmissionValue = r + 0.02f;  
        }  
        else if (r < 0.5f) {  
            nextEmissionValue = r + 2.0f * r * r;  
        }  
        else if(r >= 0.99f) {  
            nextEmissionValue = r - 0.01f;  
        }  
        else {  
            nextEmissionValue = r - 2.0f * (1.0f - r) * (1.0f - r);  
        }  
  
        nextEmissionValue = Mathf.Max(nextEmissionValue, minEmissionValue);  
    }  
}  
  

計算した値を以下のコードでシェーダに送っています。

// シェーダーに発光値をセット  
renderer.material.SetFloat ("_EmissionOOFN", v);  

今回は1/fゆらぎを計算する方法として、間欠カオス法を使用しました。

間欠カオス法:
x = 0.0 ~ 1.0
x < 0.5の場合 x = x + 2 * x * x
x >= 0.5の場合 x = x - 2 * (1 - x) * (1 - x)

この公式を使用した場合、1.0と0.0になったまま動きにくくなることがあるので、調整をしています。

実際にこのシェーダを適応したモデルの動画です。
[youtube width="500" height="375" video_id="25kQqRSueCk"]

この様にEmissionの計算の違いによって、様々な明滅をさせることができます。
例えば、Unityには高速フーリエ変換(FFT)の結果を受け取れるので、音楽に合わせて明滅するシェーダなんてのも面白いかもしれませんね。
(https://docs.unity3d.com/Documentation/ScriptReference/AudioSource.GetSpectrumData.html)