Unityな日々(Unity Geek)

Unityで可視化アプリを開発するための試行錯誤の覚書

Canvas UI(uGUI)のイベント処理

CanvasUI(uGUI)システムでは、ユーザの操作をEventで授受する。たとえばButton要素の場合、インスペクタのButtonスクリプト・ブロックの一番下に、OnClick()という項目がある。ここにOnClick()イベントを受け取る関数と渡す引数を設定する。

f:id:yasuda0404:20150307145538p:plain

タブの"+"をクリックし、左下の枠にイベントを渡すGameObjectを設定し、右上の枠にイベントを送る関数をドロップダウンリストから設定する。左下は関数に渡す引数で、

  • なし
  • float
  • int
  • string
  • bool
  • Object

のいずれか。最後の"Object"はUnityのほとんどのクラス(たとえばGameObject, Component, Monobehaviourなど)のベースクラスなので、実質的にはほとんどのクラスを引数に指定できる。たとえばTransformや、GameObjectそのものも渡すことができる。

ユーザー定義関数を呼び出すサンプル

たとえば、ボタン要素のクリック時のアクションとして、

1) UIControllerのButtonClick()メソッドに、"CustomButton"というstringを渡す場合:

f:id:yasuda0404:20150307142006p:plain

2) ButtonClick2()に、カメラのTransformコンポーネントを渡す場合:

f:id:yasuda0404:20150307141924p:plain

等というように設定する。

なお、UI要素から呼び出すユーザ定義関数はpublic voidのみ、渡せる引数の個数は0か1であることに注意。

ちなみに受け側の関数は次のようになる。

public void ButtonClick(string name)
    {
        Debug.Log("Button "+name+" clicked.");
    }

    public void ButtonClick2(Transform cameraTransform)
    {
        Debug.Log("Camera Position "+cameraTransform.position);
    }
その他のイベント

OnClick()以外のイベントを生成・処理したい場合は、EventTriggerを使う。

イベントを生成したいUI要素を選択し、Component-Event-EventTriggerを選択。

f:id:yasuda0404:20150307144247p:plain

UI要素にEventTriggerコンポーネントが追加される。Add New Event Typeをクリックして該当するイベントタイプを選択、後はOnClick()と同様に、イベント受信先のGameObject、イベントを受け取る関数、引数を設定する。

f:id:yasuda0404:20150307144540p:plain

EventTriggerを使えば自由にEventを生成できる。たとえば、UIImage要素はデフォルトではイベントを持っていないが、EventTriggerを使ってOnClickPointer Enterイベントを追加することができる。

参考:

qiita.com

Canvas(uGUI)の基本レイアウト

Unity4.6から導入された新GUI、"uGUI"。従来のGUIより柔軟な設定ができるuGUIの導入についてまとめた。

CanvasUI要素をシーンに配置する

ヒエラルキビュー、Create-UIで、CanvasのUI要素が表示される。CanvasUIは、まず土台となるCanvasをシーンに配置し、その上に各UI要素を乗せていくという概念。

しかし、いきなりUI要素を選択しても、自動的にCanvasを配置してくれる。ここではUI要素のPanelを選択してみる。

f:id:yasuda0404:20150307111027p:plain

するとこのように、ヒエラルキビューに、Canvas-Panelが配置される。EventSystemというオブジェクトも自動生成される。

f:id:yasuda0404:20150307111600p:plain

続いて、Create-UIでButtonを選択。こんな感じになる。

f:id:yasuda0404:20150307112753p:plain

f:id:yasuda0404:20150307111019p:plain

Canvasコンポーネント

以下、Canvas特有のコンポーネントについて。

Canvasのインスペクタで指定するRender Modeには、次の3つのモードがある。

  • Screen Space - Overlay すべてのカメラのレンダリング画面上にオーバーレイ表示する
  • Screen Space - Camera 特定のカメラの画角いっぱいになるよう自動拡大される
  • World Space シーン内の3D空間に配置する

OverlayとCameraは機能的には似ているが、OverlayはPost処理としてレンダリング画面に上書きされるのに対し、Cameraはあくまでも3D空間上のオブジェクトとしてカメラの画角いっぱいに正対して表示される。このため、Cameraの場合は、そのカメラのみに有効であることと、カメラの近くにオブジェクトがある場合、Canvasはその背後に表示される、という違いがある。Unity Manal-Canvas

ここではWorld Spaceを選択し、メインカメラから見える位置に移動する。

f:id:yasuda0404:20150307111035p:plain

Rect Transformコンポーネント

CanvasUI要素の配置はRect Transformコンポーネントがつかさどる。これはtransformコンポーネントと似ているが、UI設定に便利なように違いもある。

Scale

移動・回転は一般のGameObjectと同じように扱える。

大きさの変更は"Scale Tool"と"Rect Tool"の2通りの方法がある。"Scale Tool"は文字通りScaleを変更するが、"Rect Tool"をRect Transformコンポーネントを持つオブジェクト(すなわちCanvasUI要素)に適用した場合、Scaleは変わらない。その代わりにWidth/Heightが変更される。スケール変更との違いは、フォントサイズやボーダーライン太さなどに影響しないこと

