3D座標の変換

目次

はじめに

 hgimg4のgpcnvaxis(3D座標の変換を行なう)について調べてみました。 この命令は、hgimg3のhgcnvaxis命令とほぼ同じ動作をする命令です。 HSP3.6時点のHGIMG4プログラミングガイド( https://www.onionsoft.net/hsp/v36/doclib/hgimg4.html )では紹介されておらず、HDLの説明と一部のサンプルスクリプトで使用されているのが情報の全てです。

 この命令を使うと、3D空間内の座標がウィンドウ内の平面座標上のどこに表示されているかがわかります。mes命令など2D系の描画命令を使う際に、3Dモデルの画面上の位置に合わせた表示が出来るようになるのでとても便利な命令です。デバッグの際にも活躍しそうですね。

 gpcnvaxis命令は、この他にも変換モード23が用意されているのですが使い方がわからないと言う情報しか見つかりませんでした。今回は、そのあたりについても調べてみました。

サンプル モード 0

 gpcnvaxis命令を使用しているサンプルには、cnvaxis.hspとpronama3.hspがあります。 cnvaxis.hsp は、モード0を使ったわかり易い例です。

 pronama3.hsp は、モード3が使われています。しかし実際にはgpcnvaxisを使う意味がない記述になっていて参考にはなりません。値が発散しないように0.8をかけて程よい値に収束するように調整してあります。修正忘れでしょうね。

 まずは、モード0のサンプルを書いてみました。 カーソルキーで視点が移動します。今回もアヒルの3Dモデルを使うのでsample\hgimg4からresフォルダをコピーして素材に使ってください。


#include "hgimg4.as"

title "HGIMG4 Test"

	gpreset

	setcls CLSMODE_SOLID, $404040

	gpload id_model,"res/duck"		; モデル読み込み
	setang id_mode, 0, 3.141592		; モデルの回転を設定

	gpfloor id_floor, 8,8, $00ffff		; 床ノードを追加

	setpos GPOBJ_CAMERA, 0,2,5		; カメラ位置を設定

repeat
	stick key,15
	if key&128 : end

	redraw 0

	;-----------------------------
	;	ノードオブジェクト操作
	;-----------------------------
	;	カーソルキーでカメラ位置を動かす
	if key&1 : addpos GPOBJ_CAMERA, -0.2, 0
	if key&4 : addpos GPOBJ_CAMERA, 0.2, 0
	if key&8 : addpos GPOBJ_CAMERA, 0, 0 , 0.2
	if key&2 : addpos GPOBJ_CAMERA, 0, 0 , -0.2
	gplookat GPOBJ_CAMERA, 0,0,0		; カメラから指定した座標を見る

	addang id_model,0,0.02		; ノード回転
	gpdraw						; シーンの描画

	;-----------------------------
	;	テキストメッセージ
	;-----------------------------
	color 255,255,255
	pos 8,8:mes "HGIMG4 sample"
	color

	; アヒル
	px = 0.0 : py = 0.0 : pz = 0.0
	gosub *l_mespos
	mes "アヒル"

	; 床
	px =  4.0 : py = 0.0 : pz =  4.0 : gosub *l_mespos
	px =  4.0 : py = 0.0 : pz = -4.0 : gosub *l_mespos
	px = -4.0 : py = 0.0 : pz =  4.0 : gosub *l_mespos
	px = -4.0 : py = 0.0 : pz = -4.0 : gosub *l_mespos

	redraw 1			; 描画終了
	await 16			; 待ち時間

loop

;-----------------------------
;	テキストメッセージ
;-----------------------------
; 3D座標(px, py, pz)をウィンドウ座標(vx, vy)に変換して表示します。
*l_mespos
	gpcnvaxis vx, vy, vz,  px, py, pz, 0
	line vx-5, vy, vx+5, vy
	line vx, vy-5, vx, vy+5
	pos vx, vy
	mes strf("(%5.2f, %5.2f, %5.2f)", px, py, pz)
	return

モード 0

gpcnvaxis vx, vy, vz,  px, py, pz, 0

 gpcnvaxis命令をモード0で実行する場合、3D座標(px, py, pz)を指定すると指定した3D座標が表示されているウィンドウ座標(vx, vy)が返されます。 モード1も正規化(長さが1になる)されますが、同じような情報が返されます。正規化して何に使うんでしょうね?

 また、Zバッファ値(vz)というカメラからの遠さを示す情報も返されます。この情報については今回は保留します。

 取得したウィンドウ座標(vx, vy)は、2D系の描画命令で使用します。自機や敵機のノードオブジェクトの位置に手軽にパラメータを表示するなど、デバッグ作業で大活躍しそうな機能ですね。

サンプル モード 2

 モード2は情報がHDL以外になく、インターネット上でも「わからん」以外の情報が見つかりませんでした。色々試しているうちに分かったような気がしてきましたので整理してみます。

 サンプルは立体視(交差法)画像を作るサンプルです。 交差法ができない方は、少し書き換えれば平行法に変更できます。

 両目の間隔程度に左右に離して設置した2つのカメラは、同じ座標点を向いています。 これら2つの映像を1つのウィンドウ上に左右に並べて配置しています。 左の画像を右目で、右の画像を左目で見ると画像が立体的に見えるはずです。


; 立体視(交差法)サンプル
#include "hgimg4.as"

title "HGIMG4 Test"

gpreset
setcls CLSMODE_SOLID, $404040		; 画面クリア設定

; モデルを準備
gpload id_model,"res/duck"			; モデル読み込み
gpfloor id_floor, 8,8, $00ffff		; 床ノードを追加

; メインバッファ
widMain = 0			; ウィンドウID

; レンダリングバッファ 1
widBuffer2 = 1				; ウィンドウID
pxBWidth  = ginfo_winx/2	; ウィンドウ横幅
pxBHeight = ginfo_winy		; ウィンドウ高さ
buffer widBuffer2, pxBWidth, pxBHeight, screen_offscreen
;logmes "" + screen_offscreen

; レンダリングバッファ 2
widBuffer3 = 2				; ウィンドウID
buffer widBuffer3, pxBWidth, pxBHeight, screen_offscreen

;	2台目のカメラ
gpnull   id_camera2
gpcamera id_camera2, , double(pxBWidth) / pxBHeight
setpos   id_camera2, 0,2,5

;	3台目のカメラ
gpnull   id_camera3
gpcamera id_camera3, , double(pxBWidth) / pxBHeight

;	注視点
lx = 0.0
ly = 0.3
lz = 0.0

gsel widMain

*main
	stick key, $3C00F
	if key&128 : end

	; オブジェクトを回転
	addang id_model,0,0.02

	;-----------------------------
	;	カメラを調整
	;-----------------------------
	; デフォルトのカメラ GPOBJ_CAMERA は使用しません。
	
	;	カメラ2の位置を変更
	; カーソルキーでカメラ位置を動かす
	if key&1 : lx += -0.1
	if key&4 : lx +=  0.1
	if key&8 : ly += -0.1
	if key&2 : ly +=  0.1
	; 注視点を移動
	if key&$04000 : addpos id_camera2, -0.2, 0
	if key&$10000 : addpos id_camera2,  0.2, 0
	if key&$20000 : addpos id_camera2,  0  , 0 , 0.2
	if key&$08000 : addpos id_camera2,  0  , 0 , -0.2
	
	;	カメラ2の向きを調整
	gpusecamera id_camera2
	gplookat id_camera2, lx,ly,lz

	;	カメラ3の位置と向きを調整
	; カメラ2の少し左側に設定します。
	; 位置 :カメラid_camera2 のローカル座標系で(-1,0,0)の位置
	; 注視点:カメラid_camera2 と同じ点
	gpcnvaxis vx, vy, vz, -0.5,0,0, 2
	setpos   id_camera3, vx, vy, vz
	gplookat id_camera3, lx,ly,lz

	;-----------------------------
	;	レンダリングバッファ
	;-----------------------------
	; 2台目のカメラ映像を使用します。
	; 右目用
	gsel widBuffer2
	gpusecamera id_camera2
	gpdraw

	;-----------------------------
	;	レンダリングバッファ
	;-----------------------------
	; 3台目のカメラ映像を使用します。
	; 左目用
	gsel widBuffer3
	gpusecamera id_camera3
	gpdraw
	
	;-----------------------------
	;	メインバッファ
	;-----------------------------
	; 2つのバッファの画像を結合
	; 交差法にしたいので、右目用をウィンドウの左側、左目用をウィンドウの右側に置く
	; 平行法にしたい場合は、左右の配置を逆にしてください。
	gsel widMain
	gmode 0
	pos 0, 0
	celput widBuffer2	; 右目用
	pos pxBWidth, 0
	celput widBuffer3	; 左目用

	; メッセージ
	color 255,255,255
	pos 8,8
	mes "立体視(交差法)"
	mes "カメラ移動:WASD"
	mes "注視点移動:カーソルキー"
	
	redraw 1			; 描画終了

	;-----------------------------
	;	ループ終了処理
	;-----------------------------
	await 16
	goto *main

モード 2

 立体視画像を作る際、2台のカメラの相対位置はとても重要です。 見る位置が移動したり傾けた際に2台のカメラの相対値がズレてしまうということは、人間で言えば2つの目玉の配置がズレることを意味します。カタツムリならともかく哺乳類の場合、歩いたり首傾けるだけで両目の配置が変わることはないですよね。

 2つのカメラの相対位置が重要とは言え、自分で2点の位置を計算しようとすると簡単ではありません。こういうときにgpcnvaxis命令のモード2を使うと、カメラからの相対位置(ローカル座標)を指定するだけで簡単に絶対座標を求めることができます。

 まず前提として、カメラのローカル座標系は、カメラが向いている方向が-Z方向、右手方向が+X方向、上方向が+Y方向です。ローカル座標系なので、setposgplookatで何が設定されても関係ありません。

 前提を理解できれば、モード 2の使い方は難しくありません。例えば、

gpcnvaxis vx, vy, vz, -0.5, 0, 0, 2

とした場合、現在のカメラ位置・向き・傾きを基準に、0.5左側の位置の絶対座標が(vx, vy, vz)に返されます。カメラの位置や向き、傾きがどのような状態であっても、常に現在のカメラを中心にして 0.5 左側の絶対座標を返してくれます。

用途例

 モード2の具体的な用途としては、次のようなものが考えられます。

  • 視点に対して空間的な位置が固定されている必要があるヘッドアップディスプレイや操縦席
  • 視点を基準に3Dオブジェクトを配置したい場合(アイコンやステータス、GUIなど)
  • 2台のカメラ相対位置が固定されていなければいけない立体視

 gpcnvaxis命令は、モード0も2も「画面上に見た目の配置を固定したいものがある」場合に使用するように作られたような印象ですね。 モード3も同じようなものかと想像したのですが、わかりませんでした。

関連記事

  1. 2画面 はじめに サンプル レンダリングバッファ カメラを追加 レンダリング レンダリンググループ gmod...