前回の続きです。原因?がわかりました。
環境なんかは前回の記事を参照してください。
原因は頂点座標の丸め誤差によるズレでした。
ここ、当たりはつけてたのですが透視変換後の値が
どうなるのか理解が足りてなかったので気づかなかったのですT◇T
PIX for Windowsで透視変換後の座標を確認していたら
オヤ。それぞれの頂点座標が
x軸は0.995〜0.998
y軸は0.994〜0.998
となっているではありませんか。
おそらくこの0.001の差がy方向を1ドット下に描画させている原因でしょう。
なぜこの誤差が現れるのか手動で計算してみました。
まず、1ドットなのでモデル座標は以下のようになっているでしょう。
ちょっと行間のせいで長方形に見えるけどこれ正方形だと思ってください。
またz座標は0、同次座標は1固定とし、表記はしません。
y ↑ (-0.5,0.5) ┌─┼─┐(0.5,0.5) │ │ │ ─┼─┼─┼→x │ │ │ (-0.5,-0.5) └─┼─┘(0.5,-0.5)
ここでワールド変換行列をかけます。
ワールド変換行列は平行移動のみです。
今回は右上隅に描画したいので(319,239)移動させます。
┌ ┐ │ 1 0 0 0│ │ 0 1 0 0│ │ 0 0 1 0│ │319 239 0 1│ └ ┘
適用したときの頂点座標は以下のようになります。
y ↑ (-318.5,239.5) ┌─┼─┐(319.5,239.5) │ │ │ ─┼─┼─┼→x │ │ │ (-318.5,-238.5) └─┼─┘(319.5,-238.5)
次にカメラ行列ですが、これは上で計算したそのままが
カメラに映っているものとします。つまり単位行列をかけときます。
次に透視変換行列です。
透視変換行列はカメラ行列適用後の各頂点座標が
-スクリーンX〜+スクリーンX
-スクリーンY〜+スクリーンY
に収まっているものとし、それを最終的に-1〜+1に収めます。
例を出すと
(x,y)=(320,0)だったら 320 / (スクリーン幅/2) = 320 / 320 = 1 →つまり画面右端です。 0 / (スクリーン高さ/2) = 0 / 240 = 0 →つまり画面中央です。
といった感じです。
ここで先ほどの頂点座標を計算してみましょう。
注目するのは小さいほうの値です。
y (-0.9953125,0.9979166..)↑ ┌─┼─┐(0.9984375,0.9979166..) │ │ │ ─┼─┼─┼→x │ │ │ └─┼─┘(0.9984375,-0.99375) (-0.9953125,-0.99375)
この結果はどうやら小数点第四位が四捨五入されるようです。
このへんはドライバとかによって挙動が違うかもしれませんが。
y (-0.995,0.998)↑ ┌─┼─┐(0.998,0.998) │ │ │ ─┼─┼─┼→x │ │ │ └─┼─┘(0.998,-0.993) (-0.995,-0.993)
こうしてみると明らかにy方向の座標が小さいです。
ワールド変換行列のy方向の平行移動量を微妙に変えて描画状況を見ると
319.0f, 239.49987f → 右上隅から1ドット下に描画される 319.0f, 239.49988f → 描画されなくなった 319.0f, 239.49988f → 右上隅に描画される
という結果になりました。このあたりが境界値のようです。
スクリーンサイズが480のときは1ドット小さくなり、
スクリーンサイズが640のときは通常通りとなります。
つまりスクリーンのサイズによってズレたりズレなかったり
してしまうのです^^;
うーん。そもそも2D描画するときのこのやり方自体がマズいんですかね〜?
みんなどうやってるんでしょ...
今のところ思いついた回避方法は
プログラム開始時に上のテストを行って
カメラを常に1ドットずらしたりするくらいしかないです。
でもそんなの絶対イヤですよねT_T
明らかにスマートじゃないというか泥沼ですよね。。
うーん。なんかひらめけ俺><
あ、あと640x480のスクリーンサイズを
そろそろやめて800x600か1024x768にするという手もありますね。
今640x480はさすがに時代遅れだと思いますし
サポートしなくても問題はないでしょう。たぶん。