WebGL 2のファーシェーダー

アイデア



新しいWebGL 2標準は最近、FirefoxとChromeの最新バージョンで利用可能になったため、いくつかの新しい機能を試してみたいという要望があります。 WebGL 2(およびそのベースとなるOpenGL ES 3.0)の最も便利で人気のある機能の1つは、 ジオメトリ複製 (英語のインスタンス化されたレンダリング)です。 この機能を使用すると、パラメータを変更して同じジオメトリを繰り返し描画することにより、描画呼び出しの回数を減らすことができます。 この機能はWebGL 1の一部の実装に存在していましたが、特定の拡張が必要でした。 ほとんどの場合、この関数はパーティクルシステムと植生を作成するために使用されますが、毛皮をシミュレートするためにも非常に頻繁に使用されます。









コンセプトとデモ



OpenGLでファーをシミュレートする方法はかなりありますが、この実装はこのビデオチュートリアルで説明されている手法に基づいています。 Unityのシェーダーの作成について説明しているという事実にもかかわらず、このビデオからの詳細で明確なステップバイステップの説明は、OpenGL ESシェーダーをゼロから作成するための基礎として採用されました。 ファーシミュレーションの一般的な原理に慣れていない場合は、このビデオを13分間見て、その動作の一般的な原理を理解することをお勧めします。



デモのグラフィック素材はすべてゼロから作成されました(さまざまな羊毛サンプルの写真を見るだけです)。 これらのテクスチャは非常に単純であり、その作成には現実的なテクスチャを作成するための特別なスキルは必要ありませんでした。



完成したデモはこちらからご覧ください 。 ブラウザがWebGL 2をサポートしていない場合(たとえば、現時点でモバイルブラウザがWebGL 1のみをサポートしている場合)、ビデオデモは次のとおりです。





実装



ファーシミュレーションがどのように機能するかを明確に示すために、十分な厚さ(レイヤー間の距離)でファーの2つの追加レイヤーをレンダリングすることから始めます。 次の画像は、毛皮のない元のオブジェクトとその上に2つの半透明のレイヤーを示しています。









レイヤーの数を増やし、レイヤーの厚さを減らすと、徐々に現実的な結果が得られます。 この6つの比較的密なレイヤーの画像では、レイヤーの透明度が完全に不透明から完全に透明に徐々に低下していることがすでにはっきりと見えています。









そして、20の非常に薄いレイヤーを使用したかなり現実的な最終結果:









デモでは、5つの異なる定義済みパラメーター(4つの毛皮と1つの苔)を使用します。 それらはすべて、同じシェーダーでレンダリングされますが、指定されたパラメーターは異なります。



最初に、ファーのすべてのレイヤーに使用されるのと同じ拡散テクスチャを持つキューブがレンダリングされます。 ただし、ファーの最初のレイヤーと混ざるように暗くする必要があります。これにより、テクスチャの色がファーの初期色の色で乗算されます。 テクスチャから色を取得し、別の色で乗算する最も単純なシェーダーを使用します。



次に、ファーレイヤーのレンダリングが開始されます。 それらは透明であるため、現実的に見えるように正しい色混合モードを選択する必要があります。 通常のglBlendFunc()を使用すると、このモードはアルファチャンネルに影響し、色を歪めるため、ファーの色が明るすぎるか、またはくすみすぎます。 glBlendFuncSeparate()関数を使用すると、フラグメントのRGBチャンネルとアルファチャンネルに異なる色混合モードを設定し、それを使用して、アルファチャンネルを変更せずに維持し(シェーダーによって完全に制御されます)、同時に各ファーレイヤーの色をそれ自体および他のジオメトリと正しく混合できます。



デモでは、次の色混合モードが使用されます。



gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE);
      
      





混合モードを選択する試みの例:









色を混合するための正しいモードを設定した後、ファーの実際のレンダリングに進むことができます。 ファー全体のレンダリングは1回の呼び出しで実装されます-ジオメトリを複製するすべての作業は1つのシェーダーで実行されます。 ドライバなしのビデオカードでは、ジオメトリレンダリングを指定した回数繰り返す必要があるため、OpenGLコマンドへの追加呼び出しの費用はかかりません。 以降の説明はすべて、このシェーダーにのみ適用されます。 WebGL 2およびOpenGL ES 3.0で使用されるGLSL 3.0構文は、GLSL 1.0とは若干異なります 。古いシェーダーの移植に関する違いと手順については、 こちらをご覧ください