Pivot

PivotはUI要素の2次元平面上の回転中心。左下が(0,0)、右上が(1,1)の相対座標であたえる。ヒエラルキビュー上では、Pivot点は中抜きの青丸で表示される。PivotモードではPivotを中心とした回転に、Centerモードではオブジェクト中心を基準に回転させることができる。

"Rect Tool"は、シーン上のUI要素の辺または角をつかんでドラッグすることで行える。また、

f:id:yasuda0404:20150307111002p:plain f:id:yasuda0404:20150307111011p:plain

Anchor

CanvasUIの要素の位置はすべて親要素にたいする相対位置で指定する。この相対位置を定義するのがAnchor。ヒエラルキビュー上は(4つの)中抜きひし形で示される。

AnchorにはMax/Minの2つの座標がある。

Max/Minを同じ値にすると、Anchorha一点になる。たとえば、親要素の右下コーナーにAnchorを設定すると、親要素のサイズが変わっても、子要素の右下からの位置は一定に保たれる。

Max/Minに違う値を設定すると、位置とともに子要素のサイズも変わる。たとえば、親要素の左下と右下にAnchorを設定すれば、親要素の幅が変わったときに、親要素との左右のすき間が一定となるように、子要素の幅が変わる。

Unity APIにあるアニメーションが参考になる。

なお、Anchorにはプリセットが用意されているので、通常はここから選べば事足りる(と思う)。厳密に指定したい場合はインスペクタで数値を入力することもできる。

f:id:yasuda0404:20150307121623p:plain

余談

Overlayモードでは、Canvasはシーン上でZ方向にしか動かせないらしい。もっともどこに配置してもいいわけだが。また、Z座標が±1000を越えるとカメラスクリーンに表示されなくなるようなので、Z座標で表示非表示を変更できる?

ジェネリックリストのジェネリックリスト(ジェネリックリストの入れ子)を作る

以前、「Unityで使える広義の配列」で書いたように、最近のC#では、ArrayListよりGeneric Listが推奨されている。

要素の型を指定できるので、よりロバストなコードがかけるGeneric Listだが、Generic Listの要素にGeneric Listを指定する、すなわち、Generic Listの入れ子はできるのだろうか?

答えはYes.

もともとGeneric ListはJavascript配列のように後から要素を追加(Addメソッドを使う)できるのが便利。なので、Generic ListのGeneric Listが可能なら、いわゆる「ジャグ配列」のようなリストを作ることができる。

これをやりたかった理由として、ツリー階層構造にあるGameObjectから、下の階層のGameObjectを自動的に取り出してGeneric List化したかったことがある。たとえば、

f:id:yasuda0404:20150306214040p:plain

のようなGameObjectの階層があり、このトップのMenuObjectのみから下の子GameObjectを自動的にリスト化したかった(なお、今回は階層数は固定とする)。

