2画面

目次

はじめに

 hgimg4では、うまく作れば1つのウィンドウに複数のカメラの映像を表示させることができます。 マリカーの画面分割のように複数人プレイ出来るように画面を分割したり、後方映像を出してみたり、真上の映像を平行投影して簡易的なマップ表示にしたりといろいろな使い方ができそうですね。

 画面分割を利用するには、大きく分けて2つの機能を使用する必要があります。

  • 2台目のカメラを準備する。
  • レンダリングバッファを使用する。

 関連したhgimg4の同梱サンプルには、 buffer.hsp や feedback.hsp がありますが、いつものアヒルを使ってサンプルを作ってみました。

サンプル

 3Dモデルはいつものアヒルを使うのでHSP3に同梱されたsampleのresフォルダをコピーしておいてください。 今回のサンプルは、2画面以外にも少し欲張って色々やっています。

サンプル実行結果


; 2台のカメラ映像を1つの画面上に表示するサンプル
#include "hgimg4.as"

title "HGIMG4 Test"

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

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

; レンダリングバッファ
widBuffer = 1		; ウィンドウID
pxBWidth  = 900		; ウィンドウ横幅
pxBHeight = 480		; ウィンドウ高さ
buffer widBuffer, pxBWidth, pxBHeight, screen_offscreen
;logmes "" + screen_offscreen


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

;	デフォルトのカメラ(1台目)を設定
setpos   GPOBJ_CAMERA, 0,2,5		; カメラ位置を設定
gplookat GPOBJ_CAMERA, 0,0.3,0		; カメラから指定した座標を見る

;	2台目のカメラを作成して設定
; ヌルノードにカメラを割り当てて、カメラノードを作成します。
gpnull   id_camera
gpcamera id_camera, , double(pxBWidth) / pxBHeight	; カメラとして設定する
setpos   id_camera, 0,4,0.1				; カメラ位置を設定する
gplookat id_camera, 0,0.3,0

;	レンダリンググループ
; カメラと同じグループのオブジェクトだけ描画されます。
; アヒル    2
; 床      1
; カメラ0 1|2 = 床とアヒル
; カメラ1   2 = アヒルのみ
setobjrender id_model, 2
setobjrender GPOBJ_CAMERA, 1|2
setobjrender id_camera, 2


gsel widMain

*main
	stick key,15
	if key&128 : end

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

	;-----------------------------
	;	レンダリングバッファ
	;-----------------------------
	; 2台目のカメラ映像を使用します。
	gsel widBuffer
	gpusecamera id_camera
	gpdraw $ffff ^ GPDRAW_OPT_OBJUPDATE
	
	;-----------------------------
	;	メインバッファ
	;-----------------------------
	; 1台目のカメラ映像の上に、レンダリングバッファの画像を貼り付けます。
	gsel widMain
	redraw 0			; 描画開始
	gpusecamera GPOBJ_CAMERA
	gpdraw
	gmode 0
	pos 10, 380
	gmode 0 ; 通常のコピー
	; gmode 1	; アヒルが切り抜かれる?!
	; gmode 2	; アヒルが切り抜かれる?!
	; gmode 6	; 赤い画像?!
	celput widBuffer, 0, 0.5, 0.5

	; celput以外を使用する例
	; gzoom pxBWidth/2,pxBHeight/2, widBuffer, 0,0, pxBWidth,pxBHeight
	; gcopy widBuffer,0,0,pxBWidth, pxBHeight
	
	; 左右反転表示の例
	; pos 640/2+10, 380
	; celput widBuffer,0,-0.5, 0.5
	
	; 描画時のフレームレート
	getreq fps, SYSREQ_FPS
	color 255,255,255
	pos 8,8
	mes "" + fps + " fps"
	
	redraw 1			; 描画終了

	;-----------------------------
	;	ループ終了処理
	;-----------------------------
	await 1000/60			; 待ち時間
	goto *main

レンダリングバッファ

 カメラを追加する前に、カメラの投影先について少し説明します。

 基本的に、1台のカメラ映像は1つのウィドウにしか投影できません。 2台のカメラを同時に使うには、2つのウィンドウが必要です。

 1つのウィンドウに複数のカメラ映像を投影するには、次のような方法を取ります。 まずbuffer命令で見えないウィンドウを作成して、2台目のカメラ映像を投影します。 次にメインのウィンドウに1台目のカメラ映像をレンダリングし、buffer命令で作成した見えないウィンドウの画像をメインウィンドウ上に縮小コピーして表示させます。 標準命令でもbuffer命令は使用されるので、イメージはしやすいかなと思います。

 hgimg4で実装する場合、レンダリングバッファという機能を使用します。 作り方は標準命令同様にbuffer命令を使用します。


