Unityな日々(Unity Geek)

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

uGUIのテキストにブラーがかかってしまう問題

uGUI(Canvas UIシステム)のTextUI要素にブラーがかかってしまう問題は、既知のバグのようだ。

現時点では、対応策はピクセル解像度の大きなテキストを作り、Scaleを小さくして縮小する、という手しかないようだ。

Text UI要素のインスペクタは次のような感じになる。RectTransformのWidth/Hight、FontSizeを大きくし、RectTransformのScaleを0.1とか0.01とかに小さくする。

f:id:yasuda0404:20150312141620p:plain

しかし、この設定でもすこしぼやけた感じがする。早急な対応をお願いします!

uGUI: スクリプトからUIのパラメータを変更する

uGUIへユーザが操作した際にメソッドを呼び出すのとは逆に、スクリプトからUIの状態やパラメータを変更したい場合もある。代表的な例を下にあげる。

テキスト

uGUIのテキストを変更するには、テキストGameObjectのTextコンポーネントを取得し、このText.textプロパティを変更すればよい。

サンプルスクリプトは次。

ポイントは、UnityEngine.UIネームスペースを追加すること!

using UnityEngine;
using UnityEngine.UI;

public class Example : MonoBehaviour
{

 [SerializeField]Text btnText;
  [SerializeField]Toggle toggle;
  [SerializeField]Slider slider;

  private bool isOn = false;
  
  REIPacket packet=new REIPacket();

  public void OnOff()
  {
      isOn = !isOn;
      if(isOn) {
         btnText.text = "On";
         toggle.isOn  = true;
         slider.value = 1.0f;
         slider.Select();
      } else {
         btnText.text = "Off";
         toggle.isOn  = false;
         slider.value = 0.0f;
         toggle.Select();
      }
  }

トグル(チェックボックス)状態

Toggleコンポーネントbool isOnを変更する。

スライダの値

Sliderコンポーネントvalueを変更する。

フォーカス(共通)

各UIコンポーネント.Select()メソッドを呼ぶ。該当UIにフォーカスが移り、その他はフォーカスが外れる。

※注)ゲームコントローラやキーボードからの操作を可能にするよう、EventSystemのFirst Selectedに、最初にハイライトするUI要素を設定しておくこと!

スクリプトのテンプレートを追加する

スクリプトを新規作成する際に、独自のテンプレートを使いたいことがあるとおもう。

その場合は、Unityのインスールフォルダ/Editor/Data/Resources/ScriptTemplates/の下にある、

  • 80-Javascript-NewBehaviourScript.js.txt
  • 81-C# Script-NewBehaviourScript.cs.txt
  • 82-Boo Script-NewBehaviourScript.boo.txt
  • 83-Shader-NewShader.shader.txt
  • 84-Compute Shader-NewComputeShader.compute.txt

を修正するか、あるいは、新規にテンプレートファイルを追加する。

僕の場合、主に使うC#用テンプレートにヘッダコメントとNamespaceをデフォルトで入れたテンプレートを作っている。

// Title
// First Version 2015/mm/dd by A.Y.@XOOMS
// Last Update   2015/mm/dd by A.Y.@XOOMS
//
using UnityEngine;
using System.Collections;
using Xooms;

namespace Xooms{
    public class #SCRIPTNAME# : MonoBehaviour {

        // Initialization
        void Start () {
    
        }
    
        //
        void Update () {
    
        }
    }
}

これを、オリジナルのC#テンプレートに上書きしてもよいが、オリジナルのテンプレートは残したい場合は、別名で保存する。

ファイル名は、'番号+表示名-スクリプト種類.拡張子.txt'となり、'-'ハイフンより前の、番号+表示名は自由に設定できる。番号は数字のほかアルファベットでもいいようだ。要は番号部分のテキストの小さい順番に、メニューに並ぶ。

たとえば81A-C#XOOMS Script-NewXoomsScript.cs.txtという別の名前で保存すると、正規のC#テンプレートの次に、オリジナルテンプレートが表示される。

f:id:yasuda0404:20150309164902p:plain

Materialを比較する

