tanaka's Programming Memo

プログラミングについてのメモ。

Amplify Shader Editorでロを描くシェーダーを作ってみた

目次

はじめに

Unity1週間ゲームジャムのSpaceの回で開発したで、シェーダーが使いたくなりました。当初公開版のロの発射は、以下のようなアニメーションをしています。

f:id:am1tanaka:20171201005113g:plain

この時はシェーダーが分からなかったので、テクスチャーを複製してアルファ値を書き換えて、以下のようなアニメーションパターンを生成してアニメさせました。

f:id:am1tanaka:20171122221058p:plain

しかし、手段として不細工だし、本来は以下のような感じで、書き順で出現させたかったのです。

f:id:am1tanaka:20171201000651g:plain

このようなシェーダーをAmplify Shader Editorで作ってみました。この記事で使ったバージョンは 1.3.9です。

準備

Amplify Shader Editorは、2017/11/29現在、Asset Storeでトップセールスを叩き出している人気アセットです。Unity1週間ゲームジャムのキャンペーンでいただいたバウチャーでゲットしました。

まず、どのようなことができるかを汗人柱様のこちら( 【Amplify Shader Editor】ノードベースでシェーダ作りのAmplifyを触ってみました。エディタの操作性、学習コスト、サンプルデモを大量に紹介! - Unity AssetStoreまとめ )でざっと確認。その上で、実際に自分でもアセットをインポートして、サンプルを動かしてみました。うちの数年前のしょぼいLen○vo E440でも問題なく動きます。凄い。

そして、上記のブログにリンクがありました以下の小林 信行様のスライドを見ながら、基本的な使い方をヨチヨチと学びました。時間的には3時間ぐらいやったと思います。

www.slideshare.net

バージョンが変わっていて、ちょくちょく違う部分がありました。大きく引っかかったのは以下の3つほど。

  • Node Propertiesの表記がなくなって、マスターノードの時はOutput Nodeなど、選択時のノードの名前に変わった(マスターノードではなく、アウトプットノードという名前かも。もしそうだったら読み替えてください)
  • マスターノードプロパティの項目が色々変わっている。Available Propertiesとかなくなってる(多分)
  • ノーマルマップのNormalという項目がUnpack Normal Mapに変わった

UVアニメーションを操作してみた辺りでシェーダーの凄さに感動しました。

作ってみる

雰囲気がつかめたので、なんとなく方針が思い浮かびました。やりたかったことは以下の2点です。

  • 半透明のオブジェクトに影が落ちるようにする
  • floatのパラメーターをMaterialに追加して、0~1を変化させて、ロが徐々に描きあがるようにする

書き順は、テクスチャーの赤の要素で表すことにしました。黒から真っ赤までで0~255の値を表せます。0が描画開始時で、255で完成です。

利用したテクスチャー

まずは、普通に描画するための四角のテクスチャーです。

f:id:am1tanaka:20171130001925p:plain

これに、書き順を表す赤色の以下のようなテクスチャーを用意しました。暗いところが早く描かれて、真っ赤な部分が最後に描かれるというように使います。

f:id:am1tanaka:20171130001821p:plain

背景と、ノーマルマップの作成に以下の画像を使いました。

f:id:am1tanaka:20171130174014p:plain

シーンの作成

作業用のシーンは以下のように作りました。

f:id:am1tanaka:20171130183837p:plain

Main Camera

以下の設定をしました。

  • Position 0, 0, -10
    • カメラのデフォルトはYが1で、配置しずらいので0
  • Projection [Orthographic]
    • 平行投影にしました
  • Size 2
    • 高さの半分を2、つまり、画面の高さは4にしました
  • Clipping PlanesのFarを50
    • 奥行きは不要なので、距離を縮めて少しでもデプスバッファの精度を上げようとしました。うちのAndroid端末ではあまり効果なしでしたが^^;

Directional Light

影が綺麗になっていなかったら、Shadow TypeのResolution欄を[Very High Resolution]にします。

背景

Quadを作成して、以下を設定しました。

  • Positoin 0, 0, 0
  • Scale 4, 4, 1

マテリアルを作成して、背景画像を設定しました。

ボール

影が落ちるかのテストのためのボールをSphereで作成しました。特に設定はないので、適当に配置します。

Quadを作成して、Positionを0, 0, -0.01に設定しました。Zを0より少し手前にしないと、背景と重なってしまうので、Zには-0.01を設定してあります。

このオブジェクトは他のオブジェクトに影を落とす必要は無いので、Mesh RendererのCast Shadow欄を[Off]にします。

カスタムシェーダーの作成

Asset StoreからAmplify Shader Editorをインポートしたら、シェーダーを作成します。

ProjectビューのCreateから、Amplify Shader -> Surface Shaderを選択します。

