落書きシェーダー効果

このチュートリアルでは、シェーダーを使用して人気のあるスプライト落書き効果をUnityで再現する方法について説明します。 あなたのスタイルがこのスタイルを必要とする場合、この記事から追加の画像の束をレンダリングせずにそれを達成する方法を学びます。



過去数年にわたって、このスタイルは次第に人気を集め、 GoNNERBaba is Youなどのゲームで積極的に使用されています









このチュートリアルでは、シェーダーコーディングの基礎から使用する数学まで、必要なすべてをカバーしています。 記事の最後に、完全なUnityパッケージをダウンロードするためのリンクがあります。



Doodle Studio 95の成功は、このチュートリアルを作成するきっかけになりました



はじめに



私のブログでは、逆運動学の数学から大気のレイリー散乱まで、非常に複雑なトピックを調査しています。 私はこのような難しいトピックを幅広い聴衆に理解できるようにするのが本当に好きです。 しかし、彼らに興味を持ち、十分な技術レベルを持っている人の数はそれほど多くありません。 したがって、最も人気のある記事が最も単純であることを驚かないでください。 これは最近のNick Camanのツイートにも当てはまり、彼はUnityでDoodle エフェクトを作成する方法を示しまし





1000件のいいね!と4000件のリツイートの後、シェーダーの作成に関する知識がほとんどない人でも学習できる、よりシンプルなチュートリアルが強く求められていることが明らかになりました。



高度な芸術的制御で2Dスプライトをアニメーション化するプロフェッショナルで効果的な方法を探しているなら、 Doodle Studio 95を強くお勧めします! (以下のGIFを参照)。 ここでは、このツールを使用するいくつかのゲームを見ることができます。









落書き効果の解剖学



Doodleエフェクトを再作成するには、まずそれがどのように機能し、どのテクニックが使用されているかを理解する必要があります。



シェーダー効果。 まず、この効果をできるだけシンプルにし、追加のスクリプトを必要としないようにします。 これは、3Dモデル(フラットモデルも含む)のレンダリング方法をUnityに伝えるシェーダーを使用することで可能になります。 シェーダーコーディングの世界精通していない場合は、私の記事「シェーダーの優しい紹介」をご覧ください



スプライトシェーダー。 Unityには、多くの種類のシェーダーが付属しています。 Unityが提供するツールを使用する場合、ほとんどの場合スプライトを使用します。 その場合は、 Sprite Shader - SpriteRenderer



Unityと互換性のある特別なタイプのシェーダーが必要です。 または、より伝統的なUnlitシェーダーから始めることもできます。



頂点オフセット。 スプライトを手動で描画する場合、他のフレームと完全に一致するフレームはありません。 この効果をシミュレートするために、スプライトを何らかの方法で「ぐらつき」させる必要があります。 シェーダーには、 頂点オフセットを使用してこれを行う非常に効果的な方法があります。 これは、3Dオブジェクトの頂点の位置を変更できる手法です。 それらをランダムに移動すると、目的の効果が得られます。



所要時間。 通常、フリーハンドアニメーションのフレームレートは低くなります。 たとえば、1秒間に5フレームをシミュレートする場合、スプライトの頂点の位置を1秒間に5回変更する必要があります。 ただし、Unityは、はるかに高いリフレッシュレートでゲームを実行する可能性があります。 おそらく、1秒あたり30または60フレームです。 スプライトが毎秒60回変化しないように、アニメーションタイミングコンポーネントで作業する必要があります。



ステップ1:スプライトシェーダーを完成させる



Unityで新しいシェーダーを作成する場合、選択はかなり制限されます。 最初に使用できる最も近いシェーダーはUnlit Shaderですが 、必ずしも目的に最適なわけではありません。



SpriteRenderer



シェーダーがSpriteRenderer



Unityと完全に互換性を持つようにするには、既存のSprite Shaderを補完する必要があります。 残念ながら、Unity自体から直接アクセスすることはできません。



Unityのダウンロードアーカイブページにアクセスし、使用しているUnityのバージョン用のビルドシェーダーパッケージをダウンロードすることでアクセスできます。 これは、Unityビルドに付属するすべてのシェーダーのソースコードを含むzipファイルです。









ダウンロードしたら、それを解凍し、 builtin_shaders-2018.1.6f1\DefaultResourcesExtra



Sprites-Diffuse.shader



Sprites-Diffuse.shader



