エンジニア

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

投稿日:2014年4月18日 更新日:

今回のエンジニアブログを担当する、廣田です。
今回は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)に、発光色×発光係数をセットする

実際にこのシェーダを適応したモデルの動画です。

この様に、サーフェイスシェーダ内のアウトプットの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になったまま動きにくくなることがあるので、調整をしています。

実際にこのシェーダを適応したモデルの動画です。

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

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-,

© WonderPlanet Inc.