f:id:am1tanaka:20171130002337p:plain

新しいシェーダーが作成されて、Amplify Shader Editorが開きます。名前をつけて保存して、すぐにQuadに割り当てます。

ウィンドウ左上のShader Name欄をRo Draw Shaderなどに変更します。変更されたら自動的に保存されるように、灰色の丸いアイコンをクリックして緑にします。

f:id:am1tanaka:20171130192111p:plain

これで、UnityのProjectビューに新しいシェーダーが保存されます。シェーダーのファイル名もRo Draw Shaderなどに変更しておきましょう。

f:id:am1tanaka:20171130192314p:plain

これをQuadに割り当てます。まずはProjectビューのCreateボタンから、Materialを選択して、マテリアルを作成して、名前をRo Materialなどにします。

作成したマテリアルを選択したら、InspectorビューのシェーダーをRo Draw Shaderに切り替えます。

f:id:am1tanaka:20171130192533p:plain

作成したマテリアルを、Quadにドラッグ&ドロップすると、まだ何も設定していないシェーダーでQuadが描画されるようになります。

f:id:am1tanaka:20171130192717p:plain

実際に画面の変化を確認しながら、シェーダーを作成していきます。

Amplify Shader EditorのCanvasの操作

Amplify Surface Shaderを表示するウィンドウをCanvasといいます。表示するには、シェーダーのダブルクリック、あるいは、Windowメニューから Amplify Shader Editor -> Open Canvas メニューを選びます。

f:id:am1tanaka:20171130211014p:plain

Canvasウィンドウは以下のようになっています。

f:id:am1tanaka:20171130211758p:plain

編集エリアの操作方法は以下の通りです。

  • マウスのホイール回転で拡大縮小
  • マウスのホイールか右ドラッグ移動で、平行移動
  • マウスの左クリックで選択
  • 端子上で左ドラッグで、線を引く
  • マウスの左ドラッグで、複数のノード選択

Canvasに表示されている四角いものをノードと呼びます。最初に表示されているものをマスターノードといい、このノードの端子に様々な素材や計算結果を接続することで、シェーダーを作ります。マスターノードのプロパティーで、シェーダーの本質的な属性を設定します。

テクスチャーを描画する

では手始めに、テクスチャーを描画してみます。

Canvasのなにも無いところを右クリックして、Search欄にTextureと入力して候補を絞り、[Texture Sample [ T ]]を選択します。

f:id:am1tanaka:20171130212553p:plain

テクスチャーを設定するためのノードが作成されます。

f:id:am1tanaka:20171130212613p:plain

メニューに書いてあった[ T ]というのは、[T]キーを押しながら画面を左クリックすると、そのノードを作れる、という意味です。よく利用するノードにはこのようなショートカットキーが割り振られています。覚えられる人は覚えておくとよいでしょう(僕はあまり覚えられませんが)。

最初、名前が[Texture Sample 0]などになっているので、プロパティーウィンドウのName欄か、名前の部分をダブルクリックして選択して、Textureなどに変更しましょう。

f:id:am1tanaka:20171130212802p:plain

これをマスターノードに繋げます。ノードには端子と呼ばれる丸い要素が並んでいて、隣に文字が書いてあります。例えばTexture Sampleノードには[Tex][UV]というノードと、[RGBA][R][G][B][A]という合計7つの端子があります。このうち、左側にある[Text]と[UV]は入力端子で、データを入力するためのものです。右側に並んでいる残りの端子は出力端子で、このノードの出力結果を次のノードの入力端子に接続することで渡します。

TextureノードのRGBAノードをドラッグして、マスターノードのAlbedoノードにドロップしてください。

f:id:am1tanaka:20171130213300p:plain

これでTextureノードに設定したテクスチャーを描画する準備ができました。あとは、このノードにテクスチャーを渡します。これはUnityのMaterialでやりましょう。

Unityに切り替えて、先ほど作成したロ用のマテリアルを選択します。

f:id:am1tanaka:20171130213606p:plain

Projectビューからロ用のテクスチャーをドラッグして、InspectorビューのTexture欄にドロップします。

f:id:am1tanaka:20171130213712p:plain

これでテクスチャーが表示されるようになりました。

f:id:am1tanaka:20171130214134p:plain

テクスチャーの調整

