C++幼女先輩

プログラミング成分多め

UNITY メニュー拡張 part2 値を保存する

はじめに

UNITYのメニュー拡張をし Windowの表示は出来ました
ところが、このままでは一度ウインドウを閉じたあと再び開いても設定した値がデフォルトに戻ります
前回設定した値が残っていて欲しい時は多いと思うので、その設定です

方針

EditorWindowの値をファイルに保存すれば良いが、UNITYにはそれを補助する機能がある
ScriptableObjectを継承していれば簡単に yamlの形でAssetとして保存出来る

アセット作成

まず保存するためのAssetを作る必要があるが、おそらくコードで作成させることも出来るとおもうが
エディタ拡張でぱぱっとやってみる

まず保存するデータのクラスを ScriptableObjectを継承し作成する

[CreateAssetMenu]
public class TestObject : ScriptableObject
{
    [SerializeField] public  string hoge_;
}

[CreateAssetMenu] のアトリビュートをつけると、メニューの Asset->Create に表示されるようになる
つまり メニューから、保存先のAssetを作成する

ScriptableObjectを継承するのはSerializeのためである
今回は string hoge_ を保存する

とりあえず上記で 保存するオブジェクトは出来た

シリアライズ&デシリアライズ

先ほど作った TestObjectを使い シリアライズ、デシリアライズを行う

まず、アセットが読みこまれてない場合は読みこむ
if (this.data == null) { this.data = LoadData(); }

中身は、アセット一覧より TestObjectのものを探す

そして、データのアクセスは TestObjectを経由する

EditorGUI.BeginChangeCheck();
EditorGUI.EndChangeCheck();
にて、GUIの値が変更された時のみ、Assetに書き込む
data.hoge = hoge;
EditorUtility.SetDirty(this.data);

public class TestWindow : EditorWindow
{
    [SerializeField] private TestObject data;


    [MenuItem("Tools/Test")]
    public static void ShowWindow()
    {
        EditorWindow.GetWindow(typeof(TestWindow));
    }

    void OnGUI()
    {
        if (this.data == null) { this.data = LoadData(); }

        EditorGUI.BeginChangeCheck();

        string hoge_ = data.hoge_;
        hoge_ = EditorGUILayout.TextField("HOGE", hoge_);

        if (EditorGUI.EndChangeCheck()) 
        {
            data.hoge_ = hoge_;
            EditorUtility.SetDirty(this.data); 
        }

    }
    static TestObject LoadData()
    {
        return (TestObject)AssetDatabase.FindAssets("t:ScriptableObject")
           .Select(guid => AssetDatabase.GUIDToAssetPath(guid)) 
           .Select(path => AssetDatabase.LoadAssetAtPath(path, typeof(TestObject))) 
           .Where(obj => obj != null) 
           .FirstOrDefault(); 
    }

}

これで簡単だが、EditorWindowの値をシリアライズさせることができる

UNITY メニュー拡張 part1

コード
https://github.com/YukiMiyatake/UnityLesson/tree/MenuItem_1

はじめに

UNITYでは、インスペクターやメニューを簡単に拡張できる
エディタ拡張により開発効率も大幅に上げる事が可能である
面倒でもこの手の機能は初期に作っておけば、その後の開発期間が大幅に短縮される事が多い
そのため、ぜひとも覚えておきたい

メニュー拡張基本

f:id:murasame-labo:20171015190149p:plain 上記のように メニューにオリジナルの項目を追加できる
メニューは階層化出来、メニュー選択でウインドウ表示することが多い
まず、インスペクタやメニュー等の拡張を エディタ拡張と呼び
Editorフォルダ以下にスクリプトを置く必要がある

メニューの場合は クラスメソッド(static関数)に MenuItemアトリビュートをつければいい

Assets/Menu/Editor/TestMenu.cs

using UnityEngine;
using UnityEditor;

public class TestMenu
{
    [MenuItem("Test/Menu %a")]
    public static void ShowMenu()
    {
        Debug.Log("ShowMenu");
    }
}

MenuItemの第一引数に メニューの階層を指定する。
また、文字列に %a などを入れると、ショートカットキーを設定できる
そのあたりの詳細は是非マニュアルを見て欲しい

Unity - スクリプトリファレンス: MenuItem

非常に簡単にメニュー拡張が出来た

MenuItemの引数

