はじめに

 前回スケルトンについて書いたので、今回は階層構造を…と思っていたのですが、あれはまだ手軽に使える段階ではないようです。

 今回はgpcnvaxisで取得できるZバッファ値について、数値の意味と取り扱い範囲について少しわかったので書いてみます。

gpcnvaxis モード0の戻り値

 gpcnvaxis をモード0で実行すると、2D座標(X,Y)位置とZバッファ値が返されます。

 2D座標(X,Y)位置はスクリーン座標(ウィンドウ座標)で、そのままpos命令など2D系の描画命令の引数として使用できます。

 Zバッファ値という値も返されます。出てきた数値を確認しても、単純なカメラからの直線距離というわけではなさそうです。またZバッファ値についての説明はマニュアルには記述がありません。 他の項目でも名前だけは出てくるのですが、困ったものです。

問題点

 gpcnvaxis をモード0で実行した結果の2D座標をそのままウィンドウ上にプロットすると、問題が起きる場合があります。 例えばカメラの真後ろにある3D座標をgpcnvaxis命令に与えて取得した2D座標を使ってそのままウィンドウ上に描画すると、カメラの後ろにも関わらずウィンドウ上に描画されてしまうことがあります。

 原因は追求していないのでわかりませんが、gpcnvaxisが返す2D座標はそういう結果を含むということを理解しておく必要があります。

 先に回避方法を書いておきます。カメラ後方の座標を使用しない方法は2通りあります。

  • カメラ方向ベクトルと調べたい3D座標をカメラから見たベクトルで内積を取る。プラスならカメラの前方、マイナスならカメラの後方。
  • Zバッファ値が0.0~1.0の間にいる場合は、必ずカメラの前方。

 Zバッファ値の使い道がわかったところで、少し掘り下げて解説してみます。

サンプル

 3D座標がカメラの後方だった場合でもウィンドウ座標を出してしまう現象を確認するサンプルです。 視覚的に確認するサンプルではないので、数値をよく見てください。

 カメラの前でも後ろでも関係なくX,Y座標が取得できていることが確認できます。


#include "hgimg4.as"
gpreset
setpos GPOBJ_CAMERA, 0,0,5
gplookat GPOBJ_CAMERA, 0,0,0

; ニアクリップ、ファークリップを変更したカメラ
;gpnull   id_camera2
;gpcamera id_camera2, , double(ginfo_winx) / ginfo_winy, 0.4, 1768
;setpos   id_camera2, 0,0,5
;gplookat id_camera2, 0,0,0
;gpusecamera id_camera2

; ウィンドウサイズ
mes "" + ginfo_winx + ", " + ginfo_winy

; ファークリップZ値より遠い場所
gpcnvaxis x,y,z, 0,0,5.0-1768.0, 0
mes "ファークリップZ値より遠い場所 " + x + ", " + y + ", " + z

; ファークリップZ値
gpcnvaxis x,y,z, 0,0,5.0-768.0, 0
mes "ファークリップZ値             " + x + ", " + y + ", " + z

; カメラの正面
gpcnvaxis x,y,z, 0,0,0, 0
mes "カメラの正面                  " + x + ", " + y + ", " + z

; ニアクリップZ値
gpcnvaxis x,y,z, 0,0,5.0-0.5, 0
mes "ニアクリップZ値               " + x + ", " + y + ", " + z

; ニアクリップZ値より近い場所
gpcnvaxis x,y,z, 0,0,5.0-0.5+0.1, 0
mes "ニアクリップZ値より近い場所   " + x + ", " + y + ", " + z

; カメラ位置
gpcnvaxis x,y,z, 0,0,5, 0
mes "カメラ位置                    " + x + ", " + y + ", " + z

; カメラの後
gpcnvaxis x,y,z, 0,0,10, 0
mes "カメラの後                    " + x + ", " + y + ", " + z

; カメラの後
gpcnvaxis x,y,z, 0,0,5+768, 0
mes "カメラのもっと後              " + x + ", " + y + ", " + z

; カメラの後
gpcnvaxis x,y,z, 0,0,16777216, 0
mes "カメラのずっと後ろ            " + x + ", " + y + ", " + z
redraw 1

