お米 is ライス

C#やらUnityやらを勉強していて、これはメモっといたほうがええやろ、ということを書くつもりです

【Cg Programming/Unity】Transparent Surfaces ~ Cutaways【順番にやっていく】

今回はこちら
en.wikibooks.org
この節では描画するピクセルの一部を破棄することについて学ぶ。

ピクセルの一部を表示しない

Shader "Custom/Cutaways"
{
    SubShader
    {
        pass
        {
            Cull Off
            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            struct vertexOutput
            {
                float4 position : SV_POSITION;
                float4 vertexPos : TEXCOORD0;
            };

            vertexOutput v(float4 vertexPos : POSITION)
            {
                vertexOutput output;
                output.position = UnityObjectToClipPos(vertexPos);
                output.vertexPos = vertexPos;
                return output;
            }

            float4 f(vertexOutput input) : COLOR
            {
                //頂点のローカル座標のyが0以上の場合(半分より上の部分)
                //ピクセルを描画しない
                if(input.vertexPos.y > 0)
                {
                    discard;
                }
                return input.vertexPos + float4(0.5, 0.5, 0.5, 0);
            }
            ENDCG
        }
    }
}

(Cull Offというキーワードについては後述)

discard

注目すべきはフラグメントシェーダf関数でdiscardと書かれている箇所だ。
input.vertexPos.yという条件が真の場合にdiscardが実行される。
discardが実行された場合、そのピクセルの描画は破棄され、何も描画されない。
(つまり後ろに何か別のオブジェクトがあった場合透けて見える)
f:id:spi_8823:20200505185454p:plain

ただし、このdiscardを使ってしまうと一部の最適化が利かなくなってしまうため、パフォーマンスのためになるべく使用しないほうがいいらしい。
ではピクセルの破棄をdiscard無しでどのようにやるのかはまあそのうちあるでしょう。

Culling

さて、上のコードにはdiscardのほかにもCull Offという見慣れないキーワードがあった。
これは「Culling、つまり間引きをしない」ことを表している。

3Dオブジェクト上の面には必ず(カメラから見て)表と裏があり、裏面の描画をされても役に立たない場合が多い。
3Dモデルでは大抵の裏面はモデルの後ろ側に存在し、表を向いている面で隠されてしまうだろう。
したがって、特にこれといった事情が無い限りは裏を向いている面は描画しないということにすれば大幅にGPUの計算コストを下げることができる。

このようにモデルの面がカメラから見て表裏どちらを向いているかによって描画を省略してしまうための機能がこのCullingである。

Cull Back

デフォルトではCull Backとなっており、裏を向いている面の描画は省略されている。

Cull Front

逆に、表を向いている面の描画を省略したい場合はCull Frontという風に指定してやる。

Cull Off

描画の省略をしたくないという場合はCull Offと指定する。
今回の場合、オブジェクトの上から半分の描画を破棄したことによりオブジェクトの中身が見えるようになってしまっている。そこで、不自然な描画とならないようにCull Offしているのだ。
もしこれを指定せずにCull Backのままであれば、以下のように描画されてしまう。
f:id:spi_8823:20200505191858p:plain

Passの追加

上記でCullingについて学んだ。
表面と裏面で描画の方法を変えてやりたい場合はCull Frontした描画の上にCull Backした描画を重ねてやればよい。
このように1つのオブジェクトに対して複数の描画を行えるようにするため、UnityではPassを複数書けるようになっている。
Passは上から順に実行される。
例えば、表を向いた面の色は白っぽくするといったことができる。
f:id:spi_8823:20200505192636p:plain
具体的なコードは以下である。