MenuItemは3つの引数をもてる
第一引数はString型。メニューの項目名で、ショートカットキーも設定できる
第三引数はint型。表示の優先度

問題が第二引数
同じ名前のメニュー項目がある場合、メニュー表示時に第二引数がtrueのものが先に呼ばれ
falseを返すとそのメニュー項目はDisableになり、trueの場合はEnableになりクリックするともう一つの関数が呼ばれる
つまり メニュー表示時にValidationを行う事ができる

例えば、選択中のオブジェクトにScriptなりコンポーネントを自動で追加するメニュー拡張をしようと思った場合
GameObject以外を選択していたら 無効にすることができる

また、MenuItemの関数には、MenuCommandという引数を取る事が可能
調べたところ、このMenuCommandは Hierarchyの ルートが取れるようだ。まだ何に使うかわかってない。再調査が必要
Selection.activeObject で、現在選択中のオブジェクトが取得できる

ので、今回は下記のものを作る
Test/AddObject %b メニューを作り、選択しているものがGameObject以外は無効にする
GameObjectの場合は
MenuCommandという名前のGameObjectを MenuCommand.contextを親にして作成
NewGameという名前のGameObjectを Selection.activeObjectを親にし作成し RidgedBodyをつける
共にUndoに登録(これをしなければ Ctrl+Zで取り消せない)

    [MenuItem("Test/AddObject %b", true)]
    static bool AddObjectValidate()
    {
        // GameObjectか?
        return Selection.activeObject.GetType() == typeof(GameObject);
    }

    [MenuItem("Test/AddObject %b", false)]
    static void AddObject(MenuCommand command)
    {
        GameObject objMenuCommand = new GameObject("MenuCommand");
        // Undo登録
        Undo.RegisterCreatedObjectUndo(objMenuCommand, "Test_AddObject " + objMenuCommand.name);
        GameObjectUtility.SetParentAndAlign(objMenuCommand, command.context as GameObject);


        GameObject obj = new GameObject("NewGame");
        GameObjectUtility.SetParentAndAlign(obj, Selection.activeGameObject );

        // Undo登録
        Undo.RegisterCreatedObjectUndo(obj, "Test_AddObject " + obj.name);
        Undo.AddComponent<Rigidbody>(obj);

    }

f:id:murasame-labo:20171016053405p:plain

実行の結果、MenuCommandは Hierarchyルートに、NewGameは選択したGameObject(ここではTest)の下に出来た
なお GameObject以外を選択した場合には MenuがDisableする事も確認できた

UNITYシェーダー研究 Vertexライティング?

コード
https://github.com/YukiMiyatake/UnityLesson/tree/Shader_7

はじめに

前回まではピクセルシェーダにてライティング計算をしていました
これはクオリティの高いライティングになりますが、少し処理が重くなるので
ディフューズカラー、スペキュラを頂点シェーダで計算しようという計画

シェーダー入出力の説明

今まで説明省略してたのを大雑把に
頂点シェーダ(vert)、フラグメントシェーダ(frag)には引数がある

頂点シェーダの引数は構造体で表現し、頂点ごとのパラメータが入ってくる
例えば ポジション、法線、UVなど

そして頂点シェーダの返り値も構造体で、頂点毎の情報を設定する

そして 頂点毎にフラグメントシェーダに その時 ポジション(SV_POSITION)以外は線形補完されます
UVが保管され、テクスチャマッピングが正しく出来るのはわかると思うが
法線なども補完され、ライティングがスムースになります

シェーダ引数は、型のほかにセマンティクスがあり、目的別に指定します
細かい事は説明しません

たとえば前回は

 struct appdata
    {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
        float2 uv     : TEXCOORD0;
    };

    struct v2f
    {
        float4 vertex : SV_POSITION;
        float4 vertexW: TEXCOORD0;
        float2 uv     : TEXCOORD1;
        float3 normal : TEXCOORD2;
    };

頂点シェーダの引数は ポジション、法線、UV座標
フラグメントシェーダ引数は ポジション、ワールド座標でのポジション、UV、法線

フラグメントシェーダ引数はピクセルごとに補完される

方針

1個目のライト(ForwardBase)は現状通りのピクセルでのライティングを行う

2個目以降のライト(ForwardAdd)は、頂点シェーダでライト計算を行い ピクセルシェーダでは補完された値を使う
少し精度はわるくなるが、計算量は削減されるはずである

