インデックスに戻る ←前のチャプター →次のチャプター

5. Delphi+Quadruple Dを用いた3D画像表示機能の実装

2.ポリゴンモデルによる背景の表示

今回のソース

まず、今回のチャプターで作成することになるソースコード(バイナリ付)を参照しながら今回のチャプターを読んでください。

確認

今回のチャプターで使うソースは、Quadruple D3に付属しているサンプル(チュートリアルで言うところのDG-Carad2章「ポリゴンモデルを表示しよう」)を元にしています。
又、今回のチャプターについてもこのDG-Carad2章のチュートリアルの理解を前提に書いていますので、予め、チュートリアルを精読しておいてください。

また、ここでは、FPSにおけるフレームと、DG-Caradでのポリゴン表示におけるフレームを混同しないように、FPSにおけるフレームをフレーム時間と記すことにします。

このチャプターですること

このチャプターでは、Quadruple DのDG-Caradチュートリアル第2章「ポリゴンモデルを表示しよう」で使われているサンプルを参考にして、

・約60FPSで動く
・少しだけど、背景ポリゴンモデルを動く
・少しだけど、カメラが動く

コードを作成しましたので、こちらを解説します。
約60FPSの部分に関しては、チャプター3−0でやったことと全く同じことをやります。
今回新たにやることは、ポリゴンモデルを動かすこととカメラを動かすだけですので、そちらに集中してください。
ここまでできてしまえば、前の前のチャプターまでに作ったソースコードに今回のコードを取り入れることで、背景3D、キャラクターは2Dスプライト、というゲームを作ることが可能になります。
全てをスプライトで描写するよりは、かなりかっこいいゲームができますよ。

Interface(宣言部)

では、まず、このソースコードの宣言部からです。
下のコードを御覧ください。
赤くかかれているのがこのソースで定義されている新たな手続きで、5つしかありません。
しかも少なくともその名前はチャプター3−0で使われているものばかりです。
やろうとしていることもチャプター3−0とほぼ同じで、つまり、

・Form1のOnCreateイベントで必要な各オブジェクトの生成、各変数の初期化を行ってプログラム開始
 ↓
・CPUに余裕がある限り、何度でもApplicationEvents1Idle手続きを呼んで、時間を計る。
 そして、17ms(ミリ秒)に1回、mainloop()手続きを呼ぶ。
 ↓
・mainloop()手続きが呼ばれたら、画面の表示を更新する処理を行う。
 (この際、makegraphic()手続きは、キャラクター・背景の描画を行う)
 ↓
・プログラム終了時にForm1のOnDestroyイベントで各オブジェクトの開放を行う。

ということをやっていきます。
つまり、1秒間に60回画面を切り替えるという点では、なんら変わらないので、必然的に同じようなコードになります。
では、チャプター3−0の時との相違点を挙げると、

・makegraphic()手続きの中でポリゴンモデルの描出を行う。
・それに伴う変数が異なり、扱うクラスが若干多い。

となりますが、逆にいうと、たったそれだけの違いで3Dポリゴンモデルの表示が行えるわけです。

なお、今回は、前のチャプターで作った筒型ポリゴンモデルを10個使ってチューブ様空間を表現しています。
これに伴い、Quadruple Dのサンプルとは異なり、変数MechFrameをTSXFrame単体ではなく、TSXFrame10個分の配列にしています。
この10という数字を backnumber=10 という定数を使ってはじめの方で定義しています。
この backnumberを変えてやるだけで、背景の筒型ポリゴンモデルの数を変更できます。
他は基本的にQuadruple Dのサンプルと同じ変数を使用しています。


interface

uses//TimeGetTimeを使うのでMMSystemを追加する。
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DGCarad9, SXLib9, d3d9, s_mathpack, AppEvnts, Ddidex, MMSystem;

const
  //今回は、同じ筒状のポリゴンモデルを複数使って、さもチューブの中を移動しているように見せる。
  backnumber=10;//今回使う背景ポリゴンモデルの数

