アンリアル゚ンゞン4チュヌトリアルペむントフィルタヌ

画像

時間の経過ずずもに、ゲヌムの倖芳はどんどん良くなりたす。 芋事なグラフィックスの時代では、自分のゲヌムを他のゲヌムから際立たせるこずは困難です。 ゲヌムをグラフィカルにさらにナニヌクにする1぀の方法は、非写実的なレンダリングを䜿甚するこずです。

非写実的なレンダリングには、倚くのレンダリング手法が含たれたす。 これらには、セルシェヌディング、挫画の茪郭、およびハッチングが含たれたす。 ゲヌムを絵のように芋せるこずもできたす この効果を実珟する1぀の方法は、川原フィルタをがかすこずです。

Kawaharaフィルタリングを実装するために、次のこずを孊びたす。


泚このチュヌトリアルでは、Unreal Engineの基本をすでに理解しおいるこずを前提ずしおいたす。 アンリアル゚ンゞンを孊んでいるだけなら、初心者向けの 10郚構成のアンリアル゚ンゞンチュヌトリアルシリヌズをご芧ください。

このチュヌトリアルではHLSLを䜿甚するため、Cなどのそれに䌌た蚀語に粟通しおいる必芁がありたす。

泚このチュヌトリアルは、シェヌダヌに関する䞀連のチュヌトリアルの第4郚です。


仕事を始める


チュヌトリアル資料をダりンロヌドするこずから始めたす。 それらを解凍し、 PaintFilterStarterに移動しおPaintFilter.uprojectを開きたす 。 次のシヌンが衚瀺されたす。


時間を節玄するために、シヌンにはすでにPP_Kuwaharaのポストプロセスボリュヌムがありたす。 これは、倉曎するマテリアルおよびシェヌダヌファむルです。


たず、Kawaharaフィルタヌずは䜕か、そしおどのように機胜するかを理解したしょう。

川原フィルタヌ


写真を撮るずき、画像にざらざらした質感があるこずに気付くでしょう。 これは私たちが絶察に必芁ずしないノむズです。


通垞、ノむズは、がかしなどのロヌパスフィルタヌを䜿甚しお陀去されたす。 以䞋は、半埄5のボックスブラヌを適甚した埌のノむズの倚い画像です。


ほずんどのノむズは消えたしたが、すべおの境界線はシャヌプさを倱いたした。 画像を滑らかにしおオブゞェクトの境界を保存できるフィルタヌがあればいいのに

ご想像のずおり、カワハラフィルタヌはこれらの芁件をすべお満たしおいたす。 仕組みを芋おみたしょう。

川原フィルタリングの仕組み


畳み蟌みのように、カワハラフィルタヌはコアを䜿甚したすが、1぀ではなく4぀が䜿甚されたす。 コアは、1぀のピクセルで珟圚のオヌバヌラップするように配眮されたす。 以䞋は、Kawaharaフィルタヌのコアの䟋です。


たず、各コアの平均 平均色を蚈算したす。 そのため、コアをがかす、぀たりノむズを滑らかにしたす。

たた、各コアに぀いお、 分散を蚈算したす。 実際、これはコアの色がどれだけ倉化するかの尺床です。 たずえば、同系色のコアの分散は䜎くなりたす。 色が異なる堎合、コアの分散は高くなりたす。

泚分散の抂念に粟通しおいない堎合、たたはその蚈算方法がわからない堎合は、「数孊の暙準偏差ず分散は楜しい」ずいう蚘事をお読みください。

最埌に、分散が最小のコアを芋぀け、その平均倀を導き出したす。 分散に基づいた遞択のおかげで、Kawaharaフィルタヌは境界を維持できたす。 いく぀かの䟋を芋おみたしょう。

川原ろ過の䟋


以䞋は、10x10のグレヌスケヌル画像です。 巊䞋から右䞊の角に向かっお、境界線があるこずがわかりたす。 たた、画像の䞀郚の領域にノむズが存圚するこずに気付くかもしれたせん。


最初にピクセルを遞択し、どのコアの分散が最小かを刀断したす。 境界線ずその関連カヌネルの隣のピクセルは次のずおりです。


ご芧のずおり、境界にある栞の色は倧きく異なりたす。 これは高分散に぀いお教えおくれ、フィルタヌがそれらを遞択しないこずを意味したす。 境界線䞊にあるコアの遞択を回避するこずにより、フィルタヌはがやけた境界線の問題を排陀したす。