良く見ると、真ん中が透過していません。また、不要なデータが生成されたりしているので、テクスチャーの読み込み方を変更します。テクスチャーを選択して、Inspectorビューを以下のように設定します.

  • Alpha Is Transparentにチェックを入れて、透過を有効にする
  • 今回は縮小して使うことはないので、Generate Mip Mapのチェックを外して、不要な縮小イメージの生成をやめる
  • ループして表示することはないので、Wrap Modeは[Clamp]に変更
  • 拡大縮小時の修正は不要なので、Filter Modeは[Point (no filter)に変更

以上設定したら、[Apply]ボタンを押して、変更を確定します。

f:id:am1tanaka:20171130214519p:plain

今度は謎の模様が表示されるようになりました。マスターノードの設定をして解決します。

f:id:am1tanaka:20171130213806p:plain

マスターノードの設定

Amplify Shader EditorのCanvasを開きます。マスターノードをクリックするか、右上の[+]をクリックすると、マスターノードが選ばれます。必要な属性を設定していきます。設定したら、少し待つと保存とシェーダーのビルドが実施されて、Unityの画面も変わります。色々と設定をいじって、動作を確認するとよいでしょう。

  • 影を落とす必要はないので、Cast Shadowsのチェックを外します
  • 半透明が使えて、かつ、影を受けるには独自の設定が必要なので、Blend Modeを[Custom]に変更します
  • Render Typeを[Transparent]に変更します
  • Render Queueは、ジオメトリ後に描画した方が半透明の結果がよくなりそうなので、[Alpha Test]に変更しておきます
    • ここをTransparentにすると、影のあとの描画になるため、影が落ちなくなります

f:id:am1tanaka:20171201000043p:plain

Render TypeをTransparentに変更したので、マスターノードのOpacity端子が使えるようになりました。テクスチャーのアルファ端子をマスターノードのOpacity端子に繋げば、期待の結果になります。

f:id:am1tanaka:20171130220516p:plain

f:id:am1tanaka:20171130220635p:plain

以上で、やりたいことの1つである半透明のオブジェクトに影を落とすことができました。次は描き順を実装します。

描き順の考え方

出力 -> 入力 -> 処理 の順番で検討するとまとめやすいです。

出力

線を描きたい部分はテクスチャーのアルファ値をそのまま出力して、まだ描画したくない場所のアルファ値を0にできればうまくいきそうです。

入力

  • どこまで描いているかを表すfloat値Stroke
  • 描き順のテクスチャーを設定するStroke Texture

以上の入力があればいけそうです。

処理

知識がないので、if的なものしか思い浮かびませんでした。とりあえず動かすということで、今回はこれでいきます。

必要なノードを揃える

floatパラメーターの作成

描き順のパラメーターを表すノードを作ります。Amplify Shader EditorのCanvasの何もないところを、[1]キーを押しながら左クリックします。そうすると、Floatノードが作成されます。名前を`Stroke‘にしておきます。

Materialのインスペクターに表示するために、Typeを[Property]に変更します。Materialが選択されていれば、これでマテリアルから値を設定することができます。

このパラメーターは0~1の範囲で値をとるので、Minを0、Maxを1にしておきましょう。

f:id:am1tanaka:20171130224657p:plain

テクスチャーノードの作成

シェーダーに描き順のテクスチャーを設定できるようにします。[T]キーを押しながら何もないところをクリックするなどして、Texture Sampleを追加します。NameをStroke Textureなど、分かるように変えておきましょう。

f:id:am1tanaka:20171130224959p:plain

Compareノードの作成

以下の判定ができるCompareノードを追加します。Canvasの右のウィンドウからLogical Operatorsの左の三角をクリックして開きます。その中に[Compare (A <= B)]を表すものがあるので、それをドラッグして、Canvasの編集エリアにドロップしてください。

f:id:am1tanaka:20171130225327p:plain

f:id:am1tanaka:20171130225452p:plain

接続する

ノードを接続していきます。

  • Stroke TextureノードのR端子を、CompareノードのA端子に接続します
    • 描き順テクスチャーの赤色が比較対象になります
  • Strokeノードの出力ノードを、CompareノードのB端子に接続します
    • これで、A端子に入力されるStroke Textureの赤要素の値が、Materialで設定したStrokeの値以下だった場合にTrue、大きい場合にFalseに入れた値が出力されるようになります
  • TextureノードのA端子を、CompareノードのTrue端子に接続します
    • 比較結果が成立している時に、Textureノードの元のアルファ値が設定されるようにします
  • Stroke TextureノードのG端子(B端子でも可)を、CompareノードのFalse端子に接続します
    • Stroke Textureは赤のみ使っているので、GとBは常に0です。比較結果が不成立の時、0を出力したいので、この値を設定しました
  • Compareノードの出力端子を、マスターノードのOpacity端子に接続します
    • 比較結果を、透明度として設定します

以上でシェーダーは完成です!

f:id:am1tanaka:20171130230951p:plain

Unityの設定

シェーダーは完成したので、Unityで必要なパラメーターを設定します。

テクスチャーファイルの形式を設定

描き順を赤色で示したテクスチャーをProjectビューに読み込ませて選択して、Inspectorで以下の設定をします。

  • Alpha Sourceは不要なので[None]に変更
  • MipMapは不要なので、Generate Mip Mapのチェックを外す
  • リピート描画はしないので、Wrap Modeを[Clamp]に変更
  • 拡大縮小時の補間は不要なので、Filter Modeを[Point (no filter)]に変更
  • [Apply]ボタンをクリック

f:id:am1tanaka:20171130222142p:plain

シェーダーにパラメーターを設定

Projectビューから、シェーダーを設定したマテリアルを選択します。Inspectorビューを確認すると、Stroke Texture欄と、Stroke欄が増えています。Stroke Texture欄に、赤色の描き順テクスチャーを設定してください。

f:id:am1tanaka:20171130231301p:plain

動作確認

これで準備完了です。InspectorビューのStrokeを操作して、値を変更させてみてください。0の時は左上に少し描くだけで、値を増やすに従って、ロが描かれていくようになります。

f:id:am1tanaka:20171201000651g:plain

複数のオブジェクトに対応させる

これで完成!と思ったのですが、実用段階で問題が出てしまいました。オブジェクトが1つなら問題ないのですが、複数のオブジェクトを別々にアニメーションさせようとするとうまくいきません。

原因は、StrokeプロパティーをMaterialで渡していることです。Materialは全てのオブジェクトが共通のものを見ますので、個別にアニメーションさせることができないのです。

対策は、StrokeプロパティーのTypeを、[Property]から[Instanced Property]に変更することです。Instanced Propertyは、GPU内で複製を生成して、個別に値を持たせる機能です。このプロパティーはスクリプトからしか値が渡せないので、開発時はMaterialで渡す方が楽そうです。あるいは、以下に汎用的に使えるスクリプトを掲載しましたので、最初からこれをテスト用のオブジェクトにアタッチする手もあります。

対応手順

Amplify Shader EditorのCanvasで、Strokeノードを選択して、パラメーターのTypeを[Instanced Property]に変更します。

f:id:am1tanaka:20171201215542p:plain

灰色でProperty Nameと書いてある欄の_Strokeが、このプロパティーにアクセスする時の名前です。

マテリアルが、Instanced Propertyを扱えるように設定します。Projectビューから、シェーダーを割り当てたマテリアルを選択します。InspectorビューのEnable GPU Instancingにチェックを入れます。

f:id:am1tanaka:20171201220733p:plain

シェーダーをアタッチしたオブジェクトに、新規でC#スクリプトを作成してアタッチして、RoAnimatorScriptなどの名前にします。スクリプトは、以下の通りです。

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

public class RoAnimatorScript : MonoBehaviour {

    [TooltipAttribute("描き順の進捗を0-1で表します。")]
    public float Stroke = 0f;
    // メッシュレンダラーのインスタンス
    private MeshRenderer meshRenderer;
    // 設定するプロパティー
    private MaterialPropertyBlock props;

    void Start () {
        meshRenderer = GetComponent<MeshRenderer>();
        Stroke= 0f;
        props = new MaterialPropertyBlock();
        Update();
    }

    // Update is called once per frame
    void Update () {
        props.SetFloat("_Stroke", Stroke);
        meshRenderer.SetPropertyBlock(props);
    }
}

オブジェクトにfloat型のStrokeプロパティーを持たせて、Update()時にMeshRendererにプロパティーを渡すスクリプトです。_Strokeが、先ほどシェーダーで確認した、シェーダー側のプロパティー名です。

これで今度こそ完成です。オブジェクトごとに別のStroke値を持つことができるようになったので、あとはスクリプトやアニメーションで、このオブジェクトのStrokeプロパティーの値を変更するようにしてください。

f:id:am1tanaka:20171201221418g:plain

Animationでのパラメーターの調整

RoAnimatorScriptのStrokeパラメーターをAnimationに追加すれば、赤色テクスチャーで調整をしなくても、Animationの方で描き方に緩急を付けることができます。

f:id:am1tanaka:20171201222042p:plain

まとめ

目的のシェーダーが完成しました。ノードのグラフを見ながら操作できるので、コードで組み立てるよりも遥かに分かりやすく、直感的でした。ぼんやりと作り方の方針が思い浮かんだ状態で、ぽちぽちと操作をしていったら、いつの間にか完成していて驚きです。シェーダー初心者で、これから学んでみようという方にはオススメです。

ビジュアルスクリプティングはマウス操作が面倒で好きではないのですが、これは認めざるを得ません。Amplify Shader Editorで作成したシェーダーを通常のコードで見ることもできるので、自分が作ったシェーダーがどのようなコードで動くのかを見ることができて、コードでの組み立て方の雰囲気も掴めました。

今回はCompareで分かりやすく比較しましたが、もっとエレガントな方法がある気がします。いずれそれが分かる時がきたら追記しようと思います。

参考URL