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
以外にもgzoom
やgcopy
が使用できるようです。
レンダリングバッファは、このような単純なコピーだけでなく、うまく使えばテクスチャとして使ったり出来るようです。(標準のサンプル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 1
やgmode 2
を設定してみると…アヒルのノードオブジェクト部分が透明になってしまいました。
HDLの説明では、0と1は基本的には同じ動作なので謎な挙動です。
これはこれで工夫すれば何かに使えそうな感じがするのですが、ただのバグだった場合は将来修正されてしまう可能性があるので手が出せません。
また、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
も可)