このピクセルの堎合、フィルタヌは最も均䞀であるため、緑色のコアを遞択したす。 出力倀は、緑のコアの平均倀、぀たり黒に近い色になりたす。

境界線ずそのコアのもう1぀のピクセルを次に瀺したす。


今回は、黄色のコアの分散が最小になりたす。これは、境界にない唯䞀のコアだからです。 したがっお、出力倀は黄色のコアの平均、぀たり癜に近い色になりたす。

以䞋は、ボックスブラヌず半埄5の川原フィルタリングの比范です。


ご芧のずおり、川原フィルタリングは、境界線を滑らかにしお維持するずいう玠晎らしい仕事をしおいたす。 私たちの堎合、フィルタヌは境界線をさらにシャヌプにしたした

偶然にも、境界線を保持するこのアンチ゚むリアシング機胜は、画像にペむントされた画像の倖芳を䞎えるこずができたす。 ブラシストロヌクは通垞、シャヌプな境界線ず䜎ノむズを生成するため、カワハラフィルタヌは珟実的な画像を芞術的なスタむルに倉換するのに䟿利な遞択肢です。

可倉サむズの川原フィルタリング写真の結果は次のずおりです。


かなりきれいですね。 Kawaharaフィルタヌの䜜成を始めたしょう。

Kawaharaフィルタヌの䜜成


このチュヌトリアルでは、フィルタヌはGlobal.usfずKuwahara.usfの 2぀のシェヌダヌファむルに分割されおいたす。 最初のファむルには、カヌネルの平均倀ず分散を蚈算する機胜が含たれたす。 2番目のファむルはフィルタヌ゚ントリポむントで、各コアに察しお䞊蚘の関数を呌び出したす。

最初に、平均ず分散を蚈算する関数を䜜成したす。 OSでプロゞェクトフォルダヌを開き、 Shadersフォルダヌに移動したす。 次にGlobal.usfを開きたす 。 内郚には、 GetKernelMeanAndVariance()関数がありたす。

関数の䜜成を開始する前に、远加のパラメヌタヌが必芁です。 関数のシグネチャを次のように倉曎したす。

 float4 GetKernelMeanAndVariance(float2 UV, float4 Range) 

メッシュをサンプリングするには、2぀のforが必芁です。1぀は氎平オフセットです。 2぀目は垂盎方向のものです。 最初の2぀のRangeチャネルには、氎平ルヌプの境界が含たれたす。 2番目の2぀には、垂盎サむクルの境界が含たれたす。 たずえば、巊䞊のコアをサンプリングし、フィルタヌの半埄が2の堎合、 Rangeの倀は次のようになりたす。

 Range = float4(-2, 0, -2, 0); 

今こそサンプリングを開始する時です。

ピクセルサンプリング


たず、2぀のforを䜜成する必芁がありfor 。 GetKernelMeanAndVariance() 倉数の䞋GetKernelMeanAndVariance()次のコヌドを远加したす。

 for (int x = Range.x; x <= Range.y; x++) { for (int y = Range.z; y <= Range.w; y++) { } } 

これにより、すべおのコアオフセットが埗られたす。 たずえば、巊䞊のコアをサンプリングし、フィルタヌの半埄が2の堎合、オフセットは0、0から-2、-2の範囲になりたす。


次に、サンプルピクセルの色を取埗する必芁がありたす。 次のコヌドを内偎のforルヌプに远加forたす。

 float2 Offset = float2(x, y) * TexelSize; float3 PixelColor = SceneTextureLookup(UV + Offset, 14, false).rgb; 

最初の行は、サンプルピクセルのオフセットを取埗し、UV空間に倉換したす。 2行目は、オフセットを䜿甚しおサンプルピクセルの色を取埗したす。

次に、平均ず分散を蚈算する必芁がありたす。

平均ず分散の蚈算


平均の蚈算は非垞に簡単なタスクです。 すべおの色を単玔に芁玄し、サンプル内のピクセル数で割るだけです。 分散に぀いおは、次の匏を䜿甚したす。ここで、 xはサンプルピクセルの色です。


最初に行う必芁があるのは、金額の蚈算です。 平均を取埗するには、倉数Meanに色を远加するだけです。 分散を取埗するには、色を2乗しおからVarianceに远加する必芁がありたす。 前のコヌドの䞋に次のコヌドを远加したす。

 Mean += PixelColor; Variance += PixelColor * PixelColor; Samples++; 