Shader "Custom/Cutaways"
{
    SubShader
    {
        //Passは書いた順番で実行される
        pass
        {
            Cull Front  //まず裏面の描画から
            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            struct vertexOutput
            {
                float4 position : SV_POSITION;
                float4 vertexPos : TEXCOORD0;
            };

            vertexOutput v(float4 vertexPos : POSITION)
            {
                vertexOutput output;
                output.position = UnityObjectToClipPos(vertexPos);
                output.vertexPos = vertexPos;
                return output;
            }

            float4 f(vertexOutput input) : COLOR
            {
                //頂点のローカル座標のyが0以上の場合(半分より上の部分)
                //ピクセルを描画しない
                if(input.vertexPos.y > 0)
                {
                    discard;
                }
                return input.vertexPos + float4(0.5, 0.5, 0.5, 0);
            }
            ENDCG
        }

        pass
        {
            Cull Back   //表面の描画
            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            struct vertexOutput
            {
                float4 position : SV_POSITION;
                float4 vertexPos : TEXCOORD0;
            };

            vertexOutput v(float4 vertexPos : POSITION)
            {
                vertexOutput output;
                output.position = UnityObjectToClipPos(vertexPos);
                output.vertexPos = vertexPos;
                return output;
            }

            float4 f(vertexOutput input) : COLOR
            {
                //頂点のローカル座標のyが0以上の場合(半分より上の部分)
                //ピクセルを描画しない
                if(input.vertexPos.y > 0)
                {
                    discard;
                }
                return input.vertexPos + float4(1, 1, 1, 1);
            }
            ENDCG
        }
    }
}

次回はこちら。
spi8823.hatenablog.com

【Cg Programming/Unity】Basics ~ Shading in World Space【順番にやっていく】

今回はこれ
en.wikibooks.org
この節の目的は、頂点シェーダにおいて頂点のローカル座標をワールド座標に変換する方法を通して、その過程で用いる「uniform変数」を理解すること。

オブジェクトの座標をワールド座標に変換する

これまで頂点シェーダにおいて : POSITIONセマンティクスを付与することによって受け取っていた座標は頂点の相対的な座標、つまりローカル座標である。
これを世界に対する絶対的な座標、つまりワールド座標に変換することを、頂点に対して4x4の正方行列をかけることによって実現する。
(座標は3次元なのに4x4行列なの?と思うかもしれないが、簡単に言うと回転用に3、平行移動用に1が用意されていると理解するとよい)

モデル行列

この時に用いる4x4正方行列を「モデル行列」と呼ぶらしい。
(モデルを変換する行列だからこう呼ぶらしいが、ちょっとミスリーディングな名前と感じる)
このモデル行列は自前で計算せずともTransformコンポーネント側で計算してくれていて、シェーダからはunity_ObjectToWorldという変数を参照することで使用することができる。

uniform変数

unity_ObjectToWorld変数のように(簡単に言うと)各頂点における値が共通のものを「uniform変数」と呼ぶ。
The Book of Shaders: uniforms
uniform変数を使うために本来はuniform float4x4 unity_ObjectToWorldというuniform変数の宣言が必要であるが、unity_ObjectToWorldのようによく使用される変数に関してはUnity側で自動で宣言されているので自前で宣言する必要はない。

コード

Shader "Custom/ShadingInWorldSpace"
{
    Properties
    {
        //Materialの設定から変更するためにはここで定義しておく
        _ColorOffset ("offset of color", Color) = (0, 0, 0, 0)
    }
    SubShader
    {
        pass
        {
            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            //Unityが宣言してくれているので宣言しなくてよい
            //(宣言するとエラー)
            //uniform float4x4 unity_ObjectToWorld;

            //Propertiesで定義した変数をシェーダ側で使用するため、
            //ここでuniform変数宣言する
            uniform float4 _ColorOffset;

            struct vertexOutput
            {
                float4 position : SV_POSITION;
                float4 color : TEXCOORD0;
            };

            vertexOutput v(float4 vertexPos : POSITION)
            {
                vertexOutput output;
                output.position = UnityObjectToClipPos(vertexPos);
                output.color = mul(unity_ObjectToWorld, vertexPos) + _ColorOffset;  //uniform変数を使用して出力する色を決定する
                return output;
            }

            float4 f(vertexOutput input) : COLOR
            {
                return input.color;
            }
            ENDCG
        }
    }
}

