おもちゃラボ

Unityで遊びを作ってます

【Unity】地形作りで学ぶShader Forge超入門

シェーダをグラフィカルに作れるツール「Shader Forge」を使って、ノイズ画像から立体的な地形を作ったり、シェーダで色をつけたりする方法を紹介します。作成する地形は次のような感じになります。

f:id:nn_hokuson:20170921195508j:plain:w600

Shader Forgeを使うことで、難しいシェーダ言語を覚えなくてもノードを繋ぐだけで簡単にシェーダを作ることができます。画面を見ながら試行錯誤できるので、実験的なシェーダを作るのにも最適です。Shader Forgeはアセットストアから入手できます。

今回の記事の内容は次のとおりです。

プロジェクトを作る

今回はハイトマップに使うノイズ画像と、高さごとの色を指定するランプ画像、スムーズな地形を作るためのハイポリゴンなPlaneを用意します。

今回使用するサンプルは↓からダウンロード出来ます。

www.dropbox.com

ダウンロードしたら、必要な素材をプロジェクトウインドウにドラッグ&ドロップし、mapをシーンビューに配置しておきましょう。

f:id:nn_hokuson:20170920193738j:plain

メニューバーからWindow -> Shader Forgeを選択してShader Forgeのウインドウを起動します。起動したらNew Shaderを選択して下さい。

f:id:nn_hokuson:20170920194144j:plain:w400

今回はライトの影響を考慮した影付きのモデルを表示したいので「Lit(PBR)」を選択します。

f:id:nn_hokuson:20170920200206j:plain

起動直後は次のような画面になります。一旦不要なノードは削除しておきましょう。

f:id:nn_hokuson:20170920200315j:plain

ノードを削除するには、ノードを選択して⌘+Xを押します。複数のノードを選択したいときはAltキーを押したまま左ボタンでドラッグします。また、接続した線を切りたい場合は、Altキーを押した状態でマウス右ボタンをドラッグします(ちょっとヤヤコシイですね・・・)

f:id:nn_hokuson:20170920194645j:plain

テクスチャを表示する

まずはPlaneにテクスチャを貼り付けるところから始めましょう。右クリックしてGeometory Data -> UV Coordinatesを選択(または「uキー」で表示されるメニューから選択)してUV Coordノードを追加します。同様にProperties -> Texture 2Dを選択し、Texture2Dノードを追加します。

f:id:nn_hokuson:20170920200549j:plain

これらのノードが追加できたらUVノードからTexture2DノードのUVへ、Texture2DのRGBからMainのBase Colorへ接続します。

f:id:nn_hokuson:20170920200738g:plain

Texture2Dノードの名前をbaseに変更し、Texture2Dノード内のSelectボタンを押してノイズ画像を設定してみましょう。

f:id:nn_hokuson:20170920200956j:plain

また、PBRではMainノードのMetallicに値を指定する必要があります。ここではConstant Vectors -> Valueを選択し、作成したValueノードをMainノードのMetalicに接続して下さい。

f:id:nn_hokuson:20170920202220j:plain

これで、テクスチャを貼り付けるだけのシェーダが完成しました。Unityエディタに戻りmapシェーダを見てみるとシェーダプログラムが自動的に記述されていることがわかります。このmapシェーダは通常のシェーダファイルと同じように扱えます。

シェーダに対応するマテリアルを作成する

mapシェーダを選択した状態で右クリックし、Create -> Materialを選択して新しくマテリアルを作成してください。作成したMaterialをシーンビューのmapにドラッグ&ドロップしてください。マテリアルがアタッチできたらmapのインスペクタに表示されるMaterialの欄のbaseにノイズ画像を設定してください。シーンビューのPlaneに、いま選択したノイズ画像が表示されます。Shader Forgeで設定したテクスチャはMaterialには反映されないので注意してください!

f:id:nn_hokuson:20170920202734j:plain

これで、①Shader Forgeでシェーダを作成、②マテリアルの作成とセットアップ、③シーンビューで表示という一連の流れが完成しました。次からは立体的な地形になるようにシェーダを作っていきましょう。

高さをつける

続いて地形に高低差をつけましょう。「どれくらい高くするか」はノイズ画像の明度によって決めます。ここでは画像が白いところは高く、画像が黒いところは低くなるように高さを調節していきましょう。

f:id:nn_hokuson:20170921191449p:plain:w400