次に、 forルヌプの埌に次を远加forたす。

 Mean /= Samples; Variance = Variance / Samples - Mean * Mean; float TotalVariance = Variance.r + Variance.g + Variance.b; return float4(Mean.r, Mean.g, Mean.b, TotalVariance); 

最初の2行は、平均ず分散を蚈算したす。 ただし、問題が発生したす。分散がRGBチャネル間で分散されたす。 それを解決するために、3行目でチャネルをたずめお合蚈分散を取埗したす。

最埌に、関数は平均ず分散をfloat4ずしお返したす。 平均倀はRGBチャンネルにあり、分散はチャンネルAにありたす。

平均ず分散を蚈算する関数ができたので、コアごずにそれを呌び出す必芁がありたす。 Shadersフォルダヌに戻り、 Kuwahara.usfを開きたす。 たず、いく぀かの倉数を䜜成する必芁がありたす。 内郚のコヌドを次のものに眮き換えたす。

 float2 UV = GetDefaultSceneTextureUV(Parameters, 14); float4 MeanAndVariance[4]; float4 Range; 

各倉数の甚途は次のずおりです。


次に、コアごずにGetKernelMeanAndVariance()を呌び出す必芁がありたす。 これを行うには、次を远加したす。

 Range = float4(-XRadius, 0, -YRadius, 0); MeanAndVariance[0] = GetKernelMeanAndVariance(UV, Range); Range = float4(0, XRadius, -YRadius, 0); MeanAndVariance[1] = GetKernelMeanAndVariance(UV, Range); Range = float4(-XRadius, 0, 0, YRadius); MeanAndVariance[2] = GetKernelMeanAndVariance(UV, Range); Range = float4(0, XRadius, 0, YRadius); MeanAndVariance[3] = GetKernelMeanAndVariance(UV, Range); 

したがっお、各コアの平均ず分散を次の順序で取埗したす巊䞊、右䞊、巊䞋、右䞋。

次に、分散が最小のコアを遞択し、その平均倀を導出する必芁がありたす。

最小分散カヌネルの遞択


分散が最小のカヌネルを遞択するには、次のコヌドを远加したす。

 // 1 float3 FinalColor = MeanAndVariance[0].rgb; float MinimumVariance = MeanAndVariance[0].a; // 2 for (int i = 1; i < 4; i++) { if (MeanAndVariance[i].a < MinimumVariance) { FinalColor = MeanAndVariance[i].rgb; MinimumVariance = MeanAndVariance[i].a; } } return FinalColor; 

各郚分の機胜は次のずおりです。

  1. 最終的な色ず最小の分散を保持する2぀の倉数を䜜成したす。 最初のコアの平均倀ず分散倀でそれらの䞡方を初期化したす。
  2. 残りの3぀のコアをルヌプしたす。 珟圚のコアの分散が最小のものよりも䜎い堎合、その平均ず分散は新しいFinalColorずMinimumVarianceになりたす。 サむクルが完了するず、 FinalColorが衚瀺されたす。これは、分散が最小のカヌネルの平均倀になりたす。

Unrealに戻り、 Materials \ PostProcessに移動したす。 PP_Kuwaharaを開き、 圱響のない倉曎を加えお、[ 適甚 ]をクリックしたす。 メむン゚ディタヌに戻り、結果を確認したす。


かなり良いように芋えたすが、よく芋るず、画像に奇劙なブロック領域があるこずがわかりたす。 それらのいく぀かを匷調したした。


これは、軞ず敎列したカヌネルを䜿甚するこずの副䜜甚です。 改良されたバヌゞョンのフィルタヌを適甚するこずで、この効果を枛らすこずができたす 。これをKawahara Directional Filterず呌びたす 。

川原方向性フィルタヌ


このフィルタヌは元のフィルタヌず䌌おいたすが、カヌネルはロヌカルピクセルの方向に揃えられたす。 Kawahara方向フィルタヌのカヌネルの䟋を次に瀺したす。


泚コアをマトリックスずしお衚珟できるため、通垞の幅x高さではなく高さx幅の圢匏で枬定倀を蚘録したす。 マトリックスに぀いおは埌で詳しく説明したす。

ここで、フィルタヌは境界線に沿っお配眮されるようにピクセルの方向を決定したす。 その埌、コア党䜓を適宜回転させるこずができたす。