type
  TForm1 = class(TForm)
    DG: TDGCarad;
    ApplicationEvents1: TApplicationEvents;
    DDIDEX1: TDDIDEX;

    procedure FormCreate(Sender: TObject);//起動時に呼び出される命令

    //↓CPUが暇になると自動的に呼び出される命令
    procedure ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);

    procedure FormDestroy(Sender: TObject);//form1のイベントで追加すること

  private
    { Private 宣言 }
    Root:TSXFrame;      //全てのフレームの親になるフレーム

    MechFrame:array[0..backnumber-1] of TSXFrame;//背景のフレーム

    Camera:TSXFrame;    //カメラの位置・姿勢を示すフレーム

    Mech:TSXMesh;       //背景の形状データ(背景のフレーム内に描画される)

    Lights:TSXLightGroup; //照明管理オブジェクト

    Scene:TSXScene;       //描画の面倒を見るオブジェクト
    steptime:cardinal;    //60FPSを作り出すために、TimeGetTimeの戻り値を入れる
    tick:integer;         //ゲーム開始後何フレーム時間目か。

    procedure mainloop();//この処理をApplicationEvents1Idle手続きの中で、1秒に約60回やってくれる
    procedure makegraphic();//この処理をApplicationEvents1Idle手続きの中で、1秒に約60回やってくれる

  public
    { Public 宣言 }

  end;
var
  Form1: TForm1;

Form1のOnCreateイベント

Form1のOnCreateイベントです。
ぱっと見、長く見えますが、各クラスのオブジェクト(=インスタンス)生成と、各変数の初期化を行っているだけです。
今回はポリゴンモデルの表示を行いますので、当然ですが、スプライトを表示するためのチャプター 3−0とは大きく違います。
チャプター 3−0でも出ていたDG.Windowmodeに関しても、第三引数にDGFMT_ZCheapをあてがうことを忘れないでください。

又、Quadruple DのDG-Caradサンプル第二章を読まれているはずですので、もうお分かりだろうと思いますが、3D空間は、フレームを用いて表現します。
今回は、カメラをRootフレームのZ軸上、つまり、(x,y)=(0,0)に設置しますので、Root空間の(x,y)=(0,0)なZ軸は画面の真中にきます。
スプライト表示の際は画面の左上に原点が来るのとは違う点に注意してください。

なお、今回は、Quadruple Dのサンプルと違って、照明を二方向から与えています。
理由は、チューブの中側は、少なくとも二方向の光を与えないと光が当たらない真っ暗な部分ができるからです。
光の強さもサンプルより強めにしています。
興味のある方は、Lights[0].SetupDiffuse(8,8,8); 等の引数をいろいろ変えてみてください。


////////////////////
//
//アプリ起動時に呼ばれる
//
/////////////////////
procedure TForm1.FormCreate(Sender: TObject);
var
  tmpint:integer;//for文の作業用変数
begin
//解像度とデプスステンシルサーフェスの設定 ポリゴンモデルを表示できるようにする。
//スプライト表示のみの時とは第三引数が違うが、このままでちゃんとスプライト表示できる。
  DG.WindowMode(ClientWidth, ClientHeight, DGFMT_ZCheap);

  //シーンオブジェクトの生成 
  Scene:=TSXScene.Create(DG);

  //ビューポートの設定、つまり、三次元空間の見え方の設定。
  Scene.SetProjection(Pi/2, ClientHeight/ClientWidth, 0.01, 10000.0);
  Scene.SetViewport(0,0,ClientWidth,ClientHeight);

    //フレームの作成
  Root:=TSXFrame.Create(Nil);
  Camera:=TSXFrame.Create(Root);
  Scene.CameraFrame:=Camera;

  //カメラの位置・姿勢を決める
  Camera.SetTranslation(Root, Vector(0,0,1));//カメラの位置
  Camera.SetOrientation(Root, Vector(0,0,1), Vector(0,1,0));//カメラの角度

  //背景ポリゴンモデル読み込み
  Mech:=TSXMesh.Create(DG);
  Mech.LoadFromFile('3dback_l3.sx');//ポリゴンモデルをファイルから読み込む

  //ポリゴンモデルにそれぞれテクスチャを貼り付ける
  //スプライトのテクスチャ作成とやり方は一緒。
  for tmpint:=0 to backnumber-1 do
    begin
      MechFrame[tmpint]:=TSXFrame.Create(Root);//親フレームはRoot
      MechFrame[tmpint].Mesh:=Mech;//内容はMech
      //2Dスプライトの時と同様、テクスチャを設定する
      mechframe[tmpint].Texture:=TDGTexture.Create(DG, DGFMT_ARGB); //できればαチャンネル付きで
      mechframe[tmpint].Texture.BorderColor:=$00000000;
      MechFrame[tmpint].Texture.LoadFromFile('mecha1.bmp');//テクスチャをファイルから読み込む
    end;

    //ライトの設定
    //今回は二方向から光を当てる。
  Lights:=TSXLightGroup.Create(DG, 2);
  //光の色、強さ
  Lights[0].SetupDiffuse(8,8,8);
  Lights[0].SetupSpecular(4,4,4);
  Lights[0].SetupAmbient(2,2,2);
  //光の向き
  Lights[0].SetupDirectional(Vector(0.3,0.3,1));
  //Trueにしないと有効にならない。デフォルトはOFFなので。
  Lights[0].Enabled:=True;

  Lights[1].SetupDiffuse(6,6,6);
  Lights[1].SetupSpecular(4,4,4);
  Lights[1].SetupAmbient(1,1,1);

  Lights[1].SetupDirectional(Vector(-0.2,-0.2,1));
  Lights[1].Enabled:=true;
  //Tick:ゲームがはじまってから何フレーム時間目か。
  Tick:=0;
  TimeBeginPeriod(1);//TimeGetTimeの精度を上げる
