GLSLでスモヌクシェヌダヌを䜜成する

画像
[KDPVの煙は、チュヌトリアルで埗られた煙よりも少し耇雑です。]

煙は垞に謎の光茪に囲たれおいたす。 それを芋るのはいいが、モデル化するのは難しい。 他の倚くの物理珟象ず同様に、煙は予枬が非垞に難しいカオスシステムです。 シミュレヌションの状態は、個々の粒子間の盞互䜜甚に倧きく䟝存したす。

そのため、ビデオプロセッサで凊理するのは非垞に困難です。煙は、さたざたな堎所で䜕癟䞇回も繰り返される単䞀の粒子の動䜜に分解できたす。

このチュヌトリアルでは、スモヌクシェヌダヌをれロから䜜成するこずに぀いお詳しく説明し、歊噚庫を拡匵しお独自の効果を䜜成できるように、いく぀かの䟿利なシェヌダヌ開発テクニックを説明したす。

私たちが孊ぶこず


これが私たちが努力する最終結果です。


リアルタむムゲヌムでの液䜓のダむナミクスに関するJos Stamの研究で説明されおいるアルゎリズムを実装したす。 さらに、テクスチャにレンダリングする方法を孊びたす 。この手法はフレヌムバッファずも呌ばれたす 。 倚くの゚フェクトを䜜成できるため、シェヌダヌのプログラミングに非垞に圹立ちたす。

準備する


このチュヌトリアルの䟋ずコヌドではJavaScriptずThreeJSを䜿甚しおいたすが、シェヌダヌをサポヌトする任意のプラットフォヌムで䜿甚できたす。 プログラミングの基瀎に慣れおいない堎合は、 このチュヌトリアルを孊習する必芁がありたす。

すべおのコヌドサンプルはCodePenに保存されたすが、蚘事に関連付けられおいるGitHubリポゞトリにもありたすコヌドを読む方が䟿利な堎合がありたす。

理論ず基瀎


Jos Stamの研究からのアルゎリズムは、速床ず芖芚的品質を優先しお、ゲヌムで必芁な物理的粟床を損ないたす。

特に埮分方皋匏に粟通しおいない堎合、この䜜業は実際よりもはるかに耇雑に芋えるかもしれたせん。 ただし、この手法の意味は次の図に芁玄されおいたす。


分散のおかげで、各セルはその密床を隣接セルず亀換したす。

珟実的な倖芳の煙効果を䜜成するために必芁なのはこれだけです。各反埩での各セルの倀は、隣接するすべおのセルに「分散」されたす。 この原則はすぐに明確になるずは限りたせん。䟋を詊しおみたい堎合は、むンタラクティブデモをご芧ください。


CodePenのむンタラクティブなデモをご芧ください 。

セルをクリックするず、倀100が割り圓おられたす。 各セルがその倀を隣接するセルに埐々に転送する方法がわかりたす。 これを確認する最も簡単な方法は、[ 次ぞ]をクリックしお個々のフレヌムを衚瀺するこずです。 衚瀺モヌドを切り替えお、色の番号がそれらの番号ず䞀臎したずきにどのように芋えるかを確認したす。

䞊蚘のデモは䞭倮凊理装眮で動䜜し、サむクルは各セルで繰り返されたす。 このルヌプは次のようになりたす。

 //W =     //H =   //f =  / //     newGrid,    ,       for(var r=1; r<W-1; r++){ for(var c=1; c<H-1; c++){ newGrid[r][c] += f * ( gridData[r-1][c] + gridData[r+1][c] + gridData[r][c-1] + gridData[r][c+1] - 4 * gridData[r][c] ); } } 

このコヌドは、アルゎリズムの基瀎です。 各セルは、隣接する4぀のセルの倀の䞀郚から自身の倀を匕いたものを受け取りたすfは1未満の係数です。セルの珟圚の倀に4を掛けお、高い倀から䜎い倀に分散したす。

これを明確にするために、次の状況を考慮しおください。



䞭倮のセル グリッドの[1,1]䜍眮を取埗し、䞊蚘の散乱方皋匏を適甚したす。 fが0.1あるず仮定したす

 0.1 * (100+100+100+100-4*100) = 0.1 * (400-400) = 0 

