Unity を検索

シェーダーグラフのカスタムライティング:Unity 2019 でグラフを拡張する

2019年7月31日 カテゴリ: テクノロジー | 13 分 で読めます
取り上げているトピック
シェア

Unity エディター 2019.1 で、シェーダーグラフパッケージが正式にリリースされました!続く Unity 2019.2 では、シェーダーグラフに新機能が追加され、既存機能も強化されています。

Unity 2019 での変更点

カスタム関数とサブグラフのアップグレード

シェーダーグラフ内でカスタムコードを管理できるように、新しく Custom Function ノードが追加されました。このノードでは、カスタム入力と出力を定義したり、並べ替えたりすることができるほか、カスタム関数をノード自体に直接記入、あるいは外部ファイルを参照して挿入できます。  この変更に合わせてサブグラフもアップグレードされ、各種タイプ、カスタム名、並べ替え可能なポートを持つ独自の出力をサブグラフに対して定義できるようになりました。さらに、サブグラフのブラックボードが、メイングラフでサポートされているすべてのデータタイプに対応しました。

カラーモードと精度モード

シェーダーグラフで高度なシェーダーを作成、最適化しやすくなりました。Unity 2019.2 では、グラフ内の計算の精度を、グラフ全体またはノードごとに手動で設定できます。また、新機能のカラーモードを使用すれば、精度のフローやノードのカテゴリの視覚化、独自のカスタムカラーの表示を短時間で簡単に行えます。 これらの新機能の詳細については、シェーダーグラフに関するドキュメントを参照してください。

サンプルプロジェクト

新しくなったカスタム関数ワークフローをすぐに使い始められるよう、サンプルプロジェクトとステップバイステップ形式の手順説明をご用意しました。レポジトリからプロジェクトをダウンロードし、あとは説明に従って進めてください。このプロジェクトでは、カスタム関数ノードを使用して、ライトウェイトレンダーパイプライン(LWRP)のカスタムライティングシェーダーを作成する方法について説明します。新規プロジェクトを作成して下記手順を実行する場合は、バージョン 2019.2 のエディターとバージョン 6.9.1 以降の LWRP パッケージを使用してください。

メインライトからデータを取得する

はじめに、シーンのメインライトから情報を取得する必要があります。「Create」「Shader」「Unlit Graph」の順に選択して、新しい Unlit シェーダーグラフを作成します。「Create Node」メニューで、新しく追加された「Custom Function」ノードを選択し、右上にある歯車アイコンをクリックしてノードメニューを開きます。このメニューでは、入力と出力を追加できます。DirectionColor という名前の 2 つの出力ポートを追加し、各ポートで「Vector 3」を選択します。「undeclared identifier」というエラーフラグが表示されても、心配はいりません。コードを入力すればこのエラーは解消されます。「Type」ドロップダウンメニューで「String」を選択します。関数名を入力します。今回は、「MainLight」という名前を付けましょう。それでは、テキストボックスにカスタムコードを入力していきましょう。

まず、`#ifdef SHADERGRAPH_PREVIEW` という名前のフラグを作成します。ノードのプレビューボックスからライトのデータにアクセスすることはできないので、グラフ内のプレビューボックスで表示する内容をノードに指示しなければなりません。`#ifdef` により、状況に応じて異なるコードを使うようにコンパイラーに命令するわけです。まずは、出力ポートのフォールバック値を定義しましょう。

次に `#else` を使用して、プレビュー時以外に行うべき処理をコンパイラーに指示します。実際にライトのデータを取得するのはここになります。ここで使用するのは、LWRP パッケージの組み込み関数である `GetMainLight()` です。この情報を使用することによって、DirectionColor に出力を割り当てることができます。現段階のカスタム関数は次のようになります。

それでは、このノードが実行する処理をひと目で確認できるように、ノードをグループに追加しましょう。ノードを右クリックして「Create Group from Selection」を選択し、グループのタイトルをノードの動作内容がわかるようなものに変更します。今回は、「Get Main Light」と入力します。

これでライトのデータが手に入ったので、シェーディングを計算できるようになりました。さっそく、標準的なランバートライティングを作成してみましょう。まず、ワールド法線ベクトルとライト方向のドット積を取ります。ドット積の出力を「Saturate」ノードに渡し、ライトのカラーを乗算します。乗算結果を「Unlit Master」ノードの「Color」ポートにつなげば、プレビューが更新され、簡単なカスタムシェーディングが表示されます!

カスタム関数のファイルモードを使用する

カスタム関数ノードを使用してライトのデータを取得する方法がわかったので、次は関数を拡張していきましょう。メインライトから方向と色だけでなく、減衰に関する値も取得するように関数を更新します。 今回は関数の複雑さが増すので、ファイルモードに切り替えて HLSL のインクルードファイルを使用することにします。こうすることで、複雑な関数を適切なコードエディターで作成してから、グラフに挿入できます。これには、コードのデバッグを一箇所で行えるようになるという利点もあります。 まず、プロジェクトの Assets/Include フォルダー内にある `CustomLighting` ファイルを開きます。今回は `MainLight_half` 関数のみに注目します。この関数は次のようになっています。