ファーレイヤーを作成するために、シェーダーは各頂点を法線方向にシフトします。 これにより、必要に応じてモデルの法線を傾けることができるため、ヘアスタイリングの方向を調整する際にある程度の柔軟性が得られます(デモでは、法線はメインジオメトリに垂直です)。 組み込み変数gl_InstanceIDから、シェーダーは現在のジオメトリインスタンスの値を取得します。 この値が大きいほど、頂点がより遠くに移動します。



 float f = float(gl_InstanceID + 1) * layerThickness; // calculate final layer offset distance vec4 vertex = rm_Vertex + vec4(rm_Normal, 0.0) * vec4(f, f, f, 0.0); // move vertex in direction of normal
      
      





ファーがリアルに見えるようにするには、ベースでより濃く、先端で徐々にテーパーする必要があります。 この効果は、レイヤーの透明度を徐々に変更することで実現されます。 また、アンビエントオクルージョンをシミュレートするには、ファーはベースでより暗く、サーフェスでより明るくなければなりません。 ファーの一般的なパラメーターは、初期色[0.0、0.0、0.0、1.0]と最終色[1.0、1.0、1.0、0.0]です。 したがって、ファーは完全に黒で始まり、拡散テクスチャの色で終わりますが、透明度は完全に不透明なレイヤーから完全に透明なレイヤーに増加します。



まず、色係数が頂点シェーダーで計算され、開始と終了の間の色がこの係数に基づいて補間されます。 フラグメントシェーダーでは、この色に拡散テクスチャの色が掛けられます。 最後のステップは、フラグメントのアルファチャンネルに白黒テクスチャの色を掛けることです。これにより、コートの分布が決まります。



 // vertex shader float layerCoeff = float(gl_InstanceID) / layersCount; vAO = mix(colorStart, colorEnd, layerCoeff); // fragment shader vec4 diffuseColor = texture(diffuseMap, vTexCoord0); // get diffuse color float alphaColor = texture(alphaMap, vTexCoord0).r; // get alpha from alpha map fragColor = diffuseColor * vAO; // simulate AO fragColor.a *= alphaColor; // apply alpha mask
      
      





風に揺れる毛皮を販売するための多くの異なるオプションがあります。 デモでは、シェーダーに渡される周期的に変更されたパラメーターに基づいて、各頂点をわずかにシフトします。 すべてのレイヤーを同期的に移動するには、同じ座標を持つ各頂点の一意の値を計算する必要があります。 この場合、組み込み変数gl_VertexIDを使用することはできません。これは、座標が同じ頂点であっても、頂点ごとに値が異なるためです。 そこで、頂点の座標から「マジックサム」を計算し、それを正弦関数で使用して、風の「波」を作成します。 時間パラメーターの値に基づく頂点の変位の例:



 const float PI2 = 6.2831852; // Pi * 2 for sine wave calculation const float RANDOM_COEFF_1 = 0.1376; // just some random float float timePi2 = time * PI2; vertex.x += sin(timePi2 + ((rm_Vertex.x+rm_Vertex.y+rm_Vertex.z) * RANDOM_COEFF_1)) * waveScaleFinal; vertex.y += cos(timePi2 + ((rm_Vertex.x-rm_Vertex.y+rm_Vertex.z) * RANDOM_COEFF_2)) * waveScaleFinal; vertex.z += sin(timePi2 + ((rm_Vertex.x+rm_Vertex.y-rm_Vertex.z) * RANDOM_COEFF_3)) * waveScaleFinal;
      
      





さらなる改善



かなり現実的な結果にもかかわらず、このファーの実装は大幅に改善できます。 たとえば、風の力と方向の適用を実装し、モデルのさまざまな領域にさまざまな長さのウールを配置して、各頂点の長さ係数を設定することができます。



Githubからコードを取得し、プロジェクトで使用および改善できます-MITライセンスを使用します。



All Articles