レンダリングされる範囲

 Zバッファの説明をする前に、レンダリングされる範囲について説明しておきます。言葉の説明です。

 hgimg4は、シーン内に配置した全てのオブジェクトをレンダリングして表示するわけではありません。 カメラのフレームに収まっていないものや、遠すぎて点にしか見えないものをレンダリングしても計算時間の無駄にしかなりません。 そこで、無駄な計算を抑えるため、次の条件を満たす場合にだけレンダリングが行われるようになっています。

  • シーン内に配置されている。
  • カメラの視野角に収まっている。
  • ファークリップ面より近くに配置されている。
  • ニアクリップ面より遠くに配置されている。

 図にするとこのようなイメージです。
レンダリング視野角、ファークリップ、ニアクリップ

 この扇状の領域に配置されたものだけがレンダリングされる対象となります。 市販のゲームをやっていても、オブジェクトに接近しすぎるとポリゴンが欠けてしまう経験をしたことがあると思います。 あの現象もニアクリップよりも近くにオブジェクトが来てしまったために起きている現象だと思われます。

 これらの数値条件は、gpcamera命令で設定することができます。初期値およびデフォルトのカメラ(GPOBJ_CAMERA)の設定は次のとおりです。

視野(FOV)45度
ニアクリップZ値0.5
ファークリップZ値768

Zバッファ値

 一般にはZバッファは、デプスバッファ深度バッファなどの名前で呼ばれているようです。 いずれもおおよそは、カメラから見た奥行方向の距離を意味しています。 いろいろ調べてみると、どうやらhgimg4でのZバッファの定義は次のようになっているようです。

Zバッファの値がファークリップ平面で 1.0、ニアクリップ平面で 0.0 となる。

 hgimg4はどうやらUnity5.4までの挙動に合わせて作成されているようです。(ちなみにUnity5.5からは逆転し、0がファー、1がニアに変更になりました。)

 ではファークリップ面とニアクリップ面の外は、どうなっているのでしょうか。 実験して確認してみました。カメラからの距離とZバッファ値の関係を表にしたものがこちらです。 前述のサンプルスクリプトでも確認できます。

位置 Zバッファ値
ファーより遠い 1.0より大
ファー 1.0
ニア 0.0
ニアより近い マイナス
カメラ位置 -inf
カメラより後ろ 1.0より大(遠いほど1.0に近づいていく)

 どういうグラフになるのかイメージし辛いと思うので、グラフも描いてみました。 横軸はZ軸、縦軸はZバッファ値です。 カメラを Z=5 の位置に配置して注視点を Z=0 に設定している場合です。カメラは、-Z方向を向いています。 赤い線がZバッファ値です。青い線はニアクリップ面、緑の線はカメラ位置を示しています。
Zバッファ値

 Zバッファがニアクリップ面で0.0になっていることがわかります。更にカメラに近いところでは、マイナスになっています。 また、カメラの後方では1.0以上になっていて、距離が離れるほど1.0に近づいていることがわかります。 画面外ですが、カメラからファークリップの距離まで離れるとZバッファは1を超えます。

 このグラフから、値が直線状に変化しないことがわかります。

問題の解決

 レンダリング可能な距離のZバッファ値は、0.0~1.0となっています。 gpcnvaxis 命令モード0の実行結果が返す2D座標は、Zバッファが0.0~1.0以内であることを 確認した場合だけ使うようにすればいいですね。カメラの後方も除外できるし、レンダリングされず画面上からは見えない座標も除外できます。 解決解決。

問題点2

 ではレンダリングされないくらい遠い座標を2D座標変換したい場合はどうなるでしょうか。 敵がファークリップよりも遠い場所にいるが、画面に表示されるより早くウィンドウ上に敵位置を示すアラートを出したい。 というようなケースはどうすれば?

 Zバッファ値が1より大きいものも表示するように許容すればいいのですが、それでは敵が後方に流れていった後もウィンドウ上に正面にいないはずの敵の位置が表示されたてしまいます。

 このような場合は、内積を使用することで3D座標がカメラの前側か後ろ側かを判定できます。

サンプル

 カメラが向いている方向ベクトル(カメラ座標から注視点まで伸ばしたベクトル)とカメラから3D座標までのベクトルの2ベクトルで内積を計算します。 2つのベクトルをABとし、2つのベクトルの間の角度をθとした場合、内積は次のような式になります。

AB = |A||B|cosθ

 左辺が内積の式で、fvinner命令で算出できます。内積の計算式はとても簡単なのですがここでは省略します。

 右辺のベクトルAとベクトルBは、絶対値(長さ)を取るので必ずプラスです。 cosは角度が-90~90度の範囲ではプラス、90~180度, -180~-90度の範囲ではマイナスとなります。

 つまり内積「AB」の結果がプラスなら角度は -90~90度、カメラの正面側です。 またA・Bの結果がマイナスなら角度は 90~180度, -180~-90度、カメラの後ろ側です。

 サンプルを作成しました。