ファイルを見つけSprites-Diffuse.shader



。 これは、チュートリアルで使用するファイルです。









Sprites-Diffuseは標準のスプライトシェーダーではありません!
新しいスプライトを作成するとき、その標準マテリアルはSprites-Diffuse.shader



ではなくSprites-Diffuse.shader



Sprites-Default.shader



と呼ばれるシェーダーを使用しSprites-Default.shader







2つの違いは、1つ目は照明を使用せず、2つ目はシーンの照明に応答することです。 Unity実装の性質により、拡散バージョンは照明なしのバージョンよりも編集がはるかに簡単です。



このチュートリアルの最後に、ライティングありとライティングなしのDoodleシェーダーをダウンロードするためのリンクがあります。


ステップ2:頂点のオフセット



Sprites-Diffuse.shader



内には、 vert



という関数があります。これは、上で説明した頂点関数です。 その名前は重要ではありません。主なことは、 #pragma



vertex:



セクションで指定されている名前と一致することです。



 #pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing
      
      





つまり、頂点関数は3Dモデルの各頂点に対して呼び出され、2次元の画面空間にどのように重ね合わせるかを決定します。 このチュートリアルでは、オブジェクトの移動方法のみに関心があります。



appdata_full v



パラメーターには、オブジェクト空間内の各頂点の3D位置を含むvertex