end;

Form1のOnDestroyイベント

先にプログラム終了時に呼ばれるForm1のOnDestroyイベントを説明しておきます。
OnCreateイベントで生成したオブジェクト(=インスタンス)を開放する処理を書いていきます。
基本的にはそれだけです。(TimeEndPeriod(1)もありますが。)


////////////////////
//
//アプリケーションが終了時呼び出される
//
/////////////////////
procedure TForm1.FormDestroy(Sender: TObject);//Form1のOnDestroyイベント
var
  tmpint:integer;//for文の作業用
begin
  TimeEndPeriod(1);//TimeBeginPeriod(1)とセットで記述する。
//それぞれ解放
  camera.Free;

  for tmpint:=0 to backnumber-1 do
    begin
      MechFrame[tmpint].Free;
    end;
   mech.Free;
  Lights.Free;
  root.Free;
  scene.free;
end;

ApplicationEvents1のOnIdleイベント

CPUに余裕ができれば何回でも繰り返して呼ばれる手続きです。
今回の講座では、これを用いて約60FPSを生成します。
というわけで、ポリゴン表示には直接的には全く関わっていませんので、チャプター 3−0の時と全く変わっていません。
読み飛ばしていただいても結構です。


////////////////////
//
//CPUが暇になったら真っ先に呼ばれる。
//
/////////////////////
procedure TForm1.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
var
  m:integer;//mは次のTGTまでの時間をいれとく
begin
  //TimeGetTimeはWindowsが起動してからの時間をミリ秒で返す関数
            if TimeGetTime>=steptime+17 then//1000/60=16.7だいたい17ということで
            begin
            //メインループ
            mainloop();

            steptime:=steptime+17;//次タイミングを決定

            m:=steptime-TimeGetTime;
            if m>3 then  //残り3msまでは一気にとばす。

              begin
//下二行の処理は、この間に他のアプリにCPU時間を分ける。じゃないと、OSがとても重くなる。
                Sleep(m-3);//このアプリは(m-3)ms処理を休む
              end;
        end else
            begin
              Sleep(0);//ここらへんの処理は結構いろんな意見がある。
            end;

  //これを設定すると、他のイベントが実行されなくてもまたこのイベントが呼ばれる
  Done:=False;
end;

mainloop()手続き

上のApplicationEvents1のOnIdleイベントで17ms毎に呼ばれる手続きです。
これも基本的にはチャプター 3−0と同じなんですが、唯一、
Scene.Render(root);
だけ、引数が、nil から、 root になっていることに注意してください。
無論、このRootというのは、背景ポリゴンモデルフレームやカメラの親フレームとなるRootのことです。
チャプター 3の時は、3D表示を行う対象が無かったので、nilをいれてやりますが、今回は3D表示を行いますので、座標の基準となるフレーム(←全ての親フレーム、とかいいます。今回は、root。)を指定します。

チャプター 3−0と同じようにmakegraphic()手続きの中で、キャラクター個別の(今回は背景ポリゴンモデルの)描画処理を行っていきます。


////////////////////
//
//メインループの中身。1秒に60回呼ばれる。つまり1フレーム時間分の処理。
//
/////////////////////
procedure TForm1.mainloop;
var
  tmpHRESULT:HRESULT;//DG.D3DDevice.TestcooperativeLevelの返り値を一時的に格納させる
begin
//1フレーム時間に一回、DG.D3DDevice.TestcooperativeLevelでD3D_OK を得なくてはならないので、
//Quadruple Dのサンプルとは違ってこんな風にした。

  tmpHRESULT:=DG.D3DDevice.TestcooperativeLevel;
  while tmpHRESULT<>D3D_OK do
    begin
    //デバイスは生きてるけど、ロスト後でリセットされてない
        // 何故かここで少なくともD3DERR_DEVICENOTRESETをintegerにキャストしないと
        //警告が出る。そもそも次の行の意味があるのかがよくわからないが一応。
                //QD3のサンプルでも警告が出てるし。
      if  integer(tmpHRESULT)=integer(D3DERR_DEVICENOTRESET) then DG.Reset;
      tmpHRESULT:=DG.D3DDevice.TestcooperativeLevel;
    end;
    //ここまでくるってことは、D3D_OKを得たと言うこと。生きてます。
      //描画開始
      DG.D3DDevice.BeginScene;