このスクリプトが次。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Test2 : MonoBehaviour {

    [SerializeField] protected Transform top;  //'MenuObject'を指定

    private List<GameObject> layer1;
    private List<List<GameObject>> layer2;

    // Use this for initialization
    void Start () {
        layer1 = new List<GameObject>();    //第1階層のGneric List
        layer2 = new List<List<GameObject>>();   //第2階層のGeneric List
        foreach(Transform elem1 in top) {
            layer1.Add(elem1.gameObject);
            List<GameObject> list = new List<GameObject>();
            foreach(Transform elem2 in elem1) {
                list.Add(elem2.gameObject);
            }
            layer2.Add(list);
        }

        //チェック1 Generic List的に扱う
        foreach(List<GameObject> list in layer2) {
            foreach(GameObject elem in list) {
                Debug.Log("child:"+elem.name);  //第2階層の要素
            }
        }

        //チェック2 ArryList的に扱う
        for(int i = 0; i < layer1.Count; i++) {
            Debug.Log("layer1["+i+"] = "+layer1[i].name);   //第1階層の要素
            for(int j = 0; j < layer2[i].Count; j++) {
                Debug.Log("layer2["+i+"]["+j+"] = "+layer2[i][j].name); //第2階層の要素
            }
        }
    }
    
<<<以下略>>>

layer1が第1階層のGameObjectのGeneric List、layer2が第2階層のGameObjectからなるGeneric Listになる。

上の"Check Process"は、layer1, layer2の要素のGameObjectの名前をDebug.Logで出力するものだが、Generic ListはArrayListのように要素インデックスでも指定できる。(たとえば、layer2[i][j])。場合によってはインデックスで指定したほうが便利なときもあるので、使い分ければよいと思う。

インスペクタでプルダウンリストを使う

インスペクタにプルダウンリストを表示するには、enumを使う。

たとえば、iTweenプラグインのイージングタイプをプルダウンリストで指定するには、

public enum EaseType {
        easeInQuad,
        easeOutQuad,
        easeInOutQuad,
        easeInCubic,
        easeOutCubic,
        easeInOutCubic,
        easeInQuart,
        easeOutQuart,
        tQuart,
        easeInQuint,
        easeOutQuint,
        easeInOutQuint,
        easeInSine,
        easeOutSine,
        easeInOutSine,
        easeInExpo,
        easeOutExpo,
        easeInOutExpo,
        easeInCirc,
        easeOutCirc,
        easeInOutCirc,
        linear, 
        spring,
        easeInBounce,
        easeOutBounce,
        easeInOutBounce,
        easeInBack,
        easeOutBack,
        easeInOutBack,
        easeInElastic,
        easeOutElastic,
        easeInOutElastic
    }
    [SerializeField] EaseType easeType = EaseType.easeOutQuart;

のように書けばいい。

public enum..enumクラス("EaseTyp")を定義し、EaseType easeTypeでそのクラスの変数を定義している。

インスペクタには次のように表示される。

f:id:yasuda0404:20150306202549p:plain

ところで、enumの実態は配列のIndex番号の整数なので「演算」ができる。たとえば、

Debug.Log("EaseType.easeInBounce "+(EaseType.easeInBounce+1)); //'easeOutBounce' を出力

となる。

これはこれで便利なのだが、iTweenのイージングタイプのように、文字列(string)変数として使いたい場合もある。

この解決策は、ToString()メソッドを使うこと。

string easetype = easeType.ToString();   //文字列に変換

とすれば、"easeInBounce"などの文字列を得ることができる。

レンダリングの品質設定

レンダリングの品質設定は Edit-Project Settings-Quality でおこなう。

各パラメータを個別に設定もできるが、Levelsに"Fastest"~"Fantastic"までのプリセットがあり、それを選択することもできる。また、カスタム品質レベルを登録することも可能。

f:id:yasuda0404:20150224094541p:plain

個人的にはAnti Aliasingは画質に大きく影響すると思うのだが、Unityのプリセットは低めに抑えられている。(Fantasticでも"2X Multi Sampling")。これを、"4X"、"8X"にあげると画質はかなり向上する。もちろん、パフォーマンスとのトレードオフになる。

覚えておくと作業がはかどるショートカット

シーンビューの操作と重複するが、シーン編集時に作業がはかどるUnity標準のショートカットキーをまとめた。

選択モード

ショートカット 操作
q ハンドモード
w オブジェクトの移動モード
e オブジェクトの回転モード
r オブジェクトの拡大・縮小モード
t 矩形モード
z Center/Pivot切り替え
x Global/Local切り替え
Shift+Space ビューの拡大・縮小(*1)

(*1) Play中のゲームビューは拡大・縮小できない

なお、キーアサインは、Edit-Preferences...で変更できる。

C#のプロパティを理解する

プロパティの使用 (C# プログラミング ガイド)

C#にかぎらずオブジェクト指向言語のクラス内の変数を外部から読み出したり、変数に値を設定する場合は、get/setメソッドを使うのが基本。しかし、変数ごとにいちいちget/setメソッドを設定するのは煩雑になってしまう。

そこでC#では「プロパティ」というしくみがつくられた。プロパティは、クラスの外から見ればget/setメソッドのように見え、クラス内部から見れば普通の変数として扱える。

次のサンプルコードでは、int Monthにgetアクセサとsetアクセサが設定されている。getアクセサはクラス外から値を読むときに実行され、setアクセサは値を設定するときに実行される。 setは、プロパティと同じ方のvalueという暗黙のパラメータを介して値を設定する。

public class Date
{
    private int month = 7;  // Backing store

    public int Month
    {
        get
        {
            return month;
        }
        set
        {
            if ((value > 0) && (value < 13))
            {
                month = value;
            }
        }
    }
}

プロパティを使うメリットは、メソッドを増やさずにメソッドと同等のことができること。たとえば、値が読まれた時にデバッグ出力したり、イベントを発行する、値が設定される際に範囲チェックを行なう(上の例)、などだ。

ShaderLabの基礎:Culling & Depth Testingを理解する

参考:ShaderLab syntax: Culling & Depth Testing

f:id:yasuda0404:20150214204213p:plain

英語の'cull'は「選びとる・間引く」という意味だが、CGの世界では「除外する」ととらえるとわかりすい。たとえば、「バックフェース・カリング」はポリゴンの裏面を表示しない処理になる。また、オブジェクトのもっとも近いサーフィス以外を表示しない「デプス・テスト」もカリングの一種。


Cull Back|Front|Off ポリゴンのどの面をカリングするかの指定。 デフォルトは'Back'で、裏面を表示しない。'Off'は両面を表示。

ZWrite On | Off このオブジェクトのからのピクセルをDepth Bufferに書き込むかどうか。デフォルトはOn。

ZTest Less | Greater | LEqual | GEqual | Equal | NotEqual | Always Depth Testの方法。デフォルトは'LEequal' = 存在するオブジェクトより近いものと同じ距離にあるものは表示、それより背後にあるものは非表示)

Offset Factor, Units Depthパラメータのオフセット。FactorはZの最大値を、X,Y値に対してスケーリングする。UnitsはDepth Buffer値の最小値をスケーリングする。 これによって、あるポリゴンを常に上側に表示できる。たとえば、'Offset 0, -1'は、ポリゴンの傾きを無視してポリゴンをカメラに近づける。一方、'Offset -1, -1'は、ポリゴンをさらに近くに表示する。


サンプル

1. オブジェクトの裏面のみをレンダリングする

Shader "Show Insides" {
    SubShader {
        Pass {
            Material {
                Diffuse (1,1,1,1)
            }
            Lighting On
            Cull Front
        }
    }
}


2. 深度を考慮したトランスペアレント・シェーダ

通常のトランスペアレント・シェーダではDepthを考慮せず、単純に描画順で前後が決まるらしい。これを回避するには、深度情報を考慮したトランスペアレント・シェーダを作る必要がある。下がその例。 参考:Unity で Transparent/Diffuse で描画順が崩れてしまう際の対処法

ZWrite OnでDepth Bufferに書き込み、ColorMask 0レンダリングはしない。そのかわりに

Shader "Transparent/Diffuse ZWrite" {
Properties {
    _Color ("Main Color", Color) = (1,1,1,1)
    _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
}
SubShader {
    Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
    LOD 200

    // extra pass that renders to depth buffer only
    Pass {
        ZWrite On
        ColorMask 0
    }

    // paste in forward rendering passes from Transparent/Diffuse
    UsePass "Transparent/Diffuse/FORWARD"
}
Fallback "Transparent/VertexLit"
}


3. 法線ベクトルの修正

最初のパスでは通常のVertex Lightingでレンダリングし、2番めのパスでオブジェクトの背面をピンク色でレンダリングする。裏向きポリゴンを発見する際に便利なシェーダ。

Shader "Reveal Backfaces" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" { }
    }
    SubShader {
        // Render the front-facing parts of the object.
        // We use a simple white material, and apply the main texture.
        Pass {
            Material {
                Diffuse (1,1,1,1)
            }
            Lighting On
            SetTexture [_MainTex] {
                Combine Primary * Texture
            }
        }

        // Now we render the back-facing triangles in the most
        // irritating color in the world: BRIGHT PINK!
        Pass {
            Color (1,0,1,1)
            Cull Front
        }
    }
}