output.color = mul(unity_ObjectToWorld, vertexPos)のように書けば、RGBCubeの節で各頂点のローカル座標を色として出力していた代わりに、各頂点のワールド座標を色として出力することができる。
以下が実際に描画したもの。CubeをY軸に45度回転させ、少しX軸正方向に移動させてあるので、赤の成分が角に向かって全体的に多くなっている。
f:id:spi_8823:20200504235633p:plain

Properties

ところで、unity_ObjectToWorldとは別にもう一つ_ColorOffsetというuniform変数を使っているのに気付いただろうか?
この変数は私が自分で勝手に定義したもので、どこで定義しているかというとコードの上のほう、Propertiesという部分である。
ShaderLab記法ではこのようにPropertiesの中で定義すればその値をインスペクタからMaterialの変数として操作することができる。
多分アンダーバー始まりで命名しないと怒られる。
上の状態のCubeに対して_ColorOffsetを(1, 1, 1, 0)とすると以下のような見た目になる。座標が負の部分以外は真っ白になっている。
f:id:spi_8823:20200504235806p:plain

以下のように書くとインスペクタだけでなくC#コードからも値を制御することができる。

GetComponent<Renderer>().sharedMaterial.SetColor("_ColorOffset", new Color(0, 0, 0, 0));

その他のuniform変数

Unityにおいてデフォルトで定義されているuniform変数はほかにも色々ある。
いっぱいあるが、ここではごく一部だけ紹介する。

float4 _Time

_Time.yでゲームが始まってからの経過時間が取得できる。なぜfloat4になっているのかというと、経過時間に1/20, 1, 2, 3をかけたものがそれぞれx, y, z, wに入っているからだ。これらの変数はシェーダの中でアニメーションをするのに使用できる。
他にも_SinTime(1/8, 1/4, 1/2, 1)や_CosTime(左に同じ)で時間の正弦、余弦をとったものが参照できたり、unity_DeltaTimeで前のフレームからの経過時間が取れたりする。

float4x4 UNITY_MATRIX_MVP

UNITY_MATRIX_MVPという変数名で頂点のローカル座標をクリッピング座標に変換する行列が取得できる。
mul(UNITY_MATRIX_MVP, vertexPos)としてやればUnityObjectToClipPos(vertexPos)と等価なやつだ。
他にも、ワールド座標をクリッピング座標に変換するUNITY_MATRIX_VPなどの行列が定義されている。
(なんとなく察したと思うがMがモデル行列、Vがビュー行列、Pがプロジェクション行列であり、MVなど2つ組み合わせたものやPのようにプロジェクション行列のみのものもある)

Unityで定義されているuniform変数は以下の公式ドキュメントでまとめられているので詳しくはそちらを参照されたし。
docs.unity3d.com


以上。
ようやっと基礎編が終わったぜよ~~。
次はこちら。
spi8823.hatenablog.com

【Cg Programming/Unity】Basics ~ Debbuging of Shaders【順番にやっていく】

今回はこれ。
en.wikibooks.org

内容

前回までに紹介したように、例えば : POSITIONというセマンティクスをつけることで頂点の座標を頂点シェーダのインプットとして使用することができる。
このように頂点シェーダのインプットとして使用できるパラメータはほかにも色々用意されており、この節ではそれらの紹介と、実際に各ピクセルの色として出力してやって確認してみようということである。