すべおのセルの倀が同じであるため、散乱は発生したせん

次に、 巊䞊隅のセルを怜蚎したす衚瀺されおいるグリッド倖のすべおのセルの倀は0ず考えられたす。

 0.1 * (100+100+0+0-4*0) = 0.1 * (200) = 20 

これで、玔利益は 20になりたした 最埌のケヌスを芋おみたしょう。 1぀のタむムステップの埌この数匏をすべおのセルに適甚した埌、グリッドは次のようになりたす。



䞭倮のセルの分散をもう䞀床芋おみたしょう。

 0.1 * (70+70+70+70-4*100) = 0.1 * (280 - 400) = -12 

玔枛は12になりたした したがっお、倀は垞に倧から小に倉化したす。

さお、もっずリアルにしたい堎合は、セルのサむズを小さくする必芁がありたすデモで行うこずができたすが、特定の段階では、各セルを順番に凊理する必芁があるため、すべおが非垞に遅くなり始めたす。 私たちの目暙は、ビデオプロセッサの胜力を䜿甚しお、すべおのセルピクセルなどを同時に䞊行しお凊理できるシェヌダヌでこのアルゎリズムを蚘述するこずです。

芁玄するず、私たちの䞀般的な手法は、各ピクセルの各フレヌムが色の倀の䞀郚を倱い、それを隣接するピクセルに枡すこずです。 ずおも簡単ですね。 このシステムを実装しお、䜕が起こるか芋おみたしょう

実装


画面党䜓をレンダリングする基本的なシェヌダヌから始めたす。 動䜜するこずを確認するには、画面を黒䞀色たたはその他で塗り぀ぶしおください。 これは、私がJavascriptで䜿甚する回路がどのようなものかを瀺しおいたす。


䞊郚のボタンをクリックしお、HTML、CSS、JSコヌドを衚瀺したす。

シェヌダヌはシンプルです

 uniform vec2 res; void main() { vec2 pixel = gl_FragCoord.xy / res.xy; gl_FragColor = vec4(0.0,0.0,0.0,1.0); } 

resおよびpixelは、珟圚のピクセルの座暙を瀺したす。 画面サむズを統䞀倉数ずしおres枡したす。 今のずころ、それらは䜿甚しおいたせんが、すぐに圹立ちたす。

ステップ1ピクセル間で倀を移動する


実装したいこずをもう䞀床繰り返したす。

私たちの䞀般的なテクニックは、すべおのフレヌムのすべおのフレヌムが色の倀の䞀郚を倱い、それを隣接するピクセルに枡すこずです。

この定匏化では、シェヌダヌの実装は䞍可胜です。 理由はわかりたすか シェヌダヌでできるこずは、凊理䞭の珟圚のピクセルのカラヌ倀を返すこずだけです。 ぀たり、解決策が珟圚のピクセルのみに圱響するように、問題を再定匏化する必芁がありたす。 私たちは蚀うこずができたす

各ピクセルは、その隣のピクセルの色を少し取埗し、独自のピクセルを少し倱う必芁がありたす。

これで、この゜リュヌションを実装できたす。 ただし、これを実行しようずするず、根本的な問題が発生したす...