4. グラス・カリング

ガラスのような半透明なオブジェクトでは、オブジェクトの裏面が透けて見えるようにしたい。そのためのシェーダ。裏面と表面を2パスでレンダリングする。 spheres, cubes, car windscreensなどの凸形状のオブジェクト適用できる。

Shader "Simple Glass" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,0)
        _SpecColor ("Spec Color", Color) = (1,1,1,1)
        _Emission ("Emmisive Color", Color) = (0,0,0,0)
        _Shininess ("Shininess", Range (0.01, 1)) = 0.7
        _MainTex ("Base (RGB)", 2D) = "white" { }
    }

    SubShader {
        // We use the material in many passes by defining them in the subshader.
        // Anything defined here becomes default values for all contained passes.
        Material {
            Diffuse [_Color]
            Ambient [_Color]
            Shininess [_Shininess]
            Specular [_SpecColor]
            Emission [_Emission]
        }
        Lighting On
        SeparateSpecular On

        // Set up alpha blending
        Blend SrcAlpha OneMinusSrcAlpha

        // Render the back facing parts of the object.
        // If the object is convex, these will always be further away
        // than the front-faces.
        Pass {
            Cull Front
            SetTexture [_MainTex] {
                Combine Primary * Texture
            }
        }
        // Render the parts of the object facing us.
        // If the object is convex, these will be closer than the
        // back-faces.
        Pass {
            Cull Back
            SetTexture [_MainTex] {
                Combine Primary * Texture
            }
        }
    }
}

ShaderLabの基礎:Texture Combinersを理解する

f:id:yasuda0404:20150215211036p:plain

参考:ShaderLab syntax: Texture Combiners

基本的なVertex Lightingが計算された後、テクスチャが適用される。これはSetTexture()コマンドによっておこなわれる。

基本形は、SetTexture テクスチャ名 テクスチャブロックとなる。

※フラグメント・プログラムが有効な場合、SetTextureコマンドは無効になる。すなわち、ピクセル操作はすべてシェーダで行われる。

テクスチャブロックは、テクスチャがどのように適用されるかを指定するもので、combine,constantColor,matrixの3つからなる。

1. combine

テクスチャ同士をどのように合成するかの指定。

  • combine src1 * src2 2つのソースを乗算で合成。いずれよりも暗くなる。

  • combine src1 + src2 加算で合成。どちらよりも明るくなる。

  • combine src1 - src2 ソース1からソース2を減じる。

  • combine src1 +- src2 ソース1とソース2を加算し0.5を引く=符号付でソース2を加える。

  • combine src1 lerp (src2) src3 ソース2のアルファ値を使って、ソース1とソース3を内挿する。アルファ1の時ソース1となり、アルファ0のときソース3となる。

  • combine src1 * src2 + src3 ソース1にソース2のアルファ値を掛け、ソース3を加える。

  • combine src1 * src2 +- src3 ソース1にソース2のアルファ値を掛け、符号付でソース3を加える。

  • combine src1 * src2 - src3 ソース1にソース2のアルファ値を掛け、ソース3を引く。

ソース すべてのソースは次のどれかになる。

  • previous 直前のSetTextureの結果
  • primary ライティング計算結果の色、または、頂点カラー(バインドされている場合)
  • texture TextureNameで指定したテクスチャの色
  • constant ConstantColorで指定した色