Shader "Custom/DebbugingOfShaders"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            struct vertexInput
            {
                float4 position : POSITION; // 頂点座標
                float3 normal : NORMAL;     // 法線ベクトル
                float4 tangent : TANGENT;   // 法線に垂直なベクトル(面に平行なベクトル)
                float4 texcoord0 : TEXCOORD0;   // 1番目のテクスチャUV座標
                float4 texcoord1 : TEXCOORD1;   // 2番目のUV座標(1番目とは異なる座標が入っているが、よく知らん)
                float4 texcoord7 : TEXCOORD7;   // 8番目のUV座標
                float4 color : COLOR;
            };

            /* 以下、デフォルトで定義されているもの
            struct appdata_base 
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct appdata_tan 
            {
                float4 vertex : POSITION;
                float4 tangent : TANGENT;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct appdata_full 
            {
                float4 vertex : POSITION;
                float4 tangent : TANGENT;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
                float4 texcoord1 : TEXCOORD1;
                float4 texcoord2 : TEXCOORD2;
                float4 texcoord3 : TEXCOORD3;
                fixed4 color : COLOR;
                // and additional texture coordinates only on XBOX360
            };

            struct appdata_img 
            {
                float4 vertex : POSITION;
                half2 texcoord : TEXCOORD0;
            };
            // デフォルトで定義されているものここまで
            */

            struct vertexOutput
            {
                float4 pos : SV_POSITION;
                float4 col : TEXCOORD0;
            };

            vertexOutput v(vertexInput input)
            {
                vertexOutput output;
                output.pos = UnityObjectToClipPos(input.position);

                output.col = input.position + float4(0.5, 0.5, 0.5, 0.0);
                //output.col = input.normal;
                //output.col = input.tangent;
                //output.col = input.texcoord0;
                //output.col = input.texcoord1;
                //output.col = input.texcoord7;

                // ベクトルの各要素は(x,y,z,w)あるいは(r,g,b,a)のように名前がついており
                // それらを任意の組み合わせ・順番で4つ並べたベクトルを以下のように取得することができる
                //output.col = input.position.xyzw;
                //output.col = input.position.rgba;
                //output.col = input.position.xxyy;
                // position.xやposition.aaのようにスカラーや2次元ベクトル、3次元ベクトルも取得できる

                return output;
            }

            float4 f(vertexOutput input) : COLOR
            {
                return input.col;
            }
            ENDCG
        }
    }
}

セマンティクスとは

ここまでセマンティクスという言葉をなんとなく使ってきてしまったが、改めて何を表しているのか確認しておきたい。
といってもUnityの公式ドキュメントで詳しく説明されているのでそこを参照されたし。
docs.unity3d.com
粉々にかみ砕いて説明すると、「その変数が何をするための値なのかを指定」している。
変数名はこっちで勝手に決めちゃうから、中身にはこういう値を入れてねとプログラムに伝えるためのものがセマンティクスである。

ベクトルへの便利なアクセス方法

頂点シェーダの最後のほうに書いてあるinput.position.xyzwなどがそれである。
コメントで説明しているのでここでは省略する。
このようにCG言語(というかシェーダ言語)ではベクトルや行列を便利に操作するための機能が豊富にそろえられている。
どのような関数があるかはNVIDIAによるこちらの公式ドキュメントを参照されたし。(バージョンが合ってるかは知らん)
http://developer.download.nvidia.com/cg/Cg_3.0/Cg_Users_Manual_JP.pdf#page=40

(この辺についてググろうとしたのだけど、「CG言語」って検索性悪すぎやろ。。。ストレスたまる~~)

各パラメータについて

コメントで書いてある通りです。(丸投げ)(この書き方めっちゃ昔のインターネットっぽい)
いちいち自分で構造体を定義しなくてもappdata_baseみたいにいくつかよく使う組み合わせで定義してくれてあるのでそれを使ってもいいよって書いてありました。
あとは、「そのままの値だと負の数になってたりして上手く色として出力できないのでいい感じに変形してね」とか「色んなベクトル操作をしてシェーダの書き方に慣れよう」みたいな感じなので飛ばす。

以上。
正直この節はいろいろあるよ~っていう紹介だけです。

次はこちら。
spi8823.hatenablog.com

【Cg Programming/Unity】Basics ~ RGB Cube【順番にやっていく】