シェーダで頂点の座標を操作するにはMainの「Vertex Offset」を使います。Vertex Offsetには「どの方向にどの程度移動させるか」のベクトルを指定します。

今回作るベクトルは、頂点の法線方向にノイズ画像の明度を掛けたものになります。数式にすると次のようになります。最後のKは頂点の移動量を調節するための係数です。

Vertex Offset = N(法線ベクトル)✕ ノイズ画像の明度(0.0〜1.0)✕ K

まず、法線の方向に関してはNormal Dirノードが用意されているので、これを配置しましょう。Geometry Data -> Normal Dirを選択してノードを配置します。続いて法線ベクトルとノイズ画像の明度を掛け合わせるため、Multiplyノードを配置します。MultiplyノードはArithmatic->Multiplyを選択して配置して、次のように接続して下さい。

f:id:nn_hokuson:20170920224640j:plain

頂点の移動量を調節する係数(K)にはSliderノードを使います。SilderノードはProperies ->Sliderを選択してノードを追加ください。Siderの範囲を0〜3、Sliderの名前をheightに変更しておきましょう。

f:id:nn_hokuson:20170920225138j:plain

これでノイズ画像の明度によって法線方向に頂点を移動させるシェーダが完成しました。Unityエディタに戻って、インスペクタからSliderの値を調節してみてください。テクスチャと同様、Shader Forgeで設定したSliderの値は保存されないので注意してください!初期状態ではSliderの値は0になるため、スライダを動かすまでは地形の高さは変化しません。

f:id:nn_hokuson:20170920225742j:plain

地形に色を付ける

地形が真っ白では味気ないので、色を付けていきます。色をつける方法も先程と同様、ノイズテクスチャの明度に沿って色分けします。具体的には次のような感じにしてみましょう。

明度
0~0.4
0.4~0.5
0.5~0.7
0.7~0.9
0.7~1.0

テクスチャの明度値から色をつくるためには、Rampテクスチャと呼ばれる仕組みを使います。Rampテクスチャとは次のように、一方向に色合いの変化があるテクスチャのことで、UV座標のU値に応じた色を取り出すことが出来ます。

f:id:nn_hokuson:20170921192540j:plain:w550

ノイズ画像の明度値をランプテクスチャののU値として扱うと、明度が0.2(u=0.2)なら青色、明度が0.8(u=0.8)なら茶色を取り出すことができます。これで、上に書いた表どおりの色が取り出せますね。

今回はノイズ画像の明度をU値としましたが、物体表面の明度をU値とすればトゥーンシェーダのような表現にも使うことができます。

nn-hokuson.hatenablog.com

この流れを作るため、Appendノード(Vector Operation -> Append)と、Texture2Dノードを追加して次のように接続してください。Texture2DノードにはRampという名前をつけて、Selectボタンでランプテクスチャを選択します。

f:id:nn_hokuson:20170920231441j:plain

ここでは、Appedノードを使ってUV座標を作っています。U値にはノイズ画像の明度値、V座標には0を入れています。作成したU座標を使ってランプテクスチャから色を取り出し、最後にその色をMainノードのDiffuseに設定しています。Unityエディタに戻って、インスペクタからRampの欄にランプテクスチャを設定して実行してみてください。

f:id:nn_hokuson:20170920231725j:plain

地形に陰影を付ける

実行してみると地形の高さによって色合いが変化しているのがわかります。でも、まだ全体的にのっぺりして陰影がついていません。これは頂点の座標を変化させたのは良いけれども、法線の方向を再計算していないため、すべての頂点が上を向いてしまっていることが原因です。

これでは陰影がつかずにのっぺりして見えても仕方がありません。実際の地形に沿った法線の再計算を行いましょう。

f:id:nn_hokuson:20170921200354p:plain

と・・・ここまで書きましたが、法線を再計算するのはやっぱり結構大変です。なので、ここではなんちゃってのライティングを考えてみましょう。

山があって、光が横から当たっていれば当然ですが明暗がつくられますね。この明暗は次の図のようにノイズテクスチャの位相を45度ずらしたものと考えることができます(ちょっと無理矢理ですが・・・)。

f:id:nn_hokuson:20170926195902p:plain:w550

要するに、ノイズテクスチャは「高いところを白、低い所を黒」としているので位相を45度回せば高いところの頂点を中心として「半分は白、半分は黒」となります(何度も書きますが、この計算はなんちゃって、英語ではpseudoです。笑)

