20190前回の記事の応用編ということで、Rust+WebAssemblyで3Dのメタボールを作ってみました。コードや動作サンプルは以下をご覧ください。コード動作サンプル画面上でカーソルを左右に動かすと、2つのボールが離れたりくっついたりします。前回の記事では立方体だったので、立方体のポリゴンを定義して表示させました。メタボールでは円がくっついたり離れる度に形状が変化するため、レイトレーシングと呼ばれる方法で描画を行いました。レイトレーシング法は、立体空間の中に光線(レイ)を走らせて物体に衝突させ、その結果に基づいて3Dを描画する方法です。従い、レイトレーシング法で描画するためのポリゴンを定義し、更にマウスイベントや時間に対応させます。基本的にはwasm-bindgenでWebGLのAPIを地道に呼ぶだけですので、Rustで書くのにちょっとコツがいる非同期処理の部分のみ抜粋します。mousemoveイベントのハンドリングです。})?;}再描画の度にマウス座標を参照したいので、マウス座標を保存しておく必要があります。Rustのライフタイムにより関数の処理が終了した時点で変数は破棄されてしまいます。そのため、スマートポインタを用いて非同期処理でも変数にアクセスできるようにしています。Rcの参照カウントでクロージャ内外両方のアクセスを可能にし、RefCellでミュータブルな書き換えを可能にしています。一箇所、アニメーションを実行する部分に関しても簡単に説明します。参考文献ではsetTimeoutを用いていますが、ブラウザの再描画タイミングに合わせて計算を行いたいのでrequestAnimationFrameを用います。requestAnimationFrameは再帰的に呼び出す必要があるので、それを行う関数を定義します。request_animation_frame(g.borrow().as_ref().unwrap());}再帰処理で意識しなければいけないのは、クロージャをクロージャの内外両方で呼ぶ必要があることです。クロージャへの参照が複数存在することになるので、Rcを使う必要があります。Rustではクロージャから代入前の変数にアクセスできないので、事前にミュータブルなメモリ領域を確保してから、クロージャを保存しています。球体の描画ここまででレイトレーシング法の準備は完了です。図形を描画していきます。レイトレーシング法ではひたすらGLSLでシェーダを書いていきます。Rustはもう書きません(笑)。今回はレイトレーシング法の中でもレイマーチング法と呼ばれる手法を用います。参考にフラグメントシェーダを書き換えると、簡単に球体を描画できます。レイマーチング法では距離関数により図形を定義します。距離関数は走査中の光線と物体の距離を表す関数です。レイマーチング法では距離関数が0になった場合に図形と衝突したとみなします(実際の計算では0に十分近づいたら衝突とみなします)。0)(0,0,0)に球を置いた場合の距離関数です。引数のpは光線の先端の位置ベクトルです。sphereSize;}球体を増やす1つの球体を表示できたので、球体を2つに増やしてみましょう。2つの球体は別々の位置に配置する必要があるので、まずは球体の位置をずらします。sphereSize;}球体の位置をずらすのは簡単で、光線の位置ベクトルから球の中心の位置ベクトルを引くだけです。物体を増やす場合には、それぞれの物体への距離を計算して最小値を取ります。sphere2);}球体をスムーズにつなげるメタボールはWikipediaに説明がありますが、距離空間の式ではないのでこのままでは実装できません。紹介されているsmoothMin関数を用います。各物体との距離にこの関数を適用すると、いい感じに図形が補完されて球が滑らかに結合されます。マウスの位置によって球の距離を変化させるようにすると、最終的に以下のようなコードになります。2.0);}まとめRust+WebAssemblyでメタボールを実装することができました。結局GLSLばかり書くことになってしまったので、次はちゃんとRust+WebAssemblyをやろうと思います(笑)。