VRエンジン作成日記

ひたすらVRエンジンを作っていきます

ライティングの調整

ライティングの処理を調整した。今までは平行光源だけを実装していたけど、点光源とスポットライトを追加した。スポットライトは今のところフラグメントシェーダで処理した方がいいところを頂点シェーダで処理してしまっているので、表示に不自然さが残っている。また、ライトを4つまで同時に追加できるようにした。GLSL内のfor文でループできるのかと思いきや、WebGL1で書いているからかハードウェアの影響かはよく分かっていないけど、for文の繰り返し回数が変数だとエラーが出て処理できなくなってしまう。例えば次のような文はエラーになる

// エラーになる for 文

for (int i = 0; i < length; i++) {

}

エラーにならないようにするためには定数で書く必要があった。

// エラーにならない for 文

for (int i = 0; i < 4; i++) {

}

for文の制限については理解できたものの、今度は配列のインデックス指定でも同様の制限があり、array[i] と書くとエラーになる。今のところ簡単な解決策が分からなかったので、ライトの個数分だけループを展開して書くことにした。ソースコードが膨れ上がってくるのと、同じことを何か所にも書いているのでメンテナンス性も良くない。

GLSLのvarying句の使い方がきちんと理解できてきた。頂点シェーダでvarying変数に入れた値は、頂点が3つ揃った段階で面の部分のピクセル1つ分ごとに3点間で補間されて、その値がフラグメントシェーダに渡されるようだ。WebGL2では補間方法を選べるようだけど、WebGL1では補間方法が1種類で、滑らかな補間しかできないようになっているらしい。

・関連するコミット

add point light and spot light · hikipuro/tea.js@575af61 · GitHub

 

OBB同士の衝突判定を追加

OBB同士の衝突判定だけど、昨日コミットするところまで行かなかったので修正してコミットしておいた。マルペケさんのサイトを見なかったら、おそらく実装できていなかっただろうと思う。

あと、Color.white とかの固定の値を static readonly にしていた。毎回同じ値を新規で生成する必要がない場面もあるので、1つだけインスタンスを持つ構造にした方が良いだろうと考えてそうした。ただ、white と言いつつどこかの場所で他の色にされては都合が悪いので、JavaScriptの機能を使ってメンバ変数の変更もできないようにした。次のように書くと、メンバ変数の変更ができないオブジェクトになる。

Object.freeze(obj);

フリーズすると、メンバ変数の変更時に例外が発生するようになる。どうやらフリーズ系のメソッドは3種類あるようだ。それぞれ微妙に挙動が違うらしい。

あとはオブジェクトの親子関係の位置、回転、拡大が、まだ想像通りに動いていないケースがあったので修正した。子オブジェクトを追加した時に親オブジェクトの座標空間に再配置する、子オブジェクトを取り外した時にワールド座標に再配置する、の2つがうまく動いていなかった。

 

・関連するコミット

add hit test obb and obb · hikipuro/tea.js@5795e13 · GitHub

 

OBB同士の衝突判定を追加しようとした

OBB同士の衝突判定を追加しようとしたけど、今日中にはできなかった。マルペケさんのサイトで詳しく解説されてるから、理解してから実装しようと思ったら理解するのが難しかった。

その13 OBBとOBBの衝突

他には、さらに最適化を入れようとしたけど失敗したりしてたので、今日は何もコミットできていない。

 

OBJファイルの読み込み処理を修正

OBJファイルの読み込み処理を修正した。わりと複雑なモデルも読み込めるようになった。

f:id:hikipuro:20180912183809p:plain

画像のドラゴンのモデルは 871,306 ポリゴンもあるようだ。読み込み処理自体は数秒かかるものの、画面に表示する時はタスクマネージャで確認するとCPU負荷は増えていなかった。画面に描くモデルの複雑さによってCPU負荷も増えると思っていたけど、そうでもないようだ。GPU負荷は増えてそうだけど。

このモデルは次のサイトからお借りした。

McGuire Computer Graphics Archive

インターネットで軽く検索した感じではOBJファイルの全貌が分かるほど細かいドキュメントがなさそうだった。上のドラゴンのモデルは "f" の項目にマイナスの値が入っていて、どの頂点を指すのか謎だった。

色々試していくと、マイナスのfの値は、その行までに登場した頂点の個数 (vの行数) からfの値分、前に戻るという指示のようだった。具体的には、

v  -1.01  0.00   0.99
v   1.00  0.00   0.99
v   1.00  0.00  -1.04
v  -0.99  0.00  -1.04

f -4 -3 -2 -1

v  -1.02  1.99   0.99  
v  -1.02  1.99  -1.04
v   1.00  1.99  -1.04
v   1.00  1.99   0.99

f -4 -3 -2 -1