一日坊主を何としてでも避けるため、中途半端に寝てしまって夜中に妙に冴えてしまった頭に鞭を入れつつ筆を執る。
今回はこちら。
en.wikibooks.org

ピクセルの色の指定方法

ピクセルの色はRGBAであらわされる。シェーダをやろうとしてこれ知らん人はおるまいので飛ばす。

各頂点のローカル座標によって出力する色を変える

Shader "Custom/RGBCube"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex v
            #pragma fragment f

            //頂点シェーダで値を入れてフラグメントシェーダに渡すための構造体
            struct vertexOutput
            {
                float4 pos : SV_POSITION;
                float4 col : TEXCOORD0;
                //nointerpolation float4 col : TEXCOORD0;
            };

            vertexOutput v(float4 vertexPos : POSITION)
            {
                vertexOutput output;
                output.pos = UnityObjectToClipPos(vertexPos);
                output.col = vertexPos + float4(0.5, 0.5, 0.5, 0.0);
                return output;
            }

            //やっていることはv関数とまったく同等
            void v2(float4 vertexPos : POSITION, out float4 pos : SV_POSITION, out float4 col : TEXCOORD0)
            {
                pos = mul(UNITY_MATRIX_MVP, vertexPos); // UnityObjectToClipPos(vertexPos)と同等
                col = vertexPos + float4(0.5, 0.5, 0.5, 0.0);
                return;
            }

            float4 f(vertexOutput input) : COLOR
            {
                return input.col;
            }

            //nointerpolationを指定すると補間をせず頂点シェーダで代入された値をそのまま使用する
            //float4 f2(float4 pos : SV_POSITION, nointerpolation float4 col : TEXCOORD0) : 
            float4 f2(float4 pos : SV_POSITION, float4 col : TEXCOORD0) : COLOR
            {
                return col;
            }
            ENDCG
        }
    }
}

まずは頂点シェーダとしてv関数、フラグメントシェーダとしてf関数を見てほしい。

構造体の宣言

目立つのはvertexOutputという構造体の宣言である。
頂点シェーダで値を決定し、フラグメントシェーダに渡す値はこうして構造体にすることができる。ここではTEXCOORD0というセマンティクスで宣言した変数をRGB値の受け渡しに使っている。一般的にこのセマンティクスはテクスチャの座標指定に使用されることが多いが、どう使用するかは基本的に自由である。

このようにして宣言した構造体を頂点シェーダの返り値およびフラグメントシェーダの引数として用いれば、シェーダ間の値の受け渡しをまとめて行うことができる。

色の指定

v関数ではcolという出力に頂点のローカル座標にオフセットを足した値を代入している。
(オフセットを足しているのは、-0.5 ~ 0.5となっている値を0 ~ 1とするためである。)
フラグメントシェーダのf関数ではこの値を各ピクセルの値として出力している。

フラグメントシェーダの線形補間

さて、上記のように実装したシェーダを用いてCubeを描画してみよう。
すると以下のような見た目になる。
f:id:spi_8823:20200503033055p:plain
見ればわかるように、各頂点から他の頂点に向かって徐々に色が変わって描画されている。
頂点シェーダでは各頂点の座標、つまり離散的な値を出力したはずなのになぜこのような見た目になるのか。
フラグメントシェーダでは3つの頂点で定められる"面"に含まれる"点"が描画される。したがってフラグメントシェーダでは3つの頂点の出力をミックスした値が引数として渡されているのだ。

別の書き方

v関数とv2関数、f関数とf2関数はそれぞれ全く同じ処理を書いている。

構造体でやりとりしていた値は頂点シェーダ側ではout引数に、フラグメントシェーダ側では通常の引数に指定してやることでもやりとりすることができる。

また、v関数でUnityObjectToClipPos関数を用いて計算していた各頂点のクリッピング座標は、mul(UNITY_MATRIX_MVP, vertexPos)のように変換行列と頂点のローカル座標をかけたものに等しい。
(ちなみにこちらの方法で書くとUnityが勝手にUnityObjectToClipPosのほうに書き換えてしまう)