モディファイヤ

  • 上の指定のあとにオプションとして、Double(2倍)、Quad(4倍)のキーワードを付加し、明度をあげることができる。
  • lerp以外のソースは、前に1をつけることでカラーは無効になる
  • ソースの後にalphaをつければ、アルファチャンネルのみを取り出す。

2. constantColor

combineコマンドで使われるコンスタントカラーを指定

3. matrix

matrix[マトリクス・プロパティ名] 与えたマトリクスでテクスチャ座標を変換する。

フラグメント・プログラミングができる前の古いグラフィック・カードは、テクスチャをひとつずつ重ね合わせながら色を計算していた。

「純粋な固定関数デバイス」 (OpenGL, OpenGL ES 1.1, Wii) は、各SetTextureの値が0~1に制限されていたが、それ以降 (Direct3D, OpenGL ES 2.0)はその制限がなくなった。これが、SetTextureの値に影響する恐れがある。

カラーとアルファの分離 通常、combineRGBカラーとアルファを同じ方法で合成するが、カラーとアルファを異なった方法で合成することも可能。 次のケースでは、RGBカラーは乗算で、アルファは加算で合成している。

SetTexture [_MainTex] { combine previous * texture, previous + texture }

スペキュラ・ハイライト 通常、プライマリ・カラーは、ディフューズ、アンビエント、スペキュラの合計になる。しかし、SeparateSpecularをOnにした場合は、スペキュラカラーはCombine合成計算の後で別に加えられる。 ビルトイン・頂点シェーダーではこちらの方法がデフォルト。


サンプル

1.アルファブレンディング

Baseテクスチャに、Alpha Blendedテクスチャの色を、Alppha Blendedテクスチャのアルファチャンネル値で合成。 最初の'previous'は、頂点カラーとライティングが適用された状態をさす。これがベースカラーとなる。

Shader "Examples/2 Alpha Blended Textures" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _BlendTex ("Alpha Blended (RGBA) ", 2D) = "white" {}
    }
    SubShader {
        Pass {
            // Apply base texture
            SetTexture [_MainTex] {
                combine texture
            }
            // Blend in the alpha texture using the lerp operator
            SetTexture [_BlendTex] {
                combine texture lerp (texture) previous
            }
        }
    }
}


2.アルファ制御セルフ・イルミネーション

_MainTexのアルファ値でどこにライトを適用するかを決定する。 第1段階で、頂点カラーと白色を_MainTexのアルファ値で合成。 第2段階で、第1段階の結果に_MainTexのカラーを乗算。 ※アルファがすべて1であれば、_MainTexのカラーそのものになる。アルファが1より小さい部部分は色が弱くなる。

Shader "Examples/Self-Illumination" {
    Properties {
        _MainTex ("Base (RGB) Self-Illumination (A)", 2D) = "white" {}
    }
    SubShader {
        Pass {
            // Set up basic white vertex lighting
            Material {
                Diffuse (1,1,1,1)
                Ambient (1,1,1,1)
            }
            Lighting On

            // Use texture alpha to blend up to white (= full illumination)
            SetTexture [_MainTex] {
                constantColor (1,1,1,1)
                combine constant lerp(texture) previous
            }
            // Multiply in texture
            SetTexture [_MainTex] {
                combine previous * texture
            }
        }
    }
}


_MainTexのアルファ値を使って、_IlluminCol(=constant)previousを合成、それに_MainTexの色を乗算。イルミネーションを白色ではなく、任意のカラーに設定するもの。

Shader "Examples/Self-Illumination 2" {
    Properties {
        _IlluminCol ("Self-Illumination color (RGB)", Color) = (1,1,1,1)
        _MainTex ("Base (RGB) Self-Illumination (A)", 2D) = "white" {}
    }
    SubShader {
        Pass {
            // Set up basic white vertex lighting
            Material {
                Diffuse (1,1,1,1)
                Ambient (1,1,1,1)
            }
            Lighting On

            // Use texture alpha to blend up to white (= full illumination)
            SetTexture [_MainTex] {
                // Pull the color property into this blender
                constantColor [_IlluminCol]
                // And use the texture's alpha to blend between it and
                // vertex color
                combine constant lerp(texture) previous
            }
            // Multiply in texture
            SetTexture [_MainTex] {
                combine previous * texture
            }
        }
    }
}


すべてのLightを適用したもの。 最初のpreviousは、Diffuse,Ambient, Shiness, Specular, Emissionが適用された状態になる。そこに、_IlluminCol_MainTexのアルファ値で合成し、さらに_MainTexのカラーを乗算する。

