本当の北はこっち!!

 前回webkitCompassHeadingから北を求める方法を明らかにしたが、実はまだ足りない。

本当の北

 またまたAndroidは実機を持ってないので、どうなってるか分からない。 absolutetrueになってれば問題ない気がするけど。

カクカク動く北

 これまたiOSの話。

ステンシルによるクリッピング

重なっている半透明オブジェクト

 半透明オブジェクトが重なっている場合、深度テストを有効にすると重なったオブジェクトのうちどちらかが描画されない。 実際、サンプルプログラムでも地面の向こう側に隠れた端末が見えていない。 では深度テストを無効にすればよいかというとそんなに簡単ではない。

 アルファによる色の計算は、元々その場にあった色を \(d\) 、これから描く色を \(s\) とすると、実際に描かれる色は \((1-a)d + as\) と表現できる(色ごとに計算する)。

 物体Aがアルファ0.7の赤、物体Bがアルファ0.7の緑だったとする。 背景が白で、その上に物体Aが重なれば、 \(d\) はRGB=(1, 1, 1)、 \(s\) はRGB=(1, 0, 0)だから、描かれる色はRGB=(1, 0.3, 0.3)になる。 その上に物体Bが重なると、 \(s\) はRGB=(0, 1, 0)だから、最終的にはRGB=(0.3, 0.79, 0.09)になる。

 これが逆に重なると、ご想像のとおりRGB=(0.79, 0.3, 0.09)になり、赤と緑の濃さが逆になる。 日常でも色合いの異なる半透明のものが重なっている場合、どちらが手前にあるかは実際に見えている色合いで判断できるが、これを計算で示したことになる。

 これだけなら順序正しく奥から描画すればいいだけだが、今回の場合は地面と端末が軸を境にして前後が入れ替わるので、単純にどちらかを奥、と決められない。 理屈の上では端末手前側、地面、端末奥側、と描画すればよいが、交差位置が端末の姿勢によって変わるため、どこまでが手前でどこから奥かを計算するのが非常に面倒である。

 半透明の面が重なっているとこの問題に突き当たる。 条件によってはデザイナーのための半透明の描画順(KLab)のような対応もできる。 満たしていなければいけない条件は 「ポリゴンの各面から任意のふたつを選び出し、その面を延長して交わった線の両側にまたがってポリゴンが描画されていないこと」 である。 竜巻の例の場合、ポリゴンの各面はざっくり円筒の接平面になる。 接平面が交わってできる直線は必ず円筒の外にあり、すべてのポリゴンはこの直線より手前で終わると保証できる。 この条件を満たさない場合は視点の位置(見る方向)によって描画順を変更しなければならない。

 この図で斜めの線2本はポリゴンを真横から見た状態、丸数字は描画順である。 ひとつのポリゴンに対して丸数字がふたつ描かれているが、両面を描画する、という意味である。 矢印は視線方向を表している。 例えば右向き矢印ならばまず1の面が描画され、続いて2の面が描画される。 3と4はカリングされて描画されず、これで所望の結果になる。 左向きの場合は1・2はカリングされて描画されず、3・4がこの順で描画されるのでやはり所望の結果になる。

 もし、両方の平面が交点Pを超えていなければこれで問題ない。 しかし、いずれか一方でも交点Pを超えてしまうと、垂直方向の矢印のような重なりが発生する。 この場合は固定の描画順では対応できない。 たとえば、上から見た場合(下向き矢印)では1・3がこの順で描かれるため、手前にある1が奥にあるような描画結果になってしまう。

 一般的にはデプスピーリング(床井研究室)のような手法が使える。 デプスピーリングで深度値を奥に向かって求めていき、逆に奥側から深度テスト有効でポリゴンを描画していけばよく、機械的に描画が可能である。 これならばどれだけ面が重なっていても平気だが、重なりが多いほど描画回数が増えるため、消費メモリ量・負荷とも増大する。 求める深度値は半透明オブジェクトの数とは限らない。 どこから見ても絶対に重ならないのならデプスピーリングをする必要はないし、ふたつのオブジェクトが2回以上重なって見える(先ほどの竜巻の例は二重円筒なので最大4つの面が重なっている)と、オブジェクトの数よりも求める深度値の方が多くなる。 逆に、不透明な物体に関しては普通に深度テストが効くため、デプスピーリングの対象に含めることもないが、透けて見えている部分はそれより手前にある半透明オブジェクトの重なりの順序・回数の影響を受けるため、後半戦で半透明オブジェクトを描くごとに不透明なオブジェクトを描き直す必要がある。

 要するに半透明の面が重なるとチョー面倒くさいのだが、重なっている面が最大2面の場合は手抜きができる。 今回の場合は交差するオブジェクトが端末と地面だけで、どちらも平面なので視線方向で2回以上重なることもなく、この条件を満たしている。 指標類を半透明オブジェクトの仲間に含めなかったのはこの条件を満たすためである。

 やり方は深度値がひとつしかないデプスピーリングと同じだが、深度値の保持に深度バッファをそのまま使う。 以下のような手順になる。

 少し奥の深度値を使うのがポイントである。 奥の面を描いたときに、描画された部分の深度値は奥の面(右側太線、黒)、描画されていない部分は手前の面(左側太線、赤)の値になっている。 このため、そのままの位置に深度値を残してしまうと、手前の面を描画するときに、

という結果になってしまう。 深度値を奥にオフセットしておけば、重なりのない手前の面は自分自身の深度値の「少し奥」と比較することになり、きちんと描画される。

 この方法では不透明・半透明なオブジェクトはそれぞれを分けて複数回描画する必要がある北はこっち!で不透明なオブジェクトと半透明なオブジェクトの描画をdrawOpaquedrawTranslucentという関数に分けたのはそういう事情である。 コード上ではこのうちdrawTranslucentdepthという引数を追加して、これがtrueならばpolygonOffsetでポリゴンを奥へオフセットしている。 drawTranslucentではZファイティングを防ぐために赤道と端末を少し手前にオフセットしている。 深度値の計算時は普通に平面として計算すればよいので、オフセットの合計を求めるのではなく、depthtrueの時は単純に手前へのオフセットをしないようにしている。

 以下にここまでの結果を示す。

※次回は端末から見た世界の予定


09 Sep 2024: 本編

31 Aug 2024: 予告編

Copyright (C) 2024 akamoz.jp