具体的には ディフューズの値を補完することができる
スペキュラに関しては補完することも可能であるが、大きくクオリティが落ちる可能性がある
今回は例として スペキュラも補完して軽くしよう

コード

具体的には ディフューズとスペキュラの計算を頂点シェーダに持っていく
ディフューズとスペキュラの値を フラグメントシェーダに渡す

構造体は下記になる

    struct appdata
    {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
        float2 uv     : TEXCOORD0;
    };

    struct v2f
    {
        float4 vertex : SV_POSITION;
        float2 uv     : TEXCOORD1;

        float3 diffuse : TEXCOORD2;
        float3 specular : TEXCOORD3;
    };

頂点シェーダの入力に変更なし

フラグメントシェーダの入力は
仕様にて vertexは削れない
ワールド座標のポジションは不要 法線も不要

ディフューズ値とスペキュラ値を追加

    v2f vert(appdata v)
    {
        v2f o;
        o.vertex = UnityObjectToClipPos(v.vertex);
        float4 vertexW = mul(unity_ObjectToWorld, v.vertex);

        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
        float3 normal = UnityObjectToWorldNormal(v.normal);


        float3 L = normalize(_WorldSpaceLightPos0.xyz);
        float3 V = normalize(_WorldSpaceCameraPos - vertexW.xyz);
        float3 N = normal;
        float3 H = normalize(L + V);
        float3 lightCol = _LightColor0.rgb * LIGHT_ATTENUATION(i);
        float3 NdotL = dot(N, L);

        o.diffuse = (NdotL*0.5 + 0.5) * lightCol;
        o.specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz * lightCol;  // Half vector

        return o;
    }

    float3 frag(v2f i) : SV_Target
    {

        // texture albedo
        float4 tex = tex2D(_MainTex, i.uv);

        return i.diffuse * tex + i.specular;
    }

フラグメントシェーダで計算していたディフューズとスペキュラを頂点シェーダで計算
フラグメントシェーダでは 補完されたディフューズとスペキュラをそのまま利用する

通常
f:id:murasame-labo:20170925004107p:plain

軽量化 f:id:murasame-labo:20170925004122p:plain