ということで、ここではノイズのテクスチャを少しだけ移動させたものを明度として、最終的なテクスチャに掛け合わせてみましょう。次のようにノードを配置してください。

f:id:nn_hokuson:20170926202107j:plain

ここではUVの値に(0.02, 0.02)のベクトルを足すことで、U方向とV方向のテクスチャを左上の方向に移動させたテクスチャを作っています(ここでは分かりやすいようにTexture2Dのノードを配置して、base2という名前にしています)。

最後に作成したテクスチャと、色分けしたテクスチャをMultiplyノードでかけ合わせたものをMainノードのBase Colorに接続して出力しています。また、テクスチャどおしを掛け合わせると暗くなるので、Sliderノードを使って少し明るくしています。

実行すると次のようになります。

f:id:nn_hokuson:20170926204951j:plain

実行する際はインスペクタからbase2にノイズテクスチャを設定するのを忘れないようにしてください。

f:id:nn_hokuson:20170926204358j:plain:w300

Texture2Dノードをまとめる

先程、Texture2Dノードを追加してbase2と名前をつけましたが、インスペクタから設定する箇所が増えて不便なので、一箇所にまとめておきましょう。

次のようにもう一つTexture Assetノード(Properties->Texture Asset)を追加しbaseという名前にしましょう。続けてTexture AssetノードのTexから、Texture2DノードのTexに接続してください。Texture2DのTexに接続するとこで、インスペクタからは入力できないノードになります。

f:id:nn_hokuson:20170926202304j:plain

インスペクタを確認すると、テクスチャを入力する欄がbaseとrampの2つに減っていることが確認できますね。

f:id:nn_hokuson:20170926204041p:plain:w240

UVスクロールして地形を動かす

最後にUVスクロールを使って地形を動かす方法を紹介します。UVスクロールは2Dゲームの背景スクロールや、水面、滝の表現など、様々な場面で使える技術です。

nn-hokuson.hatenablog.com

ここではShader Forgeを使ってUVスクロールする方法を紹介します。Shader Forgeで「時間の変化に合わせてhogehogeしたい」というときにはTimeノード(External Data->Time)を使います。

Timeノードはゲーム開始からの時間を扱うノードです。4つの出力がありますが、それぞれ時間をスケーリングしてるだけなので、使う場面に合わせたものを選びましょう。

ここでは、時間とともにUV座標のうちU座標を変化させたいので、時間変化と移動速度(u方向に0.02)をMultiplyノードで掛け合わせたものをUV座標に足しています。

次のようにノードを配置してください。

f:id:nn_hokuson:20170926203059j:plain

実行すると、次のように地形がu方向にスクロールしていくのがわかります。移動速度のベクトルを変化させることでUV方向にスクロールさせたり、逆方向にスクロールすることもできますよ!

真面目に法線の再計算をして影をつける

真面目に法線の再計算をする方法を最後にサラっと紹介しておきます。法線は隣の頂点との傾きを見ることで計算できます。でも、シェーダは並列で計算されるため「隣の頂点」を見るのがとても苦手です。なので、ノイズテクスチャから仮想的に地形の傾きを計算して、その傾きから法線ベクトルを算出します。

現在注目しているテクスチャの座標値と、少し左にずれたテクスチャの明度値の差が、X方向の地形の傾きになります。同様に、現在注目しているテクスチャの座標値と、少し上にずれたテクスチャの明度値の差が、Y方向の地形の傾きになります。

f:id:nn_hokuson:20170921193358p:plain:w240

この2つのベクトルとZ方向のベクトルをあわせた3次元の傾きから、法線の方向を再計算します。すこし複雑になりますが、次のようにノードを配置してください。

f:id:nn_hokuson:20170921194335j:plain

急に複雑になってしまいましたね(笑)でも、やっていることは通常のテクスチャを少しだけx方向に移動させたテクスチャと、少しだけy方向に移動させたテクスチャを作り、そのテクスチャと通常のテクスチャの差分をとって法線ベクトルを作成しているだけです(赤枠内)。オレンジ色で囲った部分は、次の数式のように法線の大きさが1になるようにZ方向のベクトルの大きさを決めています。


{ \displaystyle
dz = \sqrt{1-dx^2 - dy^2}
}


保存できたらUnityエディタで実行してみて下さい。ちゃんと陰影が反映されて立体的な地形に見えると思います。流石になんちゃっての陰影よりも遥かに綺麗ですね!