線形補間をあえてさせない

フラグメントシェーダに渡される引数の値は3つの頂点の値を線形補間したものだった。
col変数の各宣言の個所でnointerpolationというオプションを指定することによって線形補間しない値を使用することができる。
(コード中にはコメントアウトして書いてあるので適宜入れ替えて実行されたし)
このようにして描画を行ったものは以下のような見た目になる。
各頂点座標がそのまま使用されているのがわかる。
f:id:spi_8823:20200503035036p:plain

余談

和訳するのが面倒なので本家のページの内容そのままではないけど許してくれ~



次はこちら。
spi8823.hatenablog.com

【Cg Programming/Unity】Basics ~ Minimal Shader【順番にやっていく】

en.wikibooks.org

やっていくぞう!
前にGLSLはチラッとやったことがあるのだけど、CGとかHLSLになると色々書き方が違うのでMinimal Shaderからやり直し。
全部書き起こすのはめんどくさいのでなるべく要点だけにとどめておきたい。

Unityを開いてプロジェクトを作るんだ

せやね

シェーダを新規作成しろ

「Projectウィンドウ」→「Create」→「Shader」→「Standard Surface Shader」で作成する
作成したファイルをダブルクリックするとエディタが開いてシェーダファイルを編集できる。
このファイルは拡張子が「.shader」で、「ShaderLab」というUnity独自の記法で書かれている。

さて、作成したファイルの中身を以下のように書き換える。

Shader "Custom/MinimalShader"
{
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            float4 vert(float4 vertexPos : POSITION) : SV_POSITION
            {
                return UnityObjectToClipPos(vertexPos);
            }

            float4 frag(void) : COLOR
            {
                return float4(1.0, 0.0, 0.0, 1.0);
            }
            ENDCG
        }
    }
}

Passでくくられた部分が1つのまとまりとなって描画を行う。
Passをいっぱい書けばそれだけ複雑な描画を行うことができる。

CGPROGRAMからENDCGまでの間にシェーダ言語であるCGスクリプトを書く。

シェーダには頂点シェーダやフラグメントシェーダなどがあり、#pragma vertex vertは頂点シェーダとしてvertという関数を用いるということを宣言している。フラグメントシェーダについても同様である。
頂点シェーダやフラグメントシェーダが何なのかは別途調べていただきたい。

vert関数では各頂点のローカル座標であるvertexPosを、UnityObjectToClipPosという既定の関数を使ってクリッピング座標に変換している。
: POSITIONと書くことによってvertexPosが頂点のローカル座標であることを宣言している。
: SV_POSITIONはこの関数の返り値がクリッピング座標であることを宣言している。

frag関数ではそのピクセルでの色を定数値(1, 0, 0, 1)と設定する(つまり赤色)。
: COLORはこの関数の返り値がピクセルの色であることを宣言している。

作成したシェーダファイルで実際に描画してみるのだ

  1. 適当にMaterialファイルを作成する
  2. 作成したシェーダファイルをドラッグ&ドロップ等してMaterialファイルに適用する
  3. 適当にCubeなどのオブジェクトをScene上に配置する
  4. MaterialファイルをCubeにアタッチする

これで今回作成したシェーダスクリプトを使ってCubeを描画できる。
frag関数内の(1, 0, 0, 1)(0, 1, 0, 1)などに変更してやれば、書いたスクリプト通りにCubeが描画されていることがより実感できるだろう。

シェーダファイルのエディタ

シェーダファイルを編集しようとすると当然デフォルトではVisual Studioが開くのだが、Visual StudioさんでUnityのシェーダを書こうとすると非常にストレスフルな挙動になっているので何とかしたい。
とりあえず、VSCodeに「ShaderlabVSCode」という拡張を入れればいい感じに補完とかもしてくれそう。
とはいえ、C#Visual Studioで書きたいとなるとExternal Editorはそのままにしておきたいのでどうしたものか。。。
応急処置としてエディタ拡張でシェーダファイルの横にVSCodeで開くボタンを出しておくことでVisual StudioVSCodeを使い分けられるようにしておく。