///////
      makegraphic();//ここで各キャラクター、背景等の画像処理を行う
///////

      //シーンの内容を全部描く
      Scene.Render(Root);

      //描画終わり
      DG.D3DDevice.EndScene;

      //表示
      DG.D3DDevice.Present(Nil, Nil, 0, Nil);
end;

makegraphic()手続き

今回のメインとも言える、背景用のポリゴンモデルを表示するmakegraphic()手続きです。

チャプター 3−1のmakegraphic()と違って、今回、
Scene.Clear
の第一引数が変わっていることに気をつけてください。
意味はわからなくてもかまいませんが、これを忘れると、ポリゴンモデルは表示されません。
ちなみに、スプライト表示のみのチャプター 3では、
Scene.Clear(D3DCLEAR_TARGET, $000000, 1.0, 0);
でした。

Camera.SetOrientation(Root, NowRotY( Vector(0,0,1),round(sin(tick/700)*1000)), Vector(0,1,0))
では、1フレーム時間ごとにカメラの角度を少しずつ変えています。
SetOrientationという関数が、角度を指定する関数で、第二引数にカメラのZ軸の向き、第三引数にカメラのY軸の向きが入ります。
今回、NowRotY関数と三角関数のsinを使って、カメラのZ軸の向きを少しずつ変えています。
NowRotY関数は、第一引数の3次元ベクトル(つまり向きのこと)をY軸を中心に第二引数分(4096で360°と同じ)回転させます。
sinは、高校数学を勉強していないとわからないかも知れませんが、ここでは単純に-1から1まで、引数によって少しずつ変化する関数として扱っています。

MechFrame[tmpint].SetTranslation(Root, Vector(0,0,((10-((tick+(300*tmpint div backnumber)) mod 300)/27) ) ))
では、背景ポリゴンを10個並べつつ、それぞれを三次元座標(0,0,10)から(0,0,-1)まで移動させ、(0,0,-1)に来たら(0,0,10)に戻すということをやっています。
これによって、さも、視点が前進しているように見えます。

MechFrame[tmpint].SetOrientation(Root, Vector(0,0,1), NowRotZ(vector(0,1,0),tick))
でも、先程同様、背景用のポリゴンモデルを回転させています。
先ほどと違うのは、Y軸をZ軸を中心に回転させています。


////////////////////
//
//キャラクターおよび背景の配置処理
//
/////////////////////
procedure TForm1.makegraphic;
var
  tmpint:integer;//for文の作業用
begin

//背景のポリゴンモデルをかいていきましょう
    //画面を黒でクリアー。 2Dの時とは、第一引数が違うことに注意。が、このままでも
    //2Dで使える。
    Scene.Clear(D3DCLEAR_TARGET Or D3DCLEAR_ZBUFFER, $00000000, 1.0, 0);

	Application.ProcessMessages;//他のアプリに処理をさせる
                    
    //1フレーム時間ごとにtickを1増やす
    inc(tick);
    //1フレーム時間ごとにカメラを左右に首ふりさせる。
    Camera.SetOrientation(Root, NowRotY( Vector(0,0,1),round(sin(tick/700)*1000)), Vector(0,1,0));
    //1フレーム時間ごとに背景の各ポリゴンモデルの位置と角度を変える。
    for tmpint:=0 to backnumber-1 do
      begin
        //1フレーム時間ごとにポリゴンモデルを手前(Z軸的に負方向)に移動させ、
        //十分手前に来たら、また奥に戻す。
        MechFrame[tmpint].SetTranslation(Root, Vector(0,0,((10-((tick+(300*tmpint div backnumber)) mod 300)/27) ) ));
        //1フレーム時間ごとに、ポリゴンモデルをZ軸を中心に回転させる。見栄え良し。
        MechFrame[tmpint].SetOrientation(Root, Vector(0,0,1), NowRotZ(vector(0,1,0),tick));
      end;
end;

以上で、今回のソースの解説を終わります。
コンパイル・実行してみてください。
照明のパラメーター等を変えてみるのも一興だとおもいます。
次回のチャプターでは、チャプター 3−3で作ったソースコードと組み合わせていきます。


インデックスに戻る ←前のチャプター →次のチャプター