Shader "Examples/Self-Illumination 3" {
    Properties {
        _IlluminCol ("Self-Illumination color (RGB)", Color) = (1,1,1,1)
        _Color ("Main Color", Color) = (1,1,1,0)
        _SpecColor ("Spec Color", Color) = (1,1,1,1)
        _Emission ("Emmisive Color", Color) = (0,0,0,0)
        _Shininess ("Shininess", Range (0.01, 1)) = 0.7
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }

    SubShader {
        Pass {
            // Set up basic vertex lighting
            Material {
                Diffuse [_Color]
                Ambient [_Color]
                Shininess [_Shininess]
                Specular [_SpecColor]
                Emission [_Emission]
            }
            Lighting On

            // Use texture alpha to blend up to white (= full illumination)
            SetTexture [_MainTex] {
                constantColor [_IlluminCol]
                combine constant lerp(texture) previous
            }
            // Multiply in texture
            SetTexture [_MainTex] {
                combine previous * texture
            }
        }
    }
}

ShaderLabの基礎:SubshaderとColor, Material, Lightingを理解する

1つのShaderには複数のSubshaderを記述できる。Unityは上からSubshaderを見ていき、現在のマシン(GraphicCard)に適用できるシェーダがあれば、それを適用する。

1つのSubshaderの中には、1つ以上のレンダリングパス(Pass)を指定できる。また、オプションとして特定のタグ(Tag)を指定できる。

レンダリングパスには - Regular Pass (Pass) - Use Pass (UsePass) - Grab Pass (GrapPass) の3種類がある。

次の例は、シングルパスで、ライトを消し、_MainTexというテクスチャを設定してメッシュを表示するもの。

// ...
SubShader {
    Pass {
        Lighting Off
        SetTexture [_MainTex] {}
    }
}
// ...

Passは Pass {[Name and Tags\] [RenderSetup\] [TextureSetup] } という構造になる。

Name and Tagsは、Passの名前と、複数の任意のタグを指定するもの。

  1. レンダリング設定

パスの中にはレンダリングに関するさまざまな設定を記述する。レンダリング設定には次のブロックがある。

  • Material 下のMaterialブロックを参照
  • Lighting: Materialブロックの設定を有効にするには、LightingをOnにする必要あり。
  • Cull
  • ZTest
  • ZWrite
  • Fog
  • AlphaTest
  • Blend
  • Color
  • ColorMask
  • Offset
  • SeparateSpecular: テクスチャリングの後にスペキュラを設定するか?
  • ColorMaterial: Materialの設定カラーではなく、Per-vertexカラーを使う。AmbientAndDiffuseとEmissionの2つがある。

f:id:yasuda0404:20150214190647p:plain

レンダリングパスの最初は、Vertex ColoringとLighting。ここではColor、Material、Lightingが関係する。

Materialブロックには次の設定がある。

  • Diffuse color:オブジェクトの基本色

  • Ambient color: アンビエントライトの色

  • Specular color:スペキュラの色

  • Shininess number:スペキュラの集中度。0(全体に拡散)~1(一点に集中)

  • Emission color: オブジェクトの発光色。ライトの影響を受けない。

サンプル

Shader "VertexLit" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,0)
        _SpecColor ("Spec Color", Color) = (1,1,1,1)
        _Emission ("Emmisive Color", Color) = (0,0,0,0)
        _Shininess ("Shininess", Range (0.01, 1)) = 0.7
        _MainTex ("Base (RGB)", 2D) = "white" {}
    }
    SubShader {
        Pass {
            Material {
                Diffuse [_Color]
                Ambient [_Color]
                Shininess [_Shininess]
                Specular [_SpecColor]
                Emission [_Emission]
            }
            Lighting On
            SeparateSpecular On
            SetTexture [_MainTex] {
                Combine texture * primary DOUBLE, texture * primary
            }
        }
    }
}

※SetTextureについては、ShaderLab:Texture Cobiners、または、ShaderLabの基礎:Texture Combinersを理解するを参照のこと。

ShaderLab:Propertiesの種類

Properties{}ブロックの中に記述するPropertyは次の種類がある。

  1. Float Range (Range):スライダで範囲指定
  2. Color(Color):RGBAの4次元配列。各値は0~1
  3. Texuture2D(2D):テクスチャ
  4. Rectangle(Rect):
  5. Cubemap(Cube):Cubemap
  6. Float(Float):不動小数点数
  7. Vector(Vector):ベクトル配列

サンプル

Properties {
    // properties for water shader
    _WaveScale ("Wave scale", Range (0.02,0.15)) = 0.07 // sliders
    _ReflDistort ("Reflection distort", Range (0,1.5)) = 0.5
    _RefrDistort ("Refraction distort", Range (0,1.5)) = 0.4
    _RefrColor ("Refraction color", Color) = (.34, .85, .92, 1) // color
    _ReflectionTex ("Environment Reflection", 2D) = "" {} // textures
    _RefractionTex ("Environment Refraction", 2D) = "" {}
    _Fresnel ("Fresnel (A) ", 2D) = "" {}
    _BumpMap ("Bumpmap (RGB) ", 2D) = "" {}
}

プロパティ名("表示名",種類) = デフォルト値

という構造はすべて同じ。

