LoginSignup
8
14

More than 3 years have passed since last update.

ドラゴンボールファイターズの光表現カッコいいから真似してみた

Last updated at Posted at 2019-08-24

▼普通のとき

▼かめはめ波撃ったとき

▼爆発したとき

いちいち光の色がアニメっぽくモデルに反映されて超カッコいいから真似したい。
けど何も考えずに真似すると エフェクトの数だけLightを置かないといけなくて、負荷がシャレにならない。
というわけで1ライトで表現するにはどうすべきか考えてみた。

やってみた

こうなった。いい感じ?

今回デモ用に使ったAsset

考え方

  • シェーダーに光の方向を直接入れるパラメータを追加
  • 光の範囲、色を持つ なんちゃってライト をシーンに配置
  • なんちゃってライト の範囲内にモデルが入ったら色・角度を設定して見た目に反映
TIPS:落ち影は無視

ここまで見てピンと来るかもですが、メッシュが受ける光の色と角度を改ざんするだけなので、落ち影の角度は変わりません。
(違和感は出るけど、Light増やさずリッチな光表現できるので許して!)
ドラゴンボールファイターズみたいにエフェクトの光を受けるような一時的な演出として使うと良さげ。

作り方

モデル用意

まず好きなモデルに ユニティちゃんトゥーンシェーダー(以下UCTシェーダー)を当てて好きなパラメータを反映。

UCTシェーダーのパラメータを拡張

  • 光の角度を直接指定するか否か
  • 光のベクトル(x, y, z)

のパラメータを追加してシェーダーに反映します。
編集するファイルは3つです。

  • Toon_DoubleShadeWirhFeather.shader:シェーダーの外部用パラメータの定義場所
  • UCTS_DoubleShadeWirhFeather.cginc:シェーダーの実際の計算をしているファイル
  • UCTS2GUI.cs:Inspector上の表示を作る処理
Toon_DoubleShadeWirhFeather.shader
// 150行目~
[Toggle(_)] _Inverse_Z_Axis_BLD (" Inverse Z-Axis (Built-in Light Direction)", Float ) = 1 // ←既存行
[Toggle(_)] _Is_MLD ("Advanced : Activate Manual Light Direction", Float ) = 0
_X_MLD (" X (Manual Light Direction)", Range(-1, 1)) = 0
_Y_MLD (" Y (Manual Light Direction)", Range(-1, 1)) = 0
_Z_MLD (" Z (Manual Light Direction)", Range(-1, 1)) = 1
UCTS_DoubleShadeWirhFeather.cginc
// 111行目~
uniform fixed _Inverse_Z_Axis_BLD; // ←既存行
// マニュアルのライト角度
uniform fixed _Is_MLD;
uniform float _X_MLD;
uniform float _Y_MLD;
uniform float _Z_MLD;

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// 226行目~
lightDirection = lerp(lightDirection, customLightDirection, _Is_BLD);
// マニュアルのライト角度
float3 munualLightDirection = normalize(float3(_X_MLD,_Y_MLD,_Z_MLD));
lightDirection = lerp(lightDirection, munualLightDirection, _Is_MLD);
UCTS2GUI.cs
// 147行目~
MaterialProperty offset_Y_Axis_BLD = null; // ←既存行
MaterialProperty manualLightDirectionX = null;
MaterialProperty manualLightDirectionY = null;
MaterialProperty manualLightDirectionZ = null;

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// 239行目~
offset_Y_Axis_BLD = FindProperty("_Offset_Y_Axis_BLD", props); // ←既存行
manualLightDirectionX = FindProperty("_X_MLD", props);
manualLightDirectionY = FindProperty("_Y_MLD", props);
manualLightDirectionZ = FindProperty("_Z_MLD", props);

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// 1945行目~
    EditorGUI.indentLevel--; // ←既存行
} // ←既存行

EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Manual Light Direction");
//GUILayout.Space(60);
if(material.GetFloat("_Is_MLD") == 0){
    if (GUILayout.Button("Off",shortButtonStyle))
    {
        material.SetFloat("_Is_MLD",1);
    }
}else{
    if (GUILayout.Button("Active",shortButtonStyle))
    {
        material.SetFloat("_Is_MLD",0);
    }
}
EditorGUILayout.EndHorizontal();
if(material.GetFloat("_Is_MLD") == 1){
    GUILayout.Label("    Manual Light Direction Settings");
    EditorGUI.indentLevel++;
    m_MaterialEditor.RangeProperty(manualLightDirectionX, "● Manual X Direction");
    m_MaterialEditor.RangeProperty(manualLightDirectionY, "● Manual Y Direction");
    m_MaterialEditor.RangeProperty(manualLightDirectionZ, "● Manual Z Direction");
    EditorGUI.indentLevel--;
}

