ForgeVision Engineer Blog

フォージビジョン エンジニア ブログ

黒体輻射シェーダの作り方~星の欠片の物語技術解説

こんにちは!VR事業部の長谷川(id:waffle_maker)です。
今回は『星の欠片の物語。しかけ版』で使用した、Unityシェーダを利用して黒体輻射を表現するテクニックについて紹介します。
黒体輻射は物体が放射する熱エネルギーに関する現象であり、光や色彩のリアリティを向上させる重要な要素です。

はじめに

本編ではShader Forgeを使用していましたが、今回ご紹介するに当たって、重要な部分のみShader Graphに移植したものをサンプルとして掲載しています。

黒体輻射とは

熱をもつ物体が発光する現象で、温度が上がるにつれて光エネルギーが増加し、低温では主に赤っぽく、高温では青白くなります。
※黒体放射とも呼びます。

金網を加熱した例

Shader ForgeとShader Graphについて

前回の記事で簡単に解説していますので、気になる方は参照してください。

techblog.forgevision.com

バージョン情報など

各種バージョン情報は以下の通りです。

方針

黒体輻射スペクトルの計算などは行わず、LUT(Lookup table)テクスチャを用意して、温度を入力すると自己発光色が出力されるようにしましょう。
また、熱源に近いほど温度が高くなるはずなので、熱源の座標を受け取って色の変化に反映したいと思います。

LUTテクスチャの作成

下記サイトでLUTテクスチャを作成しました。
https://angrytools.com/gradient/image/

こちらから実際に使用したパラメータをご確認いただけます。

実装

新規のShader Graphを作成し、以下の様にノードを接続します。

グラフ変更例

熱源からピクセルの距離[m]を求める

①Distance

HeatPointパラメータ(熱源の座標)とPositionノード(ピクセルの座標)を、Distanceノードに入力して熱源からピクセルの距離を求めます。

温度を求める

②Temperature

HeatValueパラメータ(加熱の度合いを0から1で指定)を、Multiplyノードで2倍にします。
HeatRangeパラメータ(熱源からピクセルの距離のうち色を変化させる範囲、オブジェクトの直径くらい)とRemapノードで、①の結果を0から1に収めます。
Subtractノードで上記の2つを減算することで、熱源からの距離と加熱の度合いによって増加する値を求めます。
Clampノードで②の結果を0から1に収めてから、Remapノードで0から1の値を更に任意の範囲に割り当て直します。

文章だと分かりづらいと思いますが、ここまでの結果をBase Colorとして表示すると以下の様になります。
(横幅3[m]のオブジェクトで、HeatRangeパラメータを(0,3)、HeatValueパラメータを0から1にリニアに変化)

モデル適用例(②まで)

LUTテクスチャのルックアップ

③Lookup Table

②の結果を、CombineノードでFloatからVector 2に変換し、Sample Texture 2DノードでLUTテクスチャから色情報を取得します。
これによって、②の値が大きいほど赤~黄~白と色が変化するようになりました。

モデル適用例

カスタマイズ例

上記のグラフ作成例は、重要な部分のみを切り出して分かりやすくしたもので、理想に近付けるためには色々な工夫が必要になります。
ここからは、いくつかのカスタマイズ例を紹介します。

グラデーション(Gradient)

③のSample Texture 2Dノードを、GradientおよびSample Gradientノードに置き換えてみましょう。
これによって、LUTテクスチャを用意せずに同様の効果が得ることができます。

グラデーションのグラフ変更例
グラデーションのモデル適用例

座標を更新するスクリプト

熱源の座標が移動する場合は、以下の様に任意のTransformのPositionを監視して、変化があればMaterialのHeatPointパラメータを更新するようなスクリプトを用意すると便利です。
UniRxなどを使用する事で、より実装を簡略化することが出来るかと思います。

using UnityEngine;

public class Heatable : MonoBehaviour
{
    private static readonly int HeatPointId = Shader.PropertyToID("_HeatPoint");
    
    [SerializeField] private Transform heatPoint;
    [SerializeField] private Renderer heatableRenderer;
    [SerializeField] private int materialIndex;

    private Vector3 _prevPosition;
    private Material _material;

    private void Start()
    {
        _material = heatableRenderer.materials[materialIndex];
    }

    private void Update()
    {
        var heatSourcePosition = heatPoint.position;
        if (_prevPosition != heatSourcePosition)
        {
            _material.SetVector(HeatPointId, heatSourcePosition);
            _prevPosition = heatSourcePosition;
        }
    }
}

座標を更新するスクリプトのモデル適用例

冷えやすい部分

冷えやすい部分の例

ポリゴン数が多く複雑な形状であれば、熱源の座標を調整することで上記の様に先端が冷えやすい表現はできます。
また、ポリゴン数が少なく単純な形状であっても、ハイトマップやAOマップがあれば工夫次第で結果を近付けることはできます。

冷えやすい部分のグラフ変更例

温度の範囲

結果が分かりやすいように、②の温度が上限まで上がりきらないよう、温度の範囲を設定しました。

以下の例では、TemperatureRangeパラメータをSplitノードで分解して、ClampノードのMinとMaxに入力しています。

温度の範囲のグラフ変更例

ハイトマップ

ハイトマップはオブジェクトの凹凸が定義したもので、凹が0、凸が1になります。
凸部分が冷えやすいはずなので、温度からハイトマップの値を減算すると良さそうです。

以下の例では、HeightMapパラメータをSample Texture 2Dノードで抽出した色の値を、Remapノードで-0.5から0.5の値に変換し、最後にMultiplyノードでHeightPowerノードを乗算してハイトマップの適用度合いを調整できるようにしています。

ハイトマップのグラフ変更例

AOマップ

AOマップはオブジェクトの奥まっている部分の陰影を定義したもので、通常は1、陰影部分が0になります。
奥まっている部分が冷えにくいはずなので、温度にAOマップの値を減算すると良さそうです。

※やっていることはハイトマップと同様です。

AOマップのグラフ変更例

減算

AddノードでハイトマップとAOマップの結果を加算し、Subtractノードで②改の結果から乗算しています。

減算のグラフ変更例

結果

以下の様に凸部分は色の変化が少なく、凹部分や奥まった部分は色の変化が大きくすること出来ました。

冷えやすい部分のモデル適用例

今回は、ハイトマップとAOマップの影響を②温度に適用しましたが、①距離に適用しても良いかも知れません。

まとめ

Unityシェーダを使用した黒体輻射の表現は、光や色彩のリアリティを向上させるために重要な要素です。
本記事で紹介した手法やテクニックを活用して、魅力的な視覚効果を実現してみてください。

使用した素材について

本記事の執筆に当たって、下記アセットを使用させて頂きました。

casual-effects.com assetstore.unity.com

宣伝

最後に『星の欠片の物語。しかけ版』『星の欠片の物語、ひとかけら版』が各種プラットフォームで発売中です。
もし、ご興味を持って頂けたらポチッとお願いします。
star.anos.jp