#include "hgimg4.as"
gpreset

; カメラ位置
camx = 0.0
camy = 0.0
camz = 5.0
; 注視点
lookx = 0.0
looky = 0.0
lookz = 0.0

; カメラを設定
setpos   GPOBJ_CAMERA, camx,  camy,  camz
gplookat GPOBJ_CAMERA, lookx, looky, lookz

; ウィンドウサイズ
mes "" + ginfo_winx + ", " + ginfo_winy

; ファークリップZ値より遠い場所
tx = 0.0 : ty = 0.0 : tz = 5.0-1768.0	; 目標座標
gosub *l_muki
gpcnvaxis x,y,z, tx, ty, tz, 0
mes "ファークリップZ値より遠い場所 " + x + ", " + y + ", " + z + " " + muki

; ファークリップZ値
tx = 0.0 : ty = 0.0 : tz = 5.0-768.0	; 目標座標
gosub *l_muki
gpcnvaxis x,y,z, tx, ty, tz, 0
mes "ファークリップZ値             " + x + ", " + y + ", " + z + " " + muki

; カメラの正面
tx = 0.0 : ty = 0.0 : tz = 0.0	; 目標座標
gosub *l_muki
gpcnvaxis x,y,z, tx, ty, tz, 0
mes "カメラの正面                  " + x + ", " + y + ", " + z + " " + muki

; ニアクリップZ値
tx = 0.0 : ty = 0.0 : tz = 5.0-0.5	; 目標座標
gosub *l_muki
gpcnvaxis x,y,z, tx, ty, tz, 0
mes "ニアクリップZ値               " + x + ", " + y + ", " + z + " " + muki

; ニアクリップZ値より近い場所
tx = 0.0 : ty = 0.0 : tz = 5.0-0.5+0.1	; 目標座標
gosub *l_muki
gpcnvaxis x,y,z, tx, ty, tz, 0
mes "ニアクリップZ値より近い場所   " + x + ", " + y + ", " + z + " " + muki

; カメラ位置
tx = 0.0 : ty = 0.0 : tz = 5	; 目標座標
gosub *l_muki
gpcnvaxis x,y,z, tx, ty, tz, 0
mes "カメラ位置                    " + x + ", " + y + ", " + z + " " + muki

; カメラの後
tx = 0.0 : ty = 0.0 : tz = 10	; 目標座標
gosub *l_muki
gpcnvaxis x,y,z, tx, ty, tz, 0
mes "カメラの後                    " + x + ", " + y + ", " + z + " " + muki

; カメラの後
tx = 0.0 : ty = 0.0 : tz = 5+768	; 目標座標
gosub *l_muki
gpcnvaxis x,y,z, tx, ty, tz, 0
mes "カメラのもっと後              " + x + ", " + y + ", " + z + " " + muki

; カメラの後
tx = 0.0 : ty = 0.0 : tz = 16777216	; 目標座標
gosub *l_muki
gpcnvaxis x,y,z, tx, ty, tz, 0
mes "カメラのずっと後ろ            " + x + ", " + y + ", " + z + " " + muki
redraw 1

stop

;
;	内積を使ってカメラの前後を判定
;
*l_muki
	; カメラから注視点までのベクトル
	; getpos GPOBJ_CAMERA, camx,camy,camz
	clx = lookx - camx
	cly = looky - camy
	clz = lookz - camz
	
	; カメラから目標座標までのベクトル
	ctx = tx - camx
	cty = ty - camy
	ctz = tz - camz
	
	; 内積
	; ・カメラから注視点までのベクトル
	; ・カメラから目標座標までのベクトル
	fv = clx, cly, clz
	fvinner fv, ctx,cty,ctz
	; カメラ向きに対する目標座標の位置
	if fv(0) > 0 : muki = "前" : else : muki = "後"
	return

まとめ

 Zバッファ値は0.0~1.0が有効な値。近いほうが0で、遠いほど値が大きい。値の変化は直線ではない。 1.0以上はカメラの後ろかもしれないので内積で確認する。

 まとめるとこんな感じでしょうか。性質がわかったので、安心して使えますね。 ただし公式の方では説明文が無いので、もしかしたら仕様が変更になる場合があるかもしれません。ご注意ください。