編集後、さきほどシェーダーを設定したモデルの表示が破綻していないか確認しておきましょう。
見た目がピンク色になったり真っ黒になったりしてたら、編集ミスがあるので見直し!

なんちゃって光 と光を受けるモデル用のスクリプトを追加

  • UTCシェーダーを持つモデルに作用させる光情報を持った CellLookLight.cs
  • CellLookLight の影響をUTCシェーダーモデルに反映させる CellLookLightReceiver.cs

を追加します。

CellLookLight.cs
using UnityEngine;

public class CellLookLight : MonoBehaviour {
    public Color Color {
        get {
            return _color;
        }
        private set {
            _color = value;
        }
    }
    [SerializeField]
    private Color _color = Color.white;

    public float Range {
        get {
            return _range;
        }
        private set {
            _range = value;
        }
    }
    [SerializeField]
    private float _range = 10f;

    private void OnDrawGizmos() {
        Gizmos.color = Color;
        Gizmos.DrawWireSphere(transform.position, Range);
    }
}

OnDrawGizmos の処理で光の色と範囲がシーンビュー上で可視化されるようになっています。

CellLookLightReceiver.cs
using UnityEngine;

public class CellLookLightReceiver : MonoBehaviour {
    #region 定数
    // UCTシェーダーの各種パラメータ
    private const string PARAM_NAME_BASE_COLOR = "_BaseColor";
    private const string PARAM_NAME_1ST_SHADOW_COLOR = "_1st_ShadeColor";
    private const string PARAM_NAME_IS_MLD = "_Is_MLD";
    private const string PARAM_NAME_X_MLD = "_X_MLD";
    private const string PARAM_NAME_Y_MLD = "_Y_MLD";
    private const string PARAM_NAME_Z_MLD = "_Z_MLD";
    /// <summary>
    /// 光に当たったときどのくらい影を暗くするかのパラメータ(0~1)
    /// 値が大きいほど光に当たったときに明るいところと暗いところの差が強くなる
    /// </summary>
    private const float SHADE_DARKNESS_WEIGHT = 0.4f;
    #endregion

    #region 変数
    [SerializeField]
    private Renderer[] _renderers;
    // デフォルトのテクスチャカラー
    private Color[] _defaultBaseColors;
    private Color[] _default1stShadowColors;
    // CellLookLightPointによって受ける光情報
    private Color _addColor = Color.black;
    private bool _isManualLightDir = false;
    private Vector3 _manualLightDir = Vector3.up;
    #endregion

    private void Awake() {
        _defaultBaseColors = new Color[_renderers.Length];
        _default1stShadowColors = new Color[_renderers.Length];
        for (int i = 0; i < _renderers.Length; i++) {
            _defaultBaseColors[i] = _renderers[i].material.GetColor(PARAM_NAME_BASE_COLOR);
            _default1stShadowColors[i] = _renderers[i].material.GetColor(PARAM_NAME_1ST_SHADOW_COLOR);
        }
    }

    private void Update() {
        CellLookLight[] lights = FindObjectsOfType<CellLookLight>();
        float nearestDistanceRate = 1;
        Vector3 position = transform.position;
        CellLookLight targetLight = null;
        for (int i = 0; i < lights.Length; i++) {
            CellLookLight light = lights[i];
            float distanceRate = Vector3.Distance(light.transform.position, position) / light.Range;
            if (distanceRate >= nearestDistanceRate) {
                continue;
            }
            nearestDistanceRate = distanceRate;
            targetLight = light;
        }
        if (targetLight == null) {
            ResetLightInfo();
            return;
        }
        SetActiveManualLightDirection(true);
        SetAddColor(targetLight.Color);
        Vector3 diraction = (targetLight.transform.position - transform.position).normalized;
        SetManualLightDirection(diraction);
    }

