マインスイーパーで光源位置の変更をできるようにしました。2020年4月26日に光源編集の開発を開始しました。
以下当初の計画です。
- メニュー項目から光源編集を選択する。
- 光源位置を表すアイコンをゲームボードに表示する。
- 光源を移動するためにゲームボードをタップする。
- 光源とそのアイコンがタップした位置に移動する。
- 移動した光源位置で表示の更新をする。
このアプリは、webglで3Dイメージをレンダリングしています。ゲーム内の全ての要素は3dの情報を持っています。今回は、2dの座標から、3dの座標を計算する処理を書く必要がありました。表示(Display)は2dの座標系だからです。システムからは、x-y座標のみが取得できます。x-y座標に関連するz座標が必要になります。コンピュータプログラムにおいて、”z深度”または単に”深度”と呼ぶ値のことです。ほとんどの3dグラッフィックライブラリは3dオブジェクトをディスプレイに表示する時、z深度を保持した状態にしています。保存されたz深度から単に読み込めばz深度を取得できます。いくつかのwebglのz深度に関する技術資料を読みました。webgl(ver 1.0)において、直接z深度を取得できないことが判りました。x-y座標における色の値を得ることはできます。さらにいくつかの資料を読んで色の値としてz深度を取得するようにしたいと考えました。
- ユーザーに表示されないオフスクリーンバッファを用意する。
- z深度を色の値に変換し、色としてオフスクリーンバッファに書き込むshaderプログラムを用意する。
- 3dオブジェクトをオフスクリーンバッファにレンダリングする。
- オフスクリーンバッファから色を読み取る。
- 色の値をz深度に変換する。
webgl(ver 1.0)では、色を16ビットの値として読み取ることができます。rgbaの各要素は、4ビットの値となります。z深度はshader言語仕様によると、24ビット以上の不動小数点値として表現されていることになっています。これは、一部の深度情報が失われることになります。 オフスクリーンバッファから深度を読み取ってみました。いくつかの値は1.0を超えていました。これは正しくない値です。というのも深度は0.0から1.0の間に収まらなければならないからです。約4時間ほどソースコードをチェックしましたが、1.0を超えるような値を生成するような処理は見当たりませんでした。そこで、深度として0.5をオフスクリーンバッファに書き込むテストコードを書きました。すると0.4を超えて0.5未満の数と0.5を超えて0.6未満の値が取得され、正確に0.5となる値を取得することができませんでした。取得した値の平均をとると、0.5となりました。先に挙げたビットがすくないことの情報欠損により、色情報は正確に0.5を表現することができないのではと考えました。shaderプログラムの深度から色への変換を確認してみました。8ビットで値を格納している不具合を発見しました。 rgbaの各要素は4ビットの値を持つことができます。もし、6/8より大きい値要素は、1.0となることになります。これが、1.0を超える深度を取得してしまう原因でした。このバグを修正し、1.0未満値のz深度を取得できるようになりました。こうして得られたz深度に逆マトリックスをかけて、光源のz座標を取得できたのでした。この計算されたz座標、元のz座標よりも小さい値になります。その差0.3。これは、16ビット精度のz深度からくるものだと考えました。この差は、許容できない大きさでした。オフスクリーンバッファによる、深度取得を諦めました。
第2案に進みました。投影マトリックスとその逆マトリックスを使って深度を読み取る処理を書きました。光源が移動できる範囲を表す矩形を投影します。投影された座標点はz座標を持ちます。矩形は2つの三角形で構成されていると考えることができます。つぎのようにして深度を計算することができます。
- x-y座標を内包する三角形を見つけます。
- 三角形内での座標の補完値を見つけます。
- 三角形座標の補完値からz深度を計算します。
逆マトリックスを生成する処理が必要になしました。webglは 4×4のマトリックスを使用しています。4 x 4の逆マトリックスを求めるアルゴリズムを書くことはそれほど難しいことではないです。個人的には、n x nの逆マトリックスを求めたいと考えました。逆マトリックスを求める方法として、余因子を使用する方法を採用することにしました。この方法は、マトリックスが大きい場合に、早い処理とはなりません。4 x 4のような小さいマトリックスならば、計算時間は問題にならないと考えました。このアルゴリズムの為の言語として、kotlinを使用するのが自然ですが、Rustを使用することにしました。Rustはweb assemblyを生成することができます。web assemblyを使用することで、javascriptよりも早い処理を期待できます。Rustを使用することにしたもう一つの理由がありますが、それは、別の投稿としたいと思います。
第2案において、満足いくz深度計算の結果を得ることができました。