デフォルト値の指定方法はPropertyの種類によって異なる。 - Range/Float 数値 - Color/Vector 4次元配列 - 2D/Rect/Cube 空文字""、または、ビルトイン・テクスチャ名(“white”, “black”, “gray” or “bump")

テクスチャ系(2D/Rect/Cube)の {}内にはオプションを指定できる。

  • TexGen: texgenmode=テクスチャ座標を自動生成モード。ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormalのいずれかを指定。

  • LightmapMode:指定した場合はlightmapパラメータの影響を受ける。Renderer.lightmapIndex

Unity Shaderの基本構造

シェーダーとはレンダリングを行うプログラム。Unityには80以上のビルトイン・シェーダーが組み込まれている。

自分でシェーダーを書く場合の基礎知識をまとめる。Unity Manual/Shaders Overview

Shader全体はShderLabでラップされる。基本構造は次のとおり。

Shader "MyShader" {
    Properties {
        _MyTexture ("My Texture", 2D) = "white" { }
        // other properties like colors or vectors go here as well
    }
    SubShader {
        // here goes the 'meat' of your
        // - surface shader or
        // - vertex and fragment shader or
        // - fixed function shader
    }
    SubShader {
        // here goes a simpler version of the SubShader above that can run on older graphics cards
    }
}

UnityのReferenceには、まずShaderLabを理解してから各論に進め、と言っているので、ShaderLabについて。

そもそもシェーダとは、そのシェーダを適用したマテリアルがどのようにレンダリングされるかを定義するプログラム。

Shader "MyShader" { ... } でシェーダを定義する。1つのファイルに1つのシェーダしか定義できない。 "MyShader"には任意の名前をつける。"MyDirectory/MyShader"のようにすれば、MyDirectoryグループの下のMyShader、というように階層(グループ)構造にできる。

Properties {…}は、プロパティ、つまり、ユーザが入力・変更できる変数を定義する場所。オブジェクト指向言語でいえば、Public変数に対応する。

Subshaderは、 Subshader {...} Subshader {...} ... fallback というように、Shaderの中に複数のSubshaderを記述できる。Subshaderは少なくとも1つないといけない。 Shaderが読み込まれると、UnityはSubshaderのリストを上から見ていって、使用しているマシン(=Graphic Card)がサポート可能な最初のSubshaderを適用する。 もしどのSubshaderも使えない場合は、fallbackに指定したシェーダを使う。 これによって、ハイエンドユーザ向けのSubshader、ミドルエンド用、ローエンド用、と書き分けることができる。

    各Subshaderの書き方には次の3種類の方法がある。

  1. サーフィスシェーダ  「光と影」に関するシェーダ。つまりライトと組み合わせて使用される。逆にライトと関係ないならサーフィスシェーダは使うべきではない。Cg/HLSLを使える。

  2. 頂点シェーダ・フラグメントシェーダ  ライトと関係ない効果、あるいは、サーフィスシェーダでは扱えないような特殊な効果を得たいときに使う。Cg/HLSLを使える。  

  3. 固定関数シェーダ  プログラマブル・シェーダを使えない旧式、あるいはモバイル・ハードウェア用。ShaderLabのみで書かれる。

Inputの種類

Unityにはさまざまな入力APIが準備されている。
Unity - Scripting API: Input


Static関数としては次がある。Down/Up=Yesは、たとえばGetKeyに対して、GetKeyDown/GetKeyUpというメソッドもあるということ。

入力機器 メソッド 引数 戻り値 Down/Up
キー GetKey(string) キー名 bool Yes
マウスボタン GetMouseButton(int) 0:左,1:右,2:中 bool Yes
ボタン GetButton(string) 仮想ボタン名(*1) bool Yes
アクシス(スムージングあり) GetAxis(string)(*2) 仮想アクシス名(*3) float -1~+1 No
アクシス(スムージングなし) GetAxisRaw(string)(*2) 仮想アクシス名(*3) float -1~+1 No
タッチ GetTouch(int).phase 0-Input.touchCount:指番号 タッチフェーズ No(*3)


(*1)バーチャルボタンは、Project Settings-Inputで設定する。デフォルトでは、'Fire1','Fire2','Fire3','Jump','Submit','Cancel'が設定されている。

(*2)スティックは、ジョイスティック以外に、マウスの動き、キーにもアサインできる。
GetAxisRaw()の場合のキーボード入力は、-1,0,+1のいずれかになる。

(*3)GetTouch()は、

Input.GetTouch(i).phase == TouchPhase.Began

のように、.phaseプロパティと、TouchPhaseクラスとのセットで使う。

SourceTreeでGitのコミットができない時の対応

UnityでGitを使う方法は【連載Git入門 第6回】SourceTreeでGitを始めよう!UnityのプロジェクトをGitで管理しよう | naichilab - Android iOSアプリ開発メモが参考になる。

しかし、コミットしようとすると、

git: fatal unable to auto-detect email address

なるエラーが発生。環境はWindows7


この解決策は、http://tizio1976.blogspot.jp/2013/12/sourcetree-git-commit-fatal-unable-to.htmlが参考になった。

ただし、この記事には誤記あり! 正しい手順は次のとおり。


SourceTreeのターミナルを開き、

$ git config --global user.email "otoiawase@xooms.co.jp"
$ git config --global user.name "Atsuhiko Yasuda"

として、それぞれを設定する。user.email/name の後は`=`は不要で、テキストを""で囲むこと。


一度設定した情報を上書きするには、--replace-all をつけて

$ git config --global --replace-all user.email "admin@xooms.co.jp"

とする。

$ git config --global -l

で、設定した情報を確認できる。

f:id:yasuda0404:20160510160349p:plain




SourceTreeのリポジトリ設定の「ユーザー情報」で、「グローバルユーザー設定を使う」にチェックを入れる。(デフォルトはそうなっているはず)

f:id:yasuda0404:20150210114351p:plain

これで、無事コミットができた。

Marmoset Skyshopを使う

f:id:yasuda0404:20150126155741p:plain
Mamoset/SkyshopはUnity上でHDRIを利用できるライブラリ。クオリティの高い表現は定評がある。Tutorial: Introduction to Skyshop | Marmosetを参考に、Marmoset/Skyshopを触ってみる。

まず当然のことだがSkyshopを購入し、unitypackageを読み込む必要がある。
パッケージを読み込んだ状態。
f:id:yasuda0404:20150126161733p:plain


HDRIデータを準備する。Skyshopが扱うのは、2DまたはCube(立方体)の360°パノラマ・テクスチャ。ファイル形式は.HDR形式または.PFM をインポートできる。
今回はDOSCHのHDRI素材を使うことにした。

トップメニューのWindowにSkyshopという項目が追加される。これはシーンに環境マッピングを設定するためのツール。これを使って、HDRI画像をDiffuseとSpecularのマップに変換し、シーンに配置するまでをやってくれる。
f:id:yasuda0404:20150126161850p:plain

これを選択すると、Skyshopのインスペクタが開く。一番上の'INPUT PANORAMA'にHDRI画像を適用すると自動的にCubemap(SKYBOXとSPECULAR OUTPUT)が作られる。が、まず最初にSKYBOXとSPECULAR OUTPT、それぞれで'New'をクリックしてダミーデータを作っておかなければいけないようだ。
すると、Assetの直下にシーン名と同じフォルダが作られ、その中にSkyboxとSpecular Outpuのファイルができる。こんな状態。
f:id:yasuda0404:20150126164102p:plain

さらに、Assetの中にHDRI画像データを読み込む。Texture Typeは'Advanced'または'Texture'、MaxSizeは4096とした。(これがベストの設定かどうかはわからない)
f:id:yasuda0404:20150126164626p:plain

画像の解像度は任意だが、Skyshopのサンプルシーンでは2048X1024pixelの矩形画像をつかっているので、これを踏襲。DOSCHの場合は'SN'シリーズに相当。(DOSCHのHDRIデータは末尾の2つのアルファベットで画像の種類と解像度を表す)

一番上の'INPUT PANORAMA'スロットのSelectボタンをおし、該当するHDRI画像データを選択する。

Mipmapは不要、というエラーが出たら'Fix Now'をクリック。
f:id:yasuda0404:20150126175441p:plain

INPUT PANORAMAにHDRI画像データが読み込まれる
f:id:yasuda0404:20150126175510p:plain

次にSkybox、Specular Outputを自動作成する。これをおこなうにはSkyShopインスペクタの'Compute'ボタンをクリックする。(GPU Computeでもいい。こちらのほうが速い)
このとき、Basic OptionsのQualityで品質を変えられる。とりあえずは'Medium'でやってみる。
f:id:yasuda0404:20150126175853p:plain


しばらくするとSkybox, Specular Output画像ができる。'Add to Scene'をクリック。
f:id:yasuda0404:20150126180839p:plain

新規作成の場合、'Sky Manager'がない、というメッセージが出るので'Create'。
f:id:yasuda0404:20150126181100p:plain

プロジェクトの他のシーンでSkyMangerを使っている場合はエラーメッセージがでないので、マニュアルでSky Managerを加える。
この方法は、ヒエラルキービューで作成したSkyObjectを選択し、Component-Scripts-mset-SkyMangerを選択。これでMainCameraにSkyObjectが表示される。
f:id:yasuda0404:20150128190010p:plain


これでシーンにHDRIが適用される。シーンのメインカメラにComponent-Scripts...からFree Cameraを適用すれば360°ビューを見ることができる。
f:id:yasuda0404:20150126181201p:plain


ためしに鏡面反射するSphereオブジェクトをおいてみる。
シーンにSphereをつくり、プロジェクトにあらたなMaterialを追加、名前をChromeTestとでもしておく。
このMaterialのShaderを、Marmoset/Specular IBLに設定。Diffuse Colorを黒、Specular Colorを白、Specular Sharpnessを1近くに設定する。
f:id:yasuda0404:20150126182548p:plain

このMaterialをSphereに適用すると鏡面反射するオブジェクトが表現できる。
f:id:yasuda0404:20150126182743p:plain



※補足
Marmoset/SkyshopパッケージのAsset/Marmosetフォルダ内のShaderForgeExtension.zipを解凍すると、次のエラーが出る。
f:id:yasuda0404:20150126173846p:plain
継承しているSF_Nodeクラスがない、というエラーのようで確かにSF_Nodeクラスは見当たらない。。。?
なお、ShaderForgeExtensionはShaderを改良・作成するためのツールのようでなくてもSkyshopは機能する。とりあえず解凍せずに使っている。