f:id:nn_hokuson:20170921195508j:plain

まとめと参考資料

Shader Forgeを使って、ノイズ画像から立体的な地形を作ったり、シェーダで色をつけたりする方法を紹介しました。Shader Forgeは簡単にシェーダが作れて試行錯誤できるのでオススメです!

Shader Forge公式サイト

Shader Forgeのサイトでは各ノードの働きを確認できます。どんな種類のノードがあるのかを一通り眺めて置けるのがGoodです!

Shader Forge

小林信行さんの資料

Shader Forgeの使い方から、テクスチャブレンド、ライティング、トゥーンシェーダまでとってもわかり易く説明されている資料です。もはや小林さんの資料を読めば、このページはいらんのちゃうかと思わなくもないぐらい・・・笑

www.slideshare.net

Unityの教科書もよろしくおねがいしまーす!(Shaderには触れていませんが・・・笑)

【Unity】Androidの実機上でDebug.Logの内容を確認する2つの方法

Unity EditorではDebug.Logはコンソールウインドウに表示されますが、Androidの実機に転送した場合、コンソールウインドウでDebug.Logの表示は確認できません。

そこで、Androidの実機で実行しているアプリのDebug.Logを見る方法を2つ紹介します。

Debug.Logをフックする

Debug.Logの内容をフックして、uGUIのTextで表示させる方法です。結構手軽で気に入ってますが、こだわりだすとツールを作るのが苦痛になるので、機能はそこそこで(笑)

Debug.Logのメッセージをフックする

まずはログをフックするスクリプトを作成します。プロジェクトビューで右クリックしCreate -> Script -> C# Scriptを選択して、LogDisplayという名前で保存します。

スクリプトができたら、次のプログラムを入力してください。

using UnityEngine;
using UnityEngine.UI;

public class LogDisplay : MonoBehaviour
{
    public Text message = null;

    private void Awake()
    {
        Application.logMessageReceived  += HandleLog;
    }

    private void OnDestroy()
    {
        Application.logMessageReceived  += HandleLog;
    }

    private void HandleLog( string logText, string stackTrace, LogType type )
    {
        message.text = logText;
    }
} 

