今回のエンジニアブログを担当する、廣田です。
今回は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
上記がEmissionを使用したシェーダコードです。
発光の流れとしては、以下のようになります。
EmissionColor --- 発光色
EmissionTex --- 発光マップテクスチャ
CosTime --- コサインタイム
SinTime --- サインタイム
◇ 2倍角の公式を使い、時間係数を計算(t)
↓
◇ surf関数内で発光マップテクスチャから値を取得し、時間係数をかける(e)
↓
◇ アウトプットのEmission(ここではo.Emission)に、発光色×発光係数をセットする
実際にこのシェーダを適応したモデルの動画です。
この様に、サーフェイスシェーダ内のアウトプットのEmissionを使うことで発光を簡単に表現できます。
試しに、1/fゆらぎを使った発光シェーダを作成してみました。
OOFNoiseEmissionShader.shader
先ほどのシェーダとの違いは、発光係数を計算する際の時間係数に_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)