「トゥーン輪郭」について話すとき、それらはオブジェクトの周りに線をレンダリングする任意のテクニックを意味します。 セルシェーディングと同様に、アウトラインはゲームをよりスタイリッシュに見せます。 それらは、オブジェクトがペイントまたはインクでペイントされているという感覚を作り出すことができます。 このスタイルの例は、 Okami 、 Borderlands 、 Dragon Ball FighterZなどのゲームで見ることができます。
このチュートリアルでは、次のことを学びます。
- 逆メッシュを使用して輪郭を作成する
- 後処理と畳み込みを使用してパスを作成する
- マテリアル関数を作成して使用する
- 隣接ピクセルのサンプル
注:このチュートリアルは、Unreal Engineの基本をすでに知っていることを前提としています。 Unreal Engineを初めて使用する場合は、初心者向けのUnreal Engineの一連の10部チュートリアルを検討することをお勧めします。
後処理マテリアルに慣れていない場合は、まずセルシェーディングに関するチュートリアルを学習する必要があります。 この記事では、セルシェーディングチュートリアルで概要を説明したいくつかの概念を使用します。
仕事を始める
開始するには、このチュートリアルから資料をダウンロードしてください。 それらを解凍し、 ToonOutlineStarterに移動してToonOutline.uprojectを開きます 。 次のシーンが表示されます。
最初に、 逆メッシュを使用して輪郭を作成します。
反転メッシュの輪郭
このメソッドを実装するための原則は、ターゲットメッシュを複製することです。 次に、複製には単色(通常は黒)が割り当てられ、元のメッシュよりもわずかに大きくなるようにサイズが大きくなります。 したがって、シルエットを作成します。
複製のみを使用する場合、元のメッシュと完全に重なります。
これを修正するために、複製の法線を反転できます。 バックフェースカリングパラメータが有効な場合、外部ではなく内部の面が表示されます。
これにより、元のメッシュが複製を通して輝きます。 また、複製は元のメッシュよりも大きいため、アウトラインが取得されます。
利点:
- アウトラインはポリゴンで構成されているため、常に明確な線が表示されます。
- 輪郭の外観と太さは、頂点を移動することで簡単に調整できます
- 距離とともに、輪郭は小さくなります(これは欠点かもしれません)。
短所:
- 通常、この方法では、メッシュ内のパーツの輪郭を作成できません
- アウトラインはポリゴンで構成されているため、クリッピングが発生しやすくなります。 これは、複製が地面と交差する上記の例で見ることができます。
- この方法では、速度を下げることができます。 メッシュ内のポリゴンの数に依存します。 複製を使用するため、基本的にポリゴンの数は2倍になります。
- このような輪郭は、滑らかで凸状のメッシュでより良く機能します。 シャープなエッジと凹面領域は、アウトラインに穴を作成します。 これは下の画像で見ることができます。
3Dモデリングプログラムでは、逆メッシュを作成する方が適切です。これにより、シルエットをより詳細に制御できます。 骨格メッシュを使用する場合、これにより、元の骨格の複製をスキンすることもできます。 これにより、複製は元のメッシュと一緒に移動できるようになります。
このチュートリアルでは、3Dエディターでメッシュを作成しません。 しかし、Unrealでは。 方法は若干異なりますが、概念は同じままです。
まず、複製マテリアルを作成する必要があります。
反転メッシュマテリアルの作成
この方法では、面が外側にあるポリゴンをマスクし、その結果、面が内側にあるポリゴンを作成します。
注:マスキングのため、この方法は手動でメッシュを作成するよりもわずかに高価です。
Materialsフォルダーを参照し、 M_Invertedを開きます 。 次に、詳細パネルに移動して、次の設定を変更します。
- ブレンドモード : マスクを選択します。 これにより、領域を表示または非表示としてマークできます。 Opacity Mask Clip Valueを編集して、しきい値を変更できます。
- シェーディングモデル: Unlitを選択します。 これにより、メッシュは照明の影響を受けません。
- 両面: [ 有効 ]を選択します。 デフォルトでは、Unrealはエッジをカットバックします。 このオプションを有効にすると、背面のクリッピングが無効になります 。 背面のクリッピングをオンのままにすると、面が内側にあるポリゴンを表示できなくなります。
次に、 ベクトルパラメーターを作成し、 OutlineColorという名前を付けます。 アウトラインの色を制御します。 それをEmissive Colorと組み合わせます。
外向きの面を持つポリゴンをマスクするには、 TwoSidedSignを作成し、 -1を乗算します。 結果を不透明マスクと結合します。
TwoSidedSignは、前面に1 、背面に-1を印刷します。 これは、前面が表示され、背面が表示されないことを意味します。 ただし、逆の効果が必要です。 これを行うには、 -1を掛けて符号を変更します。 これで、前面では出力として-1が 、背面では1が出力されます。
最後に、輪郭の厚さを制御する方法が必要です。 これを行うには、選択したノードを追加します。
Unrealエンジンでは、 World Position Offsetを使用して各頂点の位置を変更できます。 頂点の法線にOutlineThicknessを掛けて、メッシュを厚くします。 元のメッシュを使用したデモは次のとおりです。
これで、資料の準備が完了しました。 [ 適用]をクリックしてM_Invertedを閉じます 。
次に、メッシュを複製して、新しく作成したマテリアルを適用する必要があります。
メッシュ複製
Blueprintsフォルダーに移動し、 BP_Vikingを開きます 。 Static MeshコンポーネントをMeshの子として追加し、 Outlineという名前を付けます。
[ アウトライン]を選択し、[ 静的メッシュ]をSM_Vikingに設定します 。 次に、そのマテリアル値MI_Invertedを選択します。
MI_InvertedはM_Invertedのインスタンスです。 再コンパイルせずにOutlineColorおよびOutlineThicknessパラメーターを変更できます 。
[ コンパイル]をクリックし、 BP_Vikingを閉じます 。 これで、バイキングの概要がわかります。 MI_Invertedを開き、パラメーターを調整することで、パスの色と太さを変更できます。
そして、この方法で完了です! 3Dエディターで反転メッシュを作成してから、Unrealに転送してください。
輪郭を別の方法で作成する場合は、このために後処理を使用できます。
後処理回路の作成
エッジ認識を使用して、後処理輪郭を作成できます。 これは、画像領域のギャップを認識する技術です。 以下に、探すことができるいくつかのタイプのギャップを示します。
利点:
- この方法は、シーン全体に簡単に適用できます。
- ピクセルごとにシェーダーが常に実行されるため、一定の計算オーバーヘッド
- 線の太さは、距離に関係なく常に同じままです(これは欠点かもしれません)。
- これは後処理効果であるため、ラインはジオメトリによってクリップされません。
短所:
- 通常、すべてのエッジの認識には複数のエッジ認識機能が必要です。 これにより速度が低下します。
- この方法はノイズの影響を受けます。 これは、多くの変化がある領域にrib骨が現れることを意味します。
通常、エッジ認識は各ピクセルを折り畳むことで実行されます。
畳み込みとは何ですか?
画像処理の分野では、畳み込みは、1つの数値を計算するための2つの数値グループの演算です。 まず、数字のグリッド( カーネル )を取得し、各ピクセルの上に中心を配置します。 以下は、画像の2行にわたってコアが移動する方法の例です。
各ピクセルについて、各コア要素に対応するピクセルが乗算されます。 これを実証するために、口の左上隅からピクセルを取りましょう。 また、計算を簡素化するために、画像をグレースケールに変換します。
まず、ターゲットピクセルが中央にくるようにコアを配置します(上記で使用したものと同じものを使用します)。 次に、カーネルの各要素に、それが重ねられるピクセルを乗算します。
最後に、結果を一緒に追加します。 これは、中心ピクセルの新しい値になります。 この場合、新しい値は0.5 + 0.5または1です。 各ピクセルの畳み込み後の画像は次のとおりです。
得られる効果は、使用するコアによって異なります。 上記の例のカーネルは、エッジを認識するために使用されます。 他のエッジの例を次に示します。
注:画像エディターのフィルターとして使用されていることに気付くかもしれません。 実際、画像エディターのフィルターを使用した多くの操作は、畳み込みを使用して実行されます。 Photoshopでは、独自のカーネルに基づいて畳み込みを実行することもできます!
画像のエッジを認識するには、 ラプラスエッジ認識を使用できます。
ラプラスエッジ認識
まず、ラプラスのエッジ認識の中核となるものは何ですか? 実際、前のセクションの例でこのコアを見てきました!
ラプラシアンは急峻性の変化を測定するため、このコアはエッジ認識を実行します。 大きな変化のある領域はゼロから逸脱し、これがエッジであることを報告します。
これを整理するために、ラプラシアンを1次元で見てみましょう。 そのコアは次のとおりです。
まず、エッジのピクセルの上にコアを配置してから、畳み込みを実行します。
これにより、値1が得られます。これは、大きな変化があったことを示しています。 つまり、ターゲットピクセルはエッジである可能性があります。
次に、ばらつきの少ないエリアをロールアップします。
ピクセルの値は異なりますが、グラデーションは線形です。 つまり、勾配に変化はなく、ターゲットピクセルはエッジではありません。
以下は、畳み込み後の画像とすべての値のグラフです。 エッジのピクセルがゼロからより強く外れていることに気付くかもしれません。
はい、かなりの理論をカバーしましたが、心配しないでください、興味深い部分が始まります。 次のセクションでは、深度バッファでラプラスエッジ認識を実行する後処理素材を作成します。
Laplace Rib Recognizerの構築
Mapsフォルダーに移動してPostProcessを開きます 。 黒い画面が表示されます。 これは、マップに空のポストプロセッシングマテリアルを使用するポストプロセスボリュームが含まれているためです。
エッジレコグナイザーを構築するために変更するのはこのマテリアルです。 最初のステップは、隣接するピクセルをサンプリングする方法を見つける必要があることです。
現在のピクセルの位置を取得するには、 TextureCoordinateを使用できます。 たとえば、現在のピクセルが中央にある場合、 (0.5、0.5)を返します。 この2成分ベクトルはUVと呼ばれます。
別のピクセルをサンプリングするには、TextureCoordinateにオフセットを追加するだけです。 100×100の画像の場合、UV空間の各ピクセルのサイズは0.01です。 右側のピクセルをサンプリングするには、X軸に沿って 0.01を追加する必要があります。
ただし、問題があります。 画像の解像度を変更すると、ピクセルサイズも変更されます。 200×200の画像に同じオフセット(0.01、0)を使用すると、右側の2ピクセルがサンプリングされます。
これを修正するために、ピクセルサイズを返すSceneTexelSizeノードを使用できます。 それを適用するには、次のようにする必要があります。
いくつかのピクセルをサンプリングするので、数回作成する必要があります。
明らかに、グラフはすぐに混乱します。 幸いなことに、グラフを読みやすくするために、マテリアルの機能を使用できます。
注:マテリアル関数は、ブループリントまたはC ++で使用される関数に似ています。
次のセクションでは、重複するノードを関数に挿入し、オフセットの入力を作成します。
ピクセルサンプリング関数を作成する
開始するには、 Materials \ PostProcess folderに移動します 。 マテリアル関数を作成するには、[ 新規追加 ]をクリックして、[ マテリアルとテクスチャ] \ [ マテリアル関数]を選択します。
MF_GetPixelDepthに名前を変更して開きます。 グラフには1つのFunctionOutputノードがあります。 これは、サンプリングされたピクセルの値を添付する場所です。
まず、オフセットを受け取る入力を作成する必要があります。 これを行うには、 FunctionInputを作成します。
関数を使用し続けると、入力連絡先になります。
次に、ログイン用のいくつかのパラメーターを設定する必要があります。 FunctionInputを選択して、詳細パネルに移動します。 次のオプションを変更します。
- InputName:オフセット
- InputType:関数入力ベクター2。深度バッファーは2D画像なので、オフセットはベクター2型でなければなりません。
- プレビュー値をデフォルトとして使用:有効。 入力値を渡さない場合、関数はPreview Valueの値を使用します。
次に、オフセットにピクセルサイズを掛ける必要があります。 次に、結果をTextureCoordinateに追加する必要があります。 これを行うには、強調表示されたノードを追加します。
最後に、UV深度バッファを使用してサンプリングする必要があります。 SceneDepthを追加して、すべてを次のように接続します。
注: 代わりにSceneDepth値を持つSceneTextureを使用することもできます。
要約すると:
- OffsetはVector 2を取得し、 それにSceneTexelSizeを乗算します 。 これにより、UVスペースに変化が生じます。
- TextureCoordinateにオフセットを追加して、現在のピクセルからピクセルの距離(x、y)にあるピクセルを取得します。
- SceneDepthは渡されたUVを使用して、対応するピクセルをサンプリングし、出力します。
そして、この機能で材料の機能が完了しました。 [ 適用]をクリックしてMF_GetPixelDepthを閉じます 。
注: [ 統計 ]パネルでは、シーンの深さから読み取ることができるのは半透明のマテリアルまたは後処理マテリアルのみであるというエラーが表示される場合があります。 このエラーは無視しても問題ありません。 後処理素材で関数を使用するため、すべてが機能します。
次に、深度バッファを畳み込むために関数を使用する必要があります。
畳み込み
まず、ピクセルごとにオフセットを作成する必要があります。 核の角度は常にゼロであるため、スキップできます。 つまり、左、右、上、下のピクセルがあります。
PP_Outlineを開き、4つのConstant2Vectorノードを作成します。 次のオプションを提供します。
- (-1、0)
- (1、0)
- (0、-1)
- (0、1)
次に、カーネルで5つのピクセルをサンプリングする必要があります。 5つのMaterialFunctionCallノードを作成し、それぞれに対してMF_GetPixelDepthを選択します。 次に、各オフセットを独自の関数に接続します。
したがって、各ピクセルの深度値を取得します。
次は乗算段階です。 隣接するピクセルの係数は1なので、乗算をスキップできます。 ただし、中心ピクセル(ボトム関数)に-4を掛ける必要があります。
次に、すべての値を要約する必要があります。 4つの追加ノードを作成し、次のように接続します。
ピクセル値のグラフを覚えている場合、それらのいくつかが負であることに気付くでしょう。 マテリアルをそのまま使用する場合、負のピクセルはゼロ未満であるため、黒で表示されます。 これを修正するために、すべての入力を正の値に変換する絶対値を取得できます。 Absを追加して、次のようにまとめます。
要約すると:
- MF_GetPixelDepthノードは、中央、左、右、上部、下部のピクセルから深度値を取得します
- 各ピクセルに対応するカーネル値を乗算します。 この場合、中央のピクセルのみを乗算すれば十分です。
- すべてのピクセルの合計を計算します
- 合計の絶対値を取得します。 これにより、負の値を持つピクセルが黒で表示されなくなります。
[ 適用]をクリックして、メインエディターに戻ります。 画像全体に線が表示されるようになりました!
ただし、ここでいくつかの問題があります。 第一に、深さの違いが取るに足らないリブがある。 次に、背景には球体であるため円形の線があります。 エッジ認識をメッシュのみに制限する場合、これは問題ではありません。 ただし、シーン全体に線を作成する場合、これらの円は望ましくありません。
これを修正するには、しきい値を使用できます。
しきい値の実装
最初に、深さのわずかな違いにより表示される線を修正します。 マテリアルエディターに戻り、以下の図を作成します。 しきい値を4に設定します。
その後、エッジ認識結果をAと組み合わせます。 ピクセル値が4を超える場合、値1 (エッジを示す)を出力します。 それ以外の場合、 0 (エッジなし)を出力します。
次に、バックグラウンドの行を取り除きます。 以下に示す回路を作成します。 DepthCutoffを9000に設定します。
この場合、現在のピクセルの深さが9000を超える場合、値0が出力に送信されます(エッジはありません)。 そうでない場合、 A <Bの値が出力に送信されます。
最後に、すべてを次のように接続します。
これで、ピクセル値が4 ( Threshold )より大きく、その深さが9000 ( DepthCutoff )より小さい場合にのみ、ラインが表示されます。
[ 適用]をクリックして、メインエディターに戻ります。 バックグラウンドに小さな線や線はもうありません!
注: PP_Outlineマテリアルのインスタンスを作成して、 ThresholdとDepthCutoffを制御できます。
エッジ認識は非常にうまく機能します。 しかし、太い線が必要な場合はどうでしょうか? これを行うには、カーネルのサイズを増やす必要があります。
より太い線を作成する
一般的に、カーネルサイズが大きいほど、より多くのピクセルをサンプリングする必要があるため、速度に大きく影響します。 しかし、3×3コアと同じ速度を維持しながら、コアを増やす方法はありますか? ここでは、 拡張畳み込みが必要です。
拡張畳み込みでは、オフセットをさらに伝播します。 これを行うには、各オフセットに拡張係数と呼ばれるスカラーを掛けます。 コアの要素間の距離を決定します。
ご覧のとおり、これにより、同じピクセル数をサンプリングしながら、カーネルのサイズを増やすことができます。
それでは、拡張畳み込みを実装しましょう。 マテリアルエディターに戻り、 DilationRateというScalarParameterを作成します。 3に設定します。 次に、各オフセットにDilationRateを乗算します 。
したがって、各オフセットを中心ピクセルから3ピクセルずつ移動します。
[ 適用]をクリックして、メインエディターに戻ります。 線が非常に太くなっていることがわかります。 異なる膨張係数を持つ線の比較は次のとおりです。
線画のスタイルでゲームを作成していない場合、ほとんどの場合、元のシーンを表示する必要があります。 最後のセクションでは、元のシーンの画像に線を追加します。
元の画像に線を追加する
マテリアルエディターに戻り、以下の図を作成します。 ここでは順序が重要です!
次に、すべてを次のように接続します。
これで、アルファがゼロ(黒色)に達すると、 Lerpはシーンイメージを表示します。 それ以外の場合は、 LineColorを出力します 。
[ 適用]をクリックしてPP_Outlineを閉じます 。 これで、ソースシーンに輪郭ができました!
次はどこへ行きますか?
完成したプロジェクトはここからダウンロードできます 。
それでもエッジ認識を使用したい場合は、通常のバッファーで機能する認識を作成してみてください。 これにより、エッジレコグナイザーに表示されないエッジが深さで表示されます。 次に、両方のタイプのエッジ認識を一緒に組み合わせることができます。
畳み込みは、人工知能や音声処理など、積極的に使用されている広範なトピックです。 畳み込みを調べて、シャープやぼかしなどの他の効果を作成することをお勧めします。 それらのいくつかについては、カーネル値を変更するだけで十分です! 視覚的に説明される画像カーネルの畳み込みのインタラクティブな説明を参照してください。 また、他のいくつかの効果のカーネルについても説明します。
また、GDCのGuilty Gear Xrdグラフィックプレゼンテーションをご覧になることを強くお勧めします。 外線の場合、このゲームは逆メッシュ法も使用します。 ただし、内部ラインについては、開発者はテクスチャとUVを使用してシンプルだが独創的な手法を作成しました。