フィールドが含まれます 。 値が変わると、頂点が移動します。 つまり、たとえば、以下に示すコードは、オブジェクトをシェーダーとともにX軸に沿って1ユニット転送します。



 void vert (inout appdata_full v, out Input o) { v.vertex = UnityFlipSprite(v.vertex, _Flip); v.vertex.x += 1; #if defined(PIXELSNAP_ON) v.vertex = UnityPixelSnap (v.vertex); #endif UNITY_INITIALIZE_OUTPUT(Input, o); o.color = v.color * _Color * _RendererColor; }
      
      





デフォルトでは、Unityで作成された2DゲームはX軸とY軸でのみ動作するため、 v.vertex.xy



を変更して2次元平面上のスプライトを移動する必要があります。



オブジェクト空間とは何ですか?
appdata_full



構造体のvertex



フィールドには、オブジェクト空間でシェーダーによって処理された現在の頂点の位置が含まれます。 これは、オブジェクトが世界の中心(0,0,0)にあり、スケールを変更せず、回転もしないという仮定の下での頂点の位置です。



ワールド空間で表現されたピークは、Unityシーンでの実際の位置を反映しています。


オブジェクトがフレームごとに1メートルの速度で移動しないのはなぜですか?
C#スクリプトのUpdate



メソッドのtransform.position



値のコンポーネントx



に+1を追加すると、オブジェクトがフレームあたり1メートル、つまり時速約216キロメートルの速度で右に飛ぶ様子がわかります。



これは、C#による変更が位置自体を変更しているためです。 頂点関数では、これは起こりません。 シェーダーはモデルの視覚的表現のみを変更しますが、モデルの保存された頂点を更新または変更しません。 そのため、 v.vertex.x



+1を追加すると、オブジェクトが1メートルだけシフトします。


スプライトをTightとしてインポートすることを忘れないでください!
この効果は、スプライトの上部をシフトします。 従来、スプライトは四角形としてUnityにインポートされます(左の図を参照)。 これは、ピークが4つしかないことを意味します。 この場合、これらのポイントのみを移動できるため、Doodleエフェクトの強度が低下します。



より強力で現実的な歪みを得るには、 メッシュタイプパラメータにタイトを選択してスプライトをインポートする必要があります。これにより、スプライトは凸形状になります(右図を参照)。









これにより、頂点の数が増えます。 これは常に望ましいとは限りませんが、まさに今必要なことです。


ランダムオフセット



落書き効果は、各頂点の位置をランダムにシフトします。 シェーダーで乱数をサンプリングすることは常に困難な作業でした。 これは主に、ほとんどのライブラリ( Mathf.Random



を含む)で使用されているアルゴリズムの再作成の効率を複雑にし、低下させる分散GPUアーキテクチャが原因です。



Nick Camanの投稿では、サンプリングしたときにランダムな錯覚を与えるノイズテクスチャを使用しました。 プロジェクトのコンテキストでは、これは最も効率的なアプローチではない可能性があります。この場合、シェーダーによって実行されるテクスチャ検索操作の数が2倍になるためです。



したがって、ほとんどのシェーダーでは、かなり複雑で混oticとした関数が使用されます。それらの決定論にもかかわらず、規則性はないようです。 また、配布する必要があるため、各乱数は独自のシードを使用して生成する必要があります。 各頂点の位置は一意でなければならないため、これは私たちにとって素晴らしいことです。 これを使用して、各頂点に乱数をバインドできます。 このランダム関数の実装については後で説明します。 random3



呼びましょう。



random3



を使用して、各頂点のランダムオフセットを生成できます。 以下の例では、 _NoiseScale



プロパティを使用して乱数をスケーリングします。これにより、変位力を制御できます。



 void vert (inout appdata_full v, out Input o) { ... float2 noise = random3(v.vertex.xyz).xy * _NoiseScale; v.vertex.xy += noise; ... }
      
      





次に、 random3



コードrandom3



を記述する必要がありrandom3







画像






シェーダーのランダム性



シェーダーで使用される最も一般的で象徴的な擬似ランダム関数の1つは、1998年のW. Rayの記事「タイトル: 乱数の生成、y = [(a + x)sin(bx)] mod 1 」から引用されています



 float rand(float2 co) { return fract(sin(dot(co.xy ,float2(12.9898,78.233))) * 43758.5453); }
      
      





この関数は決定論的です(つまり、 真にランダムではありません)が、完全にランダムに見えるほどランダムに動作します。 このような関数は、 疑似ランダムと呼ばれます。 チュートリアルでは、 Nikita Miropolskyが考案したより複雑な機能を選択しました



シェーダーで擬似乱数を生成することは非常に複雑なトピックです。 彼女についてもっと知りたいなら、 The Book of Shadersには彼女に関する良い章があります。 さらに、 Patricio Gonzales Vivoは 、シェーダーで使用できるGLSL noiseと呼ばれる疑似ランダム関数の大きなリポジトリを構築しました。



ステップ3:時間を追加する



作成したコードのおかげで、各フレームの各ポイントは同じ量だけシフトされます。 したがって、落書き効果ではなく、歪んだスプライトが得られます。 これを修正するには、時間の経過とともに効果を変更する方法を見つける必要があります。 これを行う最も簡単な方法の1つは、頂点の位置と現在の時間を使用して乱数を生成することです。



私たちの場合、頂点の位置に現在の時間を秒_Time.y



で追加しました。



 float time = float3(_Time.y, 0, 0); float2 noise = random3(v.vertex.xyz + time).xy * _NoiseScale; v.vertex.xy += noise;
      
      





より複雑なエフェクトでは、方程式に時間を追加するためのより洗練された方法が必要になる場合があります。 ただし、断続的なランダム効果にのみ関心があるため、2つの値で十分です。









時間の切り替え



_Time.y



追加_Time.y



主な問題は、スプライトがすべてのフレームでアニメーション化されることです。 ほとんどの手描きアニメーションのフレームレートは低いため、これは望ましくありません。 時間成分は連続的ではなく、離散的であるべきです。 これは、1秒間に5つのフレームを表示する場合、1秒間に5回しか変更しないことを意味します。 つまり、時間は5分の1秒に関連付けられている必要があります。 有効な値は  frac05=0 frac15=0.2 frac25=0.4 frac35=0.6 frac45=0.8 frac55=1 と、など...



グリッドのスナップ方法のブログでスナップについて説明しました。 この記事では、空間グリッド上のオブジェクトの位置をバインドする問題の解決策を提案しました。 時間をタイムグリッドにバインドする必要がある場合、数学、つまりコードは同じになります。



以下に示す関数は、数値x



を取得し、それをsnap



倍数である整数値にバインドします。



 inline float snap (float x, float snap) { return snap * round(x / snap); }
      
      





つまり、コードは次のようになります。



 float time = snap(_Time.y, _NoiseSnap); float2 noise = random3(v.vertex.xyz + float3(time, 0.0, 0.0) ).xy * _NoiseScale; v.vertex.xy += noise;
      
      











おわりに



この効果のUnityパッケージは、Patreonから無料ダウンロードできます。



追加のリソース



過去数か月にわたって、Doodleのスタイルのゲームが多数登場しました。 この理由はDoodle Studio 95の成功だったようです! -Fernando Ramalloによって開発されたUnityのツール。 このスタイルがゲームに適している場合は、この素晴らしいツールを購入することをお勧めします。






All Articles