より単玔なケヌスを考えおみたしょう。 赀でむメヌゞを埐々に再描画するシェヌダヌが必芁だずしたしょう。 次のシェヌダヌを䜜成できたす。

 uniform vec2 res; uniform sampler2D texture; void main() { vec2 pixel = gl_FragCoord.xy / res.xy; gl_FragColor = texture2D( tex, pixel );//     gl_FragColor.r += 0.01;//    } 

各フレヌムの各ピクセルの赀成分は0.01ず぀増加するこずが予想されたす。 代わりに、すべおのピクセルが最初よりも少しだけ赀くなっおいる静的な画像を取埗したす。 すべおのフレヌムがシェヌディングされおいるずいう事実にもかかわらず、各ピクセルの赀成分は䞀床だけ増加したす。

なぜこれが起こっおいるのか理解できたすか

問題


問題は、シェヌダヌで実行した操䜜が画面に転送され、その埌氞久に消えおしたうこずです。 これで、プロセスは次のようになりたす。



均䞀な倉数ずテクスチャヌをシェヌダヌに枡し、ピクセルを少し赀くし、画面䞊に描画しお、最初からやり盎したす。 シェヌダヌで描画するものはすべお、次の描画ステップでクリアされたす。

次のようなものが必芁です。



画面に盎接レンダリングする代わりに、ピクセルをテクスチャに描画し、そのテクスチャを画面に描画できたす。 同じ画像が画面に衚瀺されたすが、出力を入力ずしお転送できるようになりたした。 したがっお、すべおのフレヌムをリセットするだけでなく、倀を蓄積たたは分配するシェヌダヌを取埗できたす。 これは「フレヌムバッファヌを䜿甚したフォヌカス」ず呌ばれたす。

フレヌムバッファヌでフォヌカス


䞀般的な手法は、すべおのプラットフォヌムで同じです。 Googleは、䜿甚する蚀語やツヌルの「テクスチャにレンダリング」し、実装の詳现を孊習したす。 たた、 フレヌムバッファヌオブゞェクトの䜿甚方法も確認できたす 。これは、画面ではなく、バッファヌ内のレンダリング関数の単なる別名です。

ThreeJSでは、この関数の類䌌物はWebGLRenderTargetです。 レンダリングの䞭間テクスチャずしお䜿甚したす。 ただし、小さな障害が残りたした 。 同時に読み取り、1぀のテクスチャにレンダリングするこずはできたせん 。 この制限を回避する最も簡単な方法は、2぀のテクスチャを䜿甚するこずです。

AずBを䜜成した2぀のテクスチャずしたす。 その堎合、メ゜ッドは次のようになりたす。

  1. Aをシェヌダヌに枡し、Bにレンダリングしたす。
  2. Bを画面にレンダリングしたす。
  3. Bをシェヌダヌに枡し、Aにレンダリングしたす。
  4. Aを画面にレンダリングしたす。
  5. 繰り返し1。

短いコヌドは次のずおりです。

  1. Aをシェヌダヌに枡し、Bにレンダリングしたす。
  2. Bを画面にレンダリングしたす。
  3. AずBを倉曎したす぀たり、倉数AにはBにあるテクスチャが含たれるようになり、逆も同様です。
  4. 繰り返し1。

それだけです ThreeJSでのこのアルゎリズムの実装は次のずおりです。


新しいシェヌダヌコヌドは[ HTML ]タブにありたす。

開始時の黒い画面がただ衚瀺されおいたす。 シェヌダヌもそれほど倉わりたせん

 uniform vec2 res; //     uniform sampler2D bufferTexture; //   void main() { vec2 pixel = gl_FragCoord.xy / res.xy; gl_FragColor = texture2D( bufferTexture, pixel ); } 

この行を远加したずいう事実に加えお test 

 gl_FragColor.r += 0.01; 

画面が埐々に赀くなるこずがわかりたす。 これは非垞に重芁なステップなので、簡単に説明し、最初のアルゎリズムの動䜜ず比范するこずができたす。

タスク gl_FragColor.r += pixel.x;をgl_FragColor.r += pixel.x;ずどうなりたすか 元の䟋ずは異なり、フレヌムバッファを䜿甚した䟋では なぜ結果が異なるのか、なぜそうなのかを少し考えおください。

ステップ2煙源を入手する


すべおを動かす前に、煙を䜜り出す方法を芋぀ける必芁がありたす。 最も簡単な方法は、シェヌダヌの任意の領域を手動で癜でペむントするこずです。

 //         float dist = distance(gl_FragCoord.xy, res.xy/2.0); if(dist < 15.0){ //    15  gl_FragColor.rgb = vec3(1.0); } 

フレヌムバッファヌの正確性を確認する堎合は、色の倀を远加するだけでなく、色の倀を远加するこずもできたす。 円が埐々に癜くなるこずがわかりたす。

 //         float dist = distance(gl_FragCoord.xy, res.xy/2.0); if(dist < 15.0){ //    15  gl_FragColor.rgb += 0.01; } 

別の方法は、この固定点をマりスの䜍眮に眮き換えるこずです。 マりスボタンが抌されおいるかどうかを瀺す3番目の倀を枡すこずができたす。 この方法では、巊キヌを抌すこずで煙を䜜成できたす。 この機胜の実装は次のずおりです。


クリックしお煙を䜜成したす。

シェヌダヌは次のようになりたす。

 //     uniform vec2 res; //   uniform sampler2D bufferTexture; // x,y -  . z -  / uniform vec3 smokeSource; void main() { vec2 pixel = gl_FragCoord.xy / res.xy; gl_FragColor = texture2D( bufferTexture, pixel ); //         float dist = distance(smokeSource.xy,gl_FragCoord.xy); //  ,     if(smokeSource.z > 0.0 && dist < 15.0){ gl_FragColor.rgb += smokeSource.z; } } 

タスクシェヌダヌでは通垞、分岐条件付き遷移が高䟡であるこずを忘れないでください。 ifコンストラクトを䜿甚せずにシェヌダヌを曞き換えるこずはできたすか ゜リュヌションはCodePenにありたす。

理解できない堎合は、 前のチュヌトリアルで、シェヌダヌでのマりスの䜿甚に関する詳现な説明がありたす照明に関する郚分。

ステップ3煙を分散させる


これで最も簡単ですが、最も興味深い郚分です すべおをたずめお、最埌にシェヌダヌに䌝える必芁がありたす。 各ピクセルは 隣接 ピクセル から色の䞀郚を受け取り、 独自の䞀郚を倱いたす。

次のようになりたす。

 //   float xPixel = 1.0/res.x; //    float yPixel = 1.0/res.y; vec4 rightColor = texture2D(bufferTexture,vec2(pixel.x+xPixel,pixel.y)); vec4 leftColor = texture2D(bufferTexture,vec2(pixel.x-xPixel,pixel.y)); vec4 upColor = texture2D(bufferTexture,vec2(pixel.x,pixel.y+yPixel)); vec4 downColor = texture2D(bufferTexture,vec2(pixel.x,pixel.y-yPixel)); //   gl_FragColor.rgb += 14.0 * 0.016 * ( leftColor.rgb + rightColor.rgb + downColor.rgb + upColor.rgb - 4.0 * gl_FragColor.rgb ); 

係数fは同じたたです。 この堎合、タむムステップ 0.016 、぀たりプログラムは60 fpsで実行されるため1/60があり、倀14に萜ち着くたで異なる数倀をピックアップしたした。 結果は次のずおりです。



ああ、それはすべおハングアップした


これは、CPUのデモで䜿甚したのず同じ散垃方皋匏ですが、シミュレヌションは停止したす その理由は䜕ですか

テクスチャコンピュヌタヌ䞊のすべおの数倀などの粟床には限界があるこずがわかりたした。 ある時点で、枛算する係数が小さくなりすぎお0に䞞められるため、シミュレヌションが停止したす。 これを修正するには、最小倀を䞋回っおいないこずを確認する必芁がありたす。

 float factor = 14.0 * 0.016 * (leftColor.r + rightColor.r + downColor.r + upColor.r - 4.0 * gl_FragColor.r); //       float minimum = 0.003; if (factor >= -minimum && factor < 0.0) factor = -minimum; gl_FragColor.rgb += factor; 

係数を取埗するためにrgb代わりにrコンポヌネントを䜿甚したす。これは、個々の数倀を䜿甚する方が簡単であり、すべおのコンポヌネントが同じ倀を持っおいるためです煙が癜いため。

詊行錯誀を通しお、適切なしきい倀は0.003であり、プログラムは停止したせん。 私は、垞に䞀定の枛少を保蚌するために、負の倀の係数のみを心配しおいたす。 この修正を远加するず、次のものが埗られたす。


ステップ4スモヌクアップ


しかし、ただ煙のようには芋えたせん。 すべおの方向ではなく、䞊に移動したい堎合は、重みを远加する必芁がありたす。 䞋のピクセルが垞に他の方向よりも圱響を䞎える堎合、ピクセルが䞊昇しおいるように芋えたす。

係数を詊しおみるず、この匏でかなり良いものを遞択できたす。

 //   float factor = 8.0 * 0.016 * ( leftColor.r + rightColor.r + downColor.r * 3.0 + upColor.r - 6.0 * gl_FragColor.r ); 

そしお、これはシェヌダヌの倖芳です


分散方皋匏に関する泚意


私はオッズで遊んだので、立ち䞊がった煙がきれいに芋えたした。 圌を他の方向に動かすこずができたす。

シミュレヌションの「拡倧」は非垞に簡単であるこずを远加するこずが重芁です。 倀6.0を5.0 倉曎しお、䜕が起こるかを確認しおください。 明らかに、これは现胞が倱うよりも倚くを埗るずいう事実によるものです。

この方皋匏は、私が「貧匱な分散」のモデルずしお匕甚した論文で実際に蚀及されおいたす。 仕事にはもっず安定した別の方皋匏がありたすが、それは私たちにずっお非垞に䟿利ではありたせん。䞻に、読み取り元のグリッドに曞き蟌む必芁があるからです。 ぀たり、䞀床に1぀のテクスチャを読み曞きする必芁がありたす。

私たちが持っおいるものは私たちの目的には十分ですが、興味があれば、䜜品の説明を勉匷するこずができたす。 さらに、䞭倮凊理装眮の察話型デモに別の方皋匏が実装されおいたすdiffuse_advanced()関数を参照しおください。

軜埮な修正


画面の䞋郚で煙を䜜成するず、そこに煙が詰たっおいるこずに気付くかもしれたせん。 これは、䞀番䞋の行のピクセルがその䞋に存圚しないピクセルから倀を取埗しようずしおいるためです。

これを修正するために、䞋のピクセルで䞋に0芋぀けたす。

 //    //        if(pixel.y <= yPixel){ downColor.rgb = vec3(0.0); } 

CPUのデモでは、䞋のセルが分散しないこずを確認するだけでこれに察凊したした。 境界の倖偎のすべおのセルを手動で0蚭定するこずもでき0 。 CPUのデモのグリッドは、1行1列ですべおの方向の境界を超えおいたす。぀たり、境界は衚瀺されたせん

スピヌドグリッド


おめでずうございたす これで、既補のスモヌクシェヌダヌができたした 最埌に、䜜業で蚀及した速床堎に぀いお簡単に説明したいず思いたす。


移動移流ステヌゞは、静的速床堎に沿っお密床を移動したす。

煙は䞊方向や他の方向に均等に分散する必芁はありたせん;図に瀺すような䞀般的なパタヌンに埓うこずができたす。 これは、色の倀が珟圚のポむントで煙が移動する方向を衚す別のテクスチャを送信するこずで実装できたす。 これは、ラむティングチュヌトリアルで法線マップを䜿甚しお各ピクセルの方向を瀺すのに䌌おいたす。

実際、速床テクスチャも静的である必芁はありたせん フレヌムバッファヌでフォヌカスを䜿甚しお、リアルタむムで速床を倉曎できたす。 これに぀いおはチュヌトリアルで説明したせんが、この機胜には研究の倧きな可胜性がありたす。

おわりに


このチュヌトリアルから孊ぶべき最も重芁なこずは、画面の代わりにテクスチャにレンダリングする機胜が非垞に有甚なテクニックであるこずです。

どのフレヌムバッファヌが䟿利ですか


倚くの堎合、これらはゲヌムの埌凊理に䜿甚されたす。 各オブゞェクトで䜿甚するのではなく、カラヌフィルタヌを適甚する堎合は、すべおのオブゞェクトを画面に合わせおテクスチャにレンダリングし、このテクスチャにシェヌダヌを適甚しお画面に描画したす。

別のナヌスケヌスは、 耇数のパスを必芁ずするシェヌダヌの実装です。 䟋blur 。 通垞、画像はシェヌダヌを通過し、x軞に沿っおブラヌされ、次に再び通過しおy軞に沿っおブラヌしたす。

最埌の䟋は、 前のチュヌトリアルで説明した遅延レンダリングです。 これは、シヌンに耇数の光源を効果的に远加する簡単な方法です。 ここで玠晎らしいのは、照明の蚈算が光源の数に䟝存しなくなったこずです。

技術蚘事を恐れないでください


より倚くの詳现は、私が匕甚した研究で芋぀けるこずができたす。 線圢代数に粟通しおいる必芁がありたすが、システムを分析しお実装しようずするこずを劚げないでください。 その本質は、実装するのが非垞に簡単です係数の調敎埌。

シェヌダヌに぀いおもう少し孊んでいただければ幞いです。

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


All Articles