buffer widBuffer, pxBWidth, pxBHeight, screen_offscreen

 ウィンドウIDは0以外(メインウィンドウ以外)を指定します。 ウィンドウサイズは、カメラの設定でも使うので変数に入れています。後でginfo_winx, ginfo_winyで取得してもいいですね。 初期化する画面モードは、screen_offscreenに設定しておく必要があるようです。

 作成した見えないウィンドウの使い方は、標準命令での描画と同様でgsel命令で描画先を選択してからgpdrawします。

カメラを追加

 1台目のカメラ(GPOBJ_CAMERA)はデフォルトで作成されます。 2台目以降が必要な場合は、自分で作成する必要があります。作り方は簡単です。


gpnull   id_camera
gpcamera id_camera, , double(pxBWidth) / pxBHeight	; カメラとして設定する

 ヌルノード(空のノード)を作成して、作成したヌルノードをカメラとして設定します。 double(pxBWidth) / pxBHeightと指定しているアスペクト比は、レンダリングバッファとしてbuffer命令で準備したウィンドウの縦横比です。 ここをbuffer命令で指定した値と合わせておかないと、縦横比が歪んだ画像になってしまいます。

 これで2台目のカメラができたので、通常のカメラ(GPOBJ_CAMERA)同様に位置と向きの設定を行っています。 これでカメラを使う準備ができました。

 メインループ内で2台目のカメラ映像を使いたい場合は、gpusecamera命令でカメラを切り替えて使用します。

gpusecamera id_camera

 1台目にカメラを戻す際も、gpusecamera命令を使用します。

gpusecamera GPOBJ_CAMERA

レンダリング

 2台のカメラ映像を2つのウィンドウに投影して、2つの画像をメインウィンドウに表示してみます。

 まずは、レンダリングバッファ(見えないウィンドウ)からレンダリング。


;-----------------------------
;	レンダリングバッファ
;-----------------------------
; 2台目のカメラ映像を使用します。
gsel widBuffer
gpusecamera id_camera
gpdraw $ffff ^ GPDRAW_OPT_OBJUPDATE

 gsel命令でレンダリングバッファを選択して、gpusecamera命令で2台目のカメラに切り替えて、結果をgpdraw命令でレンダリングしています。 見えないウィンドウなのでredraw 1で画面更新する必要はないようです。

 次はメインウィンドウをレンダリングします。


;-----------------------------
;	メインバッファ
;-----------------------------
; 1台目のカメラ映像の上に、レンダリングバッファの画像を貼り付けます。
gsel widMain
redraw 0			; 描画開始
gpusecamera GPOBJ_CAMERA
gpdraw
gmode 0
pos 10, 380
gmode 0 ; 通常のコピー
celput widBuffer, 0, 0.5, 0.5

 gsel命令でメインウィンドウに切り替えて、カメラをデフォルト(GPOBJ_CAMERA)に切り替えて、レンダリング。 この後、celput命令を使ってレンダリングバッファの画像をメインウィンドウにコピーしています。

 celput以外にもgzoomgcopyが使用できるようです。 レンダリングバッファは、このような単純なコピーだけでなく、うまく使えばテクスチャとして使ったり出来るようです。(標準のサンプルpronama3.hspを参照)

レンダリンググループ

 サンプルでは、2台目のカメラ映像に水面(水色の平面)がレンダリングされていません。 これには、レンダリンググループという機能を使用しています。

 レンダリンググループは、同じグループ番号に属するカメラとノードオブジェクトだけを映像投影する仕組みです。 何も設定しなければグループ番号はカメラもオブジェクトも全てに「1」が設定されるため、カメラ映像には全てのオブジェクト表示されます。 レンダリンググループを設定すると、特定のカメラには(にしか)表示されないオブジェクトを作ることもできます。


setobjrender id_model, 2
setobjrender GPOBJ_CAMERA, 1|2
setobjrender id_camera, 2

 レンダリンググループは、setobjrender命令で変更します。複数のグループを設定する場合は、+|でつなげて使います。 サンプルでは、次のようにレンダリンググループを設定しています。