あるオブジェクトにアサインされている特定のマテリアルを別のマテリアルに置き換えたかったので、次のようなコードを書いた。

[SerializeField]protected Renderer rend; //Target Renderer
[SerializeField]protected Material matFrom; //Change from this material
[SerializeField]protected Material matTo;   //Change to this material

<途中省略>

void Change()
{
    Material[] mats = rend.sharedMaterials;
    Debug.Log("ChangeMaterial: mats.Length= "+mats.Length);
    for(int i=0;i<mats.Length;i++) {
        Debug.Log(i+" Spec Int "+mats[i].GetFloat("_SpecInt"));
        if(mats[i] == matFrom) {
            Debug.Log("ChangeMaterial:Material("+i+" changed");
            mats[i]= matTo;     //Replace the material
        }
    }
    rend.materials = mats;  //Reassign Materials
}

ところがこれは機能しない。マテリアルはRunTimeでインスタンス化されるが、インスペクタでアサインしたマテリアルはインスタンスではない。このため、mats[i] == matFromという比較が真にならない。

そこで、マテリアルの名前で比較しようと思い、比較分の箇所を

if((mats[i].name == matFrom.name)...

と書いたが、これもうまくいかない。マテリアルがインスタンス化されると名前も変わるからだ。

結局、

if(mats[i].name == (matFrom.name+" (Instance)")) {

と、元の名前の後に" (Instance)"を加えれば、一応うまくいった。

しかし、もっとスマートな比較方法がないものだろうか…。

"9x9 Slice" Spriteを作成する

UI用のイメージを設定する場合、イメージを拡大しても周囲のラインは画像が荒れるので拡大したくない。これを実現するのが、「9x9スライス」というテクニック。要は、4つの角はスケーリングせず、中間の画像のみ拡大するという手法だ。

これをUnityで実現するには、SpriteとBorderを使う。

まずPhotoshopなどでUI用の画像を作る。シンプルなものなら縦横16pxの正方形にする。PNG形式で書き出す(アルファを使えるPNGがよい)。

f:id:yasuda0404:20150307182000p:plain

Unityで、この画像をアセットして読み込む。

  • Texture Type Sprite(2D and UI)
  • Sprite Mode Single

に設定。

f:id:yasuda0404:20150307182013p:plain

し、Sprite Editorを開き、左下の入力フォームでL/R/B/Tを適当に設定し、9分割のボーダーを引く。Applyで変更を反映。

f:id:yasuda0404:20150307182021p:plain

後は、各UI要素のImageコンポーネントSource Imageに、上で作成したSpriteを適用する。UI要素を拡大しても、エッジラインは保たれる。

f:id:yasuda0404:20150307182601p:plain

CanvasUI(uGUI)要素にアニメーションを設定する

CanvasUI(uGUI)のインタラクティブ要素(Button, Toggle, Sliderなど)は、「操作状態」に応じて「外観」を変更できる。

操作状態

各UI要素の状態には

  • Normal
  • Highlighted
  • Pressed
  • Disabled

の4通りがある。'Highlighted'はフォーカスがあたった状態で、マウスならなくてもよいが、ゲームコントローラで制御する場合は必須。どの要素が選択されているかをフィードバックしなければならないからだ。

外観

外観の変更は各コンポーネントTransitionで行い、

  • none 外観を変えない
  • Color Tine RGBAの変更
  • Sprite Swap 画像の変更
  • Animation アニメーションの付加

の4つの設定がある。

f:id:yasuda0404:20150307155309p:plain

アニメーション

TransitionでAnimationを選択。Animationを使うにはAnimatorコンポーネントが必要。'Auto Generate Animation Controller'をクリックし、AnimationControllerをアセット内に保存する。デフォルトでは、たとえばButton.Controllerのような名前になる。

f:id:yasuda0404:20150307154919p:plain

同時に、AnimatorコンポーネントがUI要素に追加される。

f:id:yasuda0404:20150307160633p:plain

アニメーションはAnimation Window(Window - Animation)で編集する。

f:id:yasuda0404:20150307160901p:plain

Animation Windowの左上、再生ボタンの下をクリックし、編集したいアニメーションを選択する。

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