ムラサメ研究ブログ

主にゲームやプログラミングのログ

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だけど 私は辛さには強いので物足りないが、とにかく砂糖甘さ
デザートはインドらしく 激甘
ナン、ラッシーは甘さ控えめ

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

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

UNITYシェーダ研究 Blinn-Phong

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

はじめに

ディフューズを入れて 基本的なLambertモデル実装しました

次はこれにスペキュラを追加します(念のため DirectSpecularのみです)
Blinn-Phong的な事をします(定義間違ってたら教えて下さい)

スペキュラ

鏡面反射です
前回のディフューズは、ライトが面に当たった時のライティングで、View(カメラの位置)とは関係ありません
スペキュラでは、光の反射モデルなので 鏡に光源が写った時= 光の反射を直視した時のみ強烈に光ります
3DCGでは こういう事になります
光の入射角と法線のreflectionベクトル(面法線に対してライトが同じ角度で反射)と Viewベクトルの内積と正の相関関係

スペキュラの場合は、相関は Powでかけます。細かい物理的な理由は調べてないけど
スペキュラのパラメータは今回は、スペキュラ色と Powerにします

property

 Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Spec1Power("Specular Power", Range(0, 30)) = 1
        _Spec1Color("Specular Color", Color) = (0.5,0.5,0.5,1)
    }

今回はスペキュラカラーとPowerが増えた

宣言

         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;
            };

            uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
            uniform float _Spec1Power;
            uniform float4 _Spec1Color;

念のため 今回はフラグメントシェーダで計算します。一部頂点シェーダで計算して補完してもいいものがありますが

スペキュラのパラメータ以外に フラグメントシェーダに vertexWを送ってます
vertexには ビューポート変換まで行うので、World座標での位置情報が必要なためです

頂点シェーダ

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

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

vertexWに対して World座標系への変換をかけています

フラグメントシェーダ

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


                // 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

    
                return diffuse*tex + specular;
            }

Vは ViewVectorです。  _WorldSpaceCameraPosは World座標系でのカメラのポジションで World座標系でのポジションをマイナスすると、ピクセルからカメラに向かうベクトルになります

dot(reflect(-L, N), V) これが、反射ベクトルと Viewベクトルの内積部分
reflectというのは組み込み関数で この場合は 法線を中心として 反射したライトベクトルが取れる
それを maxで 0未満(裏面)の計算を無効にし
スペキュラの強さで累乗しスペキュラ色を乗算

スペキュラは加算なので単純にディフューズに掛け合わせる

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

カメラを動かすと スペキュラのライティングだけ変わります
ディフューズはカメラ位置とは無関係なのもわかると思います

ハーフベクトル

上記の計算は 正確な反射ベクトルを計算した
具体的には reflect(-L,N)の部分だが、反射ベクトルは多少計算が重い
R = L - (2V・N) N
L - 2 * dot( V*2, N ) * N

スペキュラは ハーフベクトルを使って近似が出来る事が広く知られているのでそれを実装する

ハーフベクトルとは、ライトベクトルとViewベクトルの間のベクトルであり、それと法線の内積で近似出来る

ハーフベクトルは normalize(L+V); で表される
reflectより圧倒的に計算量が低い

float3 H = normalize(L+V);

// 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

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

計算間違えてるのか、ライトの影響パワーが大きく異なるが、スペキュラはかかった