f:id:spi_8823:20200502012340p:plain

書いたスクリプトは以下
【Unity】Projectビューにエクスプローラで開くボタンを追加するエディタ拡張 - コガネブログ を参考)

次はこちら。
spi8823.hatenablog.com

余談

いやまとめるの思った以上に体力いるな。。。
これは一日坊主説ある。

雑記

一番勝ち組な生物種ってなんだろうか?
生物の目的というのは子孫を残して自分の複製を増やすことなのだから、勝ち組というのも当然より多くの子孫を残している種が該当するのだろう。
じゃあ人間はだいたい80億人で、蟻の数はだいたい1京匹(=10^16)(ソースはしらん)なのだそうだ。
なんだ、蟻の圧勝じゃないか。
いや待て待て、人間は体が大きい分、体を構成する細胞数も大きい。蟻は数は多くとも体が小さいので細胞の数もそれだけ小さくなるはずである。生物の基本単位は細胞なのだから、細胞の数で競わないと不公平ではないか?
しかし、人間はともかく蟻の細胞数なんか知るかという話である。
なら簡略化のため、すべての細胞が同じサイズであると仮定してはどうか。細胞数は体積に比例するはずである。
人の体積はおよそ60リットルとする。80億人いるのでだいたい5*10^11リットルである。
対して蟻はというと、大きさが2mm*2mm*2mmぐらいと仮定する。体長でいうともう少し長そうだが、立方体に押し込めたとすると悪くない近似だと思う。とすると1*10^-5リットルぐらいということらしい?10^16をかけるとだいたい1^11リットルということになる。
人間は5*10^11リットル、蟻は1*10^11リットルなので体積でいうと微妙に人間の勝利である。(悪い近似をしているので余裕で10倍ぐらいの誤差がありそうだが)

ゆーて人間、生物の中では割と勝ち組なほうなのでは?(アオミドロとかそのへんと比べてみたい)

ml-agentsのconfig.yamlの「reward_signals」の中身

 ml-agentsで学習の際に使用する「trainer_config.yaml」の中にある「reward_signals」という項目について、以下のページにまとめてあったので自分なりに理解したメモ。
github.com

reward_signals

 読んで字のごとく、学習させる際の報酬をどういったものにするかというものを定義している。
 具体的には以下のように書かれる。

reward_signals:
    extrinsic:
        strength: 1.0
        gamma: 0.99
    curiosity:
        strength: 0.02
        gamma: 0.99
        encoding_size: 256
    gail:
        strength: 0.01
        gamma: 0.99
        encoding_size: 128
        demo_path: Project/Assets/ML-Agents/Examples/Pyramids/Demos/ExpertPyramid.demo

extrinsic

extrinsic:
    strength: 1.0
    gamma: 0.99

 extrinsicとは「外因的な」という意味。

 以下勝手な用語でかみ砕く。一般に使われている用語ではないので注意。
 強化学習ではゲーム世界そのものである「環境」と環境に対する「行動」があり、強化学習によって得たいのは環境の状態から最適な行動を決定するための「行動決定ロジック」である。
 そしてどのような行動が最適なのかということを判断するために、現在の環境の状態や、行動によって環境に対してどのような変化があったのかに対してゲームの目的に応じて得点をつける。この得点を「環境報酬」とする。
 「外因的な」というのはこの「環境」を指しており、つまりextrinsicの項目では「環境報酬」をどのように「行動決定ロジック」の学習に反映させるかということである。

 くどくどと説明してしまったが、要は一番スタンダードな報酬をどのように重み付けするかということである。