    private void SetAddColor(Color addColor) {
        if (_addColor == addColor) {
            return;
        }
        _addColor = addColor;
        // 光が強いほど影に当たる部分は暗くなる
        Color shadowMultiColor = new Color(
            r: 1f - (_addColor.g + _addColor.b) * SHADE_DARKNESS_WEIGHT,
            g: 1f - (_addColor.r + _addColor.b) * SHADE_DARKNESS_WEIGHT,
            b: 1f - (_addColor.r + _addColor.g) * SHADE_DARKNESS_WEIGHT
        );
        for (int i = 0; i < _renderers.Length; i++) {
            _renderers[i].material.SetColor(PARAM_NAME_BASE_COLOR, _defaultBaseColors[i] + _addColor);
            _renderers[i].material.SetColor(PARAM_NAME_1ST_SHADOW_COLOR, _default1stShadowColors[i] * shadowMultiColor);
        }
    }

    private void SetManualLightDirection(Vector3 dir) {
        if (_manualLightDir == dir) {
            return;
        }
        _manualLightDir = dir;
        for (int i = 0; i < _renderers.Length; i++) {
            _renderers[i].material.SetFloat(PARAM_NAME_X_MLD, dir.x);
            _renderers[i].material.SetFloat(PARAM_NAME_Y_MLD, dir.y);
            _renderers[i].material.SetFloat(PARAM_NAME_Z_MLD, dir.z);
        }
    }

    private void SetActiveManualLightDirection(bool isActive) {
        if (_isManualLightDir == isActive) {
            return;
        }
        _isManualLightDir = isActive;
        for (int i = 0; i < _renderers.Length; i++) {
            _renderers[i].material.SetFloat(PARAM_NAME_IS_MLD, isActive ? 1 : 0);
        }
    }

    private void ResetLightInfo() {
        SetAddColor(Color.black);
        SetActiveManualLightDirection(false);
    }
}

Updateの中でシーン内にある CellLookLight を探し、最も影響が強い※ライトの色と角度をマテリアルに反映するようにしています。
※影響が強い=光の範囲ギリギリの位置を1, 光と同じ位置を0とする距離割合の値が最も小さいもの

TIPS:陰の色に一工夫

SetAddColor メソッドでは、BaseColor にはシンプルに色を加算しているのに対して、_1st_ShadeColor に与える色はちょっとややこしいことをしています。
結論からいうと、 BaseColor が明るくなるほど、影である 1stShadow は暗くなるようになっています。(ドラゴンボールファイターズリスペクト)
定数 SHADE_DARKNESS_WEIGHT の値でその程度が変えられるようになっているので、お好みで。

使い方

光を与えるUTCシェーダーのモデルに CellLookLightReciver コンポーネントをアタッチ

image.png

Inspector内で、光の影響を与えたいモデル内のRencererオブジェクトを Renderers に参照を与えておいてください!

発光源として空のオブジェクトに CellLookLight コンポーネントをアタッチ&配置

image.png

Inspector内で、色(Color)と範囲(Range)を設定。シーンビューに表示される球のギズモを参考に。

実行!

シーンを実行して、モデルを光の範囲内に入れたり範囲内でグルグル動かしたりしてみましょう。
光源の色の影響を受けたり、光源との位置関係によって陰影が変化したりする様子が確認できます。

まとめ

メッシュに与える光の角度と色を変えることでゲーム中に光が発生したっぽい表現を実現できました。
残り課題として…

  • 範囲内・外を出たり入ったりしたときにパカパカ色が変わる
  • 光源との距離で光の影響具合が変わらない
  • 複数の光源があってもどれか一つの影響しか受けない

などがありますが、いずれも既存メソッド内の計算を調整すれば対応できそうです。
また、Bloomを使えばよりそれっぽくなりそうなので、いろいろ応用が効きそう。

今回はここまで!

追記

2019/08/26

ストックしてくださった方が何人かいらっしゃったので、

  • 範囲内・外を出たり入ったりしたときにパカパカ色が変わる
  • 光源との距離で光の影響具合が変わらない

↑ を改善したデモを作ってみました。

CellLookLightReceiver.csUpdate 内で計算してるdistanceRateが1の時は光の影響が0、distanceRateが0の時は光の影響が1となるようにしています。

【ポイント】
_Is_MLDはデフォルトの光の角度と入力した光の角度をLerpで補完する割合の値としてシェーダー内で使っているので、1/0の間の値をdistanceRate由来の値にすることで急な影の角度の変化を防いでいます

8
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
14