この関数に新しい入出力のデータが含まれているので、先ほどの Custom Function ノードに戻ってこれらを追加しましょう。DistanceAtten(距離減衰)と ShadowAtten(影の減衰率)用に新しい出力を 2 つを追加します。また、WorldPos(ワールド座標)の入力も新しく追加します。これで入力と出力が揃い、インクルードファイルを参照する準備が整いました。「Type」ドロップダウンメニューを「File」に変更します。「Source」入力で先ほどのインクルードファイルを見つけて、参照するアセットを選択します。次に、使用する関数をノードに指示しなければなりません。「Name」ボックスに「MainLight」と入力します。

インクルードファイルでは関数名の末尾に `_half` が付いていたのに、入力した名前には付けていないことに注意してください。この理由は、シェーダーグラフのコンパイラーでは、各関数名の末尾に精度形式が自動で付けられるためです。独自の関数を定義するのですから、そのソースコードでは、関数で使用する精度形式をコンパイラーに伝えなければなりません。しかし、ノードでは、メイン関数の名前を参照するだけで済みます。関数をコピーして「float」の値を使用するように変更し、float 精度モードでコンパイルすることも可能です。「精度」カラーモードを利用すれば、グラフの各ノードに設定されている精度を簡単に識別できます。青色は float、赤色は half を表します。このカスタム関数をまた別の機会に使用したくなった場合に備えて、再利用可能にしておきましょう。方法は簡単で、関数をサブグラフにラップするだけです。ノードとそのグループを選択して右クリックし、「Convert to Sub-graph」を選択します。この例では、サブグラフの名前は「Get Main Light」にしました。作成したサブグラフを開き、サブグラフの出力ノードに必要な出力ポートを追加して、ノードの出力をサブグラフの出力に接続します。その後、ワールド座標ノードを追加して、入力に接続します。

サブグラフを保存して、Unlit グラフに戻ります。既存のロジックに、新しく 2 つの Multiply ノードを追加していきます。まず、2 つの減衰の出力を乗算します。次に、その出力にライトのカラーを乗算します。この出力を先ほど作成した NdotL と乗算すれば、基本的なシェーディングにおける減衰を適切に計算することができます。

ダイレクトスペキュラーシェーダーを作成する

これまでの手順で作成したシェーダーは、マットなオブジェクトには最適です。しかし、光沢が欲しい場合には向きません。それなら、シェーダーに独自のスペキュラー計算を追加しましょう!この手順では、サブグラフにラップされている別のカスタム関数ノード、Direct Specular を使用します。再び `CustomLighting` インクルードファイルを開きます。ファイル内から別の関数を参照していることに注目してください。 

この関数では、単純なスペキュラー計算を行います。詳細に興味がある方は、こちらをご覧ください。この関数のサブグラフには、ブラックボード上の入力も複数含まれています。

新しいノードに、関数に対応した適切な入力ポートと出力ポートを設定しましょう。ブラックボードにプロパティを追加する方法はシンプルで、右上にある「Add (+)」アイコンをクリックし、データタイプを選ぶだけです。カプセルをダブルクリックして入力の名前を変更してから、ドラッグアンドドロップしてグラフに追加します。最後に、サブグラフの出力ポートを更新して保存します。 スペキュラー計算の用意ができたので、Unlit グラフに戻って「Create Node」メニューからこの計算を追加しましょう。「Attenuation」の出力を「Direct Specular」サブグラフの「Color」入力に接続します。次に、Get Main Light 関数の「Direction」出力をサブグラフの「Direction」入力に接続します。最後に、「NdotL」と「Attenuation」の乗算結果を「Direct Specular」サブグラフの出力に加算し、その結果を「Color」出力に接続します。

これで光沢を追加できました!

複数のライトを使用する

LWRP のメインライトとは、オブジェクトに対して最も明るいディレクショナルライト(通常は太陽)のことを指します。性能の低いハードウェアでのパフォーマンスを改善するため、LWRP ではメインライトと他のライトの計算を分けて行っています。最も明るいディレクショナルライトだけでなく、シーン内のすべてのライトについてシェーダーで適切な計算を行うには、関数内にループを作成する必要があります。 別のライトのデータを取得するために、新しいサブグラフを使用して新しいカスタム関数ノードをラップします。まずは `CustomLighting` インクルードファイルの `AdditionalLight_float` 関数を見てください。 