このスクリプトではlogMessageReceivedイベントにOnLogMessageイベントハンドラを追加しています。イベント?イベントハンドラ?という方は、拙書「」を参考にしてください(Unityに特価した本ではありませんが、ひととおりのC#の文法は解説しています)

[asin:4797390263:detail]

OnLogMessageイベントハンドラではフックしたログの内容をuGUIのTextに表示しています。

スクリプトをアタッチしてTextを設定する

ヒエラルキーウインドウでCreate->Create Emptyを選択し、空のオブジェクトを作成してください。そのオブジェクトにLogDisplayスクリプトをドラッグ&ドロップしてアタッチします。

f:id:nn_hokuson:20170915132136j:plain

また、ヒエラルキーウインドウでCreate-> UI -> Textを選択し、作成したTextオブジェクトをLogDisplayスクリプトのMessage欄にドラッグ&ドロップしてください。

f:id:nn_hokuson:20170915132241j:plain

あとは、実機に転送して実行すれば、ログの内容がTextに表示されます。

Android Device Monitorを使う

いや、わざわざ実機上でログを確認しなくても、ツール上で確認できれば十分だよ!という人(というか、普通はこっちですね。笑)はAndroid StudioについてくるMonitorアプリを使いましょう。

MonitorアプリはWindowsなら「C:\Users\ユーザー名\AppData\Local\Android\sdk\」、Macなら「/Users/ユーザ名/Library/Android/sdk/tools/monitor」にあります。ターミナルから次のように打ってMonitorアプリを起動しましょう。

$ /Users/ユーザ名/Library/Android/sdk/tools/monitor

起動すると次のような画面が表示されます。

f:id:nn_hokuson:20170915132609j:plain

続いてUnityで作ったアプリのログだけが表示されるよう、フィルタを作成します。画面左下の「Saved Filter」と書かれた横の緑色のプラスボタンをクリックし、Filter NameとBy Log Tagの欄に「Unity」と入力してOKボタンを押してください。

f:id:nn_hokuson:20170915132653j:plain

あとは、実機でアプリを動かすと、Monitorのコンソール上にログが表示されます。こちらも便利ですね!

【Unity】UniRxを使ってArduinoとシリアル通信

前回の記事では、シリアル通信のためのPluginを作ることで、Arduino⇔Unityのシリアル通信を実現しました。

nn-hokuson.hatenablog.com

今回はUniRxを使うことで、前回よりも簡単にArduinoとシリアル通信をする方法を紹介します。今回の記事の内容は次のとおりです。

UniRxとは

UniRxとはUnity Reactive Extensionsの略で、名前の通り(?)Unityで非同期通信をしたいときに使うと便利なツールになります。UniRxはAsset Storeから無料で手に入れることができます。

f:id:nn_hokuson:20170912190532j:plain

今回の記事では、UniRxはスレッドを作るためだけに使います(贅沢)UniRxの詳しい説明はしませんが、非常に便利なライブラリなのでいつか解説を書く・・・かも。

Arduino側のプログラムを作る

こちらはシリアル通信でデータを一方的に送りつけるだけのプログラムです。次のプログラムをスケッチに書いて下さい。

byte a = 0;

void setup() {
 Serial.begin(9600);
}

void loop() {
 a++;
 Serial.println(a);
}

プログラムの内容は・・・setupメソッドの中でシリアル通信を開始しています。loopメソッドの中では、変数aの値をインクリメントしながらシリアルにどんどん流し込んでいるだけのプログラムになります。

プログラムができたら、書き込みボタンでArduinoに書き込んでおきましょう。

f:id:nn_hokuson:20170912191350p:plain:w350

Unity側のプログラムを作る

続いてUnity側のシリアル通信プログラムを作ります。こちらはシリアル通信で送られてくるデータを受け取るプログラムです。

UniRxを使うため、Asset Storeからダウンロードしてインポートしておいてください。

f:id:nn_hokuson:20170912190532j:plain:w400

また、Unityでは標準で.NET 2.0のサブセットを使うように設定されていますが、サブセットにはSerialクラスが含まれていないため、.NET 2.0 Subsetから.NET 2.0に変更する必要があります。

メニューバーからEdit -> Project Settings -> Playerを選択し、API Compatibility Levelを.NET 2.0に変更してください。

f:id:nn_hokuson:20170912190634p:plain:w400

続いて、シリアル通信用のスクリプトを作成します。プロジェクトビューで右クリックし、「Create」→「Script」→「C# Script」を選択し、SerialController.csという名前で保存し、作成したファイルに次のスクリプトを入力してください。

using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System;
using System.IO.Ports;
using UnityEngine;
using UniRx;

public class Serial : MonoBehaviour {

    public string portName;
    public int baurate;

    SerialPort serial;
    bool isLoop = true;

    void Start () 
    {
        this.serial = new SerialPort (portName, baurate, Parity.None, 8, StopBits.One);

        try
        {
            this.serial.Open();
            Scheduler.ThreadPool.Schedule (() => ReadData ()).AddTo(this);
        } 
        catch(Exception e)
        {
            Debug.Log ("can not open serial port");
        }
    }
	
    public void ReadData()
    {
        while (this.isLoop)
        {
            string message = this.serial.ReadLine();
            Debug.Log( message );
        }
    }

    void OnDestroy()
    {
        this.isLoop = false;
        this.serial.Close ();
    }
}

ここでは、UniRxを使うため、using UniRX;を追加しています。Startメソッドの中でSerialクラスのインスタンスを作成した後、Scheduleメソッドを使ってReadDataスレッドを立ちあげています。

ReadDataメソッドの中ではSerialクラスのReadLineメソッドを使って、シリアルで送られてくるデータを読んで表示しています。

ゲームを終了する場合はOnDestroyメソッドが実行されます。その中で、スレッドを抜けて、シリアルポートを閉じています。

実行してみる

スクリプトが作成できたら、空のゲームオブジェクトを作って、それにスクリプトをアタッチしておきましょう。アタッチしたゲームオブジェクトのインスペクタから、ボーレートとポート名を設定してください。ボーレートはArduino側で指定した9600に設定します。

ポート名はArduinoを起動し、メニューバーからツール -> ポートで確認することができます。

f:id:nn_hokuson:20170912190808p:plain:w300

これで準備完了です。ArduinoをPCに接続し、Unityを実行してみてください。コンソールビューの数字が増加して1〜255を繰り返せば成功です。

f:id:nn_hokuson:20170912190819g:plain

まとめ

Unityで非同期通信やスレッドなどを使いたい場合はUniRxを使うことで、非常にスッキリとしたプログラムを書くことができます。