Unity を検索

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

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

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

Unity 2019 での変更点

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

シェーダーグラフ内でカスタムコードを管理できるように、新しく Custom Function ノードが追加されました。このノードでは、カスタム入力と出力を定義したり、並べ替えたりすることができるほか、カスタム関数をノード自体に直接記入、あるいは外部ファイルを参照して挿入できます。

この変更に合わせてサブグラフもアップグレードされ、各種タイプ、カスタム名、並べ替え可能なポートを持つ独自の出力をサブグラフに対して定義できるようになりました。さらに、サブグラフのブラックボードが、メイングラフでサポートされているすべてのデータタイプに対応しました。

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

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

これらの新機能の詳細については、シェーダーグラフに関するドキュメントを参照してください。

サンプルプロジェクト

新しくなったカスタム関数ワークフローをすぐに使い始められるよう、サンプルプロジェクトとステップバイステップ形式の手順説明をご用意しました。Unity のリポジトリからプロジェクトをダウンロードし、下記の説明に従って機能を確認してください。このプロジェクトでは、カスタム関数ノードを使用して、ライトウェイトレンダーパイプライン(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` により、状況に応じて異なるコードを使うようにコンパイラーに命令するわけです。まずは、出力ポートのフォールバック値を定義しましょう。

#if SHADERGRAPH_PREVIEW
	Direction = half3(0.5, 0.5, 0);
	Color = 1;

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

#if SHADERGRAPH_PREVIEW
	Direction = half3(0.5, 0.5, 0);
	Color = 1;
#else
	Light light = GetMainLight();
	Direction = light.direction;
	Color = light.color;
#endif

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

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

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

カスタム関数ノードを使用してライトのデータを取得する方法がわかったので、次は関数を拡張していきましょう。メインライトから方向と色だけでなく、減衰に関する値も取得するように関数を更新します。

今回は関数の複雑さが増すので、ファイルモードに切り替えて HLSL のインクルードファイルを使用することにします。こうすることで、複雑な関数を適切なコードエディターで作成してから、グラフに挿入できます。これには、コードのデバッグを一箇所で行えるようになるという利点もあります。

まず、プロジェクトの「Assets」>「Include」フォルダー内にある `CustomLighting` ファイルを開きます。今回は `MainLight_half` 関数のみに注目します。この関数は次のようになっています。

void MainLight_half(float3 WorldPos, out half3 Direction, out half3 Color, out half DistanceAtten, out half ShadowAtten)
{
#if SHADERGRAPH_PREVIEW
   Direction = half3(0.5, 0.5, 0);
   Color = 1;
   DistanceAtten = 1;
   ShadowAtten = 1;
#else
#if SHADOWS_SCREEN
   half4 clipPos = TransformWorldToHClip(WorldPos);
   half4 shadowCoord = ComputeScreenPos(clipPos);
#else
   half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
#endif
   Light mainLight = GetMainLight(shadowCoord);
   Direction = mainLight.direction;
   Color = mainLight.color;
   DistanceAtten = mainLight.distanceAttenuation;
   ShadowAtten = mainLight.shadowAttenuation;
#endif
}

この関数に新しい入出力のデータが含まれているので、先ほどの 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` インクルードファイルを開きます。ファイル内から別の関数を参照していることに注目してください。

void DirectSpecular_half(half3 Specular, half Smoothness, half3 Direction, half3 Color, half3 WorldNormal, half3 WorldView, out half3 Out)
{
#if SHADERGRAPH_PREVIEW
   Out = 0;
#else
   Smoothness = exp2(10 * Smoothness + 1);
   WorldNormal = normalize(WorldNormal);
   WorldView = SafeNormalize(WorldView);
   Out = LightingSpecular(Color, Direction, WorldNormal, WorldView, half4(Specular, 0), Smoothness);
#endif
}

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

新しいノードに、関数に対応した適切な入力ポートと出力ポートを設定しましょう。ブラックボードにプロパティを追加する方法はシンプルで、右上にある「Add (+)」アイコンをクリックし、データタイプを選ぶだけです。カプセルをダブルクリックして入力の名前を変更してから、ドラッグアンドドロップしてグラフに追加します。最後に、サブグラフの出力ポートを更新して保存します。

スペキュラー計算の用意ができたので、Unlit グラフに戻って「Create Node」メニューからこの計算を追加しましょう。「Attenuation」の出力を「Direct Specular」サブグラフの「Color」入力に接続します。次に、Get Main Light 関数の「Direction」出力をサブグラフの「Direction」入力に接続します。最後に、「NdotL」と「Attenuation」の乗算結果を「Direct Specular」サブグラフの出力に加算し、その結果を「Color」出力に接続します。

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

複数のライトを使用する

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

別のライトのデータを取得するために、新しいサブグラフを使用して新しいカスタム関数ノードをラップします。まずは `CustomLighting` インクルードファイルの `AdditionalLight_float` 関数を見てください。

void AdditionalLights_half(half3 SpecColor, half Smoothness, half3 WorldPosition, half3 WorldNormal, half3 WorldView, out half3 Diffuse, out half3 Specular)
{
   half3 diffuseColor = 0;
   half3 specularColor = 0;

#ifndef SHADERGRAPH_PREVIEW
   Smoothness = exp2(10 * Smoothness + 1);
   WorldNormal = normalize(WorldNormal);
   WorldView = SafeNormalize(WorldView);
   int pixelLightCount = GetAdditionalLightsCount();
   for (int i = 0; i < pixelLightCount; ++i)
   {
       Light light = GetAdditionalLight(i, WorldPosition);
       half3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation);
       diffuseColor += LightingLambert(attenuatedLightColor, light.direction, WorldNormal);
       specularColor += LightingSpecular(attenuatedLightColor, light.direction, WorldNormal, WorldView, half4(SpecColor, 0), Smoothness);
   }
#endif

   Diffuse = diffuseColor;
   Specular = specularColor;
}

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

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

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