心配になるぐらい 変わらなかった(心配

UNITYシェーダ研究 複数ライトの影響

コード
https://github.com/YukiMiyatake/UnityLesson/tree/Shader_6

前回まで

前回はAmbientも入れて だいたいライティング出来た
ただ、複数ライトを置いても1つ目のライトの影響しか受けない
ので、複数ライトの影響をうけるようにする

ライトの強さ

複数ライト対応するまえに、今までのライトではライトカラーの影響はうけるが
ライトの強さの影響が入っていないので追加する

ライトの種類により減衰しないライト(DirectionalLightなど)と 減衰するライト(PointLight等)があり
それらをまとめて計算するマクロが存在するので それを使う

AutoLight.cginc をIncludeし、LIGHT_ATTENUATION(i) でライトの強さが取得できる iはピクセルデータ

それをライトカラーに乗算することで、強さを反映したライト色が計算できる

float3 lightCol = _LightColor0.rgb * LIGHT_ATTENUATION(i);

ライトカラーは、ディフューズとスペキュラに影響するので、それぞれに乗算する

float3 diffuse = (NdotL*0.5 + 0.5) * lightCol;

float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz * lightCol;  // Half vector

複数ライトに対応

UNITYでは、キャラに最も影響の強いライトは LightMode=ForwardBaseで計算するが
その他のライトは LightMode=ForwardAddで入ってくる
ので、ForwardAddのパスを増やせばいい

シェーダーの中身は 今回はほぼ、ForwardBaseのものをコピーする
ただし、Ambientは1回しか計算してはいけないので ForwardAddでは計算行わない事と
BlendModeを One Oneなどにしなければ、合成されない

BlendMode設定なし
f:id:murasame-labo:20170924035237p:plain

メインライトのみ
f:id:murasame-labo:20170924035250p:plain

ライト2個(赤ライト追加)
f:id:murasame-labo:20170924035334p:plain()

複数ライトが反映されるようになった

UNITYシェーダ研究 Ambient

コード
https://github.com/YukiMiyatake/UnityLesson/tree/Shader_5

前回のシェーダ

ディフューズとスペキュラを実装し、だいぶライティングができた
テクスチャマッピングもしている

でも、Ambientも与えていません

Ambientの設定

UNITYのバージョンにより違うけど
最近のバージョンでは
Window-Lighting-Setting から行う

f:id:murasame-labo:20170922015418p:plain

Environment Lightingのあたり

Ambientの取得

Ambinetカラーの取得は定数が用意されているので簡単だ

UNITY_LIGHTMODEL_AMBIENT.rgb;

これを計算式に加えるが
テクスチャ * Ambientの値を今までのものに足せばよいので最終的には

最終カラー = Ambientテクスチャ + Diffuseテクスチャ + スペキュラ
= (Ambient+Diffuse) * テクスチャ + スペキュラ
にした

             float3 frag(v2f i) : SV_Target
                {
                    float3 L = normalize(_WorldSpaceLightPos0.xyz);
                    float3 V = normalize(_WorldSpaceCameraPos - i.vertexW.xyz);
                    float3 N = i.normal;
                    float3 H = normalize(L + V);


                    //Ambient
                    float3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;
                    
                    // texture albedo
                    float4 tex = tex2D(_MainTex, i.uv);

                    // Diffuse(HalfLambert)
                    float3 NdotL = dot(N, L);
                    float3 diffuse = (NdotL*0.5 + 0.5) * _LightColor0.rgb;

                    // Speculer
                // float3 specular = pow(max(0.0, dot(reflect(-L, N), V)), _Spec1Power) * _Spec1Color.xyz;  // reflection
                    float3 specular = pow(max(0.0, dot(H, N)), _Spec1Power) *  _Spec1Color.xyz;  // Half vector


                    return (ambient + diffuse) * tex + specular;
                }

カレーハッキング

これからは ニジマスの手に入るシーズンになるので、ニジマスカレーを極めようとおもう

1回作ったけど 普通に美味しいのだけど、これからさらにプラスしていく

まず前回はゴールデンカレールウを使った。普通に美味しいけど、ビーフ等にあう、濃厚なルーであった
また スパイスも自分で頑張りたいので、スパイス研究科 中村繭子パイセンや友達のアドバイスもらいつつ
自分のニジマスカレーを完成させたい

知見

一度作った時の知見であるが、野菜の多いカレーが好きだが、歯ざわりや触感が統一されてないと ん?? ってなる
例えばヤングコーン入れたがそれだけ固く ん?? だったので、煮込む野菜以外は 蒸し野菜でトッピングする

ニジマスはおろして骨などを取り除くと食べやすいが、身がくずれたり、骨のエキスが美味しいので、いまはぶつ切りで考える
将来的には、あら汁と分けたり、トッピングで乗せ身が崩れなくするなど 考えたい

デミ系のカレー好きだけど、私の求めるものではないので、ルウにと溶かす野菜は慎重にえらぶ

次への

インドのベンガル地方は川魚のカレーが多いので参考にする
川魚用のスパイス
フェンネル、クミン、ニゲラ、ブラウンマスタード、フェニュグリーク

魚はターメリックと塩とレモン汁で漬けておいて使うこと多い

酸味苦手ではあるが、酸につけて少し骨を柔らかくできるといいな

カレー  上野不忍池 サルガム

何か料理を極めたいのでカレーにしてみた
カレーは好きだし、みんな食べられる

前提として小麦アレルギーなので、ご飯にあうカレー
お米はジャポニカ米がベストだけど、場合によればインディカ米買ってもいい

今は色々なものを作るのが先 敵を知るにはまず己を知る必要がある

不忍池近くの インド&ネパール料理
こういう店は、インド料理ではなくネパールがメインのはず

とりあえず すべて入ってるコース頼む
バターチキンカレー、キーマカレー、サラダ、デザート、ナン、ライス、シシカバブ、タンドリーチキン、ラッシー

バターチキン、キーマカレーは甘い。辛口MAXだけど 私は辛さには強いので物足りないが、とにかく砂糖甘さ
デザートはインドらしく 激甘
ナン、ラッシーは甘さ控えめ

ナンは甘いのが好きだったので、残念
カレーも 味は普通だけど、好みというか目指す方向と大きく違う

そして 小麦アレルギーで速攻おなか壊し、営業に送れるという ダメなパターン・・