ノードオブジェクト レンダリンググループ 備考
床(水面) 1デフォルト値
アヒル 2
カメラ0(GPOBJ_CAMERA)1|2床とアヒルを表示
カメラ1(id_camera) 2アヒルのみ表示

 全てのカメラとノードオブジェクトのレンダリンググループ初期値には 1 が設定されているため、床は何もしなくても 1 となっています。

gmode

 今回のサンプルを作っている際に面白い(?)現象に遭遇しました。少し紹介しておきます。

 通常はcelput命令でバッファをコピーする前に、gmode命令でコピーモードを通常のコピー(gmode 0)に設定する必要があります。 マニュアルやサンプルではそのように説明されています。

 では他のモードだった場合の動作はどうなるだろうと試しに gmode 1gmode 2を設定してみると…アヒルのノードオブジェクト部分が透明になってしまいました。 HDLの説明では、01は基本的には同じ動作なので謎な挙動です。

サンプル実行結果

 これはこれで工夫すれば何かに使えそうな感じがするのですが、ただのバグだった場合は将来修正されてしまう可能性があるので手が出せません。

 また、gmode 6に設定すると赤い画像が。引数を適当に変えてみても反応がありません。色減算合成コピーとは異なる動作っぽいですね。

トラブル:仮想画面に描画できない

追記:2023/11/04

 buffer命令で作成しておいた仮想画面に、2台目カメラの画像をgpdraw命令で描画するためにgsel命令を実行するとエラーが出た。

#Error 3 -->パラメータの値が異常です

 ということが起きたことがあります。大変悩みました。

 原因は、buffer命令の後にgpreset命令を実行したためでした。 gpreset命令を使用すると、過去にbuffer命令やcelload命令で作成したウィンドウは削除されます。 存在しないウィンドウをgselで選択しようとしたためにエラーが発生したわけです。

 確認用のスクリプトを作成しました。


; ウィンドウIDがリセットされるサンプル
#include "hgimg4.as"

; ウィンドウID
id = 3

; ------------------------------
gpreset	; HGIMG4の初期化

; 仮想画面を作成しています。
; gselで、作成した仮想画面を操作先として選択しています。
buffer id
gsel id

; ------------------------------
gpreset	; HGIMG4の初期化

; 仮想画面を操作先として選択しようとしています。
; 仮想画面が削除されているため、エラーとなります。

;buffer id	; gpresetの後は、再度準備が必要
gsel id

 というわけで、対策は簡単です。仮想画面を使いたい場合は、gpreset命令を実行した後にbuffer命令を実行しましょう。

トラブル:動きが倍速になった

追記:2023/11/04

 2画面にしたら、モーションの再生速度が2倍速になった。落下速度が2倍になった。ということがありました。

 この問題の原因は、2台目のカメラの映像を仮想画面に描画する際に使用されたgpdraw命令にありました。 gpdraw命令は、シーン内のオブジェクトをすべて描画するだけでなく、オブジェクトの自動移動処理/アニメーション処理も行います。 オブジェクトの自動移動処理/アニメーション処理を1つのループ内で2回実行したことが、再生速度が2倍になる原因となりました。 自動移動処理には、重力による落下が含まれており、またアニメーション処理には3Dモデルのアニメーションクリップの再生が含まれます。 この現象は、1回のループ内でgpdraw命令を引数なしで2回使用すると発生してしまいます。


;倍速問題の例

; 2台のカメラ映像を1つの画面上に表示するサンプル
#include "hgimg4.as"
title "HGIMG4 Test"

;-----------------------------
;	環境構築
;-----------------------------
gpreset
setcls CLSMODE_SOLID, $404040		; 画面クリア設定


;-----------------------------
;	ウィンドウ
;-----------------------------

; メイン
widMain = 0			; ウィンドウID

; レンダリングバッファ
widBuffer = 3		; ウィンドウID
pxBWidth  = 900		; ウィンドウ横幅
pxBHeight = 480		; ウィンドウ高さ
buffer widBuffer, pxBWidth, pxBHeight, screen_offscreen


;-----------------------------
;	データ準備
;-----------------------------

; モデル読み込み
gpload  id_model,"res/duck"
setpos  id_model, 0,10,0
gppbind id_model, 20, 0.5, GPPBIND_MESH
;gppset  id_model, GPPSET_LINEAR_FACTOR, 0,0,0