strength

 「環境報酬」はml-agentsではAgent.AddRewardによって決定されていく。
 学習においてAddRewardした値にかけるスケールがこのstrengthの値である。
 デフォルト値は1、つまりAddRewardした値をそのまま学習に用いているということである。

gamma

 基本的に強化学習ではある行動をしたときに未来においてもらえる報酬の総量を最大化していくのだが、はるか未来にもらう報酬を一瞬後にもらう報酬と同列に見なすのは具合が悪そうである。
 したがって、未来の報酬に対しては現在から遠ければ遠いほど重みを減らすのがよさそうだ。
 その重みの減らし方をこのgammaで表している。
 例えば、1秒後の報酬にはgamma=0.99をかけ、2秒後の報酬にはgamma^2≒0.98、100秒後の報酬にはgamme^100≒0.36の重みをかけて学習をさせるという考え方である。
 デフォルト値は0.99であり、0.8 ~ 0.995の範囲の値とするのが典型的とのことである。

curiosity

curiosity:
    strength: 0.02
    gamma: 0.99
    encoding_size: 256

 extrinsicな学習ばかりではいったんある程度の報酬をもらってしまうとそれに味を占めて学習中に同じようなことばかりするようになってしまいがちである。
 それを防ぐために「curiosity」すなわち「好奇心」に対して報酬を与えるためのパラメータがこの項目で定義される。
 この報酬を有効にすると、以下2つのロジックも併せて学習されるようになるらしい。

  1. ある時点での「環境」とその次の時点での「環境」の状態から、その間にとられたであろう「行動」を推測するロジック
  2. ある「環境」の時に、ある「行動」をとった際、その次の時点でどのような「環境」になっているかを推測する「環境予測ロジック」

 1つ目がどのように使われているかはわからなかった。
 「環境予測ロジック」によって実際に行動をとらずとも次の環境を予測できることができるのだが、実際に行動をとったときにその予測を裏切るような結果であればそれは"新しい発見"なのである。
 その新しい発見をしたということに対して報酬を与えるために、次の環境の予測と実際の状態との差異の大きさを「好奇心報酬」として与える。
 こうすることによってより多岐にわたるトライアンドエラーができるようだ。

strength

 extrinsicのものと同様に報酬にかけるスケールの値である、
 新しい発見ばかりを重視して実際のゴールを目指さなければやはり意味がないため、extrinsicのstrength値よりも小さくするというのが典型的なようだ。
 ただ、小さくしすぎて「好奇心報酬」が「環境報酬」に埋もれてしまってはそれはそれで意味が無いので大きすぎず小さすぎずという値を指定するのが肝要なようである。
 0.001 ~ 0.1の範囲の値にするのが典型的とのことである。

gamma

 extrinsicのものと同様なので省略。

encoding_size

 「好奇心報酬」を導くためには「環境予測ロジック」を学習させる必要がある。
 複雑な「環境」の状態値をどれくらい単純化して学習させるのかというのがこのencoding_sizeということらしい。
 デフォルト値は64だが、具体的にこの値によってどれくらい環境の状態値が小さくなるのかはわからなかった。
 多分そのまま64通りの環境に分けるか、2^64通りに分けるかとかだとは思う。

gail

gail:
    strength: 0.01
    gamma: 0.99
    encoding_size: 128
    demo_path: Project/Assets/ML-Agents/Examples/Pyramids/Demos/ExpertPyramid.demo

 人が実際にゲームをプレイした時の様子を学習によって再現するというのがGAILである。
 「行動決定ロジック」が人のプレイと似ているかを判定する「モノマネ判定ロジック」を追加で学習させ、人のプレイと似ている場合はその行動に対して「モノマネ報酬」を与えるのがGAILということらしい。
 demo_pathには「モノマネ判定ロジック」を学習させるための、実際の人のプレイ中の環境と行動のパラメータを保存しておいたファイルのパスを指定する。
 これはこれで濃いので別途調べてほしいが、GAILをすることによってかなり学習速度が速くなるようだ。


 力尽きたのでここまで。