先ほどと同様に、カスタム関数ノードのファイル参照で `AdditionalLights` 関数を指定し、適切な入力と出力をすべて作成してください。ノードをラップするサブグラフのブラックボードで、スペキュラーのカラースペキュラーのスムースネスを公開してください。サブグラフで、Position ノード、Normal Vector ノード、View Direction ノードをそれぞれワールド座標ワールド法線ワールド空間のビュー方向に接続します。

関数の用意ができたら、さっそく使いましょう!まず、前手順までのメインの Unlit グラフを選択し、まとめてサブグラフに変換します。ノードを選択して右クリックし、「Convert to Sub-graph」を選択します。最後の「Add」ノードを削除し、出力をサブグラフの出力ポートに接続します。この際には、スペキュラースムースネス用の入力プロパティも作成することをお勧めします。

次に、メインライトの計算と他のライトの計算を組み合わせます。メインの Unlit グラフで、メインライトの計算と並行して他のライトの計算を実行するためのノードを作成してください。そのうえで、メインライトと残りのライトの「Diffuse」出力どうし、「Specular」出力どうしを、それぞれ足し合わせます。これで完成です!

シンプルなトゥーンシェーダーを作成する

さて、LWRP プロジェクトのシーン内の全ライトからデータを取得する方法はわかりましたが、これをどのように使えばいいのでしょう?シェーダーでカスタムライティングを利用する場面として最もよくあるものとしては、従来のトゥーンシェーダーが挙げられます。 

すべてのライトデータがあれば、トゥーンシェーダーはとても簡単に作成できます。まず、ここまでに作成したライト計算をすべて選択し、再びサブグラフにラップします。こうすることで、完成版のシェーダーが見やすくなります。最後の「Add」ノードを削除し、「Diffuse」と「Specular」をそれぞれサブグラフの別々の出力ポートに接続することも忘れないでください。 

トゥーンシェーディングの作成方法は数多くありますが、今回は、ライトの輝度を基にランプテクスチャーからカラーを検索することにします。一般的には、この手法はランプライティングと呼ばれています。 ランプライティングに必要なテクスチャーアセットのサンプルは、サンプルプロジェクトに含まれています。自身でグラデーションをサンプリングして、ランプライティングで動的なランプを使用してもかまいません。  最初の手順は、DiffuseSpecular の強度を RGB 値から HSV 値に変換することです。こうすることで、ライトのカラーの強度(HSV)値を基にシェーダーの輝度を決定できるようになります。また、アセットの水平軸に沿ったさまざまなスポットでテクスチャをサンプリングしやすくなります。UV の Y チャンネルには静的値を使用して、画像の上から下までのどの部分をサンプリングの対象とするかを指定します。この静的値をインデックスとして使用することで、単一のテクスチャアセット内でプロジェクトの複数のライティングランプを参照できます。

UV 値を設定したら、Sample Texture 2D LOD ノードを使用してランプテクスチャのサンプリングを行います。この LOD というのがポイントです。通常の Sample Texture 2D ノードを使用した場合、シーン内で自動的にランプにミップマッピングが適用されてしまい、遠くにあるオブジェクトではライティングの挙動が変わってしまうからです。Sample Texture 2D LOD ノードを使用すれば、ミップレベルを手動で指定できます。さらに、ランプテクスチャーの高さはわずか 2 ピクセルなので、テクスチャー用に独自の SamplerState も作成します。 テクスチャのサンプリングが適切に行われるように、「Filter」は「Point」、「Wrap」は「Clamp」に設定します。テクスチャアセットを変えた場合に設定を変更できるように、これをブラックボードのプロパティとして公開しておきます。

最後に、オブジェクトの色を変えられるように、ディフューズ計算のランプサンプルと色のプロパティである Diffuse を乗算します。スペキュラー計算のランプサンプルを Diffuse 出力に加算して、最終的なカラーをマスターノードに接続します。

カスタムライティングを拡張する

今回作成したカスタムライティング設定はシンプルなものですが、拡張すれば、あらゆるシーンのいろいろなユースケースに応用できます。サンプルプロジェクトには、このカスタムライティング設定を使用したシェーダーで構成した完成版シーンが含まれています。また、頂点アニメーションやシンプルなサブサーフェススキャッタリング近似のほか、深さを利用した屈折とカラーリングも含まれています。もっと高度なテクニックを身につけたい方は、プロジェクトをダウンロードして、Example アセットをご覧ください。

学び続けよう

シェーダーグラフとこの機能で作成できるシェーダーについて質問や意見がある場合は、新しく生まれ変わったフォーラムスペースにアクセスしてください!また Discord のコミュニティでも、コミュニティのメンバーや(ときおり)開発者が交流していますので、こちらもぜひご覧ください。 

最後に、SIGGRAPH 2019 のセッションをお忘れなく!シェーダーグラフを使用したカスタムライティングの作成方法をさらに詳しく説明します。

2019年7月31日 カテゴリ: テクノロジー | 13 分 で読めます
取り上げているトピック