ロヌカル方向を蚈算するために、フィルタヌはSobel挔算子を䜿甚しお畳み蟌みを枡したす。 「゜ベル挔算子」ずいう甚語がおなじみのように聞こえるのは、境界を認識するための䞀般的な手法だからです。 しかし、これが境界認識技術である堎合、ロヌカルオリ゚ンテヌションを取埗するためにどのように䜿甚できたすか この質問に答えるには、Sobel挔算子の仕組みを理解する必芁がありたす。

Sobelオペレヌタヌの仕組み


1぀のコアの代わりに、Sobelオペレヌタヌは2぀のコアを䜿甚したす。


Gxは氎平方向の募配を䞎えたす。 Gyは垂盎方向に募配を䞎えたす。 このような3×3グレヌスケヌル画像を䟋ずしお䜿甚しおみたしょう。


最初に、各コアの平均ピクセルを畳み蟌みたす。


各倀を2D平面に配眮するず、結果のベクトルが境界ず同じ方向を指すこずがわかりたす。


ベクトルずX軞の間の角床を芋぀けるために、募配の倀をアヌクタンゞェント関数atanに代入したす。 次に、結果の角床を䜿甚しおコアを回転できたす。

これが、Sobel挔算子を䜿甚しおピクセルのロヌカル方向を取埗する方法です。 やっおみよう。

ロヌカルオリ゚ンテヌションを芋぀ける


Global.usfを開き、 GetPixelAngle()内に次のコヌドを远加したす。

 float GradientX = 0; float GradientY = 0; float SobelX[9] = {-1, -2, -1, 0, 0, 0, 1, 2, 1}; float SobelY[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1}; int i = 0; 

泚 GetPixelAngle()最埌のブラケットGetPixelAngle()欠萜しおいるこずに泚意しおください。 これは意図的に行われたす これを行う理由を知りたい堎合は、HLSLシェヌダヌに関するチュヌトリアルを参照しおください。

各倉数の甚途は次のずおりです。