;	デフォルトのカメラ(1台目)を設定
setpos   GPOBJ_CAMERA, 0,2,20		; カメラ位置を設定
gplookat GPOBJ_CAMERA, 0,0.3,0		; カメラから指定した座標を見る

;	2台目のカメラを作成して設定
; ヌルノードにカメラを割り当てて、カメラノードを作成します。
gpnull   id_camera
gpcamera id_camera, , double(pxBWidth) / pxBHeight	; カメラとして設定する
setpos   id_camera, 0,4,0.1				; カメラ位置を設定する
gplookat id_camera, 0,0.3,0

gsel widMain

;-----------------------------
;	メインループ
;-----------------------------
*main
	stick key,15
	if key&128 : end

	; オブジェクトを回転
	gppapply id_model, GPPAPPLY_TORQUE, 0,10,0

	;-----------------------------
	;	仮想画面
	;-----------------------------
	; 2台目のカメラ映像を使用します。
	gsel widBuffer
	gpusecamera id_camera
	; シーン内の全オブジェクトを描画
	if 0 {
		; 正しい実装例
		; オブジェクトの自動移動処理/アニメーション処理 を行いません。
		
		;    $ffff ^ GPDRAW_OPT_OBJUPDATE  GPDRAW_OPT_OBJUPDATE 以外
		gpdraw $ffff ^ GPDRAW_OPT_OBJUPDATE

		; 3Dだけならこうしてもよい
		;    GPDRAW_OPT_DRAWSCENE          3Dシーン描画処理
		;    GPDRAW_OPT_DRAWSCENE_LATE     3Dシーン描画処理(OBJ_LATE)
		;gpdraw GPDRAW_OPT_DRAWSCENE|GPDRAW_OPT_DRAWSCENE_LATE
	} else {
		; 倍速になってしまう例
		;   引数を省略すると、自動移動やアニメーション処理が実行されてしまい
		;   倍速再生のような速度になってしまいます。
		gpdraw
	}

	;-----------------------------
	;	メイン画面
	;-----------------------------
	; 1台目のカメラ映像の上に、レンダリングバッファの画像を貼り付けます。
	gsel widMain
	redraw 0			; 描画開始
	gpusecamera GPOBJ_CAMERA
	gpdraw

	boxf 10-1, 380-1, 0.5*pxBWidth +10, 0.5*pxBHeight + 380
	pos 10, 380
	gmode 0 ; 通常のコピー
	celput widBuffer, 0, 0.5, 0.5

	; 描画時のフレームレート
	getreq fps, SYSREQ_FPS
	color 255,255,255
	pos 8,8
	mes "" + fps + " fps"
	
	redraw 1			; 描画終了

	;-----------------------------
	;	ループ終了処理
	;-----------------------------
	await 1000/60			; 待ち時間
	goto *main

 メインループ内でGPDRAW_OPT_OBJUPDATEオプションは、1回のみ実行されるように設定する必要があります。 通常は、メイン画面の描画時にgpdrawを引数なしで使用していると思います。そのため、仮想画面を描画する際は常に次のようにしておくとよさそうです。


gsel wid  ; 仮想画面
gpusecamera id_subcamera

; 引数:オブジェクトの自動移動処理/アニメーション処理 以外のオプション全て
gpdraw $ffff ^ GPDRAW_OPT_OBJUPDATE

注意点のまとめ

追記:2023/11/04

 最後に2画面表示をする際の注意点をまとめてみました。結構たくさんありますね。

  • 2台目以降のカメラ用に、仮想画面をbuffer命令のscreen_offscreenオプション付きで作成する。
  • 仮想画面は、gpreset命令を実行した後に作成する。
    gpreset命令を使用すると、過去にbuffer命令やcelload命令で作成したウィンドウは削除される。
  • 複数の画面それぞれにカメラを準備する。2台目以降のカメラは、gpcamera命令で作成できる。
  • gpdraw命令のGPDRAW_OPT_OBJUPDATEオプションは、1ループで1回のみ実行されるように設定する。
  • 仮想画面にレンダリングした画面は、celputで配置できる。(gzoom,gcopyも可)

関連記事

  1. 3D座標の変換 目次 はじめに サンプル モード 0 モード 0 サンプル ...