というようなファイルがあったとすると、最初のfが登場する時、

-4, -3, -2, -1

の代わりに、

0, 1, 2, 3

がfの値として使われる (そのように読み替える。1つめのfの登場時点でvの行数は4)。

次のfも同様に、

-4, -3, -2, -1

の代わりに、

4, 5, 6, 7

が使われる (2つめのfの登場時点でvの行数は8)。

どういうことかというと、fの行が登場するまでのvの行数をカウントしておき、その数からfの (マイナスの) 値を足すことで実際のfの値を求めることができる。つまり、

if (f[0] < 0) {
 f[0] = v.length + f[0];
}
// f[1], f[2], f[3] についても同じ処理を行う

というような値が実際のfの値になる。vn (法線)、vt (テクスチャ座標) でも同じように処理する必要がある。

 

・関連するコミット

fix ObjReader · hikipuro/tea.js@5368861 · GitHub

 

OBBとRayの衝突判定を追加

OBBとRayの衝突判定を追加した。ようやく画面内のオブジェクトをマウスで綺麗にクリック判定できるようになった。OBB同士の衝突判定を追加すれば、簡単なゲームなら作れるようになったのではないかと思う。

あと、今日も実行速度の最適化処理を念入りに追加した。Vector3、Quaternion、Matrix4x4のコンストラクタに呼び出し回数をカウントする処理を書いて、1フレーム中にnewされるオブジェクトを削減していった。数日前に比べると、CPU負荷が目に見えて下がってきた。しかし同時にコードが読みにくくなってきた。newを避けて再利用している項目を分かりやすくした方が良さそうだ。

・関連するコミット

add hit test obb and ray · hikipuro/tea.js@5f7da69 · GitHub

 

実行速度を最適化した3

今日も実行速度を最適化していた。キャッシュは今のところできる限り書いたように思える。あとは、Vector3クラスの初期化時等のタイミングで、x: number = 1 と書いていたところを、x: number = 1.0 というような書き方に変えたりしていた。整数型から浮動小数点型への暗黙の型変換を減らす目的で書き換えたんだけど、もはや効果があるのかどうか分からないオカルトの領域に入っているかもしれない。実測すれば良いんだけどね。

インターネットでJavaScriptの最適化について検索してみると、関数本体の文字数がコメントを含めて600バイトを超えると十分な最適化がかからないというような話を見つけた。GoogleのV8の場合はどうだったかちゃんと読んでいないけど、JavaScriptの実行エンジンによってはそういう作りになっているものもあるようだ。おそらく、関数の呼び出し元のコードへインライン展開する処理に置き換える時の閾値を600バイトにしている実行エンジンがあるということだと推測している。これも自分では実測していないので、実際のところどうなっているのかは分からない。

今日は他に、カプセルオブジェクトの天井のポリゴンが欠けてしまう問題も修正した。天井のポリゴンの法線と掛ける値がちょうど同じ (0.0, 1.0, 0.0) になる時に、法線の値が (0.0, 0.0, 0.0) となってしまうことに問題があった。これは、外積を取る時に2つのベクトルが完全な平行だとゼロベクトルになってしまう問題と同じものだった。なので、掛け算する前にイプシロンみたいな値をほんの少し足して、完全な平行と見なされないようにするとゼロベクトルになってしまう問題は回避された。

あとは、光源をきちんとLightオブジェクトにまとめ始めた。今のところ平行光源しか実装できていないので、点光源とかを追加したいと思っている。

・関連するコミット

optimize speed · hikipuro/tea.js@cf59d5c · GitHub

 

実行速度を最適化した2

今日も実行速度を最適化していた。1回計算すれば使いまわせる行列が1フレームに何度も作られていたりとか、無駄のある箇所を削ったりしていた。簡単な最適化を入れたつもりでも、コードの読みやすさが劇的に下がってしまう。メンテナンス性とバランスを取る方法を考えるか、いったん最適化を外しても良いかもしれない。

また、計算中に一時的に作られるベクトル等を固定の配列から借りてくる方法も入れようと考えたんだけど、都合の悪いケースもありそうで、この方法は使いどころが難しそうだ。一時的に使うだけの領域を確保するためにnewを呼んでいると、後々GCの処理負荷として時間差で負荷が効いてきてしまう。工夫しようにも、すぐに思い浮かぶような解決策では、問題も思い浮かんでしまって組み込みにくい。

あと、ゲーム3D数学という本を読み始めた。初版本のせいか、書き間違いが多く含まれているように見える。僕の読み間違いの可能性もあるけど。

実例で学ぶゲーム3D数学

実例で学ぶゲーム3D数学

 

 ・関連するコミット

optimize speed · hikipuro/tea.js@dda5869 · GitHub