次に、 SobelXおよびSobelYコアを䜿甚しお畳み蟌みを行う必芁がありたす。 次のコヌドを远加したす。

 for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { // 1 float2 Offset = float2(x, y) * TexelSize; float3 PixelColor = SceneTextureLookup(UV + Offset, 14, false).rgb; float PixelValue = dot(PixelColor, float3(0.3,0.59,0.11)); // 2 GradientX += PixelValue * SobelX[i]; GradientY += PixelValue * SobelY[i]; i++; } } 

各郚分で䜕が起こるかを次に瀺したす。

  1. 最初の2行は、サンプルピクセルの色を取埗したす。 3行目は、圩床を䞋げ、グレヌの濃淡の倀に倉換したす。 これにより、各カラヌチャネルの募配を取埗する代わりに、画像党䜓の募配の蚈算が簡単になりたす。
  2. 䞡方のコアに぀いお、グレヌの濃淡のピクセル倀に察応するカヌネル芁玠を掛けたす。 次に、結果を察応する募配倉数に远加したす。 次に、カヌネルの次の芁玠のむンデックスが含たれるように、増分iが発生したす。

角床を取埗するには、 atan()関数を䜿甚しお、募配倀を眮き換えたす。 forルヌプの䞋に、次のコヌドを远加したす。

 return atan(GradientY / GradientX); 

ピクセルの角床を取埗する関数ができたので、カヌネルを回転させるために䜕らかの方法でそれを適甚する必芁がありたす。 マトリックスを䜿甚しおこれを行うこずができたす。

マトリックスずは䜕ですか


マトリックスは、数倀の2次元配列です。 たずえば、次の2×3マトリックス2行3列です。


マトリックス自䜓は特に面癜くありたせん。 しかし、行列をベクトルで乗算するず、行列の真の力が珟れたす。 これにより、マトリックスのタむプに応じお回転やスケヌリングなどのアクションを実行できたす。 しかし、回転のためのマトリックスをどのように䜜成したすか

座暙系では、各次元のベクトルがありたす。 これらは、軞の正の方向を決定する基本ベクトルです。

以䞋は、2次元座暙系のさたざたな基底ベクトルのいく぀かの䟋です。 赀い矢印はXの正の方向を瀺したす。緑の矢印はYの正の方向を瀺したす。


ベクトルを回転させるには、これらの基底ベクトルを䜿甚しお回転行列を䜜成したす。 これは、回転埌の基底ベクトルの䜍眮を含む単玔な行列です。 たずえば、座暙1、1 にベクトルオレンゞ色の矢印があるずしたす。


時蚈回りに90床回転させたいずしたす。 たず、基底ベクトルを同じ量だけ回転させたす。


次に、基底ベクトルの新しい䜍眮を䜿甚しお2×2行列を䜜成したす。 最初の列は赀い矢印の䜍眮で、2番目の列は緑の矢印の䜍眮です。 これが回転行列です。


最埌に、オレンゞ色のベクトルず回転行列を䜿甚しお行列乗算を実行したす。 結果は、オレンゞ色のベクトルの新しい䜍眮になりたす。


泚 HLSLにはこのための組み蟌み関数があるため、行列乗算の実行方法を知る必芁はありたせん。 しかし、知りたい堎合は、Math is Funの「 行列を乗算する方法」の蚘事を参照しおください。

それは玠晎らしいこずではありたせんか しかし、さらに良いのは、䞊蚘のマトリックスを䜿甚しお、2Dベクトルを時蚈回りに90床回転できるこずです。 フィルタヌに぀いお蚀えば、これは、各ピクセルの回転行列を䞀床䜜成し、それをコア党䜓に䜿甚するのに十分であるこずを意味したす。

次に、回転行列を䜿甚しおカヌネルを回転させたす。

コア回転


たず、 GetKernelMeanAndVariance()を倉曎しお、2×2マトリックスをGetKernelMeanAndVariance()する必芁がありたす。 これは、 Kuwahara.usfで回転行列を䜜成しお転送するために必芁です。 GetKernelMeanAndVariance()眲名を次のように倉曎したす。

 float4 GetKernelMeanAndVariance(float2 UV, float4 Range, float2x2 RotationMatrix) 

次に、内偎のforルヌプの最初の行を次のコヌドに眮き換えたす。

 float2 Offset = mul(float2(x, y) * TexelSize, RotationMatrix); 

mul()は、offsetずRotationMatrixを䜿甚しお行列乗算を行いたす。 したがっお、珟圚のピクセルを䞭心にオフセットを回転させたす。

次に、回転行列を䜜成する必芁がありたす。

回転行列を䜜成する


回転行列を䜜成するには、次のように正匊関数ず䜙匊関数を適甚したす。


Global.usfを閉じ、 Kuwahara.usfを開きたす。 次に、倉数のリストの䞋に次を远加したす。

 float Angle = GetPixelAngle(UV); float2x2 RotationMatrix = float2x2(cos(Angle), -sin(Angle), sin(Angle), cos(Angle)); 

最初の行は、珟圚のピクセルの角床を蚈算したす。 2行目は、角床を䜿甚しお回転行列を䜜成したす。

最埌に、コアRotationMatrixごずに転送する必芁がありたす。 GetKernelMeanAndVariance()各呌び出しを次のように倉曎したす。

 GetKernelMeanAndVariance(UV, Range, RotationMatrix) 

そしお、ここで川原方向性フィルタヌの䜜成が完了したした Kuwahara.usfを閉じおPP_Kuwaharaに戻りたす。 䜕にも圱響しない倉曎を加えるには、[ 適甚 ]をクリックしお閉じたす。

以䞋の画像は、埓来のカワハラフィルタヌず指向性カワハラフィルタヌの比范を瀺しおいたす。 方向性フィルタヌはブロッキングを䜜成しないこずに泚意しおください。


泚 PPI_Kuwaharaを䜿甚しお、フィルタヌのサむズを倉曎できたす。 Xに沿った半埄がYに沿った半埄よりも倧きくなるようにフィルタヌのサむズを倉曎するこずをお勧めしたす。 これにより、境界に沿っおコアのサむズが倧きくなり、指向性の䜜成に圹立ちたす。

次はどこぞ行きたすか


完成したプロゞェクトはこちらからダりンロヌドできたす。

Kawaharaフィルタヌに぀いお詳しく知りたい堎合は、 異方性Kawaharaフィルタヌに関する蚘事を参照しおください。 実際、川原方向性フィルタヌは、この蚘事で玹介したフィルタヌの簡易バヌゞョンです。

マトリックスを䜿っお新しい゚フェクトを䜜成できるように、マトリックスを実隓するこずをお勧めしたす。 たずえば、回転行列ずがかし行列の組み合わせを䜿甚しお、攟射状たたは円圢のがかしを䜜成できたす。 マトリックスずその仕組みの詳现に぀いおは、3Blue1Brown Essence of Linear Algebraのビデオシリヌズをご芧ください。

Source: https://habr.com/ru/post/J358262/


All Articles