目次 次の項目

1. DirectXの概要



1.1 DirectXとは

 DirectXは,Direct3D,DirectDraw,DirectInput,DirectMusic,DirectPlay,DirectSound等を総称する名前で,ゲームなどのハイパフォーマンスなマルチメディア アプリケーション(グラフィックス,サウンド等)を作成するための下位レベルなAPI(Application Programming Interface)のセットです。開発用SDKはMicrosoftが無償で提供しています。
 最近のパソコンはCPUの性能が向上し,Windowsの標準APIを用いても十分な性能が得られるようになりましたので,特にDirectXを用いる必要がないかも知れません。しかし,DirectXはハードウェアの能力を最大限に利用できるように設計されていますので,できればこの機能を用いて,よりハイパフォーマンスなアプリケーションを作成したいものです。
 本稿では,グラフィックスを用いることに重点を置いていますので,DirectDrawを用いたプログラムの解説を中心に行います。

1.2 DirectDrawについて

 DirectDraw は,DirectXのコンポーネントの一つで,Windows GDI(Graphics Device Interface)との互換性を維持しながら,ディスプレイ ハードウェアやビデオ メモリを直接アクセスするためのソフトウェア インターフェイスです。

図1.1 DirectDrawの概念

1.3 DirectDrawのインターフェイスとオブジェクト

 DirectDrawを含むDirectXは,バージョンのレベルによってインターフェイスが異なります。現在(2002/09/10)ではDirectX 8.1というバージョンが提供されていますが,基本的には旧バージョンの仕様を包含しています。DirectDrawの初期バージョンは,IDirectDrawインターフェイスと呼んでいます。IDirectDraw2,IDirectDraw4,IDirectDraw7というインターフェイスに発展しています。DirectX 8.1では,DirectDrawとDirect3DがDirectX Graphicsという名前に統合されました。

 DirectDrawを使用するために,DirectDrawCreateというAPI関数を実行すると基本となるIDirectDrawオブジェクトが作成されます。IDirectDrawオブジェクトには,メソッドと呼ばれる幾つかの関数を含んでいます。関数のエントリポイントのアドレスはDirectDrawCreate関数を実行したときに得られるvtableと呼ばれるアドレス表から得られます。実際にどの関数がvtableのどの位置に書かれているかは,SDKとして提供されるddraw.hというヘッダーファイルを調べます。さらに,IDirectDrawSurfaceやIDirectDrawClipperなどのオブジェクトがIDirectDrawから派生して作成されます。詳しい解説はプログラムを作成するときに行うとして,ここではこの程度の予備知識に留めておきます。

1.4 FortranでDirectXを用いる?

 DirectXを用いたプログラムの開発用SDKは,主としてC++やVisual Basicでプログラムすることを前提に用意されています。市販のDirectXの解説書やインターネットで紹介されているDirectXに関する解説は,殆どがC++でプログラムするように書かれています。Fortranを用いてDirectXのプログラムを作成するという例は,インターネットで検索しても殆ど見あたりません。それは,DirectXがCOM(Component Object Module)というインターフェイスで作成されているために,C++からの利用が便利なようにできているからなのです。しかし,Fortranの愛用者としては,敢えてFortranを用いてDirectXに挑戦してみたいと思います。


目次 次の項目

2. DirectXの使用準備



2.1 DirectXのインストール

 DirectXの開発用SDKは,Microsoftから無料でダウンロードできます。仕様に基づいてインストールを行うと,サンプルプログラム(C++),インクルードファイル,Helpファイル,ライブライファイルなどが作成されます。

2,2 DirectDrawライブラリの設定

 Developper StudioのFortran開発環境でDirectDrawを使用するには,第2部で解説したWindowsプログラムの作成と同様に行います。また,図2.1のようにProject Settingsのウィンドウを開き,Linkのメニューで,CategoryのInputを選択し,Object/library modulesの項目に"ddraw.lib"を設定し,Additional library pathの項目にライブラリのddraw.libがインストールされているパスを設定します。

図2.1 DirectDrawライブラリの設定例


目次 次の項目

3. DirectDrawライブラリの使用方法



3.1 COM Object

 DirectDrawを始めDirectXのライブラリは,COM(Component Object Module)という形式になっています。COM Objectというのは,インターフェイスと呼ばれる関数を実行して得られたオブジェクトデータのことです。一つのオブジェクトは,メソッドと呼ばれる複数の関数を含んでいます。これらの関数はインターフェイスのポインタを通してアクセスします。

 IDirectDrawインターフェイスの例を図3.1に示します。



図3.1 IDirectDrawインターフェイスの例

3.2 Fortranからのアクセス

 FortranからDirectDrawオブジェクトのメソッド(関数)を実行するには,次のように行います。CreateSurface関数を用いる例で示します。

 1) interfaceとルーチンポインタの定義
interface
integer function DirectDrawCreate(lpGUID,lpdd,pUnkOuter)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_DirectDrawCreate@12' :: DirectDrawCreate
integer lpGUID, lpdd, pUnkOuter
end function
end interface
 IDirectDrawオブジェクトを取得するために最初に実行するDirectDrawCreate関数は,Win32 APIと同様に定義して用います。
interface
integer function CreateSurface(lpdd, ddsd, lpps, pUnkOuter)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_CreateSurface@16' :: CreateSurface
integer lpdd    ! Object Pointer
integer ddsd, lpps, pUnkOuter
end function
end interface

pointer (lpCreateSurface, CreateSurface) ! routine pointer
 CreateSurface関数のinterfaceとエントリのポインタ変数を定義します。

 2) オブジェクトポインタの取得
integer DD
pointer (lpdd, DD)   ! DirectDraw object pointer

i = DirectDrawCreate(NULL, LOC(lpdd), NULL)
 IDirectDrawオブジェクトを取得するDirectDrawCreate関数を実行し,オブジェクトポインタlpddを取得します。

 3) メソッド(関数)の実行
integer*4 $VTBL            ! Interface Function Table
pointer ($VPTR, $VTBL)

$VPTR = lpdd
$VPTR = $VTBL    + 24      ! Add routine table offset
lpCreateSurface = $VTBL
i = CreateSurface(lpdd, ddsd, lpps, dpUnkOuter)
 ddraw.hを見ると,CreateSurface関数はvtableの7番目(オフセット24)に定義されています。
 CreateSurface関数用のルーチンポインタにエントリアドレスを設定し実行します。また,関数の第1パラメータにはオブジェクトポインタを指定します。

3.3 関数のエラー番号

 関数を実行すると,完了コードが返されます。DirectXのヘルプにはエラーコードの値ではなくエラーメッセージの名前が記述されています。エラーコードとエラーメッセージの対応は,ddraw.hを解析すれば分かりますが,10進と16進を使い分けなければなりません。Microsoftのホームページを参照すると便利です。

DirectX エラーメッセージ


目次 次の項目

4. Windowモードのプログラム



4.1 WindowモードとFull Screenモード

 DirectDrawを用いてスクリーンに表示する方法には,他のWindowsアプリケーションとスクリーンを共有して表示するWindow(Normal)モードと,そのアプリケーションがスクリーンを占有して表示するFull Screenモードがあります。Windowモードのアプリケーションでは,GDIが表示する他のアプリケーションを考慮したプログラミングが必要です。一方,Full Screenモードのプログラムでは,スクリーンを他のアプリケーションとは排他的に用います。本稿の目的ではありませんが,ゲームなどの表示速度のレスポンスを要求されるプログラムにはFull Screenモードが適しています。

4.2 Windowモードのサンプル

 Windowモードの表示例を図4.1に示します。例では,表示したWindowにHellow world! with DirectXという文字列をDirectDrawを用いて表示するだけの原始的なプログラムです。

図4.1 Windowモードのプログラム表示の例

4.3 関連するAPI関数

・DirectDrawCreate関数
 DirectDrawCreate関数は,DirectDrawオブジェクトのインスタンスを作成し,インターフェイスのオブジェクトポインタのアドレスを取得します。

  logical(4) function  DirectDrawCreate(lpGUID,lpdd,pUnkOuter)
     integer lpGUID    ! GUIDのアドレス
     integer lpdd      ! IDirectDrawインターフェイスポインタ変数のアドレス
     integer pUnkOuter ! NULL

   戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

・ClientToScreen関数
 ClientToScreen関数は,指定された点のクライアントエリア座標をスクリーン座標に変換します。

    logical(4) function ClientToScreen(hWnd,lpPoint)
     integer hWnd      ! handle to window
     integer lpPoint   ! pointer to a POINT structure

    POINT構造体にクライアント座標値をセットし,この関数を実行するとスクリーン座標値に変換されます。

   戻り値:成功するとTRUE。失敗するとFALSEが返ります。

4.4 DirectDrawのメソッド(関数)

 DirectDrawCreate関数を実行すると,以下のメソッド(関数)が利用できるようになります。

・Release関数
 Release関数は,オブジェクトの参照カウントを1だけ減少させます。参照カウントが0になるとオブジェクトは解放されます。

    integer function Release(lpdd)
     integer lpdd      ! Object pointer

   戻り値:新しい参照カウント値。

・SetCooperativeLevel関数
 SetCooperativeLevel関数は,アプリケーションの最上位動作を決定します。

    integer function SetCooperativeLevel(lpdd,hWnd,dwFlags)
     integer lpdd      ! Object pointer
     integer hWnd      ! Window handle
     integer dwFlags   ! Flags

   Flagsには,主に以下の値を指定します。
    DDSCL_NORMAL      : 通常のアプリケーションとして機能する。
    DDSCL_FULLSCREEN  : DDSCL_EXCLUSIVEと共に使用し,画面を占有する。
    DDSCL_EXCLUSIVE   : DDSCL_FULLSCREENと共に使用し,排他レベルを要求する。
    DDSCL_ALLOWREBOOT : 排他モード時に,CTRL+ALT+DELの機能を許可する。 

   戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

・SetDisplayMode関数
 SetDisplayMode関数は,ディスプレイデバイスのモードを設定します。

    integer function SetCooperativeLevel(lpdd,dwWidth,dwHeight,dwBPP)
     integer lpdd      ! Object pointer
     integer dwWidth   ! Width
     integer dwHeight  ! Height
     integer dwBPP     ! bit/pixel

  戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

・CreateSurface関数
 CreateSurface関数は,このDirectDrawオブジェクトに対するDirectDrawSurfaceオブジェクトを作成します。

    integer function CreateSurface(lpdd,ddsd,lpps,pUnkOuter)
     integer lpdd      ! Object pointer
     integer ddsd      ! DDSURFACEDESC structure
     integer lpps      ! Surface object pointer
     integer pUnkOuter ! NULL

  戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

・SetHWnd関数
 SetHWnd関数は,クリッピング情報を取得するために使用するクリッパーオブジェクトのウィンドウハンドルを設定します。

    integer function SetHWnd(lpdc, dwFlags, hWnd)
     integer lpdd      ! Object pointer
     integer dwFlags   ! Flags
     integer hWnd      ! Window handle

  戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

・CreateClipper関数
 CreateClipper関数は,DirectDrawClipperオブジェクトを作成します。

    integer function CreateClipper(lpdd,dwFlags,lplpDDClipper,pUnkOuter)
     integer lpdd      ! Object pointer
     integer dwFlags   ! Flags
     integer lplpDDClipper ! Clipper object pointer
     integer pUnkOuter ! NULL

  戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

・SetClipper関数
 SetClipper関数は,サーフェイスにクリッパーオブジェクトをアタッチしたり,削除したりします。

    integer function SetClipper(lpps, lpDDclipper)
     integer lpps      ! Object pointer
     integer lpDDclipper ! Clipper interface pointer

   lpDDclipperは,DirectDrawClipperオブジェクトのインターフェイスのアドレス。
   NULLを指定すると,現在のDirectDrawClipperオブジェクトのアタッチを解除します。

  戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

・GetDC関数
 GetDC関数は,サーフェスに対するデバイスコンテキストのGDI互換ハンドルを作成します。この関数名はGDIのGetDC関数と同一なのでdfwinaをインクルードしているサブルーチン内では別の名前で定義するようにします。

    integer function GetDC(lpps, lphDC)
     integer lpps      ! Object pointer
     integer lphDC     ! DC pointer

  戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

・ReleaseDC関数
 ReleaseDC関数は,GetDCで取得したデバイスコンテキストのハンドルを解放します。この関数もGDIに同一名の関数があります。

    integer function ReleaseDC(lpps, hDC)
     integer lpps      ! Object pointer
     integer hDC       ! DC handle

  戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

4.5 ソースプログラムの例

・リソーススクリプトファイルの例

/////////////////////////////////////////////////////////////////////////////
// DirectDraw00 Resource description
//    2002.06.13    Y.Akatsuka
/////////////////////////////////////////////////////////////////////////////

#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
//
// Menu
//

MYMENU    MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit\tAlt+X",          109
    END
END

/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

DIRECTX         ICON    DISCARDABLE     "DirectX.ico"

/////////////////////////////////////////////////////////////////////////////
//
// Accelerator
//

MYACCEL      ACCELERATORS DISCARDABLE 
BEGIN
    VK_ESCAPE,      109,               VIRTKEY, NOINVERT
    "X",            109,               VIRTKEY, ALT, NOINVERT
END

 リソーススクリプトファイルでは,ウィンドウを表示したときのメニューとアクセラレータキー,アイコンを定義しています。アイコンは,DirextXのSDKサンプルに添付されているものを用いています。

・プログラムの例

!*********************************************************************
!*   DirectDraw00.f90
!*   2002.06.06   V01L001  Y.Akatsuka
!*********************************************************************

!*********************************************************************
!  Global variable definition 
!*********************************************************************
module WINCOM
use dfwina

type (T_RECT) grect
integer*4 ghinst
character buf*80

integer DD
pointer (lpdd, DD) ! DirectDraw object pointer

integer DS
pointer (lpps, DS) ! DirectDrawSurface object pointer 

integer DC
pointer (lpdc, DC) ! DirectDrawClipper object pointer

integer, parameter :: DD_OK                   = #00000000
integer, parameter :: DD_FALSE                = #00000001
integer, parameter :: SCREEN_WIDTH    = 640
integer, parameter :: SCREEN_HEIGHT   = 480
end module

!*********************************************************************
!  WinMain
!        2002.06.06  2002.06.06  Y.AKATSUKA
!*********************************************************************
integer function WinMain(hInstance,hPrevInstance,lpszCmdLine,nCmdShow)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_WinMain@16' :: WinMain
use WINCOM
integer hInstance, hPrevInstance, lpszCmdLine, nCmdShow
integer hWnd, hMenu, hIcon, hAccel
character lpszClassName*20

interface
integer function MainWndProc(hWnd, mesg, wParam, lParam)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_MainWndProc@16' :: MainWndProc
integer hWnd, mesg, wParam, lParam
end function
end interface
!
type (T_WNDCLASSEXA) wc
type (T_MSG)       mesg
!
   lpszCmdLine  = lpszCmdLine
   lpszClassName= "DirectDraw00"C
   if(hPrevInstance .eq. 0) then
      wc%cbSize       = SIZEOF(wc)
      wc%lpszClassName= LOC(lpszClassName)
      wc%lpfnWndProc  = LOC(MainWndProc)
      wc%style        = 0
      wc%hInstance    = hInstance
      wc%hIcon        = LoadImage(hInstance, "DIRECTX"C,IMAGE_ICON,0,0, &
                        LR_DEFAULTCOLOR)
      wc%hIconSm      = 0
      wc%hCursor      = LoadCursor(NULL, IDC_ARROW)
      wc%hbrBackground= (COLOR_WINDOW +2) ! +1:white +2:black
      wc%lpszMenuName = 0
      wc%cbClsExtra   = 0
      wc%cbWndExtra   = 0
      i = RegisterClassEx(wc)     ! i : dummy
   end if
   hMenu  = LoadMenu(hInstance, LOC("MYMENU"C))
   hAccel = LoadAccelerators(hInstance, LOC("MYACCEL"C))
   ghinst = hInstance
!  Calculate the proper size for the window given a client of 640x480
   iFrameWidth   = GetSystemMetrics(SM_CXFRAME)   !  4
   iFrameHeight  = GetSystemMetrics(SM_CYFRAME)   !  4
   iMenuHeight   = GetSystemMetrics(SM_CYMENU)    ! 19
   iCaptionHeight= GetSystemMetrics(SM_CYCAPTION) ! 19
   iWindowWidth  = SCREEN_WIDTH + iFrameWidth*2
   iWindowHeight = SCREEN_HEIGHT+ iFrameHeight*2 &
                  + iMenuHeight + iCaptionHeight
!  Create and show the main window
   hWnd = CreateWindowEx(0, lpszClassName,  &
                  "DirectDraw00"C,          &     ! TITLE
                  IOR(WS_OVERLAPPEDWINDOW,WS_CLIPCHILDREN), &
                  CW_USEDEFAULT, 0,         &
                  CW_USEDEFAULT, 0,         &
                  NULL,                     &
                  hMenu,                    &
                  hInstance,                &
                  NULL)
   i = ShowWindow(hWnd, nCmdShow)
!
   do while (GetMessage(mesg, NULL, 0, 0))
     if (.NOT. TranslateAccelerator(hWnd, hAccel, mesg)) then
       i = TranslateMessage(mesg)
       i = DispatchMessage(mesg)
     end if
   end do

   WinMain = mesg.wParam
end

!*********************************************************************
!    MainWndProc
!*********************************************************************
integer function MainWndProc(hWnd, mesg, wParam, lParam)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_MainWndProc@16' :: MainWndProc
use WINCOM

integer hWnd, mesg, wParam, lParam
type (T_PAINTSTRUCT) ps
type (T_POINT)   rc
integer hDC,dhDC
logical b
!
   select case (mesg)
   case (WM_CREATE)
     i = GetWindowRect(hWnd, grect)
     b = InitDirectDraw(hWnd)
     if (b .EQ. .FALSE.) then
       i = MessageBox(hWnd,"DirectDraw00 failed. Now exit."C, &
                 "DirectDraw00"C, MB_OK)
       i = SendMessage(hWnd, WM_CLOSE, 0, 0)
     end if
     MainWndProc = 0 
     return
   case (WM_CLOSE)
     i = ClsDirectDraw(hWnd)
     i = DestroyWindow(hWnd)
   case (WM_DESTROY)
     call PostQuitMessage(0)
   case (WM_SIZE)
     b = GetClientRect(hWnd, grect)
     i = InvalidateRect(hWnd, NULL_RECT, .FALSE.)
   case (WM_PAINT)
     hDC = BeginPaint(hWnd, ps)         ! ==> EndPaint
     i = TextOut(hDC,grect%left,grect%top,"Hello World!",12);
     b = EndPaint(hWnd, ps)             ! <== BeginPaint
   case (WM_KEYDOWN)
     rc%x = 0
     rc%y = 0
     b = ClientToScreen(hWnd,rc)        ! API function
     call XGetDC(lpps,LOC(dhDC),iRes)

     i = TextOut(dhDC,rc%x,rc%y,"Hello World! with DirectX",25);

     call XReleaseDC(lpps,dhDC,jRes)
   case (WM_COMMAND)
     select case (INT4(LOWORD(wParam)))
     case (109)    ! Received key/menu command to exit app
       i = SendMessage(hWnd, WM_CLOSE, 0, 0)
     end select
   end select
   MainWndProc = DefWindowProc(hWnd, mesg, wParam, lParam)
   return
end

!*********************************************************************
!    InitDirectDraw
!*********************************************************************
integer function InitDirectDraw(hWnd)
use WINCOM
implicit integer*4(D)
integer hWnd

integer, parameter :: DDSCL_FULLSCREEN       = #00000001
integer, parameter :: DDSCL_ALLOWREBOOT      = #00000002
integer, parameter :: DDSCL_NORMAL           = #00000008
integer, parameter :: DDSCL_EXCLUSIVE        = #00000010
integer, parameter :: DDSCL_FULL             = #00000013 ! Original

INTEGER, PARAMETER :: DDSD_CAPS              =   1
INTEGER, PARAMETER :: DDSCAPS_PRIMARYSURFACE = 512

    TYPE DDCOLORKEY
    sequence
    INTEGER*4 :: low
    INTEGER*4 :: high
    END TYPE DDCOLORKEY

    TYPE DDPIXELFORMAT
    SEQUENCE
    INTEGER*4 :: dwSize
    INTEGER*4 :: dwFlags
    INTEGER*4 :: dwFourCC
!   INTEGER*4 :: internalVal1
!   INTEGER*4 :: internalVal2
!   INTEGER*4 :: internalVal3
!   INTEGER*4 :: internalVal4
!   INTEGER*4 :: internalVal5
    INTEGER*4 :: dwRGBBitCount
!   INTEGER*4 :: lYUVBitCount
!   INTEGER*4 :: lZBufferBitDepth
!   INTEGER*4 :: lAlphaBitDepth
!   INTEGER*4 :: lLuminanceBitCount
!   INTEGER*4 :: lBumpBitCount
    INTEGER*4 :: dwRBitMask
!   INTEGER*4 :: lYBitMask
!   INTEGER*4 :: lStencilBitDepth
!   INTEGER*4 :: lLuminanceBitMask
!   INTEGER*4 :: lBumpDuBitMask
    INTEGER*4 :: dwGBitMask
!   INTEGER*4 :: lUBitMask
!   INTEGER*4 :: lZBitMask
!   INTEGER*4 :: lBumpDvBitMask
    INTEGER*4 :: dwBBitMask
!   INTEGER*4 :: lVBitMask
!   INTEGER*4 :: lStencilBitMask
!   INTEGER*4 :: lBumpLuminanceBitMask
    INTEGER*4 :: dwRGBAlphaBitMask
!   INTEGER*4 :: lYUVAlphaBitMask
!   INTEGER*4 :: lLuminanceAlphaBitMask
!   INTEGER*4 :: lRGBZBitMask
!   INTEGER*4 :: lYUVZBitMask
    END TYPE DDPIXELFORMAT

    TYPE DDSCAPS
    sequence
    INTEGER*4 :: dwCaps
    END TYPE DDSCAPS

    TYPE DDSURFACEDESC
    sequence
    INTEGER*4 :: dwSize
    INTEGER*4 :: dwFlags
    INTEGER*4 :: dwHeight
    INTEGER*4 :: dwWidth
    INTEGER*4 :: lPitch	    ! dwLinearSize
    INTEGER*4 :: dwBackBufferCount
    INTEGER*4 :: dwZBufferBitDepth ! dwMipMapCount,dwRefreshRate
    INTEGER*4 :: dwAlphaBitDepth
    INTEGER*4 :: dwReserved
    INTEGER*4 :: lpSurface
    TYPE (DDCOLORKEY) :: ddckCKDestOverlay
    TYPE (DDCOLORKEY) :: ddckCKDestBlt
    TYPE (DDCOLORKEY) :: ddckCKSrcOverlay
    TYPE (DDCOLORKEY) :: ddckCKSrcBlt
    TYPE (DDPIXELFORMAT) :: ddpfPixelFormat
    TYPE (DDSCAPS) :: ddsCaps
    END TYPE DDSURFACEDESC

    type (DDSURFACEDESC) ddsd

!   DirectDraw オブジェクトの作成
    call XDirectDrawCreate(NULL, LOC(lpdd), NULL, iRes)
    if (iRes /= DD_OK) then
      InitDirectDraw = .FALSE.
      return
    end if

!   強調レベルの設定 (ウィンドウ描画or全画面の描画)
    call XSetCooperativeLevel(lpdd, hWnd, DDSCL_NORMAL, iRes)
    if (iRes /= DD_OK) then
      InitDirectDraw = .FALSE.
      return
    end if

!   プライマリサーフェイスの作成
    call ZeroMemory(LOC(ddsd),SIZEOF(ddsd))
    ddsd%dwSize  = SIZEOF(ddsd)
    ddsd%dwFlags = DDSD_CAPS
    ddsd%ddsCaps%dwCaps = DDSCAPS_PRIMARYSURFACE

    call XCreateSurface(lpdd, LOC(ddsd), LOC(lpps), NULL, iRes)
    if (iRes /= DD_OK) then
      InitDirectDraw = .FALSE.
      return
    end if

!   ウィンドウ表示ではクリッパを用意
    call XCreateClipper(lpdd, 0, LOC(lpdc), NULL, iRes)
    if (iRes /= DD_OK) then
      InitDirectDraw = .FALSE.
      return
    end if

!   クリッパにウィンドウを関連付け
    call XSetHWnd(lpdc, 0, hWnd, iRes)
    if (iRes /= DD_OK) then
      InitDirectDraw = .FALSE.
      return
    end if

!   サーフェイスにクリッパを関連付け
    call XSetClipper(lpps, lpdc, iRes)
    if (iRes /= DD_OK) then
      InitDirectDraw = .FALSE.
      return
    end if

    InitDirectDraw =  .TRUE.
    return
end

!*********************************************************************
!    ClsDirectDraw
!*********************************************************************
integer function ClsDirectDraw(hWnd)
use WINCOM
integer hWnd
!   サーフェイスのクリッパを解除
    if(lpps.ne.0)then
      call XSetClipper(lpps, NULL, iRes)
    end if
!   利用したオブジェクトをすべて解放
    call XRelease(lpdc,iRes)
    call XRelease(lpps,iRes)
    call XRelease(lpdd,iRes)
    ClsDirectDraw = .TRUE.
    return
end

!*********************************************************************
!    DirectDraw interface routine
!*********************************************************************
subroutine XDirectDrawCreate(lpGUID,lpdd,pUnkOuter,icon)
implicit integer*4(D)

INTEGER*4 $VTBL			! Interface Function Table
POINTER ($VPTR, $VTBL)

interface
integer function DirectDrawCreate(lpGUID,lpdd,pUnkOuter)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_DirectDrawCreate@12' :: DirectDrawCreate
integer lpGUID, lpdd, pUnkOuter
end function
end interface

interface
integer function Release(lpdd)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_Release@4' :: Release
integer lpdd   ! Object Pointer
end function
end interface
pointer (lpRelease, Release) ! routine pointer

interface
integer function SetCooperativeLevel(lpdd,hWnd,dwFlags)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_SetCooperativeLevel@12' :: SetCooperativeLevel
integer lpdd, hWnd, dwFlags
end function
end interface
pointer (lpSetCooperativeLevel, SetCooperativeLevel)

interface
integer function SetDisplayMode(lpdd,dwWidth,dwHeight,dwBPP)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_SetDisplayMode@16' :: SetDisplayMode
integer lpdd   ! Object Pointer
integer dwWidth,dwHeight,dwBPP
end function
end interface
pointer (lpSetDisplayMode, SetDisplayMode) ! routine pointer

interface
integer function CreateSurface(lpdd, ddsd, lpps, pUnkOuter)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_CreateSurface@16' :: CreateSurface
integer lpdd   ! Object Pointer
integer ddsd, lpps, pUnkOuter
end function
end interface
pointer (lpCreateSurface, CreateSurface) ! routine pointer

interface
integer function SetHWnd(lpdc, dwFlags, hWnd)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_SetHWnd@12' :: SetHWnd
integer lpdc   ! Object Pointer
integer dwFlags, hWnd
end function
end interface
pointer (lpSetHWnd, SetHWnd) ! routine pointer

interface
integer function CreateClipper(lpdd,dwFlags,lplpDDClipper,pUnkOuter)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_CreateClipper@16' :: CreateClipper
integer lpdd   ! Object Pointer
integer dwFlags,lplpDDClipper,pUnkOuter
end function
end interface
pointer (lpCreateClipper, CreateClipper) ! routine pointer

interface
integer function SetClipper(lpps, lpDDclipper)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_SetClipper@8' :: SetClipper
integer lpps   ! Object Pointer
integer lpDDclipper
end function
end interface
pointer (lpSetClipper, SetClipper) ! routine pointer

interface
integer function GetDC(lpps, lphDC)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_GetDC@8' :: GetDC
integer lpps   ! Object Pointer
integer lphDC
end function
end interface
pointer (lpGetDC, GetDC) ! routine pointer

interface
integer function ReleaseDC(lpps, hDC)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_ReleaseDC@8' :: ReleaseDC
integer lpps   ! Object Pointer
integer hDC
end function
end interface
pointer (lpReleaseDC, ReleaseDC) ! routine pointer

    icon = DirectDrawCreate(lpGUID,lpdd,pUnkOuter)
    return
!=====================================================================
!    Release interface routine
!=====================================================================
entry XRelease(lpdd,icon)
    if(lpdd.eq.0)then
      icon = 1
      return
    end if
    $VPTR = lpdd
    $VPTR = $VTBL + 8
    lpRelease = $VTBL
    icon = Release(lpdd)
    return
!=====================================================================
!    SetCooperativeLevel interface routine
!=====================================================================
entry XSetCooperativeLevel(lpdd,hWnd,dwFlags,icon)
    $VPTR = lpdd
    $VPTR = $VTBL + 80          ! Add routine table offset
    lpSetCooperativeLevel = $VTBL
    icon = SetCooperativeLevel(lpdd, hWnd, dwFlags)
    return
!=====================================================================
!    SetDisplayMode interface routine
!=====================================================================
entry XSetDisplayMode(lpdd,dwWidth,dwHeight,dwBPP,icon)
    $VPTR = lpdd
    $VPTR = $VTBL + 84          ! Add routine table offset
    lpSetDisplayMode = $VTBL
    icon = SetDisplayMode(lpdd, dwWidth, dwHeight, dwBPP)
    return
!=====================================================================
!    CreateSurface interface routine
!=====================================================================
entry XCreateSurface(lpdd,ddsd,lpps,dpUnkOuter,icon)
    $VPTR = lpdd
    $VPTR = $VTBL + 24          ! Add routine table offset
    lpCreateSurface = $VTBL
    icon = CreateSurface(lpdd, ddsd, lpps, dpUnkOuter)
    return
!=====================================================================
!    SetHWnd interface routine
!=====================================================================
entry XSetHWnd(lpdc,dwFlags,hWnd,icon)
    $VPTR = lpdc
    $VPTR = $VTBL + 32
    lpSetHWnd = $VTBL
    icon = SetHWnd(lpdc,dwFlags,hWnd)
    return
!=====================================================================
!    CreateClipper interface routine
!=====================================================================
entry XCreateClipper(lpdd,dwFlags,lplpDDClipper,dpUnkOuter,icon)
    $VPTR = lpdd
    $VPTR = $VTBL + 16
    lpCreateClipper = $VTBL
    icon = CreateClipper(lpdd, dwFlags, lplpDDClipper, dpUnkOuter)
    return
!=====================================================================
!    SetClipper interface routine
!=====================================================================
entry XSetClipper(lpps,lpdd,icon)
    $VPTR = lpps
    $VPTR = $VTBL + 112
    lpSetClipper = $VTBL
    icon = SetClipper(lpps,lpdd)
    return
!=====================================================================
!    GetDC interface routine
!=====================================================================
entry XGetDC(lpps,lphDC,icon)
    $VPTR = lpps
    $VPTR = $VTBL + 68
    lpGetDC = $VTBL
    icon = GetDC(lpps,lphDC)
    return
!=====================================================================
!    ReleaseDC interface routine
!=====================================================================
entry XReleaseDC(lpps,hDC,icon)
    $VPTR = lpps
    $VPTR = $VTBL + 104
    lpReleaseDC = $VTBL
    icon = ReleaseDC(lpps,hDC)
    return
end

・プログラムの解説

 module定義では,グローバル変数にIDirectDraw,IDirectDrawSurface,DirectDrawClipperの3つのオブジェクトポインタを定義しています。

 WinMain関数は,第2部で用いたプログラムとほぼ同じです。ウィンドウサイズを計算していますが,このプログラムでは使用していません。

 MainWndProc関数では,各種メッセージに従って以下のことを行います。
 WM_CREATEでは,自前のInitDirectDraw関数を実行し,DirectDrawの初期化を行います。

 WM_CLOSEでは,自前のClsDirectDraw関数を実行し,DirectDrawのオブジェクトを解放しています。

 WM_PAINTでは,GDIの作画要求に対して,"Hello World!"の文字列を表示しています。ここでDirectDrawの表示を行うとGDIと喧嘩してシステムがハングアップしたりしますので注意してください。

 WM_KEYDOWNでは,何かキーが入力されたときにDirectDrawを用いて,"Hello World! with DirectX"の文字列を表示します。Win32 APIのClientToScreen関数を用いて,ウィンドウの座標からスクリーンの座標を取得しています。

 XGetDCサブルーチンを実行し,IDirectDrawSurfaceオブジェクトのGetDC関数を実行し,プライマリサーフェイスのデバイスコンテキストを取得します。GDIのTextOut関数を用いて,取得したDCに"Hello World! with DirectX"と表示します。そして,XReleaseDCサブルーチンを実行し,IDirectDrawSurfaceオブジェクトのReleaseDC関数を実行し,取得したDCを解放します。GetDC関数とReleaseDC関数は,同名の関数がWindows APIにも存在します。両者が混同しないように,インターフェイスサブルーチンの中ではWindows APIを引用しないようにしています。

 WM_COMMANDでは,メニューのExitが選択されたときの(109)の処理を記述しています。

 InitDirectDraw関数では,IDirectDrawオブジェクトを取得し,DirectDrawを用いてスクリーンに表示できるよう初期設定を行います。
 最初にDirectDrawで用いる定数の定義と構造体の定義を行います。これらは,ddraw.hというヘッダーファイルからプログラムで必要な部分のみを抜粋し,Fortranの形式で記述しています。手続き部分の処理は次のとおりです

 1) DirectDrawオブジェクトの取得
XDirectDrawCreateインターフェイスサブルーチンを実行し,DirectDrawCreate関数を実行してIDirectDrawインターフェイスオブジェクトを取得します。
 2) 強調レベルの設定
XSetCooperativeLevelインターフェイスサブルーチンを実行し,SetCooperativeLevel関数を実行して協調レベルの設定を行います。協調レベルとは,他のアプリケーションとスクリーンを共有して動作するか,スクリーンを排他的に占有して動作するかという意味です。ここでは,通常のウインドウプログラムとして実行しますので,パラメータにDDSCL_NORMALを設定しています。
 3) プライマリサーフェイスの作成
DirectDrawでは,スクリーンのバッファメモリのことをサーフェイスと呼んでいます。プライマリサーフェイスは,スクリーン画面と1対1に対応しています。つまり,プラマリサーフェイスにデータを記録すると,それが直スクリーンに反映されます。
プライマリサーフェイスを作成するために,DDSURFACEDESC構造体を用意し,Visual Fortranに用意されているZeroMemoryサブルーチンを用いて領域を0クリアしておきます。
dwSizeには構造体の大きさ,dwFlagsには有効とするパラメータの指標,ddsCapsのdwCapsにはプライマリサーフェイスの作成を指示するDDSCAPS_PRIMARYSURFACEを設定します。
XCreateSurfaceインターフェイスサブルーチンを実行し,CreateSurface関数を実行してサーフェイスを作成し,IDirectDrawSurfaceオブジェクトを取得します。取得したオブジェクトのポインタがlppsに格納されます。
 4) クリッパの設定
XCreateClipperインターフェイスサブルーチンを実行し,CreateClipper関数を実行してIDirectDrawClipperオブジェクトを取得します。取得したオブジェクトのポインタがlpdcに格納されます。Windows APIのGDIを用いて表示する場合には,表示する内容がウィンドウからはみ出したりしないように自動的にクリッピング処理を行ってくれました。DirectDrawを用いる描画では,クリッピングをプログラムで制御しなくてはなりません。
 5) クリッパにウィンドウを関連付け
XSetHWndインターフェイスサブルーチンを実行し,SetHWnd関数を実行して4)で作成したクリッパとウィンドウとの関連付けを行います。
 6) サーフェイスにクリッパを関連付け
XSetClipperインターフェイスサブルーチンを実行し,SetClipper関数を実行して3)で作成したサーフェイスとクリッパとの関連付けを行います。

 ClsDirectDraw関数は,ウィンドウを閉じるときにMainWndProcから呼び出されます。サーフェイスに対して設定したクリッパを解除し,IdirectDrawClipper,IDirectDrawSurface,IDirectDrawの各オブジェクトを取得したときと逆順に解放します。

 XDirectDrawCreateサブルーチンでは,DirectDrawの各メソッド(関数)を実行するためのインターフェイスを記述しています。それぞれの関数は,オブジェクトポインタからvtableを参照し,その相対位置をヘッダーファイルddraw.hを見て所定のアドレスを計算したものを用いています。なお,各インターフェイスルーチンは,翻訳速度を早くするためにentry文を用いて全体で一つのモジュールにしています。


目次 次の項目

5. FullScreenモードとWindowモード



5.1 FullScreenモード

 前項では,DirectDrawを用いてWindowモードでスクリーンに表示する方法を紹介しました。しかし,DirectDrawの本領を発揮できるのは動画などをFullScreenモードで実行する場合です。本項では,2枚の画像を正確な時間間隔で交互に表示しながら時刻を表示するプログラムを作成し,WindowモードとFullScreenモードを切り替えて実行する例を紹介します。このプログラムをアレンジすれば,研究会などの講演残り時間を正確に表示するアプリケーションの作成にも応用できます。



図5.1 FullScreen画面の例(縮小しています)

5.2 フリップ(Flip)とブリット(Blit)

 DirectDrawを用いてスクリーンに画像などを表示するには,FlipとBlitの2とおりの方法があります。Flipはプライマリサーフェイスのアドレスとバックバッファのアドレスとを切り替えることで瞬時に表示内容を切り替える方法で,FullScreenモードでのみ使用できます。Blitはプライマリサーフェイスと同じサイズのオフスクリーンサーフェイスを用意し,オフスクリーンサーフェイスの内容をプライマリサーフェイスに転送する方法で表示内容を変更します。BlitはFullScreenモードとWindowモードの両方で使用できます。一見するとFlipの方がスピードが速そうですが,一長一短があります。Flipはスクリーンを占有しているFullScreenモードでしか使用できないので,FullScreenモードとWindowモードで処理方法を別々に記述しなければなりません。また,Flipは標準ではスクリーンの表示のタイミングに同期して動作するので,最近の高速なCPUを有するPCでは逆に足かせになってしまう場合があります。本項では,これらのことを考えてFullScreenモードでもBlitを用いて表示を行います。

5.3 関連するAPI関数

・AdjustWindowRectEx関数
 AdjustWindowRectEx関数は,クライアント領域からウィンドウの領域を求めます。

  logical(4) function AdjustWindowRectEx(lpRect ,dwStyle ,bMenu ,dwExStyle)
    type (T_RECT) lpRect    ! client-rectangle structure
    integer       dwStyle   ! window styles
    logical(4)    bMenu     ! menu-present option
    integer       dwExStyle ! extended window style

  戻り値:成功時はTRUE,失敗時はFALSE。

 SetWindowPos関数を用いてウィンドウの位置やサイズをFullScreenモードにする前に保存した値に設定します。

・SetWindowPos関数
 SetWindowPos関数は,ウィンドウのサイズ,位置,及びZオーダー(奥域の順番)を変更します。

  logical(4) function SetWindowPos(hWnd, hWndInsertAfter, X, Y, cx, cy,uFlags)
    integer hWnd            ! handle to window
    integer hWndInsertAfter ! placement-order handle
    integer X               ! horizontal position
    integer Y               ! vertical position
    integer cx              ! width
    integer cy              ! height
    integer uFlags          ! window-positioning options

  戻り値:ウィンドウが以前から表示されていた場合はTRUE,非表示だった場合はFALSE。

・timeGetTime関数
 timeGetTime関数は,システムが起動されてからの経過時間をミリ秒単位で取得します。この関数は音楽を再生するときなどの正確な時間を必要とするときに用います。同様の関数にGetTickCount関数があります。

 integer(4) function timeGetTime()

 戻り値:システムが起動されてからの経過時間(ミリ秒)。

・GetLocalTimeサブルーチン
 GetLocalTimeサブルーチンは,現在の日付と時刻を取得します。

  Subroutine GetLocalTime(lpSystemTime)
   integer lpSystemTime   ! Pointer to a SYSTEMTIME structure

 SYSTEMTIME構造体

    type  T_SYSTEMTIME
       integer(2)  wYear 
       integer(2)  wMonth 
       integer(2)  wDayOfWeek 
       integer(2)  wDay 
       integer(2)  wHour 
       integer(2)  wMinute 
       integer(2)  wSecond 
       integer(2)  wMilliseconds 
    end type  T_SYSTEMTIME

5.4 DirectDrawのメソッド(関数)

・Blt関数
 Blt関数は,ビットブリット (ブロック転送) を実行します。

  integer function Blt(lpps,lpDestRect,lpDDSrcSurface,lpSrcRect,dwFlags,lpDDBltFx)
    integer lpps           ! Object Pointer
    integer lpDestRect     ! Rectangle on the destination surface
    integer lpDDSrcSurface ! Source for the blit operation
    integer lpSrcRect      ! Rectangle on the source surface
    integer dwFlags        ! Flags of effective members
    integer lpDDBltFx      ! Pointer to the DDBLTFX structure

  戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

・BltFast関数
 BltFast関数は,転送元コピーブリット又は透過型ブリットを実行します。クリッピングはできません。

  integer function BltFast(lpps,dwX,dwY,lpDDSrcSurface,lpSrcRect,dwTrans)
    integer lpps           ! Object Pointer
    integer dwX            ! X coordinate on destination surface
    integer dwY            ! Y coordinate on destination surface
    integer lpDDSrcSurface ! Source for the blit operation
    integer lpSrcRect      ! Rectangle on the source surface
    integer dwTrans        ! Type of transfer

  戻り値:成功するとDD_OK,失敗するとDDERR_XXXの各値。

5.5 ソースプログラムの例と解説

・プログラム例


目次 次の項目

6. Direct3D
(Direct3Dを用いた立方体の表示)



6.1 Direct3Dの概要

 Direct3Dは,DirectXが提供する機能の一つで,ゲームやインタラクティブ3Dグラフィックスを目的に設計された3Dハードウェア向けの描画インターフェイスです。Direct3Dでは,3Dビデオディスプレイ・ハードウェアが備わっていればそれを有効に使用します。ハードウェアがない場合にもソフトウェアでエミュレートすることができます。本項では3Dの機能を用いるのにDirectX7のインターフェイスを使用します。DirectX7を用いるにはDirectX7以降のSDKを入手する必要があります。

図6.1 Direct3Dを用いた立方体の表示

6.2 直接モード(IM)と保持モード(RM)

 DirectX7のDirect3Dには,直接モード(Immediate Mode)と保持モード(Retained Mode)の2つのモードがあります。直接モードは,アクセラレータ・ハードウェアと下位レベルでやり取りするアプリケーションのための3D APIです。ゲームやその他の高性能マルチメディア・アプリケーションをWindowsオペレーティングシステムに移植するためのツールです。

 保持モードは,短時間での開発を必要としたり,階層構造やアニメーション用に用意された機能を利用する上位レベルの3D APIで,直接モードの上位に構築されています。プログラムはIMより簡単ですが,機能が制約されます。本項ではIMを用いて処理を行います。ちなみに,DirectX8には保持モードはありません。

6.3 HALとHEL

 HALはハードウェア・アブストラクション・レイヤ(Hardware Abstruction Layer)の略で,Direct3Dをサポートするハードウェア・アクセラレータを用いてデバイスにアクセスする機能を言います。一方,HELはハードウェア・エミュレーション・レイヤ(Hardware Emulation Layer)の略で,3Dハードウェアの機能をソフトウェアでエミュレートしてデバイスにアクセスする機能のことを言います。一般的にはHALを用いてハードウェアにアクセスする方が高速に実行できますが,3Dアクセラレータなるハードウェアが実装されていなければなりません。

6.4 Zバッファ

 3Dの処理ではZバッファというものを用います。Zバッファは,3Dの処理で奥行き(Z軸)の表示を補助するために用いるサーフェイスです。画像が重なっている部分の前後の関係を判断するのに用います。Zバッファを用いると描画順序に関わらず画像の前後関係を判断して手前に見える部分だけを表示することができます。通常はバックバッファやオフスクリーンサーフェイスにZバッファを関連付けるだけで,使用者がZバッファを意識して処理することはほとんどありません。

6.5 3D座標系

 一般に,3Dグラフィックスではデカルト座標系の左手座標系と右手座標系がよく用いられます。Direct3Dでは,左手座標系が用いられます。左手座標系では,Z 軸をX 軸の正の方向から左手で握ったときに親指の向く方向がZ 軸の正の方向になります。つまり,Z 軸の正方向が画面の奥の方向を向いています。

図6.2 左手座標系

6.6 変換マトリックス

 3Dの座標変換では,行列を用いて平行移動,回転,及びスケーリングを行います。 回転とスケーリングを行うには,3×3の行列で可能ですが,平行移動を組み合わせるために4×4の行列を用い,これを同次行列と言います。一般に,n次元位置ベクトルをn+1次元ベクトルで表現することを同次座標表現といいます。

・平行移動
 点(x,y,z)を新しい点 (x',y',z') に平行移動する。

・回転
 点(x,y,z)をx軸を中心に回転して新しい点 (x',y',z') を生成。

 点(x,y,z)をy軸を中心に回転して新しい点 (x',y',z') を生成。

 点(x,y,z)をz軸を中心に回転して新しい点 (x',y',z') を生成。

・スケーリング
 点(x,y,z)を任意の値で新しい点 (x',y',z') にスケーリングする。

1) ワールド変換(モデリング変換)

 ワールド変換とは,個々のオブジェクト毎のローカル座標(ボディ座標)をシーンで共通のワールド座標に変換することを言います。ワールド変換は,平行移動,回転,及び拡大縮小を組み合わせて使用します。

図6.3 ワールド座標とローカル座標

2) ビュー変換

 ビュー変換とは,ワールド座標を視点座標に変換することをいいます。 視点座標系は,ワールド空間に視点(カメラ)を設けて,視点位置を原点とし,注視点に対する視線方向をz軸とします。通常x軸は,ワールド座標系のxz面に平行にとります。ビュー変換行列は,視点位置に原点をトランスフォームする行列と回転行列の積で求めることができます。

図6.4 ワールド座標と視点座標

 ここでは,上記の方法と等価な方法でビュー変換行列を直接作成します。ワールド空間に於ける視点位置座標とシーン内の注視点を使用して,カメラ空間の座標軸の向きをベクトルで表します。注視点から視点位置座標を減算して,視線の方向ベクトルnを生成します。次に,ベクトルnとワールド空間のy軸の外積をとり,正規化して右方向ベクトルuを生成します。同様に,ベクトルuとnの外積をとり,上方向ベクトルvを求めます。右方向 (u),上方向 (v),及びビュー方向 (n) のベクトルは,ワールド空間に於けるカメラ空間の座標軸の方向を表します。x,y,z の平行移動係数は,視点の位置座標cと u,v,n ベクトルの内積の負数をとって求めます。これらの値を次の行列に入れてビュー変換行列を生成します。

3) プロジェクション変換(透視変換)

 プロジェクション変換とは, 任意の視点から3次元空間を眺めて遠近感を出す変換を言います。 投影法には各種あり,第1部8項の3次元図形の表示では2点投影法を用いましたが,DirectXでは1点投影法を用いています。

 具体的には,視点座標系において,スクリーン面になる前方クリップ面とスクリーンに投影される後方クリップ面とでできる視錐台を立方体にトランスフォームすることに相当します。視錐台は台の底辺の方が大きいので立方体にトランスフォームすると,遠くのオブジェクトが近くのオブジェクトより小さくなり,シーンに遠近感を出すことができます。

図6.5 プロジェクション変換

 変換マトリックスは次のようになります。

 ここで,変数 w,h,Qは次のように計算します。

 Znは近くのクリップ面のZ値,Zfは遠くのクリップ面のZ値,fovwとfovhは水平方向と垂直方向の視野角(ラジアン)を表します。

図6.6 視錐台の垂直断面

6.7 ライティングとマテリアル

 Direct3Dで扱うライティングモデルには,ダイレクトライトとアンビエントライトがあります。これらはサーフェイスのマテリアルと相互に作用して様々な効果を発揮します。ダイレクトライトは,シーン内の光源が発する光で,色と強度と方向を持ちサーフェイスのマテリアルと相互作用してスペキュラハイライトを生み出します。Direct3Dでは,複数の光源を扱うことができます。一方,アンビエントライトは,光源と方向を持たず,色と強度のみを持った環境光で,シーン内の間接照明として用いられます。

 マテリアルは,サーフェイスを構成するポリゴンが光をどのように反射するかを決める光の反射特性を表し,ディフューズ反射,アンビエント反射,スペキュラ反射,及びエミッションで表現します。ディフューズ反射とアンビエント反射は,それぞれダイレクトライトとアンビエントライトに対する反射を意味します。スペキュラ反射はオブジェクト上にハイライトを作成し,オブジェクトの輝きを表現します。エミッションは,オブジェクト自体が光を発しているように表現します。

 Direct3Dでは,モデルのレンダリングを行うときにライティングをDirect3Dに依頼する方法と,ライティング済みのモデルを扱うことができます。

図6.7 ライティング済みモデルの例

6.8 頂点フォーマット

 3Dの構成要素を形成する頂点の集合をプリミティブと言います。プリミティブはポリゴン(多角形)から構成されます。Direct3Dでは,最も単純なポリゴンである三角形を組み合わせて複雑なポリゴンを形成します。Direct3Dのアプリケーションでは,モデルのプリミティブの頂点を次に示す複数の方法で定義することができます。

1)未トランスフォーム・未ライティングの頂点

 この頂点タイプは,トランスフォームとライティングの処理をDirect3Dにまかせる方法です。アプリケーションで独自のトランスフォームとライティングの手続きを実装する必要がありません。モデルの座標頂点は,ローカル座標で与え,ワールドトランスフォーム,ビュートランスフォーム,及びプロジェクショントランスフォームをDirect3Dに設定することで,画面上の位置を決定します。また,ライティングには,マテリアルと反射光の強度を求めるために頂点法線を使用します。

2)未トランスフォーム・ライティング済みの頂点

 Direct3Dは,トランスフォームのみ行い,ライティング計算は実行しません。そのため頂点法線は必要ありません。各頂点のディフューズ成分とスペキュラ成分をシェーディングに使用します。これらの色は任意に指定しても,独自のライティング方法で求めることもできます。

3)トランスフォーム済み・ライティング済みの頂点

 Direct3Dは,頂点のトランスフォームとライティングの計算は行いません。アプリケーションで独自に求める必要があります。頂点法線は必要ありません。

4)ストライド頂点フォーマット

 ストライド頂点フォーマットは,未トランスフォームの頂点成分配列へのポインタを含む構造体で,間接的なアクセスによって頂点を定義します。

 配列要素の記憶順序

 Fortranでは,2次元以上の配列要素の記憶場所は,左の添字が変化する順序に割り当てられます。次元が増えるに従って記憶域の組が増加するという意味では理解し易いと思われます。
 一方,C/C++など他の大方の言語では,右の添字が変化する順序に割り当てられます。画像メモリを配列に対応させる場合には,行方向がメモリアドレスの増加方向に対応する方が自然な感じがします。

[例] REAL*4 A(3,4) という配列では,
   A(1,1),A(2,1),A(3,1),A(1,2),A(2,2),……,A(3,3),A(1,4),A(2,4),A(3,4)

という順序に配置されます。一般に,DOループを用いたマトリックスの演算では,配列の左の添字が先に変化するように計算すると,メモリを飛び飛びにアクセスすることを防ぎ,CPUの能力を有効に働かせることができます。ただし,ベクトル計算機と言われるスーパーコンピュータでは,必ずしも一般則は成り立ちません。

    DO J = 1,N
      S(J) = A(1,J)
      DO I = 2,M
        S(J) = S(J) + A(I,J)
      END DO
    END DO

6.9 Direct3Dの初期化

 ここからがDirect3Dの本題になります。Direct3Dを用いて描画するためには,Direct3Dの初期化を行う必要があります。Direct3Dは,DirectDrawの機能の一部として動作しますが,扱う事象が2次元から3次元になるため,Zバッファや座標変換の処理が加わることになります。

1) DirectDrawオブジェクトの作成

 Direct3Dは,DirectDrawの一機能として動作するので,最初にIDirectDraw7オブジェクトを以下のようにして作成します。


integer, parameter :: DD_OK              = #00000000
type (GUID), PARAMETER :: IID_IDirectDraw7          =    &
    GUID(#15E65EC0,#3B9C,#11D2,                          &
    CHAR('B9'X)//CHAR('2F'X)//CHAR('00'X)//CHAR('60'X)// &
    CHAR('97'X)//CHAR('97'X)//CHAR('EA'X)//CHAR('5B'X))

integer DD         ! LPDIRECTDRAW7
pointer (lpdd, DD) ! DirectDraw object pointer

!   IDirectDraw7 インターフェイス オブジェクトの取得
    call XDirectDrawCreateEx(NULL,LOC(lpdd),LOC(IID_IDirectDraw7),NULL,iRes)
    if (iRes /= DD_OK) return  ! 失敗

 XDirectDrawCreateExサブルーチンは,DirectDrawCreateEx関数を用いるインターフェイスルーチンです。前章まで用いたDirectDrawCreate関数の拡張版でIDirectDraw7インターフェイスを用いる場合に使用します。第1パラメータはDirectDrawデバイスのGUIDで,NULLを指定するとデフォルトのGUIDが用いられます。 第2パラメータは,IDirectDraw7インターフェイスポインタを取得する変数のアドレス。第3パラメータは,取得するインターフェイスIID_IDirectDraw7のGUIDのアドレスを指定します。第4パラメータは,NULLを指定します。関数が成功すると,lpddには有効なIDirectDraw7インターフェイスのアドレスが,iResには完了コードが出力されます。

2) 協調レベルの設定

 強調レベルの設定は,SetCooperativeLevel関数を用いてディスプレイを標準モードで用いるか排他モードで用いるかを指定します。アプリケーションをフルスクリーンモードで実行するときは排他モードにします。


integer, parameter :: DDSCL_NORMAL           = #00000008
integer, parameter :: DDSCL_FULL             = #00000013

!   協調レベルの設定 (ウィンドウ描画 or 全画面描画)
    dwFlags = DDSCL_NORMAL
    if (gStyle == StyleFullScreen) dwFlags = DDSCL_FULL
    call XSetCooperativeLevel(lpdd, hWnd, dwFlags, iRes)
    if (iRes /= DD_OK) return

 XSetCooperativeLevelサブルーチンの第2パラメータには,ウィンドウハンドルを指定し,第3パラメータには,設定するモードのフラグを指定します。フルスクリーンモードにするときは,DDSCL_EXCLUSIVE,DDSCL_FULLSCREEN,DDSCL_ALLOWREBOOTを同時に設定します。例では予め3つのフラグ定数のORをとったDDSCL_FULLが定義してあります。DDSCL_ALLOWREBOOTは,プログラムがハングしたときにAlt+Delキーで強制終了ができるようにするフラグです。
 ウィンドウモードにするときは,DDSCL_NORMALを設定します。ディスプレイは他のアプリケーションとシェアして用いられるのでウィンドウの外に描画しないようにクリッパを設定するようにします。

3) Zバッファ深度の取得

 現在のディスプレイモードを取得し,Zバッファを取得するときに必要なZバッファ深度の情報を取得します。


type (DDSURFACEDESC2) ddsd

!   現在のディスプレイモードを取得
    call ZeroMemory(LOC(ddsd),SIZEOF(ddsd))
    ddsd%dwSize = SIZEOF(ddsd)
    call XGetDisplayMode(lpdd,LOC(ddsd),iRes)
    if (iRes /= DD_OK) return
    dwBPP = ddsd%ddpfPixelFormat%dwRGBBitCount

 インターフェイスサブルーチンXGetDisplayModeの第2パラメータには,DDSURFACEDESC2構造体のアドレスを指定します。ディスプレイの解像度は,指定した構造体のddpfPixelFormatメンバのdwRGBBitCountにセットされ,Zバッファ深度にも同じ値を使用します。

4) ディスプレイモードの設定(フルスクリーンモードのみ)

 ディスプレイモードの設定は, フルスクリーンモードのときのみ実行します。協調レベルをフルスクリーンモードに設定した場合は,ディスプレイの解像度とサイズを指定します。指定できる解像度は,ビデオカードがサポートする解像度で最近のものは24/32 bits per pixelが一般的です。ビデオカードがサポートするディスプレイモードを列挙する方法もありますが,ここでは先に取得した現在のディスプレイモードを使用します。


!     ディスプレイモードの設定      grc:desktop window
!     (lpdd,dwWidth,dwHeight,dwBPP,dwRefleshRate,dwFlags,icon)
      call XSetDisplayMode(lpdd,grc%right,grc%bottom,dwBPP,0,0,iRes)
      if (iRes /= DD_OK) return

 インターフェイスサブルーチンXSetDisplayModeは,DirectX7では前章で用いたサブルーチンと引数の数が異なります。第5パラメータにはリフレッシュレート,第6パラメータには追加オプションを設定できるようになりました。ここではデフォルト値の設定を意味する 0 を指定しています。

5) サーフェイスの作成(フルスクリーンモード)

 サーフェイスはDirectXでは重要な意味をもっています。グラフィックスをディスプレイに表示するには大きなメモリを必要とします。サーフェイスとはメモリのことを指します。サーフェイスはビデオメモリに確保する場合とシステムメモリに確保する場合があります。ハードウエアを用いて表示するときはビデオメモリに,ソフトウェアでエミュレートするときはシステムメモリを使用します。
 ほとんどのアプリケーションプログラムでは,ダブルバッファを採用します。プライマリスクリーンとオフスクリーン(バックバッファ)を用意し,オフスクリーンに描画したものをプライマリスクリーンにフリップ(flip)又はブリット(blit)して表示します。
 トリプルバッファといって2つのバックバッファを用いる場合もあります。この場合は最初のシーンは第1バックバッファに描画し,次のシーンは第2バックバッファに描画します。ディスプレイへの表示は垂直同期信号に合わせてバックバッファをフリップすることで行いますが,同期信号を待たずに次のシーンの描画に移ることができます。ただし,より大きなメモリを必要とします。
 ここでは前章と同様にフリップは用いずブリットを使用します。フリップを用いる場合はバックバッファをプライマリサーフェイスと同時に作成しますので,オフスクリーンサーフェイスを明に作成する必要はありません。


integer DS         ! LPDIRECTDRAWSURFACE7
pointer (lpps, DS) ! DirectDrawSurface object pointer 

integer DO         ! LPDIRECTDRAWSURFACE7
pointer (lpos, DO) ! Offscreen buffer object pointer

INTEGER, PARAMETER :: DDSD_CAPS              = #00000001
INTEGER, PARAMETER :: DDSD_CAPS_WID_HEI      = #00000007
INTEGER, PARAMETER :: DDSCAPS_PRIMARYSURFACE = #00000200
INTEGER, PARAMETER :: DDSCAPS_OFF_3DD        = #00002040

type (DDSURFACEDESC2) ddsd

!     プライマリ・サーフェイスの作成
      call ZeroMemory(LOC(ddsd),SIZEOF(ddsd))
      ddsd%dwSize         = SIZEOF(ddsd)
      ddsd%dwFlags        = DDSD_CAPS
      ddsd%ddsCaps%dwCaps = DDSCAPS_PRIMARYSURFACE
      call XCreateSurface(lpdd, LOC(ddsd), LOC(lpps), NULL, iRes)
      if (iRes /= DD_OK) return
!     オフスクリーン・サーフェイスを作成
      ddsd%dwFlags        = DDSD_CAPS_WID_HEI ! DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT
      ddsd%ddsCaps%dwCaps = DDSCAPS_OFF_3DD
      ddsd%dwWidth        = grc%right
      ddsd%dwHeight       = grc%bottom
      call XCreateSurface(lpdd, LOC(ddsd), LOC(lpos), NULL, iRes)
      if (iRes /= DD_OK) return

 サーフェイスを作成する前に,DDSURFACEDESC2構造体をクリアしておきます。オフスクリーンサーフェイスを作成するときは,dwCapsにDDSCAPS_OFFSCREENPLAINと3Dのレンダリングを行うのでDDSCAPS_3DDEVICEを指定します。例では,予め両者のORをとった定数DDSCAPS_OFF_3DDをパラメータに指定しています。また,サーフェイスのサイズをデスクトップウィンドウのサイズに指定します。何れの場合もサーフェイスの作成が成功すると,サーフェイスのポインタが出力されます。

6) サーフェイスの作成(ウィンドウモード)

 ウィンドウモードでは他のアプリケーションとディスプレイメモリを共有しているので,フリップは実行できません。プライマリサーフェイスとオフスクリーンサーフェイスは必ず別々に作成し,ブリットによってオフスクリーンからプライマリへ描画データを転送して表示します。


!     プライマリ・サーフェイスを作成
      call ZeroMemory(LOC(ddsd),SIZEOF(ddsd))
      ddsd%dwSize         = SIZEOF(ddsd)
      ddsd%dwFlags        = DDSD_CAPS
      ddsd%ddsCaps%dwCaps = DDSCAPS_PRIMARYSURFACE
      call XCreateSurface(lpdd, LOC(ddsd), LOC(lpps), NULL, iRes)
      if (iRes /= DD_OK) return
!     オフスクリーン・サーフェイスを作成(3Dレンダリングターゲット)
      ddsd%dwFlags        = DDSD_CAPS_WID_HEI ! 使用するフィールドを指定
      ddsd%ddsCaps%dwCaps = DDSCAPS_OFF_3DD
      ddsd%dwWidth        = grect%right   ! SCREEN_WIDTH
      ddsd%dwHeight       = grect%bottom  ! SCREEN_HEIGHT
      call XCreateSurface(lpdd, LOC(ddsd), LOC(lpos), NULL, iRes)
      if (iRes /= DD_OK) return

 フルスクリーンモードの場合とほとんど同じです。違いは,オフスクリーンサーフェイスのサイズがクライアントウィンドウのサイズになっていることだけです。

7) クリッパの作成(ウィンドウモードのみ)

 ウィンドウモードでは,クリッパを用意します。クリッパはプライマリサーフェイスに設定します。クリッパを用いることでウィンドウ外に描画するのを防ぎ,他のウィンドウがオーバラップして描画することを可能にします。


integer DC         ! LPDIRECTDRAWCLIPPER
pointer (lpdc, DC) ! DirectDrawClipper object pointer

!     ウィンドウ表示なので,クリッパを用意 
      call XCreateClipper(lpdd, 0, LOC(lpdc), NULL, iRes)
      if (iRes /= DD_OK) return

!     クリッパにウィンドウを関連付け
      call XSetHWnd(lpdc, 0, hWnd, iRes)
      if (iRes /= DD_OK) return

!     サーフェイスにクリッパを関連付け
      call XSetClipper(lpps, lpdc, iRes)
      if (iRes /= DD_OK) return

 インターフェイスサブルーチンXCreateClipperを実行し,クリッパの作成が成功すると,クリッパ・オブジェクトのポインタが出力されます。インターフェイスサブルーチンXSetHWndを用いてクリッパを描画ウィンドウに関連付けします。更に,XSetClipperサブルーチンを用いてプライマリサーフェイスに関連付けを行います。

8) Direct3Dインターフェイスの取得

 DirectDrawオブジェクトが作成できたら,Direct3D7インターフェイスを取得します。Direct3Dは,DirectDrawオブジェクトに含まれるのでQueryInterface関数を用いてインターフェイスを取得します。


integer D3         ! LPDIRECT3D7
pointer (lp3d, D3) ! Direct3D7 object pointer

!   IDirect3D7インターフェイスのポインタ取得
    call XQueryInterface(lpdd, LOC(IID_IDirect3D7), LOC(lp3d), iRes)
    if (iRes /= DD_OK) return

 関数が成功すると,lp3dにはIDirect3D7インターフェイスのポインタがセットされます。

9) Direct3Dデバイスの作成

 デバイスとは装置に対応する概念で,Windows APIのデバイスコンテキスト(DC)に相当します。DirectX7では,ハードウェアデバイスとソフトウェアデバイスがあります。 ハードウェアデバイスはHALデバイスとも呼ばれ,ハードウェアでポリゴンのレンダリングができるDirect3Dのビデオカードが備わっている場合に有効です。ハードウェアトランスフォームやライティングも可能な,より高機能なTnLHALデバイスもあります。ソフトウェアデバイスはRGBデバイスとも呼ばれ,ビデオカードがない場合にソフトウェアでエミュレートします。 インターフェイスサブルーチンXCreateDeviceを用いて,3Dデバイスの作成とレンダリングサーフェイスとの関連付けを同時に行うことができます。


type (GUID), PARAMETER :: IID_IDirect3DHALDevice    =    &
    GUID(#84E63dE0,#46AA,#11CF,                          &
    CHAR('81'X)//CHAR('6F'X)//CHAR('00'X)//CHAR('00'X)// &
    CHAR('C0'X)//CHAR('20'X)//CHAR('15'X)//CHAR('6E'X))
type (GUID), PARAMETER :: IID_IDirect3DRGBDevice    =    &
    GUID(#A4665C60,#2673,#11CF,                          &
    CHAR('A3'X)//CHAR('1A'X)//CHAR('00'X)//CHAR('AA'X)// &
    CHAR('00'X)//CHAR('B9'X)//CHAR('33'X)//CHAR('56'X))

INTEGER, PARAMETER :: DDSCAPS_ZBU_SYS        = #00020800
INTEGER, PARAMETER :: DDSCAPS_ZBU_VID        = #00024000

integer DV         ! LPDIRECT3DDEVICE7
pointer (lpdv, DV) ! IDirect3DDevice7 Device object pointer

!   Direct3Dデバイスの作成(IDirect3DDevice7インターフェイス)
    lpDeviceGUID = LOC(IID_IDirect3DHALDevice)   ! use HAL device
    iDDSCAPS = DDSCAPS_ZBU_VID                   ! use Video memory
    call XCreateDevice(lp3d,lpDeviceGUID,lpos,LOC(lpdv),iRes)
    if (iRes /= DD_OK) then
      lpDeviceGUID = LOC(IID_IDirect3DRGBDevice) ! use HEL device
      iDDSCAPS = DDSCAPS_ZBU_SYS                 ! use System memory
      call XCreateDevice(lp3d,lpDeviceGUID,lpos,LOC(lpdv),iRes)
    end if
    if (iRes /= DD_OK) return

 インターフェイスサブルーチンXCreateDeviceを実行し成功すると,lpdvにはデバイスオブジェクトのポインタが出力されます。第3パラメータには,デバイスを関連付けるサーフェイスを指定します。
 ハードウェアデバイスが使用可能かどうかチェックするのを簡略化するために,最初にHALデバイスの作成を試み,失敗したらRGBデバイスを作成しています。ついでに,後でZバッファを作成する際にデバイスに対応して必要になるメモリの選択もここで行っています。

10) ビューポートの作成

 ビューポートは,シーンのレンダリングプリミティブがラスタライズされる矩形に対して用いられます。Z方向のクリッピング範囲も設定することができます。通常[0,1]が用いられます。ビューポートを設定するには,D3DVIEWPORT7構造体に必要な値をセットし,SetViewport関数を実行します。


type (D3DVIEWPORT7) vp

!   デバイス用にビューポートをセット
    vp = D3DVIEWPORT7(0, 0, grect%right, grect%bottom, 0.0, 1.0)
    call XSetViewport(lpdv,LOC(vp),iRes)
    if (iRes /= DD_OK) return

 D3DVIEWPORT7構造体には,レンダリングサーフェス上のビューポートの左上隅位置を表すピクセル座標(通常0,0),ビューポートの大きさ(通常はレンダリングサーフェスの大きさ),レンダリングする深度範囲(通常は[0.0,1.0])を設定します。インターフェイスサブルーチンXSetViewportを用いて,3Dデバイスに対してビューポートを設定します。

11) Zバッファの作成

 Zバッファは,3D表示を行うときに奥域の情報を保持しているバッファメモリです。Zバッファをレンダリングターゲットとなる3Dのデバイスオプションを持つオフスクリーンサーフェイスに対応付けを行うだけで,オブジェクトの前後関係や描画順序を殆ど意識しないで描画することができます。ただし,サーフェイスをクリアするときには,全PixelのZ値を最大値(1.0)にセットしておく必要があります。オブジェクトの前後関係を自分で考慮して描画する場合には,Zバッファを用いる必要はありませんが,プログラムがかなり複雑になることでしょう。
 Zバッファサーフェイスは,レンダリングするサーフェイスと同じサイズでなければなりません。また,Zバッファを作成するときには,Zバッファフォーマットを指定する必要がありますが,ビット深度については16,24,32ビットの内,使用している3Dカードが対応している値を指定します。


!     Zバッファサーフェイスの作成(オフスクリーンの情報を利用)
      ddsd%dwSize         = SIZEOF(ddsd)
      call XGetSurfaceDesc(lpos, LOC(ddsd),iRes)
      ddsd%dwFlags        = DDSD_CAPS_WID_HEI_PIX
      ddsd%ddsCaps%dwCaps = iDDSCAPS   ! Zbuffer & (Video | System) memory 
      ddsd%ddpfPixelFormat%dwSize = 0
!     レンダリングターゲットであるオフスクリーンと同じバッファ深度を探す。
!     なければ再度16ビットのZバッファ深度を探す。
      call XEnumZBufferFormats      &
          (lp3d,lpDeviceGUID,LOC(EnumZBufCall),LOC(ddsd%ddpfPixelFormat),iRes)
      if (ddsd%ddpfPixelFormat%dwSize == 0) then
        ddsd%ddpfPixelFormat%dwRGBBitCount = 16
        call XEnumZBufferFormats    &
            (lp3d,lpDeviceGUID,LOC(EnumZBufCall),LOC(ddsd%ddpfPixelFormat),iRes)
        if (ddsd%ddpfPixelFormat%dwSize == 0) return
      end if
      call XCreateSurface(lpdd, LOC(ddsd), LOC(lpzb), NULL, iRes)
      if (iRes /= DD_OK) return
!     オフスクリーンにZバッファを接続する
      call XAddAttachedSurface(lpos, lpzb, iRes)
      if (iRes /= DD_OK) return

 Zバッファサーフェイスの作成では,オフスクリーンサーフェイスを作成するときに用いたDDSURFACEDESC2構造体を再利用しています。lpDeviceGUIDとdwCapsには,Direct3Dデバイスを作成するときに用意した値を用います。Zバッファをビデオメモリに作成するかシステムメモリに作成するかを設定します。Zバッファはハードウェアの機能を用いるときはビデオメモリに,ソフトウェアでエミュレートするときはシステムメモリに作成する必要があります。
 インターフェイスサブルーチンXEnumZBufferFormatsを用いて,使用できるZバッファフォーマットを列挙します。EnumZBufCallという利用可能なZバッファフォーマットを返すコールバック関数を別に用意します。コールバック関数はプログラムのサンプルを参照してください。
 インターフェイスサブルーチンXCreateSurfaceを用いて,Zバッファサーフェイスを作成し,XAddAttachedSurfaceサブルーチンを実行してレンダリングサーフェイスであるオフスクリーンサーフェイスに関連付けを行います。

12) レンダリングターゲットの指定

 3Dの環境が整ったらレンダリングターゲットの設定を行い,Zバッファを有効に設定します。


!   オフスクリーンをレンダリングターゲットに設定(必須)
    call XSetRenderTarget(lpdv, lpos, NULL, iRes)
    if (iRes /= DD_OK) return

!   Zバッファを有効に設定(必須)      lpdv : 3D device
    call XSetRenderState(lpdv, D3DRENDERSTATE_ZENABLE, D3DZB_TRUE, iRes)
    if (iRes /= DD_OK) return

 インターフェイスサブルーチンXSetRenderTargetを用いてレンダリングターゲットをオフスクリーンサーフェイスに設定します。また,XSetRenderStateサブルーチンを用いてZバッファを有効にします。この設定をしないと3D機能が使用できません。SetRenderState関数では,この他にZ比較機能の設定変更やライティングを無効にするなどのいろいろな設定ができます。

6.10 オブジェクトの定義

 3D表示の対象となるオブジェクトを定義するには,6.8 頂点フォーマットに示すように,いろいろな定義方法があります。立方体の1面を未トランスフォーム・未ライティング形式で定義する方法と,トランスフォーム済み・ライティング済み形式で定義する方法について示します。

 立方体の1面は4つの頂点から成っており,2つの三角形から構成されます。それぞれの三角形は独立に定義することもできますが,複数の三角形を三角形ストリップという頂点を共有する一連の結合された三角形で定義すると,すべての三角形の頂点を定義する必要がなくなります。ただし,定義する頂点の順序に注意が必要です。

図6.8 三角形ストリップ

・未トランスフォーム・未ライティングの頂点定義

 未トランスフォーム・未ライティングの頂点は,DirectXにトランスフォームとライティングを委ねる場合に使用し,D3DVERTEX構造体を用いて定義します。頂点法線は面が向いている方向を表し,トランスフォームしたときに面が視点の方向を向いているときに表示されます。テクスチャ座標はビットマップイメージを貼り付けるときに指定しますが,本章では用いていません。


    TYPE D3DVECTOR
        SEQUENCE
        REAL*4 :: x,y,z	
    END TYPE D3DVECTOR

    TYPE D3DVERTEX
        SEQUENCE
        type (D3DVECTOR) :: p,n  ! p:頂点の同次座標  n:頂点法線の正規座標
        REAL*4 :: tu,tv          ! tu,tv:頂点のテクスチャ座標
    END TYPE D3DVERTEX

type (D3DVERTEX) vSq(4,6)            ! 6面分の頂点
type (D3DVECTOR) n1,n2,n3,n4,n5,n6   ! 6面分の法線ベクトル

!   法線の定義
    n1 = D3DVECTOR( 0.0, 0.0,-1.0)   ! Front face
!   未トランスフォーム&未ライティング頂点(法線ベクトルを含む)の定義
!   Front face
    vSq(1,1) = D3DVERTEX(D3DVECTOR(-1.0, 1.0,-1.0), n1, 0.0, 0.0)
    vSq(2,1) = D3DVERTEX(D3DVECTOR( 1.0, 1.0,-1.0), n1, 0.0, 0.0)
    vSq(3,1) = D3DVERTEX(D3DVECTOR(-1.0,-1.0,-1.0), n1, 0.0, 0.0)
    vSq(4,1) = D3DVERTEX(D3DVECTOR( 1.0,-1.0,-1.0), n1, 0.0, 0.0)

・トランスフォーム済み・ライティング済みの頂点定義

 トランスフォーム済み・ライティング済みの頂点は,トランスフォームとライティングを自分で行う場合に使用し,D3DTLVERTEX構造体を用いて定義します。頂点法線は不要ですが,色とスペキュラ色(色の輝き)の指定が必要になります。


    TYPE D3DTLVERTEX
        SEQUENCE
        REAL*4 :: sx,sy,sz,rhw	
        INTEGER*4 :: color
        INTEGER*4 :: specular
        REAL*4 :: tu,tv	
    END TYPE D3DTLVERTEX

type (D3DTLVERTEX) Sq(4,6)

! === トランスフォーム&ライティング済み頂点(カラーを有する)の定義
!   Front
    Sq(1,1) = D3DTLVERTEX(-1.0, 1.0,-1.0, 0, #ffffffff, 0, 0, 0)
    Sq(2,1) = D3DTLVERTEX( 1.0, 1.0,-1.0, 0, #ffffffff, 0, 0, 0)
    Sq(3,1) = D3DTLVERTEX(-1.0,-1.0,-1.0, 0, #ffffffff, 0, 0, 0)
    Sq(4,1) = D3DTLVERTEX( 1.0,-1.0,-1.0, 0, #ffffffff, 0, 0, 0)

6.11 ライティングとマテリアル

1) ライティング

 未ライティングの頂点フォーマットを用いて定義したオブジェクトは,ライティングをしないと「闇夜のカラス」状態で表示が著しく見づらくなります。ライトにはダイレクトライトとアンビエントライトがあります。ダイレクトライトは,光源からの直接光で常に色と強度を持ち,特定の方向を持っています。アンビエントライトは,自然光と同様に光源も方向も持たず,環境内を散乱している光です。Direct3Dでは,ライティングアルゴリズムを用いて自然光の動きをシミュレートします。また,ライティングはマテリアルと相互作用してオブジェクトの見え方が決まります。


    << indexは光源の番号,x,y,xは光の向き,colorは光の色 >>

INTEGER, PARAMETER :: D3DLIGHT_DIRECTIONAL    = 3      ! 無限遠光源
INTEGER, PARAMETER :: D3DRENDERSTATE_LIGHTING = 137
REAL*4,  PARAMETER :: D3DLIGHT_RANGE_MAX      = 1.0e10

type (D3DCOLORVALUE) color
type (D3DLIGHT7) light

    call ZeroMemory(LOC(light),SIZEOF(light))
    light%dltType      = D3DLIGHT_DIRECTIONAL ! 光源タイプ
    light%diffuse      = color                ! 光源が放出するディフューズ色
    light%specular     = light%diffuse        ! 光源が放出するスペキュラ色
    light%ambient%r    = color%r*0.2          ! 自然光
    light%ambient%g    = color%g*0.2
    light%ambient%b    = color%b*0.2
    light%ambient%a    = color%a*0.2
    light%direction%x  = x                    ! ワールド空間で光が指す方向
    light%direction%y  = y
    light%direction%z  = z
    light%position     = light%direction      ! ワールド空間での光源の位置座標
    light%attenuation0 = 1.0                  ! 光の減衰定数
    light%range        = D3DLIGHT_RANGE_MAX   ! 光源の有効距離

    call XSetLight(lpdv, index, LOC(light), iRes)

    call XLightEnable(lpdv, index, .TRUE., iRes)

!   ダイレクトライトを有効にする(デフォルトは有効)
    call XSetRenderState(lpdv, D3DRENDERSTATE_LIGHTING, .TRUE., iRes)

 ライティングに必要な設定は,D3DLIGHT7構造体に指定します。光源タイプには,D3DLIGHT_DIRECTIONAL(無限遠光源)の他に,D3DLIGHT_POINT(点光源),D3DLIGHT_SPOT(スポットライト)が選択できます。無限遠光源の場合は,position,attenuation,rangeパラメータは意味を持ちません。インターフェイスサブルーチンXSetLightの第2パラメータは,光源が複数ある場合の区別に用いる整数値です。XLightEnableサブルーチンは,それぞれの光源の有効無効を切り替えます。また,D3DRENDERSTATE_LIGHTINGパラメータを指定したXSetRenderStateサブルーチンでは,ダイレクトライトの有効無効を切り替えるときに使用します。

2) マテリアル

 マテリアルはオブジェクトの光の反射特性を表します。ディフューズ反射(方向を持つ光の反射),アンビエント反射(方向を持たない環境光の反射),スペキュラ反射(ハイライト反射),それにエミッション(自発光)を設定することができます。


    TYPE D3DLIGHT_POINT
      SEQUENCE
      TYPE (D3DCOLORVALUE) :: diffuse    ! ディフューズ色
      TYPE (D3DCOLORVALUE) :: ambient    ! アンビエント色
      TYPE (D3DCOLORVALUE) :: specular   ! スペキュラ色
      TYPE (D3DCOLORVALUE) :: emissive   ! エミッション色
      REAL*4 :: power                    ! スペキュラハイライトの鮮明度
    END TYPE D3DMATERIAL7

type (D3DMATERIAL7) mtrl

    call ZeroMemory(LOC(mtrl),SIZEOF(mtrl))
    mtrl%diffuse%r = red
    mtrl%diffuse%g = green
    mtrl%diffuse%b = blue
    mtrl%diffuse%a = 0.0
    mtrl%ambient%r = red
    mtrl%ambient%g = green
    mtrl%ambient%b = blue
    mtrl%ambient%a = 0.0

    call XSetMaterial(lpdv, LOC(mtrl),iRes)

 マテリアルの設定は,D3DLIGHT_POINT構造体に必要な値をセットし,インターフェイスサブルーチンXSetMaterialを実行します。無限遠光源を用いるときは,ディフューズ色とアンビエント色を設定しておきます。

6.12 オブジェクトの描画

1) デバイスの初期化

 描画を開始する前に,レンダリングターゲットであるサーフェイスに関連付けされたデバイスをクリアします。併せて,Zバッファの値を初期化します。Zバッファの値は[0.0,1.0]の範囲で,通常は最も大きな値1.0を設定します。1.0より小さな値を指定し,一度作成した背景をクリアしないようなこともできそうです。


INTEGER, PARAMETER :: D3DCLEAR_TAR_ZBU    = 3

!   デバイスをクリアし,Zバッファの値を1.0に初期化する
    call XClear(lpdv,0,NULL,D3DCLEAR_TAR_ZBU,#00000000,1.0,0,iRes)

 インターフェイスサブルーチンXClearの第2パラメータに0,第3パラメータにNULLを指定すると,サーフェイス全体をクリアします。第4パラメータに,D3DCLEAR_TARGETとD3DCLEAR_ZBUFFERの両者を指定すると,第5パラメータで指定した色でターゲットをクリアし,第6パラメータで指定したZ値でZバッファをクリアします。第7パラメータはステンシルバッファを用いるときのエントリに保存する整数値ですが,今回は使用しません。

2) 描画の開始と終了

 BeginScene関数でオブジェクトの描画を開始し,EndScene関数で描画を終了します。Windows APIのBeginPaint関数とEndPaint関数に類似しています。


!   描画開始
    call XBeginScene(lpdv, iRes)

!   << この間に描画命令を記述する >>

!   描画終了
    call XEndScene(lpdv, iRes)

 インターフェイスサブルーチンXBeginSceneで描画を開始し,XEndSceneで描画を終了します。

3) 描画命令

 三角形の集合体であるプリミティブを描画するには,DrawPrimitive関数を用います。トランスフォームとライティングをDirectXに依存する場合には,マテリアルを設定しないとオブジェクトが見づらくなります。


INTEGER, PARAMETER :: D3DPT_TRIANGLESTRIP = 5	
INTEGER, PARAMETER :: D3DFVF_VERTEX       = 274
INTEGER, PARAMETER :: D3DFVF_TLVERTEX     = 452
INTEGER, PARAMETER :: D3DDP_WAIT          = 1

!   Objectのマテリアルを設定(D3DFVF_VERTEXで描画するオブジェクトに有効)
    call Material(hWnd, 0.0, 1.0, 0.0)  ! R,G,B

!   プリミティブの描画(DirectXでトランスフォームとライティングをする場合)
    call XDrawPrimitive(lpdv,D3DPT_TRIANGLESTRIP,D3DFVF_VERTEX,LOC(vSq(1,I)),4,D3DDP_WAIT,iRes)

!   プリミティブの描画(手動でトランスフォームとライティングをする場合)
    call XDrawPrimitive(lpdv,D3DPT_TRIANGLESTRIP,D3DFVF_TLVERTEX,LOC(xSq(1,I)),4,D3DDP_WAIT,iRes)

 インターフェイスサブルーチンXDrawPrimitiveの第2パラメータD3DPT_TRIANGLESTRIPは,三角形ストリップを用いることを指定します。第4パラメータのD3DFVF_VERTEXは,第5パラメータが未トランスフォーム・未ライティングの頂点フォーマットデータが指定されていることを指定します。第6パラメータは,指定された頂点フォーマットデータの数,第7パラメータのD3DDP_WAITは,レンダリングが終了するまで制御を戻さないというフラグです。

6.13 プログラムのサンプルと使用法

・プログラム例

・プログラムの操作方法

 サンプルプログラムをそのまま翻訳し実行すると,ウィンドウモードで立方体を表示します。表示された立方体はライティングとトランスフォームをDirect3Dで処理します。左右の矢印キーを押すと,視点位置がY軸を中心に左右に回転し,上下の矢印キーを押すと視点位置が上下に回転します。PF1,PF2キーは視点位置が+-X軸方向に移動します。PF3,PF4キーは視点位置が+-Y軸方向に移動します。PF12キーを押すと,フルスクリーンモードとウィンドウモードが切り替わります。プログラムを変更(Disp1=>Disp2)すると,視点位置の回転・移動でなくオブジェクトの回転・移動を行うことができます。また,Paintセクションの描画プリミティブを変更し,手動でトランスフォームとライティングをする頂点フォーマットを選択することができます。

6.14 参考資料

1) コンピュータディスプレイによる図形処理工学,山口富士夫著,日刊工業新聞社
2) コンピュータグラフィックス,山口富士夫著,日刊工業新聞社
3) Platform SDK ヘルプファイル,Microsoft
4) DirectX SDK ヘルプ, Microsoft http://www.microsoft.com/japan/msdn/directx/default.asp

目次 次の項目

7. テクスチャを貼った立方体の3D表示
(DirectX8を使用)



7.1 テクスチャ

 私のマイノートパソコンもようやくWindows XPにリプレースしました。グラフィックボードNVIDIA GeForce4が内蔵されており3Dグラフィックスが高速化できるようになりました。というわけで,前章のプログラムをDirectX8.1に移行し,テクスチャを用いる場合について紹介します。テクスチャとは簡単に言うとビットマップ画像をポリゴンに貼り付けることです。テクスチャを用いることで,よりリアルな3Dグラフィックスを実現することができます。本章ではDirectX8.1を用いましたが,現時点(2003年5月)では既にDirectX9.0がMicrosoftから提供されています。DirectX8はDirectX7(IM)と比較すると初期化処理が随分と簡単になりました。ただし,前章のプログラムをDirectX8に移行すると,3Dグラフィックスのハードウェアが使用できない場合には,DirectX7に比べてレンダリングの速度が3倍くらい遅くなっています。なお,DirectX8を用いるにはDirectX8以降のSDKを入手する必要があります。

図7.1 テクスチャを貼った立方体の表示

7.2 DirectX8の使用準備

 DirectX8以降のSDKをMicrosoftから入手し,Visual Studioのプロジェクトの設定でd3d8.libをリンクライブラリに指定します。D3DX...で始まるユーティリティを使用する場合は,d3dx8.libも同時に指定します。本章では,テクスチャを読み込むのにこのユーティリティを用います。指定方法の詳細は第2章 DirectXの使用準備を参照してください。

 コンパイルリンク時に次のような警告メッセージが出力される場合があります。

 LINK : warning LNK4089: all references to "GDI32.dll" discarded by /OPT:REF

 この警告は無視しても差し支えありませんが,気になる場合はLINKのパラメータに以下のパラメータを追加してください。

 /IGNORE:4089

7.3 DirectXGraphicsの初期化

 DirectX8からはDirectDrawとDirect3Dが統合され,DirectXGraphicsとなりました。DirectXを使用するには初期化処理が必要です。DirectX8のDirectXGraphicsを用いるための初期化処理は,前章で紹介したDirectX7のDirect3Dを用いる場合とは大幅に変更になりました。
 ウィンドウを作成するなどの前処理に必要な手続きは前章と同じですので,ここでは省略します。

・DirectX3D8インターフェイスオブジェクトの取得


integer, parameter :: D3D_SDK_VERSION    = 220 ! <== d3d8.h

integer lpdd     ! (LPDIRECTDRAW8) DirectX3D8 object pointer

!   DirectX3D8インターフェイス オブジェクトの取得
    call XDirect3DCreate8(D3D_SDK_VERSION,lpdd)
    if (lpdd == NULL) return

 DirectXGraphicsを使用するには,Direct3DCreate8という関数を実行し,DirectX3D8インターフェイスオブジェクトを取得します。第1パラメータには,DirectXのバージョンを指定します。バージョン番号は,インストールしたDirectXのSDKインクルードファイルd3d8.hを参照します。関数が成功すると,第2パラメータにDirectX3D8インターフェイスオブジェクトのアドレスがセットされます。

・現在のディスプレイモードの取得


integer, parameter :: D3DADAPTER_DEFAULT    = 0

    TYPE D3DDISPLAYMODE
        INTEGER*4 :: Width
        INTEGER*4 :: Height
        INTEGER*4 :: RefreshRate
        INTEGER*4 :: Format    ! D3DFORMAT
    END TYPE D3DDISPLAYMODE

type (D3DDISPLAYMODE) disp_mode

!   現在のディスプレイモードを取得する
    call XGetAdapterDisplayMode(lpdd,D3DADAPTER_DEFAULT,LOC(disp_mode),iRes)
    if (iRes /= DD_OK) return

 Windowモードでアプリケーションを立ち上げる場合には,カラーモードを一致させる必要があります。現在のディスプレイモードが256色パレットモードのときに24ビットカラーモードでアプリケーションを動かすことはできません。そのため現在のディスプレイモードを取得しておきます。

・Direct3Dデバイスオブジェクトの作成


    TYPE D3DPRESENT_PARAMETERS        ! <== d3d8types.h
        INTEGER*4 :: BackBufferWidth
        INTEGER*4 :: BackBufferHeight
        INTEGER*4 :: BackBufferFormat ! D3DFORMAT
        INTEGER*4 :: BackBufferCount
        INTEGER*4 :: MultiSampleType  ! D3DMULTISAMPLE_TYPE
        INTEGER*4 :: SwapEffect       ! D3DSWAPEFFECT
        INTEGER*4 :: hDeviceWindow
        LOGICAL*4 :: Windowed
        LOGICAL*4 :: EnableAutoDepthStencil
        INTEGER*4 :: AutoDepthStencilFormat  ! D3DFORMAT
        INTEGER*4 :: Flags
    ! Following elements must be zero for Windowed mode
        INTEGER*4 :: FullScreen_RefreshRateInHz
        INTEGER*4 :: FullScreen_PresentationInterval
    END TYPE D3DPRESENT_PARAMETERS

type (D3DPRESENT_PARAMETERS) pp

!   Direct3Dデバイスの作成(IDirect3D8インターフェイス)
    call ZeroMemory(LOC(pp),SIZEOF(pp))
    pp%SwapEffect             = D3DSWAPEFFECT_DISCARD
    pp%BackBufferFormat       = disp_mode%Format      ! ディスプレイと等価
    pp%BackBufferWidth        = grect%right           ! フルスクリーンモード時必須
    pp%BackBufferHeight       = grect%bottom          !
    pp%EnableAutoDepthStencil = .TRUE.                ! Zバッファを使用する
    pp%AutoDepthStencilFormat = D3DFMT_D16
    if (gStyle == StyleWindowed) pp%Windowed = .TRUE. ! ウィンドウモード時 TRUE
    do j = 1,2     ! lpdv : IDirect3DDevice8インターフェイスへのポインタ
      call XCreateDevice(lpdd, D3DADAPTER_DEFAULT, D3DDEVTYPE(j), hWnd, & 
           D3DCREATE_SOFTWARE_VERTEXPROCESSING, LOC(pp), LOC(lpdv), iRes)
      if (iRes == DD_OK) exit
    end do
    if (iRes /= DD_OK) return

 CreateDevice関数を実行し,Direct3Dデバイスオブジェクトを作成します。オブジェクトを作成するのに必要な情報をD3DPRESENT_PARAMETERS構造体にセットします。セットする主なパラメータは以下のとおりです。

SwapEffect 画面のスワップ方法を指定します。D3DSWAPEFFECT_DISCARDは,最適なスワップ方法をディスプレイドライバが選択します。
BackBufferFormat バックバッファのフォーマットを指定します。ディスプレイと等価にするため,先に取得したディスプレイモードのFormatを設定します。
EnableAutoDepthStencil 3Dの処理を行うのでTRUEを指定してZバッファを有効にします。
AutoDepthStencilFormat ステンシルフォーマットを設定します。例ではD3DFMT_D16を指定し,16ビットのZバッファビット深度を設定しています。
Windowed ウィンドウモードで実行するときは,TRUEにします。
BackBufferWidth
BackBufferHeight
フルスクリーンモードで実行するときは,バックバッファのサイズを必ず指定します。

 CreateDevice関数は,文字通りデバイスを定義します。第1パラメータは,ディスプレイアダプタの定義です。通常D3DADAPTER_DEFAULTでプライマリアダプタが選択されます。 第2パラメータは,デバイスタイプの定義です。D3DDEVTYPE_HALでHAL(ハードウェアデバイス)を,D3DDEVTYPE_REFでHELを使用します。取り敢えずHALで試し,関数が不成功のときはHELでリトライするようにします。 第3パラメータは,フォーカスウィンドウのWindowハンドルを指定します。 第4パラメータは,デバイスの全体的な動作を示すフラグで,主なパラメータは以下のとおりです。

 D3DCREATE_HARDWARE_VERTEXPROCESSING:頂点処理をハードウェアで行う。
 D3DCREATE_MIXED_VERTEXPROCESSING  :頂点処理をソフトウェアとハードウェア両方で行う。
 D3DCREATE_SOFTWARE_VERTEXPROCESSING:頂点処理をDirectXのソフトウェアで行う。

第5パラメータは,D3DPRESENT_PARAMETERS構造体のアドレス。第6パラメータは,デバイスオブジェクトのインターフェイスアドレスが格納される変数のアドレスです。この変数はグローバル変数にしておく方が便利です。

 基本的な初期設定は以上です。後は前章でも行ったビューポートの設定を行います。また,SetRenderState関数を用いて,Zバッファを有効に設定するなど必要に応じて各種の設定を行います。

7.4 テクスチャの作成と表示

 テクスチャは,ポリゴンモデルに貼り付けるビットマップ画像を言います。テクスチャは予め用意した画像ファイルをD3DXのユーティリティを用いて読み込んだり,プログラムで計算によって作り出したりすることができます。本章では最も簡単なD3DXCreateTextureFromFileという関数を用いてファイルから読み込む方法を使用します。

 テクスチャを作成するには適当なCGソフトを用いて,BMPやJPG形式の画像ファイルを作成します。使用できるテクスチャの数はグラフィックスハードウェアによって制限があります。図7.2に示すように,複数の面を1枚の画像にまとめて作成すると便利です。

図7.2 テクスチャの例

・テクスチャの読み込み


integer lpTex  ! (LPDIRECT3DTEXTURE8)     IDirect3DTexture8 object pointer

!   テクスチャを指定したファイルから読み込む
    iRes = D3DXCreateTextureFromFile(lpdv, LOC("texture2.bmp"C), LOC(lpTex))

!   テクスチャオブジェクトをDirect3DDeviceにセット
    call XSetTexture(lpdv, 0, lpTex, iRes) ! テクスチャステージ0にセット

 D3DXライブラリのD3DXCreateTextureFromFile関数を用いて,テクスチャをファイルから読み込みます。第2パラメータに読み込むビットマップファイルのファイル名のアドレスを指定します。第3パラメータには,テクスチャオブジェクトのアドレスを受け取る変数のアドレスを指定します。テクスチャオブジェクトも他のオブジェクトと同様に不要になったときはRelease関数を実行して解放します。
 SetTexture関数を実行して,読み込んだテクスチャをデバイスにセットします。第2パラメータには,使用するテクスチャステージの番号を指定します。テクスチャは最大8セット使用できるので,ステージ番号は0〜7が指定できます。第3パラメータにはセットするテクスチャオブジェクトのアドレスを指定します。

・テクスチャのブレンド方法の指定


!   テクスチャの色をそのまま使用する場合
    call XSetTextureStageState(lpdv,0,D3DTSS_COLOROP,  D3DTOP_SELECTARG1,iRes)
    call XSetTextureStageState(lpdv,0,D3DTSS_COLORARG1,D3DTA_TEXTURE,iRes)

!   テクスチャブレンディングを無効にする場合
    call XSetTextureStageState(lpdv,0,D3DTSS_COLOROP, D3DTOP_DISABLE,iRes)

 SetTextureStageState関数は,幾つか組み合わせてテクスチャの表示方法を設定します。デフォルトでは,テクスチャの色と頂点の色が掛け算されて表示されます。第2パラメータはステージ番号。第3パラメータはテクスチャステージステートを指定します。D3DTSS_COLOROPはそのステージでのブレンド方法を指定しています。第4パラメータは第3パラメータに対する設定値を指定します。テクスチャの色をそのまま表示するときは,上の例のように用います。テクスチャブレンディングを行わない(テクスチャの貼り付けをしない)ときは,下の例のように指定します。

・頂点座標とテクスチャ座標の指定


    TYPE D3DVERTEX
        SEQUENCE
        type (D3DVECTOR) :: p,n ! p:頂点の同次座標  n:頂点の正規座標
        REAL*4 :: tu,tv         ! tu,tv:頂点のテクスチャ座標
    END TYPE D3DVERTEX

    TYPE D3DTLVERTEX
        SEQUENCE
        REAL*4 :: sx,sy,sz,rhw  ! 頂点(0.0<sz<1.0),rhw(同時wの逆数)
        INTEGER*4 :: color      ! diffuse color
        INTEGER*4 :: specular   ! specular color
        REAL*4 :: tu,tv         ! テクスチャ座標
    END TYPE D3DTLVERTEX

    type (D3DVERTEX)   vSq(4,8)
    type (D3DTLVERTEX) gSq(4,8)
    type (D3DVECTOR)   n1

!   法線ベクトルの定義
    n1 = D3DVECTOR( 0.0, 0.0,-1.0)   ! Front face

!   未トランスフォーム&未ライティング頂点(法線ベクトルを含む)の定義
!   Front face
    vSq(1,1) = D3DVERTEX(D3DVECTOR(-1.0, 1.0,-1.0),n1,0.00,0.0)
    vSq(2,1) = D3DVERTEX(D3DVECTOR( 1.0, 1.0,-1.0),n1,0.33,0.0)
    vSq(3,1) = D3DVERTEX(D3DVECTOR(-1.0,-1.0,-1.0),n1,0.00,0.5)
    vSq(4,1) = D3DVERTEX(D3DVECTOR( 1.0,-1.0,-1.0),n1,0.33,0.5)

!   トランスフォーム&ライティング済み頂点(色つき)の定義
!   Front face
    gSq(1,1) = D3DTLVERTEX(-1.0, 1.0,-1.0, 0,#ffffffff,0,0.00,0.0)
    gSq(2,1) = D3DTLVERTEX( 1.0, 1.0,-1.0, 0,#ffffffff,0,0.33,0.0)
    gSq(3,1) = D3DTLVERTEX(-1.0,-1.0,-1.0, 0,#ffffffff,0,0.00,0.5)
    gSq(4,1) = D3DTLVERTEX( 1.0,-1.0,-1.0, 0,#ffffffff,0,0.33,0.5)

 頂点フォーマットの定義は,DirectX8では利用者がカスタマイズして作成できるようになりました。例ではDirectX7で用意されていた頂点フォーマットを再定義して用いています。D3DVERTEXはライティングとトランスフォームをDirectXに依存する定義です。D3DTLVERTEXはライティングとトランスフォームを自分で実行する場合など,DirectXに依存しない定義の方法です。DirectXはrhwがある場合には,座標値はスクリーン座標とみなしトランスフォームを実行しません。ライティングをDirectXに依存する場合には,法線ベクトルが必要です。
 両者ともテクスチャのUV座標(tu,tv)を含んでいます。UV座標はテクスチャの左上を基準に横方向と縦方向の座標で表し,用意したテクスチャに対して実数値(0.0〜1.0)の範囲で表現します。例では,立方体の前面の4つの頂点に対応するテクスチャの座標を指定しています(他の面の定義は省略しています)。

・テクスチャを貼ったプリミティブの表示


!   デバイスをクリアし,Zバッファの値を1.0に初期化する
    call XClear(lpdv,0,NULL,D3DCLEAR_TAR_ZBU,0,1.0,0,iRes)

!   描画開始
    call XBeginScene(lpdv, iRes)

!   テクスチャブレンディングを積にする
    call XSetTextureStageState(lpdv,0, D3DTSS_COLOROP, D3DTOP_MODULATE, iRes)

!   頂点フォーマットを指定
    call XSetVertexShader(lpdv,D3DFVF_TLVERTEX,iRes)

!   プリミティブの描画
    ISZ = SIZEOF(vSq(1,1))
    do I = 1,6
     call XDrawPrimitiveUP(lpdv,D3DPT_TRIANGLESTRIP,2,LOC(vSq(1,I)),ISZ,iRes) 
    end do

!   描画終了
    call XEndScene(lpdv, iRes)

!   画面を表示する
    call XPresent(lpdv,NULL,NULL,NULL,NULL,iRes)

 Clear関数は,表示デバイスをクリアし,Zバッファの値を1.0に初期化します。描画の開始はBeginScene関数を用います。ここはDirectX7と同様です。
 今回はテクスチャを貼るので,SetTextureStageState関数を用いてカラーブレンディングを有効にすると同時に既定の色とテクスチャの色を積算して表示色を決めるように設定しています。この値はデフォルトになっていますので,とくにテクスチャブレンディングを無効にしない限り設定する必要はありません。
 プリミティブの描画関数を実行する前に,SetVertexShader関数を用いて描画関数で使用する頂点データのフォーマットを設定します。
 プリミティブを描画する方法はいくつかあります。最も基本的なものは,頂点バッファやインデックスバッファを使わないで,直接頂点データを指定して描画するDrawPrimitiveUP関数を用いる方法です。
 DrawPrimitiveUP関数の第2パラメータはプリミティブの種類。第3パラメータはプリミティブの数。第4パラメータは頂点データのアドレス。第5パラメータは頂点データのストライド(1頂点データのバイト数)を指定します。
 描画の終了はEndScene関数を用います。
 描画したデバイスの内容を画面に表示するには,Present関数を用います。D3DPRESENT_PARAMETERSで指定した方法で表示されるので,DirectX7のようにブリットかフリップかに関わらずこの関数を用います。

7.5 頂点バッファと頂点インデックスの使用

 今までポリゴンの頂点データは,プログラムで用意したシステムメモリ領域に作成していました。 しかし,ビデオメモリやAGPメモリはシステムメモリより高速にアクセスできるので,頂点データをこのような領域に置くことができれば表示速度を高速化できると思われます。DirectXでは,頂点バッファと呼ばれる方法によって,これを実現しています。
 また,頂点バッファ内の各頂点データを参照する場合に,そのロケーションをインデックスによって参照できればメモリの利用効率が向上します。これを実現するのが頂点インデックスと呼ばれるものです。

・頂点バッファの作成


!   頂点バッファの作成
    call XCreateVertexBuffer(lpdv,SIZEOF(vSq),0,D3DFVF_TLVERTEX,D3DPOOL_DEFAULT,LOC(lpvb),iRes)
!   頂点バッファをデバイスのデータストリームにバインド
    call XSetStreamSource(lpdv,0,lpvb,SIZEOF(vSq(1,1)),iRes)

 頂点バッファは,CreateVertexBuffer関数を用いて作成します。第2パラメータには作成するバッファの大きさをバイト数で指定します。第3パラメータはリソースの使用方法。第4パラメータはバッファ内の頂点フォーマットを記述するFVFコード。第5パラメータはリソースのバッファを保持するメモリクラスを指定します。D3DPOOL_DEFAULTは,リソースに対して要求された使用方法に最適なメモリプール(ビデオメモリが利用できなければシステムメモリ)に置かれます。第6パラメータはIDirect3DVertexBuffer8インターフェイスオブジェクトのポインタのアドレスを指定します。関数が成功すると,ポインタに作成された頂点バッファリソースを表すIDirect3DVertexBuffer8インターフェイスオブジェクトのアドレスが出力されます。得られたインターフェイスが不要になったときは,他のオブジェクトと同様にRelease関数を用いて解放します。

 SetStreamSource関数は,頂点バッファをデバイスのデータストリームに関連付けします。第2パラメータは頂点バッファを設定するストリーム番号を指定します。第3パラメータはCreateVertexBuffer関数で得られた頂点バッファを示すポインタを指定します。第4パラメータは頂点データ1つ分の大きさをバイト単位で指定します。

・頂点バッファにデータを転送


!   頂点バッファをロックし,バッファアドレス取得
    ISZ = SIZEOF(vSq)
    call XLock(lpvb,0,ISZ,LOC(lpVertices),0,iRes)

!   頂点データを頂点バッファに転送
    call CopyMemory(lpVertices,LOC(vSq),ISZ)

!   頂点バッファのアンロック
    call XUnlock(lpvb,iRes)

 頂点バッファにデータを転送するには,IDirect3DVertexBuffer8インタフェイスのLock関数を用いて頂点バッファのデータ範囲をロックし,頂点バッファのアドレスを取得します。
 Lock関数の第2パラメータはロックする頂点データのオフセット。第3パラメータはロックする頂点データのサイズ (バイト単位)。第4パラメータは値を返されるポインタ変数のアドレスで,関数が成功すると頂点バッファのアドレスが返されます。第5パラメータはロックの方法を表すフラグを必要に応じて指定します。

 データの転送にはWindows APIのCopyMemory関数を使用しています。

 データ転送が終了したら,Unlock関数を用いてロックを解除します。

・頂点バッファの内容を描画(インデックスバッファを用いない場合)


!   描画開始
    call XBeginScene(lpdv,iRes)
 
!   Objectのマテリアルを設定(D3DFVF_VERTEXで描画するオブジェクトに有効)
    call Material(hWnd,0.8,0.8,0.8)  ! R,G,B

!   頂点フォーマットを指定 (UnLighted,UnTransformed)
    call XSetVertexShader(lpdv,D3DFVF_VERTEX,iRes) 

!   テクスチャブレンディングを積にする
    call XSetTextureStageState(lpdv,0,D3DTSS_COLOROP,D3DTOP_MODULATE,iRes)

!   データ入力ストリームセットからプリミティブをレンダリング
    do i = 0,31,4
      call XDrawPrimitive(lpdv,D3DPT_TRIANGLESTRIP,i,2,iRes)
    end do

!   描画終了
    call XEndScene(lpdv,iRes)

!   画面を表示
    call XPresent(lpdv,NULL,NULL,NULL,NULL,iRes)

 頂点バッファの内容をレンダリングするには,DrawPrimitive関数を用います。第2パラメータはプリミティブの種類。第3パラメータはレンダリングする頂点データの開始位置を示す頂点バッファ内のインデックス。第4パラメータはレンダリングするプリミティブの数を指定します。

・頂点インデックスとインデックスバッファの作成


! 頂点インデックスの定義(8四辺形分)
integer*2 Vi(48)/0,1,2,2,1,3, 4,5,6,6,5,7, 8,9,10,10,9,11, 12,13,14,14,13,15, &
          16,17,18,18,17,19, 20,21,22,22,21,23, 24,25,26,26,25,27, 28,29,30,30,29,31/

!   頂点インデックスバッファの作成
    ISZ = SIZEOF(Vi)
    call XCreateIndexBuffer(lpdv,ISZ,D3DUSAGE_WRITEONLY,D3DFMT_INDEX16, &
                            D3DPOOL_DEFAULT,LOC(lpix),iRes)
!   頂点インデックスバッファのロック,バッファアドレス取得
    call XLock(lpix,0,ISZ,LOC(lpIndex),0,iRes)

!   頂点インデックスデータを頂点インデックスバッファに転送
    call CopyMemory(lpIndex,LOC(Vi),ISZ)

!   頂点インデックスバッファのアンロック
    call XUnlock(lpix,iRes)

!   インデックスデータの設定
    call XSetIndices(lpdv,lpix,0,iRes)

 立方体の一面をなす四角形は2つの三角形で構成されます。この時,三角形は3つの頂点で構成されるため,四角形を描画するには6つの頂点が必要になります。しかし,もともと四角形は4つの頂点からなるので,6つの頂点のうち2つは同じ頂点が重複していることになります。
 頂点データをインデックスで修飾することにより,D3DPT_TRIANGLELISTによってシェーディングする場合には,三角形ごとに同じ頂点データを複数用意する必要がなくなります。

 頂点インデックスバッファの作成は,頂点バッファの作成と類似しています。頂点インデックスバッファを作成するには,CreateIndexBuffer関数を用います。第2パラメータには作成するバッファの大きさをバイト数で指定します。第3パラメータにはリソースの制御方法。第4パラメータにはバッファのフォーマット(インデックス1つの大きさ)を指定します。D3DFMT_INDEX16 又は D3DFMT_INDEX32 のいずれかを指定します。たいていは16ビットで十分でしょう。第5パラメータはバッファのメモリクラス。第6パラメータはIDirect3DIndexBuffer8インターフェイスへのポインタのアドレスを指定します。関数が成功すると,ポインタに作成された頂点インデックスバッファリソースを表すIDirect3DIndexBuffer8インターフェイスのアドレスが出力されます。得られたインターフェイスが不要になったときは,頂点バッファと同様にRelease関数を用いて解放します。

 頂点インデックスバッファにデータを転送するには,IDirect3DIndexBuffer8インターフェイスのLock関数を用いて頂点インデックスバッファのデータ範囲をロックし,頂点インデックスバッファのアドレスを取得します。
 Lock関数の第2パラメータはロックするインデックスバッファのオフセット。第3パラメータはロックするインデックスデータのサイズ (バイト単位)。第4パラメータは値を返されるポインタ変数のアドレスで,関数が成功するとインデックスバッファのアドレスが返されます。第5パラメータはインデックスバッファメモリのロック方法を表すフラグを必要に応じて指定します。

 データの転送にはWindows APIのCopyMemory関数を使用しています。

 データ転送が終了したら,Unlock関数を用いてロックを解除します。

 インデックスバッファへのデータ転送は,頂点データの値が変わってもデータ構造が変わらない限り一度行えば再転送する必要はありません。

 SetIndices関数を用いてデバイスに対してインデックスデータの設定を行います。第2パラメータには設定するインデックスデータを表すIDirect3DIndexBuffer8インターフェイスオブジェクトのポインタを指定します。第3パラメータには頂点インデックスのオフセット値を指定します。

・頂点インデックスを用いたプリミティブの描画

!   描画開始
    call XBeginScene(lpdv,iRes)
 
!   Objectのマテリアルを設定(D3DFVF_VERTEXで描画するオブジェクトに有効)
    call Material(hWnd,0.8,0.8,0.8)  ! R,G,B

!   頂点フォーマットを指定 (UnLighted,UnTransformed)
    call XSetVertexShader(lpdv,D3DFVF_VERTEX,iRes) 

!   データ入力ストリームセットからプリミティブをレンダリング
    call XDrawIndexedPrimitive(lpdv,D3DPT_TRIANGLELIST,0,30,0,10,iRes)

!   描画終了
    call XEndScene(lpdv,iRes)

!   画面の表示
    call XPresent(lpdv,NULL,NULL,NULL,NULL,iRes)

 頂点インデックスを用いたプリミティブの描画には,DrawIndexedPrimitive関数を用います。第2パラメータはプリミティブの種類。第3パラメータは使用する頂点インデックスの開始位置を示すインデックス。第4パラメータは使用する頂点インデックスの数,第5パラメータは使用するインデックスの範囲の中で読み取りを開始する位置。第6パラメータはレンダリングするプリミティブの数を指定します。例では,立方体の底面を除く5面を描画しています。

7.6 プログラムのサンプル

・プログラム例

・プログラムの変更

 サンプルプログラムは,前章のプログラムをDirectX8.1に移行し,テクスチャを貼った立方体を3D表示します。表示部分の回転操作は前章と同じです。このプログラムのソースの一部(Paintサブルーチン内のcall文)を変更することで,頂点バッファを用いないで表示する場合,頂点バッファを用いる場合,頂点バッファと頂点インデックスを用いて表示する場合などを選択できるようにしています。


目次 次の項目

8. Xファイルの表示
(モデラで作成した3D画像の表示)



8.1 Xファイル

 Xファイルは,Direct3Dにおけるメッシュモデルデータの標準フォーマットファイルです。Xファイルを用いると3Dモデラで作成したオブジェクトの表示が容易になります。普及している3Dモデラの多くは,Xファイルの形式でデータを入出力できます。Xファイルには,以下のような情報を含めることができますが,どの情報を含むかは任意に決めることができます。

1) 頂点位置
2) 頂点インデックス
3) 頂点法線
4) 頂点マテリアル
5) 頂点 UV 座標
6) テクスチャ ファイル名
7) 動画情報

 ただし,テクスチャはイメージデータそのものを含むのではなく,ファイル名だけしか記述できません。

 3Dモデルのなかでも人物のベアモデルは最も難易度の高いものと言えるでしょう。以下に示すサンプルは,フリーソフトのMetasequoiaを用いてモデリングした3DのメッシュデータをXファイル形式で出力し,DirectX8.1のDirect3Dを用いて作成したプログラムで表示したものです。3Dの座標計算など基本的なことは前章で述べましたが,今回はモデリングに時間がかかってしまいました。

図8.1 Xファイル(3Dモデル)の表示

 作成したプログラムでは,マウスを用いて視点の移動・拡大・縮小・回転などを3Dのビューア並にできるようにしました。

 さて,Xファイルの描画は基本的には,1) Xファイルのロード,→ 2) メッシュデータの格納,→ 3) メッシュの描画,→ 4) メッシュ領域の解放,というステップを踏みます。ただし,Xファイルの描画そのものは極めて簡単ですが,入力したメッシュデータを加工したり動かしたりすることを考慮したプログラムを作成しようとすると少々面倒になります。目標とするプログラムでは,3Dモデルが動いたり会話したりできることですが,本章のプログラムではXファイル形式の3Dモデルを表示することを行います。

8.2 Xファイルのロード


!*********************************************************************
!    InitRender Subroutine    (オブジェクトの読み込み)
!*********************************************************************
subroutine InitRender(hWnd, iRes)
use WINCOM
implicit integer*4(d)
integer hWnd

!DEC$ ATTRIBUTES STDCALL, ALIAS : '_D3DXLoadMeshFromX@28' :: D3DXLoadMeshFromX
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_D3DXCreateTextureFromFileA@12' :: D3DXCreateTextureFromFile
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_D3DXGetFVFVertexSize@4' :: D3DXGetFVFVertexSize
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_D3DXComputeNormals@8' :: D3DXComputeNormals
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_D3DXSaveMeshToX@24' :: D3DXSaveMeshToX

! Mesh options - lower 3 bytes only, upper byte used by _D3DXMESHOPT option flags
INTEGER, PARAMETER :: D3DXMESH_SYSTEMMEM   = #00000110 ! D3DXMESH_VB_SYSTEMMEM,D3DXMESH_IB_SYSTEMMEM
INTEGER, PARAMETER :: D3DXMESHOPT_ATTRSORT = #02000000
INTEGER, PARAMETER :: D3DPT_TRIANGLELIST   = 4
INTEGER, PARAMETER :: D3DPT_TRIANGLESTRIP  = 5
INTEGER, PARAMETER :: D3DPT_TRIANGLEFAN    = 6

! Custom FVF
INTEGER, PARAMETER :: D3DFVF_VERTEX        = #00000112 ! XYZ,NORMALS,TEXT1
INTEGER, PARAMETER :: D3DFVF_NORMAL        = #00000010
! D3DTEXTURESTAGESTATETYPE
INTEGER, PARAMETER :: D3DTSS_COLOROP       = 1 ! カラーブレンディング処理
INTEGER, PARAMETER :: D3DTSS_COLORARG1     = 2 ! 最初の色引数
INTEGER, PARAMETER :: D3DTSS_COLORARG2     = 3 ! 2番目の色引数
INTEGER, PARAMETER :: D3DTSS_ALPHAOP       = 4 ! アルファブレンディング処理
INTEGER, PARAMETER :: D3DTSS_ALPHAARG1     = 5 ! 最初のアルファ引数
INTEGER, PARAMETER :: D3DTSS_ALPHAARG2     = 6 ! 2番目のアルファ引数
! D3DTEXTUREOP
integer, parameter :: D3DTOP_DISABLE       = 1  ! ステージ処理を無効にする
integer, parameter :: D3DTOP_SELECTARG1    = 2  ! ステージの最初の色を出力
integer, parameter :: D3DTOP_SELECTARG2    = 3  ! ステージ2番目の色を出力
integer, parameter :: D3DTOP_MODULATE      = 4  ! 引数の成分を乗算
integer, parameter :: D3DTOP_ADD           = 7  ! 引数の成分を加算
integer, parameter :: D3DTOP_BLENDDIFFUSEALPHA = 12 ! arg1*A + arg2*(1-A)
integer, parameter :: D3DTOP_DOTPRODUCT3   = 24 ! Srgba = r1*r2 + g1*g2 + b1*b2 
! argument flag
integer, parameter :: D3DTA_DIFFUSE        = 0  ! diffuse color
integer, parameter :: D3DTA_CURRENT        = 1  ! stage destination register
integer, parameter :: D3DTA_TEXTURE        = 2  ! texture color
integer, parameter :: D3DTA_SPECULAR       = 4  ! specular colo
! DXFILEFORMAT
integer, parameter :: DXFILEFORMAT_BINARY     = 0
integer, parameter :: DXFILEFORMAT_TEXT       = 1
integer, parameter :: DXFILEFORMAT_COMPRESSED = 2

    TYPE D3DMATERIAL8
      SEQUENCE
      TYPE (D3DCOLORVALUE) :: diffuse
      TYPE (D3DCOLORVALUE) :: ambient
      TYPE (D3DCOLORVALUE) :: specular
      TYPE (D3DCOLORVALUE) :: emissive
      REAL*4 :: power
    END TYPE D3DMATERIAL8

    TYPE D3DXMATERIAL
      sequence
      TYPE (D3DMATERIAL8) :: MatD3D 
      INTEGER :: lpTextureFilename
    END TYPE D3DXMATERIAL

type (D3DXMATERIAL) d3dxMaterials(1)
pointer (lpd3dxMaterials, d3dxMaterials)

type (D3DXMATERIAL) ,allocatable, save :: MeshMaterials(:)

    TYPE D3DXATTRIBUTERANGE
      sequence
      INTEGER :: AttribId    ! 属性テーブルの識別子
      INTEGER :: FaceStart   ! 開始面
      INTEGER :: FaceCount   ! 面の数
      INTEGER :: VertexStart ! 開始頂点
      INTEGER :: VertexCount ! 頂点の数
    END TYPE D3DXATTRIBUTERANGE

type (D3DXATTRIBUTERANGE) ,allocatable, save :: SubsetTable(:)

integer lpMesh,lpMeshOpt ! (LPD3DXMESH)         ID3DXMesh interface object pointer
integer, allocatable, save :: lpMeshTex(:)      ! (LPDIRECT3DTEXTURE8)

integer lpD3DXMtrlBuffer ! (LPD3DXBUFFER)            ID3DXBuffer interface object pointer
integer lpIxBuf          ! (LPDIRECT3DINDEXBUFFER8)  IDirect3DIndexBuffer8 object pointer
integer lpVxBuf          ! (LPDIRECT3DVERTEXBUFFER8) IDirect3DVertexBuffer8 object pointer
integer FVFfmt           ! FVF format

character  msg*100
pointer (lpmsg,msg)

    if (.NOT. gactive) return
    lpMeshOpt = 0

!   Xファイルをロード
!   マテリアルバッファのアドレス,マテリアル数,メッシュオブジェクトのアドレスを取得
    iRes = D3DXLoadMeshFromX(LOC(gXfile),D3DXMESH_SYSTEMMEM,lpdv,NULL,  &
                             LOC(lpD3DXMtrlBuffer),LOC(numMaterials),LOC(lpMesh))
    if (iRes /= 0) then 
      lpmsg = IError(iRes)
      write(buf,*)msg(1:25),""C
      i = MessageBox(hWnd,buf,"D3DXLoadMeshFromX"C,MB_OK)
      return
    end if

 D3DXLoadMeshFromX()関数を用いてXファイルのロードを行います。第1パラメータには読み込むXファイルのファイル名のアドレスを指定します。 第2パラメータには,メッシュの作成オプションを指定します。D3DXMESH_SYSTEMMEMはインデックスバッファと頂点バッファをシステムメモリに作成します。 5番目のパラメータには,Xファイルに記述されたマテリアルの情報が格納されるD3DXMATERIAL構造体のアドレスを指定します。 6番目のパラメータには,マテリアルの数が格納される変数のアドレスを指定します。 最後のパラメータには,読み込んだXファイルのメッシュデータが格納されたメッシュオブジェクトを表すインターフェイスポインタのアドレスを指定します。

8.3 メッシュデータの格納


!   メッシュの面及び頂点の順番を変更し,最適化したメッシュオブジェクトを取得する。
    call XOptimize(lpMesh,D3DXMESHOPT_ATTRSORT,NULL,NULL,NULL,NULL,LOC(lpMeshOpt),iRes)

!   元のメッシュオブジェクトを解放する
    call XRelease(lpMesh,iRes)

!   頂点フォーマットとサイズを取得
    call XGetFVF(lpMeshOpt,FVFfmt)
!   iszFMT = D3DXGetFVFVertexSize(FVFfmt) 

!   アトリビュートテーブルの取得
    call XGetAttributeTable(lpMeshOpt,NULL,LOC(numAtrTbl),iRes)
    ALLOCATE (SubsetTable(numAtrTbl))
    call XGetAttributeTable(lpMeshOpt,LOC(SubsetTable),LOC(numAtrTbl),iRes)

!   柔軟な頂点フォーマット(FVF)コードを使用してメッシュのコピーを作成
    call XGetOptions(lpMeshOpt,iOpt)
    call XCloneMeshFVF(lpMeshOpt,iOpt,D3DFVF_VERTEX,lpdv,LOC(lpMesh),iRes)
    iszFMT = D3DXGetFVFVertexSize(D3DFVF_VERTEX) 

!   最適化したメッシュオブジェクトを解放し,クローンメッシュを使用する
    call XRelease(lpMeshOpt,iRes)
    lpMeshOpt = lpMesh

!   頂点法線を計算(法線がない場合に対応)
    if (IAND(FVFfmt,D3DFVF_NORMAL) == 0) then
      iRes = D3DXComputeNormals(lpMeshOpt,NULL)
    end if

!   メッシュに含まれる頂点の数を取得する
    call XGetNumVertices(lpMeshOpt,numVertices)

!   メッシュに含まれる面の数を取得
    call XGetNumFaces(lpMeshOpt,numFaces)

!   頂点インデックスのポインタ取得
    call XGetIndexBuffer(lpMeshOpt,LOC(lpIxBuf),iRes)

!   頂点バッファのポインタ取得 
    call XGetVertexBuffer(lpMeshOpt,LOC(lpVxBuf),iRes)

!   メッシュのマテリアルの合計数に基づいて,新しいメッシュマテリアルとテクスチャオブジェクトを作成
    ALLOCATE (MeshMaterials(numMaterials))
    ALLOCATE (lpMeshTex(numMaterials))

!   マテリアルバッファへのポインタを取得する。
    call XGetBufferPointer(lpD3DXMtrlBuffer,lpd3dxMaterials)

!   マテリアルバッファからマテリアル値を取得し,テクスチャを読み込む
    do k = 1, numMaterials
      MeshMaterials(k) = d3dxMaterials(k)  ! マテリアルデータのコピー
      MeshMaterials(k)%MatD3D%ambient = MeshMaterials(k)%MatD3D%diffuse ! 環境色の設定
      lpMeshTex(k) = 0
      i = D3DXCreateTextureFromFile(lpdv,d3dxMaterials(k)%lpTextureFilename,LOC(lpMeshTex(k)))
    end do

!   マテリアルバッファを解放
    call XRelease(lpD3DXMtrlBuffer,iRes)

    return

 ここでは,ロードしたXファイルのメッシュデータをそのまま表示するのではなく,一旦メモリー上に再構築しています。モデラで作成して読み込んだXファイルは,冗長度が高く,そのまま表示すると表示時間が多くかかります。メッシュデータを最適化すると表示時間の大幅短縮ができます。また,Xファイルには法線データを含まないものもあり,その場合には法線を計算しライティングに利用できるようにしています。ただし,法線はプログラムで計算するよりもモデラで出力した方が,不自然なエッジが出にくくなります。

 手順としては,元のメッシュオブジェクトを最適化し,アトリビュートテーブルを取得します。必要とする新しい頂点フォーマットコードを指定してメッシュのコピー(クローン)を作成します。ロードしたXファイルに法線データが含まれていない場合には法線を計算します。頂点インデックス,頂点バッファのアドレスを取得し,さらに,マテリアルバッファのアドレスを取得して,マテリアルの値を保存しテクスチャを作成します。

8.4 メッシュの描画


entry Render(hWnd,iRes)
    if (lpMeshOpt == 0) return

!   頂点フォーマットを指定
    call XSetVertexShader(lpdv,FVFfmt,iRes) 

!   頂点バッファをデバイスのデータストリームにバインド
    call XSetStreamSource(lpdv,0,lpVxBuf,iszFMT,iRes)

!   インデックスデータの設定(頂点ストリームの開始位置を定義)
    call XSetIndices(lpdv,lpIxBuf,0,iRes)

!   サブセット毎にマテリアルとテクスチャを設定しメッシュを描画
    do k = 1, numAtrTbl
      call XSetMaterial(lpdv,LOC(MeshMaterials(k)),iRes)
      if (lpMeshTex(k) /= 0) then
        call XSetTexture(lpdv,0,lpMeshTex(k),iRes) ! テクスチャステージ0にセット
      end if
!     プリミティブをレンダリング
!     call XDrawSubset(lpMeshOpt,k-1,iRes) ! メッシュのサブセットを描画
      call XDrawIndexedPrimitive(lpdv, D3DPT_TRIANGLELIST, &
           SubsetTable(k)%VertexStart, SubsetTable(k)%VertexCount, &
           SubsetTable(k)%FaceStart*3, SubsetTable(k)%FaceCount ,iRes)
    end do
    return

 メッシュデータを表示するには,SetVertexShader関数を実行して頂点フォーマットを指定します。作成したクローンメッシュを使用して描画する場合には,前章で解説した場合と同様に,SetStreamSource関数を用いて頂点バッファをデバイスのデータストリームにバインドし,SetIndices関数を用いてインデックスデータの設定を行います。SetMaterial関数を用いてマテリアルの設定,SetTexture関数を用いてテクスチャファイルの設定をし,DrawIndexedPrimitive関数を用いて描画を行います。Xファイルに含まれるメッシュデータは,複数のオブジェクトからなっているので,これらを構成するサブセットの数だけ繰り返し実行します。クローンメッシュを作成しないで描画だけする場合には,DrawSubset関数を用いることができます。

8.5 メッシュ領域の解放


entry ClsRender(hWnd,iRes)
    if (lpMeshOpt == 0) return

!   アトリビュートテーブルを解放
    DEALLOCATE (SubsetTable)

!   テクスチャオブジェクトを解放
    do k = 1, numMaterials
      call XRelease(lpMeshTex(k),iRes)
    end do

    DEALLOCATE (lpMeshTex)
    DEALLOCATE (MeshMaterials)

!   最適化したメッシュオブジェクトを解放
    call XRelease(lpMeshOpt,iRes)
    return

 メッシュデータを格納した時の手順と逆に,確保した領域とオブジェクトを解放します。

8.6 Xファイルの保存


entry SaveXfile(hWnd,iRes)
    if (lpMeshOpt == 0) return
!   出力ファイル名の入力
    call SaveFile(hWnd)

!   Xファイルの出力
    iRes = D3DXSaveMeshToX(LOC(gXSaveFile),lpMeshOpt,NULL,       &
           LOC(MeshMaterials),numMaterials,DXFILEFORMAT_COMPRESSED)
!   iRes = D3DXSaveMeshToX(LOC(gXSaveFile),lpMeshOpt,NULL,       &
!          LOC(MeshMaterials),numMaterials,DXFILEFORMAT_TEXT) ! Text mode
    if (iRes /= 0) then
      lpmsg = IError(iRes)
      write(buf,*)msg(1:25),""C
      i = MessageBox(hWnd,buf,"SaveXfile"C, MB_OK)
    end if
    return

 入力したXファイルは,冗長度が大きく入力や表示に時間が掛かります。最適化したXファイルをバイナリモードで出力しておくと,次回に入力するときは高速化されます。SaveFileサブルーチンは,出力ファイル名を入力するために用意したサブルーチンです。D3DXSaveMeshToX関数は,Xファイルを出力するユーティリティ関数です。6番目のパラメータは出力フォーマットを指定します。バイナリモードで出力するときは,DXFILEFORMAT_COMPRESSEDを,テキストモードで出力するときは,DXFILEFORMAT_TEXTを指定します。バイナリモードで出力すると,エディタで編集できなくなります。

8.7 透過テクスチャの使用

 透過テクスチャを用いると,人物モデルの髪の毛などの細かいモデリングを省略し,テクスチャだけを用いてリアルな表現を行うことができます。透過テクスチャは通常のビットマップ画像データのRGB成分にアルファ成分を付加したDDS形式という画像フォーマットを用います。モデラではRGBのマッピング画像と透過データを別々に用意したりしますが,Xファイルに記述する際には,DirectXのSDKに附属している「テクスチャーツール」を用いて変換したDDS形式のファイル名を指定します。


!   アルファブレンディングを有効にする(デフォルトは無効)
    call XSetRenderState(lpdv, D3DRS_ALPHABLENDENABLE, .TRUE., iRes)
    if (iRes /= DD_OK) return

!   アルファブレンディング係数をセット(テクスチャ画像のキャンバス部分を透過する)
    call XSetRenderState(lpdv, D3DRS_SRCBLEND, D3DBLEND_SRCALPHA, iRes)
    call XSetRenderState(lpdv, D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA,iRes)
    if (iRes /= DD_OK) return

 透過テクスチャを用いるには,SetRenderState関数を用いてアルファブレンディングを有効にする設定を行います。また,テクスチャのアルファ係数に基づいて透過レベルを決定するように設定します。

 透過テクスチャを用いるときは,描画順序が重要になります。既に表示されている色に対してテクスチャの色に透過係数を掛けた値で新しい色を計算するためです。従って,Zバッファによる前後判定がうまく機能しなくなります。
 サンプル画像では,人物モデルの髪の毛に透過テクスチャを用いていますが,髪の毛のオブジェクトを一番最後に描画するようにしています。

8.8 プログラムのサンプル

・プログラム例


○ よく使われる用語の説明

・UV座標
ポリゴンの頂点に対応したテクスチャの位置座標。テクスチャは2のべき乗の正方形のビットマップ画像で,画像の左上を(0.0,0.0),右下を(1.0,1.0)で正規化した値を用います。

・ディフューズ色とスペキュラ色
ディフューズ色は,3Dモデルの表面の素材の色。スペキュラ色は,3Dモデルの表面の光沢を表します。両者とも3Dモデルの表面色を表現するために使用し,透明度を表現するアルファ値と色を表現するRGB値から成る32ビットの値で表します。Direct3Dでは,ポリゴンを描画する場合に,ディフューズ色とスペキュラ色を加算した色を使用します。

・Zバッファとステンシルバッファ
Zバッファは,3D表示を行うときに奥域の情報を保持しているバッファメモリで,これを用いて描画順序に関わらず最も手前のオブジェクトを画面に表示することができます。一方,ステンシルバッファは,Zバッファと同様にピクセルごとに何ビット分かの整数値を保持するバッファで,比較関数を指定してピクセルごとに描画するかしないかを決めることができます。シャドーを作画する場合に用いることができます。

・シェーディング
シェーディングは,ポリゴンに対する光の当たり方を計算することで3Dオブジェクトの明るさ,質感,光沢などを表現する機能です。計算方法によって,シェーディングにはいくつかの方法があり,一つのポリゴン面を均一に表現する「フラットシェーディング」や,複数のポリゴンに共有される頂点の色を基準として,頂点間の色を補間し,ポリゴンの継ぎ目を目立たなくする「グーロシェーディング」などが代表的です。また,光の反射をより正確に表現するために,頂点間の法線を補間する「フォンシェーディング」という手法もあります。

・カリング
画面に投影したときに,面の法線が画面の裏側を向いているときには,その面の表示を行わないようにすること。無駄な計算を省いて表示速度を上げることができます。

・アルファ・ブレンディング
アルファ・ブレンディングとは,画像やオブジェクトの透明度を表現するアルファ値に基づいて2 つの画像を合成する処理です。アルファ・ブレンディングを使用することにより,背景画の上にキャラクタ画像を合成したり,髪の毛などの細かいテクスチャを表現するなど多彩なシーンを表現できます。 DirectX には。半透明処理と加算半透明処理などアルファ・ブレンディングを使用した様々な技術があります。

・半透明処理
半透明処理は,2 つの画像を重ねて透かしたような効果を表現します。半透明処理を利用することにより,透明な3Dモデルを表現したり,フェードインやフェードアウトなどの特殊効果を実現することができます。ただし,半透明処理ではZバッファによる前後判定ができないので描画順序に注意が必要です。

・加算半透明処理
加算半透明処理は,一枚の画像の上に別の画像を映写機で投影したような効果を表現します。加算半透明処理を利用することにより,炎,爆発や稲妻などの特殊効果を実現することができます。

・スプライト描画
スプライト描画は,特定の色を透過させて表示するという描画方法です。人物の髪の毛などをリアルに見せることができます。RGBの色情報に加え透過値のアルファ値を用います。

・DDS形式のイメージ
DDS形式のイメージは,RGBのイメージにアルファを追加したイメージフォーマットです。アルファイメージは透過部分を黒で表した白黒の画像を用います。DDS形式のイメージファイルを作るには,DirectX SDKに付属している「テクスチャーツール」を利用します。


目次 次の項目

9. 影の実装
(頂点シェーダ/ピクセルシェーダ)



9.1 影の定義

 陰影という言葉がありますが,3Dの世界では,陰(shade)と影(shadow)は表現方法がまったく異なります。陰は光源とオブジェクトのなす角度によって光の反射が違うことに基づき,物体の見え方を明暗で表すものです。一方,影はオブジェクトが光線を遮ることによって,他の物体や自分自身に光が当たらない個所を指します。このうち,自分自身の凹凸によって自分自身に生じる影をセルフシャドウと呼んでいます。3Dの表現で影があるのとないのとではリアリティが全然違います。前章のサンプルでは,陰は表現していますが,影の方は実装できていません。本章では,影の実装を試みます。

図9.1 影のある表示

 影の実装には次に示すように幾つかの手法が提案されています。ただし,DirectXでは現在のところ十分な機能が用意されているというわけではありません。また,影を実装するには,最低2回の描画が必要になるため,オーバーヘッドが大きくなってしまいます。サンプルではセルフシャドウも表示していますが,精度の関係で細部の影が十分とはいえません。

9.1 影を表示するには

 影をリアルタイムに表示するには,計算のオーバーヘッドが大きいため,ゲームソフトでは簡略化した影(まる影)や地面に影のテクスチャを貼り付けるだけの簡単な表示方法を採用している場合があります。ここでは,セルフシャドウも可能な手法で主なものを以下に示します。

・シャドウボリューム法

 シャドウボリューム法は,影を作るオブジェクトをライトの向きに引き伸ばしたシャドウボリュームと呼ばれるものを作ります。次に,シャドウボリュームの表面を描画しステンシルバッファ(深度バッファの一部を利用)に加算記録した後,裏面を描画しステンシルバッファから減算記録します。すると,バッファの値が相殺されて,正の個所が影となるポイントとして特定できます。そして,通常の描画を行った後,ステンシルバッファの値をマスクとして用い,影用のテクスチャを画面に合成します。この手法では,最低3回の描画操作が必要になります。また,影を正確に表現するには,影を作るオブジェクトは閉面体でなくてはなりません。
 この方法で例に示したサンプルのシャドウを表示してみたところ,サンプルの3Dモデルは閉面体のみでできていないために,光の軌線のような不自然な影が無数にできてしまいました。ステンシルバッファを用いて影を作成する方法は,実用的とは思われません。

・シャドウバッファ法(シャドウマッピング法)

 シャドウバッファ法は,描画用の透視座標変換行列とは別に,ライト方向からの投影変換行列を用意します。オブジェクトを投影変換行列を用いて描画し,深度Zをシャドウバッファに記録しておきます。次に,オブジェクトを通常の描画を行うときに,透視座標変換を行うと同時に,ライト方向からの投影変換を行い,投影変換した深度とシャドウバッファに記録された深度を比較します。深度がそれよりも大きい値のときは,影になっているので明度を下げて描画します。この手法では,最低2回の描画操作で済みますが,座標変換を2回ずつ行う必要があります。また,DirectX8では,シャドウバッファをサポートしていないので,プログラム出力のできるテクスチャで代用しています。そのため,深度を記録する精度が不足しています。しかし,現段階ではDirectXを用いてセルフシャドウも可能な影の表示を行うには最善の方法と思われます。

9.2 シェーダ

 シェーダには,以下に示すバーテックスシェーダとピクセルシェーダがあり,それぞれのシェーダには,DirectXに用意されている標準の固定機能シェーダとアセンブラ言語に似た特殊な記述言語を用いるプログラム可能なシェーダがあります。 DirectXを用いて影を実装するには,プログラム可能シェーダの助けを借りる必要があります。

・バーテックスシェーダ(頂点シェーダ)

 バーテックスシェーダは,トランスフォーム,ライティング,頂点色ブレンディング,テクスチャ座標生成などの処理をメッシュの頂点ごとに行う機能です。プログラム可能なバーテックスシェーダでは,これらの処理を必要に応じて任意にプログラムすることができます。

・ピクセルシェーダ

 ピクセルシェーダは,バーテックスシェーダが設定した各頂点ごとの値を基にDirectXによって線形補間され,各点ごとの色情報を受け取り,これにテクスチャの色を合成するなどの処理をして最終的な表示色を求めます。シャドウバッファ法を用いて影を表現する場合は,色情報のα値と影テクスチャのα値を比較し,影となる場所では色の明度を落とす処理を行います。

9.3 シャドウバッファ法を用いて影を表示する

 では,影を表示する手順を詳しく説明します。

(1) 影の深度を記録するテクスチャを作成します。テクスチャのサイズは画面のサイズにしています。


  !   現在のレンダリングターゲットサーフェイスへのポインタを取得
      call XGetRenderTarget(lpdv,LOC(lpBackBuf),iRes)
  !   現在の深度バッファサーフェイスのアドレスを取得
      call XGetDepthStencilSurface(lpdv,LOC(lpZbuffer),iRes)
  !   深度レンダリング用テクスチャの生成
      call XRelease(lpTexture,iRes)
      call XCreateTexture(lpdv,grect%right,grect%bottom,1,D3DUSAGE_RENDERTARGET, &
           D3DFMT_A8R8G8B8,D3DPOOL_DEFAULT,LOC(lpTexture),iRes)
  !   レンダリング用テクスチャのサーフェイスを取得
      call XRelease(lpTexSuf,iRes)
      call XGetSurfaceLevel(lpTexture,0,LOC(lpTexSuf),iRes)

(2) 用意したテクスチャをターゲットにして,深度バッファを有効にし,α値を最大に初期化した後,影を落とすオブジェクトをライト方向からレンダリングします。 このときテクスチャのα値には,バーテックスシェーダを用いて深度値Zを書き込みます。この過程ではオブジェクトの材質やライティングの処理は必要ありません。最終的に影テクスチャには,「光源から見て一番手前の物体までの深度」が書かれることになります。


!   レンダリングターゲットをテクスチャのサーフェイスに設定
    call XSetRenderTarget(lpdv,lpTexSuf,lpZbuffer,iRes)
!   テクスチャをクリア(α値を最大に設定)
    call XClear(lpdv,0,NULL,D3DCLEAR_TAR_ZBU,#FFFFFFFF,1.0,0,iRes)
!   シェーディングモードをフラットに変更
    call XSetRenderState(lpdv, D3DRS_SHADEMODE, D3DSHADE_FLAT, iRes)
!   アルファブレンド不可に設定
    call XSetRenderState(lpdv, D3DRS_ALPHABLENDENABLE, .FALSE., iRes)
!   アルファ成分のみに書き込む(R:1 G:2 B:4 A:8)
    call XSetRenderState(lpdv, D3DRS_COLORWRITEENABLE, 8, iRes)
!   ピクセルシェーダを使用しない指定
    call XSetPixelShader(lpdv,NULL,iRes)
!   頂点シェーダを指定
    call XSetVertexShader(lpdv,ghVertexShader,iRes)
!   頂点シェーダに座標変換行列の値設定
    call SetVtxShader(hWnd,iRes)
!   ビュー行列とプロジェクション行列を設定(ライトの方向に設定)
    EyePt    = D3DVECTOR(6.0, 5.0,-5.0)    ! ライトの位置
    LookatPt = D3DVECTOR(0.0, 1.0, 0.0)    ! 注視点
    call TransForm(hWnd,EyePt,LookatPt)    ! gmatView,gmatProjを設定
!   頂点シェーダにライト方向からの投影変換行列の値を設定
    call SetShadowConst(hWnd,iRes)
!   頂点バッファをデバイスのデータストリームにバインド
    call XSetStreamSource(lpdv,0,lpMeshVB,iszFMT,iRes)
!   インデックスデータの設定(頂点ストリームの開始位置を定義)
    call XSetIndices(lpdv,lpMeshIx,0,iRes)
!   サブセット毎にメッシュを描画
    do k = 1, gnumMaterials
      if (gObject(k)%Tbl == 1) then
        call XDrawIndexedPrimitive(lpdv, D3DPT_TRIANGLELIST, &
             SubsetTable(k)%VertexStart, SubsetTable(k)%VertexCount, &
             SubsetTable(k)%FaceStart*3, SubsetTable(k)%FaceCount ,iRes)
      end if
    end do

 ライト方向から見た投影の概念を図9.2に,また,作成した影テクスチャのα値を濃淡画像で表現したものを図9.3に示します。深度が1.0に近い程白く,0.0に近い程黒く表示されています。

図9.2 ライト方向からテクスチャへの投影

図9.3 テクスチャのαに記録した深度

(3) バーテックスシェーダ(頂点シェーダ)

・影テクスチャ作成バーテックスシェーダの内容

 バーテックスシェーダでは,頂点の座標を投影変換し,テンポラリレジスタr0と座標出力レジスタoPosに代入します。r0のZ値を0.0〜1.0に補正し,色出力レジスタoD0のαに代入します。各レジスタは xyzw 又は rgba の4つの実数値を扱います。


; ステージ1 深度テクスチャの作成(vshader.vsh)
; c4-7   world * ライトビュー * 透視変換行列
; c16    z値を0.0から1.0に補正する定数
; v0     頂点の座標値

vs.1.0  ; シェーダバージョン

m4x4  r0,      v0,    c4           ; 投影座標変換
mov   oPos,    r0

; 投影深度を(0.0〜1.0)に補正し,色のα成分に代入
mad   oD0.a,   r0.z,  c16.x, c16.y ; Z = (Z - Znear)/(Zfar - Znear)

・バーテックスシェーダの翻訳

 DirectX8.0では,バーテックスシェーダは別ファイルに用意し,D3DXAssembleShaderFromFile関数を用いて翻訳します。CreateVertexShader関数を用いて,シェーダに渡る変数の対応付けを行いハンドルで受け渡しを行います。頂点フォーマットの定義については,プログラム内のコメントを参照してください。プログラムの手間を省くため,ステージ1の影テクスチャ作成シェーダとステージ2のレンダリング用シェーダを同時に作成しています。


integer*4 VS_Format(5)/#20000000,#40020000,#40020003,#40010007,#FFFFFFFF/

!   頂点シェーダ1.0がサポートされてないならソフトウェアで
    iUsage = 0
    if ( iVersion < #FFFE0100 ) then     ! D3DVS_VERSION(1,0) = #FFFE0100
      iUsage = D3DUSAGE_SOFTWAREPROCESSING
    end if
!   ステージ 1 影テクスチャ作成シェーダ
    if (ghVertexShader /= -1) then
      call XDeleteVertexShader(lpdv,ghVertexShader,iRes)
    end if
!   頂点シェーダの記述をバイナリ形式にアセンブルする
    iRes = D3DXAssembleShaderFromFile(LOC("vshader.vsh"C),0,NULL,LOC(lpShader),NULL)
!   頂点シェーダを作成し,カレントシェーダとして設定する
    call XGetBufferPointer(lpShader,lpFunc)
    call XCreateVertexShader(lpdv,LOC(VS_Format),lpFunc,LOC(ghVertexShader),iUsage,iRes)
    call XRelease(lpShader,iRes)

!   ステージ 2 レンダリングシェーダ
    if (ghShadowShader /= -1) then
      call XDeleteVertexShader(lpdv,ghShadowShader,iRes)
    end if
!   影をレンダリングするシェーダをアセンブル
    iRes = D3DXAssembleShaderFromFile(LOC("shadow.vsh"C),0,NULL,LOC(lpShader),NULL)
!   影をレンダリングするシェーダを作成し,カレントシェーダとして設定する
    call XGetBufferPointer(lpShader,lpFunc)
    call XCreateVertexShader(lpdv,LOC(VS_Format),lpFunc,LOC(ghShadowShader),iUsage,iRes)
    call XRelease(lpShader,iRes)

・バーテックスシェーダの指定と定数の設定

 バーテックスシェーダを有効にするには,SetVertexShader関数を用いてシェーダのハンドルを指定します。 シェーダに渡す定数の設定は,SetVertexShaderConstant関数を用いて行います。

 ステージ1のバーテックスシェーダでは,c4〜c7とc16しか用いませんが,ステージ2で使用する定数も同時に設定しています。


!   行列乗算により行列連結(転置も行う)をする (T = gmatWorld * gmatView * gmatProj) 
    T(1:4,1)=(gmatWorld(1,1:4)*gmatView(1,1) + gmatWorld(2,1:4)*gmatView(1,2)  &
            + gmatWorld(3,1:4)*gmatView(1,3) + gmatWorld(4,1:4)*gmatView(1,4))*gmatProj(1,1)
    T(1:4,2)=(gmatWorld(1,1:4)*gmatView(2,1) + gmatWorld(2,1:4)*gmatView(2,2)  &
            + gmatWorld(3,1:4)*gmatView(2,3) + gmatWorld(4,1:4)*gmatView(2,4))*gmatProj(2,2)
    T(1:4,4)= gmatWorld(1,1:4)*gmatView(3,1) + gmatWorld(2,1:4)*gmatView(3,2)  &
            + gmatWorld(3,1:4)*gmatView(3,3) + gmatWorld(4,1:4)*gmatView(3,4)
    T(1:4,3)= gmatWorld(4,1:4)*gmatProj(3,4) + T(1:4,4)*gmatProj(3,3)
!   行列 T を定数レジスタ c0 - c3 に設定
    call XSetVertexShaderConstant(lpdv,0,LOC(T),4,iRes)         ! c0 - c3
!   行列 gmatWorld の逆行列を求め,定数レジスタ c8 - c11 に設定
    iRes = D3DXMatrixInverse(LOC(matinv),NULL,LOC(gmatWorld))
!   ライトベクトルをワールド座標にトランスフォームし,正規化する。
    ld = D3DXVECTOR4(-1.2,-1.0, 1.0, 0.0)  ! y,z,wの積算は省略しています
    lightDir%x = matinv(1,1)*ld%x - matinv(1,2) + matinv(1,3)
    lightDir%y = matinv(2,1)*ld%x - matinv(2,2) + matinv(2,3)
    lightDir%z = matinv(3,1)*ld%x - matinv(3,2) + matinv(3,3)
    iRes = D3DXVec4Normalize(LOC(lightDir),LOC(lightDir))       ! 正規化
    lightDir%w = 0.1   ! 環境光の強さ
!   トランスフォームしたライトベクトルを定数レジスタ c13 に設定
    call XSetVertexShaderConstant(lpdv,13,LOC(lightDir),1,iRes) ! c13
!   ライトの色を定数レジスタ c14 に設定
    color = D3DXVECTOR4(0.9,0.9,0.9,0.0)
    call XSetVertexShaderConstant(lpdv,14,LOC(color),1,iRes)    ! c14
!   シェーダで用いる定数の設定
    ld = D3DXVECTOR4(0.6, 0.5, 1.0, 0.4)                        ! 係数の定義
    call XSetVertexShaderConstant(lpdv,12,LOC(ld),1,iRes)       ! c12
!   c16 : z値を0.0から1.0に補正する定数
    Znear  = 0.1       ! 前方クリップ面までの距離
    Zfar   = 10.0      ! 後方クリップ面までの距離
    ld%x = 1.0/(Zfar-Znear)
    ld%y = Znear/(Znear-Zfar)
    ld%z = 0.0
    ld%w = 0.0
    call XSetVertexShaderConstant(lpdv,16,LOC(ld),1,iRes)       ! c16

 定数レジスタc4〜c7は,ライト方向からの投影変換行列を作成した後で別に設定します。


!   行列乗算により行列連結(転置も行う)をする (T = gmatWorld * LightView * LightProj) 
    T(1:4,1)=(gmatWorld(1,1:4)*gmatView(1,1) + gmatWorld(2,1:4)*gmatView(1,2)  &
            + gmatWorld(3,1:4)*gmatView(1,3) + gmatWorld(4,1:4)*gmatView(1,4))*gmatProj(1,1)
    T(1:4,2)=(gmatWorld(1,1:4)*gmatView(2,1) + gmatWorld(2,1:4)*gmatView(2,2)  &
            + gmatWorld(3,1:4)*gmatView(2,3) + gmatWorld(4,1:4)*gmatView(2,4))*gmatProj(2,2)
    T(1:4,4)= gmatWorld(1,1:4)*gmatView(3,1) + gmatWorld(2,1:4)*gmatView(3,2)  &
            + gmatWorld(3,1:4)*gmatView(3,3) + gmatWorld(4,1:4)*gmatView(3,4)
    T(1:4,3)= gmatWorld(4,1:4)*gmatProj(3,4) + T(1:4,4)*gmatProj(3,3)
!   行列 T を定数レジスタ c4 -  c7 : 投影変換行列に設定
    call XSetVertexShaderConstant(lpdv,4,LOC(T),4,iRes)         ! c4 - c7

(4) 次に表示ターゲットをスクリーンに変更し,背景を含めたモデルを視点位置から描画します。このとき,モデルに貼り付けるデカールテクスチャをテクスチャステージ 0 に割り付け,作成した影テクスチャをテクスチャステージ 1 として割付けます。


!   レンダリングターゲットを元の画面に戻す
    call XSetRenderTarget(lpdv,lpBackBuf,lpZbuffer,iRes)
!   gmatView,gmatProjを元の値に戻す
    call TransForm(hWnd,gEyePt,gLookatPt)
!   シェーディングモードをグーロに戻す
    call XSetRenderState(lpdv, D3DRS_SHADEMODE, D3DSHADE_GOURAUD, iRes)
!   カラー書き込み可に設定(R:1 G:2 B:4 A:8)
    call XSetRenderState(lpdv, D3DRS_COLORWRITEENABLE, 15, iRes)

!  《オブジェクトを描画》 

!   ディスプレイデバイスをクリアし,Zバッファの値を1.0に初期化する
    call XClear(lpdv,0,NULL,D3DCLEAR_TAR_ZBU,#001E415A,1.0,0,iRes)
!   影のテクスチャをテクスチャステージ 1 にセット
    call XSetTexture(lpdv,1,lpTexture,iRes)
!   テクスチャステージ1の 設定
    call XSetTextureStageState(lpdv,1,D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP,iRes)
    call XSetTextureStageState(lpdv,1,D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP,iRes)
!   アルファブレンド可に設定
    call XSetRenderState(lpdv, D3DRS_ALPHABLENDENABLE, .TRUE., iRes)
!   オブジェクトをシャドウ付きで描画するシェーダを指定
    call XSetVertexShader(lpdv,ghShadowShader,iRes)
!   ピクセルシェーダを指定
    call XSetPixelShader(lpdv,ghPixelShader2,iRes) 
!   サブセット毎にマテリアルとテクスチャを設定しメッシュを描画
    do k = 1, gnumMaterials
      if (gObject(k)%Tbl == 1) then
!       マテリアル色をバーテックスシェーダに設定
        call SetVtxMaterial(hWnd,gObject(k)%Materials%MatD3D%diffuse,iRes)
!       デカールテクスチャをテクスチャステージ 0 にセット
        call XSetTexture(lpdv,0,gObject(k)%lpMeshTex,iRes)
!       プリミティブをレンダリング
        call XDrawIndexedPrimitive(lpdv, D3DPT_TRIANGLELIST, &
             SubsetTable(k)%VertexStart, SubsetTable(k)%VertexCount, &
             SubsetTable(k)%FaceStart*3, SubsetTable(k)%FaceCount ,iRes)
      end if
    end do

・レンダリング用バーテックスシェーダの内容

 レンダリング用バーテックスシェーダでは,描画オブジェクトの透視座標変換を行うと同時に,ライト方向からの投影座標変換も行います。投影変換に当たっては,Y方向の座標を上下反転し,テクスチャのUV座標に対応させ,出力テクスチャレジスタoT1のxy値に代入しています。また,出力頂点色レジスタoD0のrgb値には,法線と光源ベクトルの内積にマテリアル色等を積算した値を,α値にはライト方向から投影した深度値を入れます。


; ステージ2 レンダリング用シェーダ(shadow.vsh)
vs.1.0

; c0-c3   透視変換行列
; c4-c7   投影変換行列
; c12     定数(0.6, 0.5, 1.0, 0.4)
; c13     光源ベクトル
; c14     平行光源カラー
; c15     環境光カラー
; c16     z値を0.0〜1.0に補正する定数

; v0      頂点の座標値
; v3      法線ベクトル (w成分は1.0f)
; v7      テクスチャUV座標

; oT0     出力テクスチャ座標。補間されピクセルシェーダに送られる
; oD0     出力頂点カラー。補間されピクセルシェーダに転送される
; oPos    同次クリッピング空間内の出力位置座標。頂点シェーダが書き込む。

m4x4 oPos,   v0,    c0           ; 透視座標変換

mov  oT0.xy, v7.xy               ; メッシュのテクスチャUV座標

dp4  r0.x,   v0,    c4           ; 投影座標変換
dp4  r0.y,  -v0,    c5           ; 上下を反転
dp4  r0.z,   v0,    c6
dp4  r0.w,   v0,    c7

rcp  r0.w,   r0.w                ; 逆数  r0.w = 1.0 / r0.w
mul  r0.xy,  r0.xy, r0.w         ; 投影座標をXY座標に変換
mad  oT1.xy, r0.xy, c12.y, c12.y ; 深度テクスチャのUV座標 oT1 = 0.5*xy/w + 0.5

mad  oD0.a,  r0.z,  c16.x, c16.y ; 投影深度を(0〜1.0)に補正し,頂点色のaに代入
dp3  r1,     v3,   -c13          ; 法線と光源ベクトルの内積
mad  r1,     r1,    c12.x, c12.w ; r1 = r1 * 0.6 + 0.4 (0.0〜1.0に補正)
mov  r1.w,   c12.z               ; r1.w = 1.0
mul  r1,     r1,    c14          ; ライトの色を積算
mul  r1,     r1,    c15          ; マテリアル色を乗算
add  oD0.rgb,r1,    c13.w        ; 頂点色に環境色(0.1)を加算

・ピクセルシェーダ

 ステージ2の描画時に用いるピクセルシェーダの内容を以下に示します。ピクセルシェーダでは,定数はシェーダ内に記述します。また,最終的な色情報は r0 レジスタに代入します。
 バーテックスシェーダから受け渡された頂点色とデカールテクスチャの色を積算し,基本となる色を計算します。頂点色を元に補間されたピクセルごとのα値と深度テクスチャのα値を比較し,影の判定を行います。頂点色αの方が大きいときは,影になっていると判断し,基本となる色の値から1.0より小さい値を減算又は積算します。このとき,頂点色のα値をそのまま用いると,深度判定にミスが生じ,不要な個所に影が生じてしまいます。これを回避するために,やむを得ず細かい影を犠牲にして,バイアス 0.01 を引いています。


; ピクセル単位に色の計算を行う
ps.1.0                    ; バージョン情報
;==========================================================
; v0 : 頂点色。頂点シェーダが出力するピクセル単位のカラー値
; t0 : デカールテクスチャ色(ステージ0)
; t1 : 深度テクスチャ色(ステージ1)
; t1_bias : 処理する前にy = (x-0.5)を実行する
; cnd 命令は,r0.aの値と0.5を比較する
;==========================================================

; 定数の定義
def c0, 1.0f, 1.0f, 1.0f, 0.01f ; Z オフセット
def c1, 0.8f, 0.8f, 0.8f, 0.0f  ; 影の濃さ

; テクスチャの色
tex  t0                  ; デカールテクスチャの色
tex  t1                  ; 深度テクスチャの色

mul  r1,   v0,   t0      ; 頂点色とデカールテクスチャ色の乗算

; 深度判定 r0 = (t1.a < v0.a-0.01) ? c1:c0 (影:テクスチャα < 頂点色α)
sub  r0,   v0,   c0.a    ; r0 = v0 - 0.01
sub  r0,   r0,   t1_bias ; r0 = r0 - (t1-0.5) = (v0-0.1 - t1) + 0.5
cnd  r0,   r0.a, c1,  c0 ; r0 = ( r0.a > 0.5 ? c1 : c0 )
;sub  r0,   r1,   r0      ; r0 = r1 - r0
mul  r0,   r1,   r0      ; r0 = r1 * r0

mov  r0.a,  t0.a         ; テクスチャのαを使用

・ピクセルシェーダの翻訳

 ピクセルシェーダも,バーテックスシェーダと類似の方法で翻訳処理を行います。


!   ピクセルシェーダのアセンブル
    iRes = D3DXAssembleShaderFromFile(LOC("pshader2.psh"C),0,NULL,LOC(lpShader),NULL)
    call XGetBufferPointer(lpShader,lpFunc)
    call XCreatePixelShader(lpdv,lpFunc,LOC(ghPixelShader2),iRes)
    call XRelease(lpShader,iRes)


 オブジェクトをレンダリングするときに,対応する位置をライト方向から投影した深度値Zを用いて濃淡表示したものを図9.4に示します。このときの深度値Zが対応する影テクスチャの深度値(α値)(図9.3参照)より大きい(深い)個所は影が落ちていると判定します。

図9.4 オブジェクトをライト方向から投影したときの深度値で表した画像

9.4 サンプルプログラム

 作成したサンプルプログラムでは,影の実装以外にいろいろな機能を用意しました。

 (1)最適化したXファイルの出力
最適化したXファイルを別のファイルに出力できるようにしました。
 (2)UVデータの出力
UVmapperというテクスチャ作成用フリーソフトがありますが,残念ながらXファイルには対応していないようです。そこで,UVデータを出力できる機能を追加しました。UVデータを読み込んで,テクスチャの画像ファイルに描画するプログラムを別途作成すれば,難題だったモデルに貼り付けるテクスチャの作成が容易になります。
 (3)表示オブジェクトの選択
表示オブジェクトの選択メニューを用意しました。正確には,アトリビュート単位に表示のON/OFFを選択できるようにしました。また,同じメニューでマテリアルカラーの変更とテクスチャファイル名の設定ができるようにしました。これにより,着衣のカラーを変更したり,表示をしないようにすることも可能になりました。
 プログラムのサンプル
 サンプルプログラムのダウンロード(3902KB) 04/06/17 

9.5 問題点

 (1)精度が悪い
シャドウバッファの深度をテクスチャのR8G8B8A8フォーマットのアルファビットで代用しているので,精度が悪く詳細な影を表現できません。ライトからの投影変換をするときに,できるだけ奥域を狭くとり帯域を有効に利用するようにしています。
また,床面などのポリゴン数が小さい大きな平面に落ちる影は,座標値の補間誤差が大きくなりオブジェクトからずれた位置に影ができてしまいます。回避するには,平面を分割してポリゴン数を増やすようにします。
 (2)表示速度が遅い。
リアルタイムシャドウを実現するには,同じオブジェクトを最低2回描画する必要があり,表示に時間がかかります。
ハードウェアがピクセルシェーダをサポートしていない場合には,特に時間がかかります。私のPC(Dynabook)では,表示デバイスのステンシルフォーマットをD3DFMT_D16にすると固定機能シェーダでは高速表示できるのですが,プログラマブルピクセルシェーダが利用できません。やむなくD3DFMT_D32を使用していますが,何か回避策があるのかも知れません。

9.6 参考にしたサイト

(1) http://www.twin-tail.jp/contents/vsdx8/d3d/033/index.htm
(2) http://monsho.hp.infoseek.co.jp/dx/dx39.html


目次 次の項目

10. アニメーション
(モデルを動かす)



10.1 アニメーションとは

 さて,前章を記述してから一年近くが経ってしまいました。前章の3Dモデルを生きた人間のように動かすことができたら,どんなに素晴らしいでしょう。本章ではアニメーションを行おうと思います。前章で使用した3Dモデルにモーションを付けて動かします。

右の画像をクリックすると動きます。⇒

図10.1 3Dモデルのアニメーションの例

 手始めに,DirectXにはSkinnedMeshというモーションデータ付きのサンプルプログラムが用意されているので,取りあえずC++で書かれたサンプルプログラムを参考にしてサンプルデータ(tiny)を表示できるようなプログラムをFortranで作成してみることにしました。折角だから前章で作成したプログラムはできるだけ有効に使うことにします。また,ネット上にはサンプルデータが他にもいくつかあって,それらも表示できるように汎用性のあるプログラムの作成を目指します。

 問題は,前章で作成した3Dモデルにどうやってモーションを付けるかということです。モーションを付けるソフトはいくつかあるようですが,X形式のデータを扱え,なお且つ,ワンスキンモデルが扱えるものをフリーでというとなかなか捜すのが大変でした。その中でほぼ目的に叶いそうなソフトとして,シェアウェアで試用期間30日のfragMOTIONを利用することにしました。Poserという比較的安価な有料ソフトと連携すれば歩く動作など標準的な動作が容易に作成できるようです。

 とはいえ,fragMOTIONを使ってモデルに骨入れと呼ばれる操作をマスターするのは大変な努力と忍耐が必要でした。骨と頂点の関連付けがうまくいかないと,モデルを動かしたときに足や腕がぐにゃぐにゃになってしまいます。fragMOTIONは,視点の位置の移動や拡大縮小がマウス操作だけではできないので,操作性がいまいちです。

10.2 Xファイルの形式

 Xファイルを用いてアニメーションするには,Xファイルの形式を知っておいた方が便利です。
 第8章でも述べていますが,今回使用したXファイルは,以下のデータから構成されています。Metasequoiaで出力したXファイルには,重複頂点データ,重みデータ,アニメーションデータは含まれていません。

1) フレーム
 メッシュを含む大枠のデータを記述し,基準となるトランスフォーム行列を有する。
2) メッシュデータ
 頂点座標のデータ群。
3) メッシュインデックス
 メッシュを構成する頂点のインデックスを記述したデータ群。
4) 頂点法線データ
 各頂点に対応する法線データ群。
5) 法線インデックス
 メッシュを構成する法線のインデックスを記述したデータ群。
6) 重複頂点データ
 重複頂点を記述したデータ群。DirectXが内部的に使用するらしい。なくても動作する。
7) マテリアルデータ
 テクスチャファイル名を含んだマテリアル情報とメッシュ毎のマテリアルを参照するインデックスデータ群。
8) テクスチャ座標
 頂点毎のテクスチャUV座標を記述したデータ群。
9) 重みデータ
 スキンメッシュアニメーションを行うときの,各骨に追随する頂点インデックスとウェイト値を記述したデータ群。
10) アニメーションデータ
 各骨に対する時間毎の変性マトリックスを記述したデータで,一連のアニメーション動作を一まとめにしたアニメーションセットの組み合わせ。

 Xファイルの詳細は,こちらを参照。

10.3 アニメーションの原理

 プログラムの解説の前に,アニメーションの原理について説明します。

 前書きにも述べましたが,アニメなどに登場するロボットなどは,脚や腕を動かすと関節から折れたような動きになります。一方,人体モデルのようにパーツの区別が明確に分かれていないものをワンスキンモデルと言い,脚や腕を動かしたときに関節部分が滑らかに変形するように工夫します。それを実現する一つの方法が重み付きのボーン処理です。

・ボーン処理

 前章のモデルは,全体として回転や移動,拡大といった操作はできても,そのままでは腕や脚を人体のそれらしく動かすことはできません。
 そこで考え出されたのが,人体の動きが骨の動きに追随して動くことから,モデルに仮想的な骨の概念を取り入れたものです。
 実際には個々の骨の動きは,親となる骨を基準にして,回転や移動を与えることで表現できることから,マトリックスで表すことができます。言い換えれると,骨はマトリックスそのものです。
 手の動きは,親となる腕の動きに支配され,腕はその上位の肩の動きに支配されます。従って,手の動きを表すには,その上位のマトリックスを総て合成したマトリックスで表現できることになります。
 あとは,腕や脚の表面は骨の動きに追随するわけだから,3Dモデルのメッシュの頂点がどの骨にどれだけ依存するかを決めればよいわけです。各頂点は複数の骨に依存でき,その骨に依存する割合をウェイト値で表し,合計は1.0になります。

図10.2 骨の概要

・モーション

 ワンスキンモデルにボーン処理を用いてポーズを取らせることができたら,時間を少しずつ経過した時のポーズを作成し,順に描画すればモデルが動いているように見せることができます。映画のフィルムと違って一コマごとの画面を用意するのではなく,一定時間ごとのポーズを各骨ごとにマトリックスを用いて記述します。DirectXのXファイルには,一連の動作を全ボーンについて経過時間ごとの変異を表したマトリックスの集合をアニメーションセットという項目に集められています。

10.4 プログラムの要点

(1) 処理の概要

 スキンメッシュ処理の概要を図10.3に示します。アニメーション処理を含まない場合の手続きと画面操作の部分は省略しています。

図10.3 処理の概要

(2) メッセージループ

 アニメーションの場合のメッセージループです。CPUの負荷を軽減するために1秒間に表示するフレーム数を 20 に調節しています。第5章で用いた方法を応用し,フレーム間の sleep 時間を自動調整します。スキンモデルでない場合は,通常のメッセージループの処理を行います。


   iFps = 0
   gPolygon  = 0
   iTimeOut = 50     ! 1000 / 20 (20 Frames/Second)
   nowTime = timeGetTime() - iTimeOut
   iOldTime = nowTime
   iOrgTime = nowTime
   iTime1   = nowTime
   is = 0

!  10ms でタイマーを呼び出す
   i = timeBeginPeriod(10)        ! call timer every 10 ms

!  Message loop   スキンモデルは,20(フレーム/秒)表示
 1 do
     if (PeekMessage(mesg, NULL, 0, 0, PM_REMOVE)) then
       if (mesg%message == WM_QUIT) go to 3
       if (.NOT. TranslateAccelerator(hWnd, hAccel, mesg)) then
         i = TranslateMessage(mesg)
         i = DispatchMessage(mesg)
       end if
     end if

     if (nowTime - iOldTime .GE. 990) then
       write(buf,'("Direct3D07",I5," FPS",I7," polygons/sec",A)')iFps,gPolygon,""C
       i = SetWindowText(hWnd,buf)
       iOldTime = nowTime
       gPolygon = 0
       iFps = 0
     end if
     iFps = iFps + 1

     nowTime = timeGetTime()
     gnowTime = FLOAT(nowTime - iOrgTime) * 3.8
     call Paint(hWnd)                    ! Direct3Dの描画
     is1 = nowTime - iTime1 - iTimeOut
     is  = is + is1
     iTime  = iTimeOut - is + is1 / 2
     if (iTime .GT. 0) call Sleep(iTime)
     iTime1 = nowTime
     if (giBoneCount == 0) go to 2 
   end do

!  スキンモデルでない場合,ポリゴン数の表示
 2 write(buf,'("Direct3D07",5X,I7," polygons",A)')gncFaces,""C
   i = SetWindowText(hWnd,buf)
   do while (GetMessage(mesg, NULL, 0, 0))
     if (.NOT. TranslateAccelerator(hWnd, hAccel, mesg)) then
       i = TranslateMessage(mesg)
       i = DispatchMessage(mesg)
     end if
     if (giBoneCount /= 0) go to 1
   end do

 3 i = timeEndPeriod(10)

(3) Xファイルの読み込みと解析

 Xファイルを読み込むための環境を用意します。テンプレート(Xファイルを読むためのフォーマット)を登録し,列挙オブジェクトを作成します。データを読み込み,フレーム登録を依頼します。スキンモデルの場合とそうでない場合に分けて,レンダリングの初期化を行います。スケーリング等で使用するためオブジェクトの大きさを計算しておきます。


subroutine LoadMeshHierarchy(iRes)
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_D3DXMatrixTranslation@16' :: D3DXMatrixTranslation
use WINCOM
implicit integer*4(D)

integer lpxofapi    ! LPDIRECTXFILE            DirectXFileオブジェクト
integer lpxofenum   ! LPDIRECTXFILEENUMOBJECT  DirectXFile列挙オブジェクト
integer lpxofobjCur ! LPDIRECTXFILEDATA        DirectXFileデータオブジェクト

integer, parameter :: DXFILELOAD_FROMFILE     = 0
integer, parameter :: D3DRM_XTEMPLATE_BYTES   = 3278

integer(1) D3DRM_XTEMPLATES (D3DRM_XTEMPLATE_BYTES) / &   ! <== rmxftmpl.h
        #78,#6f,#66,#20,#30,#33,#30,#32,#62, #69,#6e,#20,#30,#30,#36,#34,#1f,#00,#01, &
・・・中略・・・
    #61,#6e,#64,#65,#64,#14,  0, #b,  0/
type (SDrawElementA) de
pointer (lpdeMesh,de)

type (SFrameA) sw,cw
pointer (lpframe,sw),(lframe,cw)

    ginitpass  = 0
    ginitpass1 = 0
    giBoneCount = 0
    gAnimSetList = ""C
    ghAnimDlg  = 0
    m_pdeMesh  = 0
    m_maxBones = 0
    m_pBoneMatrices = 0

!   メッシュ管理領域の確保,lpframeRoot,lpframeAnimHead等を有する
    iHandle  = GlobalAlloc(GMEM_FIXED,SIZEOF(de))
    lpdeMesh = GlobalLock(iHandle) ! (lpdeMesh,de) : SDrawElement
    call ZeroMemory(lpdeMesh,SIZEOF(de))
    de%handle  = iHandle

!   フレームルート領域の確保
    call SFrameInt(de%lpframeRoot) ! 階層構造の情報が入る
    lpframe = de%lpframeRoot       ! 親子関係を保存するための木構造
    sw%Name = "Root"C              ! (lpframe,sw) : SFrameA

!   Xファイルを読む準備(IDirectXFileインターフェイスの取得)
    call XDirectXFileCreate(LOC(lpxofapi),iRes)

!   テンプレート(Xファイルを読むためのフォーマット)を登録
    call XRegisterTemplates(lpxofapi,LOC(D3DRM_XTEMPLATES),D3DRM_XTEMPLATE_BYTES,iRes)

!   列挙オブジェクトの作成   lpxofenum : IDirectXFileEnumObjectインターフェイスへのポインタ
    call XCreateEnumObject(lpxofapi,LOC(gXfile),DXFILELOAD_FROMFILE,LOC(lpxofenum),iRes)
    model = 0
!   データを読み込んでフレームに登録(フレーム検索)
    do
!     次のトップレベルオブジェクト(データオブジェクト)を取得
      call XGetNextDataObject(lpxofenum,LOC(lpxofobjCur),iRes)
      if (iRes /= 0) exit
!     フレームの読み込み  (この時点でm_pdeMeshは0であること)
      call SFrame_LoadFrames(de%lpframeRoot,lpxofobjCur,lpdeMesh,model)
      call XRelease(lpxofobjCur,iRes)
    end do

!   メッシュ管理領域を選択オブジェクトにする(複数オブジェクトの場合を考慮)
    m_pdeMesh = lpdeMesh

!   骨構造の行列を構成する
    call SFrame_FindBones(de%lpframeRoot,lpdeMesh,icon)

!   オブジェクトのバウンディング球を計算
    call SDrawElement_CalculateBoundingSphere(lpdeMesh,icon)

!   全体移動の行列を保存
    lframe = de%lpframeRoot               ! (lframe,cw)
    i = D3DXMatrixTranslation(LOC(cw%matRot),-de%vCenter%x,-de%vCenter%y,-de%vCenter%z)
    cw%matRotOrig = cw%matRot

!   インターフェイスを開放
    call XRelease(lpxofenum,iRes)
    call XRelease(lpxofapi,iRes)

!   レンダリングの初期化
    if (giBoneCount == 0) then
      call InitRender(ghWnd,iRes)    ! not SkinMesh model
    else
      call InitRender2(ghWnd,iRes)   ! Initialize Rendering routine for SkinMesh model
    end if
    return
!============================================================================
!   ClsMeshHierarchy
!   領域の開放
!============================================================================
entry ClsMeshHierarchy(iRes)
    if (iHandle == 0) return
!   レンダリングの終了
    call ClsRender(ghWnd,iRes)
    call ClsRender2(ghWnd,iRes)

!   SFrame_LoadMeshで確保したメッシュ情報(テクスチャ,マテリアル,ボーン)の解放
    call SFrame_FreeMesh(iRes)

!   メッシュメモリを解放
    lpdeMesh = m_pdeMesh    ! (lpdeMesh,de) : SDrawElementA
    call SFrame_ReleaseMeshes(de%lpframeRoot)
    m_pdeMesh = 0

!   すべてのフレーム領域を解放
    call SFrame_Delete(de%lpframeRoot)

!   メッシュ管理領域を解放
    iHandle = de%handle
    i = GlobalUnlock(iHandle)
    i = GlobalFree(iHandle)
    return
end

(4) レンダリング処理

 アニメーションを含むスキンメッシュモデルを読み込んだときに座標系の設定などレンダリングの初期化を行います。
 画面サイズが変更されたときは,ビュー変換/プロジェクション行列を再設定し,頂点シェーダも再作成します。
 レンダリングは,レンダリング開始後の経過時間を基に全アニメーションフレームを求めて描画します。

!****************************************************************************
!   InitRender2
!   初期化
!****************************************************************************
subroutine InitRender2(hWnd,iRes)
use WINCOM
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_D3DXMatrixTranslation@16' :: D3DXMatrixTranslation
implicit integer*4(D)
integer hWnd

type (SDrawElementA) de   ! 読み込んだメッシュを管理する領域
pointer (lpdeMesh,de)

type (SFrameA) sw,ck,cp   ! アニメーションのフレーム
pointer (lpframe,sw),(lpframeCur,ck),(lpfmCur,cp)

type (SMeshContain) sc    ! メッシュの情報を格納
pointer (lpmcMesh,sc)

    TYPE D3DVIEWPORT8
      sequence
      INTEGER*4 :: dwX,dwY           ! Viewport Top left
      INTEGER*4 :: dwWidth,dwHeight  ! Viewport Dimensions
      REAL*4    :: dvMinZ,dvMaxZ     ! Min/Max of clip volume
    END TYPE D3DVIEWPORT8

type (D3DVIEWPORT8) vp

type (D3DVECTOR) vUpVec,From

INTEGER, PARAMETER :: D3DCLEAR_TAR_ZBU =  3
INTEGER, PARAMETER :: D3DCULL_CW               =  2 ! 背面を右回りにカリング
INTEGER, PARAMETER :: D3DCULL_CCW              =  3 ! 背面を左回りにカリング

real mCur(4,4)
    if (.NOT. gactive)  return

    gmatWorld = gmatident
    lpdeMesh  = m_pdeMesh               ! (lpdeMesh,de)  SDrawElementA
    m_IxVtxShader = -1
    gnKey = 0                           ! 表示フレーム番号(0:ALL)

!   座標系の指定
    gLR       = 0                       ! 右手座標系:1
    gCULLmode = D3DCULL_CCW             ! 右手座標系:D3DCULL_CW
    gSgn      = -1.0                    ! 右手座標系:1.0

!   ライト方向指定
    gLight = D3DXVECTOR4(-1.2,-1.0, 1.0, 0.0)

!   視点位置と注視点の設定(デフォルトのカメラ位置の設定)
    gEyePt    = D3DVECTOR(0.0,de%vCenter%y,de%fRadius*12.0*gSgn) ! 視点位置
    gEyePtOrg = gEyePt
    gLookatPt = D3DVECTOR(0.0,de%vCenter%y,0.0)                  ! 注視点
    gZfar     = de%fRadius * 120.0      ! 後方クリップ面までの距離
    gZnear    = gZfar*0.01              ! 前方クリップ面までの距離
    gFov      = 20.0                    ! 視野角
    gScale    = 1.0

    lpframe = de%lpframeRoot            ! (lpframe,sw)   SFrameA
    sw%matTrans = gmatident
    sw%matTrans(2,4) = -de%vCenter%y

    ginitpass = 1
    return
!=====================================================================
!   ResizeRender2  Window Size 変更時(InitDirectDrawを実行後)に実行
!=====================================================================
entry ResizeRender2(hWnd,iRes)
    if (ginitpass /= 1) return

!   アスペクト比を求める
    gAspect = float(grect%right)/float(grect%bottom)

!   ビューポートを設定(ビューポートを指定すると指定した範囲内だけ描画される)
    vp = D3DVIEWPORT8(0,0,grect%right,grect%bottom,0.0,1.0)
    call XSetViewport(lpdv,LOC(vp),iRes)

!   RenderState の設定
    call CSkinModel_RenderState(iRes)

!   ビュー変換&プロジェクション行列の設定(視点位置,視野角)
    call TransForm2(gEyePt,gLookatPt)

!   バーテックスシェーダの作成を要求
    call CSkinModel_ClsVtxShader(iRes)
    call CSkinModel_CreateVertexShader(iRes)

!   テクスチャ,メッシュを生成しなおす
    call SFrame_Texture()
    return
!============================================================================
!   Render2  ポリゴンの描画
!============================================================================
entry Render2(hWnd,iRes)
    if (ginitpass /= 1) return

    lpdeMesh = m_pdeMesh                  ! (lpdeMesh,de)  SDrawElementA
    lpframe = de%lpframeRoot              ! (lpframe,sw)   SFrameA

!   ワールド変換行列をルートフレームの行列に設定(回転,拡大,移動)
    sw%matRot = gmatWorld
!   動く操作  全てのアニメーションフレームに時間を設定
    len2 = LSTRLEN(gAnimSetList)
    lpfmCur = de%lpframeAnimSet           ! (lpfmCur,cp)
    do while (lpfmCur /= 0)
      len1 = LSTRLEN(cp%Name)             ! 表示するアニメーションセットを名前で選択
      if (cp%Name(1:len1) == gAnimSetList(1:len2)) then
        lpframeCur = cp%lpframeAnimNext   ! アニメーションフレーム
        do while (lpframeCur /= 0)
          call SFrame_SetTime(lpframeCur,gnowTime) ! gnowTime : 経過時間
          lpframeCur = ck%lpframeAnimNext ! (lpframeCur,ck)
        end do
        exit
      end if
      lpfmCur = cp%lpframeAnimSetNext     ! (lpfmCur,cp)
    end do

!   全メッシュフレームの行列にアニメ行列を合成 (sw%matCombinedに値を設定)
    call SFrame_UpdateFrames(lpframe,gmatident,iRes)   ! gmatident:単位行列
    if (iRes /= 0) return

!   画面クリア
    call XClear(lpdv,0,NULL,D3DCLEAR_TAR_ZBU,#FF1E415A,1.0,0,iRes)

!   モデルの表示 (sw%matCombinedをworld行列に設定)
    icTriangles = 0
    call CSkinModel_DrawFrames(lpframe,icTriangles,iRes)
    gPolygon = gPolygon + icTriangles     ! icTriangles:面の数
    return
!============================================================================
!   CleanRender  後処理
!============================================================================
entry ClsRender2(hWnd,iRes)
    if (ginitpass /= 1) return
    ginitpass = 0

!   頂点シェーダーの解放
    call CSkinModel_ClsVtxShader(iRes)
    return
end

(5) 頂点シェーダの作成

 前章のプログラムでは,頂点シェーダは外部ファイルに記述していましたが,ファイル数が増えるため,今回はプログラム内に記述しています。また,骨の影響をWeight値で最大4つまで合成できるように,4種類のシェーダを用意しています。
 SkinnedMeshのサンプルプログラムとは,ライティングをより効果的にするように変更しています。

subroutine CSkinModel_CreateVertexShader(iRes)
use WINCOM
!DEC$ ATTRIBUTES STDCALL, ALIAS : '_D3DXAssembleShader@24' :: D3DXAssembleShader
implicit integer*4(D)
integer, parameter :: D3DVS_VERSION     = #FFFE0101  ! D3DVS_VERSION(1,1)
integer, parameter :: D3DUSAGE_SOFTWAREPROCESSING   = #00000010
integer IndxVtx(4),IxVtx1(6),IxVtx2(7),IxVtx3(7),IxVtx4(7)
integer*4 shader_program(4),iSize(4)
character*76 VertexShader1(14),VertexShader2(23),VertexShader3(28),VertexShader4(33)
!------------------------------------------------------------------------------
! 頂点シェーダ
!------------------------------------------------------------------------------
! v0     = 頂点の位置
! v1     = 重み
! v2     = 重みのインデックス
! v3     = 法線
! v4     = テクスチャー座標
!
! r0.w   = Last blend weight
! r1     = 重みのインデックス
! r2     = 一時的な座標
! r3     = 一時的な法線
! r4     = カメラ空間での座標
! r5     = カメラ空間での法線
!
! c0	 = {1, 0.5, 0, 765.01};  3*255+eps=765.01
! c1	 = 光源方向
! c2-c5  = 射影行列
! c7	 = 環境光
! c8	 = 平行光源
! c9-c95 = 行列パレット
!
! oPos	 = 位置
! oD0	 = 平行光
! oT0	 = テクスチャー座標
!------------------------------------------------------------------------------
data VertexShader1/ &
    "vs.1.1                            // シェーダ バージョン 1.1               \n"C, &
    "mul r1,v2.zyxw,c0.wwww            // Geforce3 で UBYTE4 がないのを補う     \n"C, &
!
    "mov a0.x,r1.x                     // 1 つめの行列を設定                    \n"C, &
    "m4x3 r4,v0,c[a0.x + 9]                                                     \n"C, &
    "m3x3 r5,v3,c[a0.x + 9]                                                     \n"C, &
!
    "mov r4.w,c0.x                     // 座標変換                              \n"C, &
    "m4x4 oPos,r4,c2                                                            \n"C, &
!
    "dp3  r1, -r5, c1                 ; 法線と光源ベクトルの内積                \n"C, &
    "mad  r1,  r1, c0.y, c0.y         ; r1 = r1 * 0.5 + 0.5 (0.0〜1.0に補正)    \n"C, &
    "mul  r1,  r1, r1                 ; r1 = r1 * r1                            \n"C, &
    "mul  r1,  r1, c8                 ; ライト・マテリアル色を積算              \n"C, &
    "add  oD0.xyz, r1,   c7           ; 頂点色に環境色を加算                    \n"C, &
    "mov  oD0.w, c8.w                 ; マテリアルのアルファを設定              \n"C, &
!
    "mov oT0, v4                       // テクスチャー座標のコピー              \n"C/
!----------------------------------------------------------------------------
data VertexShader2/ &
    "vs.1.1                            // シェーダ バージョン 1.1               \n"C, &
    "mul  r1, v2.zyxw, c0.wwww         // Geforce3 で UBYTE4 がないのを補う     \n"C, &
!                                         D3DCOLOR ARGB ==> RGBA
    "dp3  r0.w, v1.xyz, c0.xzz         // 最後の係数はウェイトの合計1から算出   \n"C, &
    "sub  r0.w, c0.x, r0.w                                                      \n"C, &
!   ボーン1
    "mov  a0.x, r1.x                   // 1 つめの行列を設定                    \n"C, &
    "m4x3 r4, v0, c[a0.x+9]            // v0 頂点                               \n"C, &
    "m3x3 r5, v3, c[a0.x+9]            // v3 法線                               \n"C, &
    "mul  r4, r4, v1.xxxx              // ウェイトをかけて合成する              \n"C, &
    "mul  r5, r5, v1.xxxx              // v1 ウェイト                           \n"C, &
!   ボーン2
    "mov  a0.x, r1.y                   // 2 つめの行列を設定                    \n"C, &
    "m4x3 r2, v0, c[a0.x+9]                                                     \n"C, &
    "m3x3 r3, v3, c[a0.x+9]                                                     \n"C, &
    "mad  r4, r2, r0.wwww, r4          // ウェイトをかけて合成する              \n"C, &
    "mad  r5, r3, r0.wwww, r5                                                   \n"C, &
!
    "mov  r4.w, c0.x                   // 4×3 行列だから w = 1                 \n"C, &
    "m4x4 oPos, r4, c2                 // 座標変換                              \n"C, &
!
    "dp3  r1, -r5, c1                 ; 法線と光源ベクトルの内積                \n"C, &
    "mad  r1,  r1, c0.y, c0.y         ; r1 = r1 * 0.5 + 0.5 (0.0〜1.0に補正)    \n"C, &
    "mul  r1,  r1, r1                 ; r1 = r1 * r1                            \n"C, &
    "mul  r1,  r1, c8                 ; ライト・マテリアル色を積算              \n"C, &
    "add  oD0.xyz, r1,   c7           ; 頂点色に環境色を加算                    \n"C, &
    "mov  oD0.w, c8.w                 ; マテリアルのアルファを設定              \n"C, &
    "mov  oT0, v4                      // テクスチャー座標のコピー              \n"C/
!----------------------------------------------------------------------------
data VertexShader3/ &
    "vs.1.1                            // シェーダ バージョン 1.1               \n"C, &
    "mul r1,v2.zyxw,c0.wwww            // Geforce3 で UBYTE4 がないのを補う     \n"C, &
!
    "dp3 r0.w,v1.xyz,c0.xxz            // 最後の係数はウェイトの合計が1から算出 \n"C, &
    "add r0.w,-r0.w,c0.x                                                        \n"C, &
!
    "mov a0.x,r1.x                     // 1 つめの行列を設定                    \n"C, &
    "m4x3 r4,v0,c[a0.x + 9]                                                     \n"C, &
    "m3x3 r5,v3,c[a0.x + 9]                                                     \n"C, &
    "mul r4,r4,v1.xxxx                 // 係数をかけて合成する                  \n"C, &
    "mul r5,r5,v1.xxxx                                                          \n"C, &
!
    "mov a0.x,r1.y                     // 2 つめの行列を設定                    \n"C, &
    "m4x3 r2,v0,c[a0.x + 9]                                                     \n"C, &
    "m3x3 r3,v3,c[a0.x + 9]                                                     \n"C, &
    "mad r4,r2,v1.yyyy,r4              // 係数をかけて合成する                  \n"C, &
    "mad r5,r3,v1.yyyy,r5                                                       \n"C, &
!
    "mov a0.x,r1.z                     // 3 つめの行列を設定                    \n"C, &
    "m4x3 r2,v0,c[a0.x + 9]                                                     \n"C, &
    "m3x3 r3,v3,c[a0.x + 9]                                                     \n"C, &
    "mad r4,r2,r0.wwww,r4              // 係数をかけて合成する                  \n"C, &
    "mad r5,r3,r0.wwww,r5                                                       \n"C, &
!
    "mov r4.w,c0.x                     // 座標変換                              \n"C, &
    "m4x4 oPos,r4,c2                                                            \n"C, &
!
    "dp3  r1, -r5, c1                 ; 法線と光源ベクトルの内積                \n"C, &
    "mad  r1,  r1, c0.y, c0.y         ; r1 = r1 * 0.5 + 0.5 (0.0〜1.0に補正)    \n"C, &
    "mul  r1,  r1, r1                 ; r1 = r1 * r1                            \n"C, &
    "mul  r1,  r1, c8                 ; ライト・マテリアル色を積算              \n"C, &
    "add  oD0.xyz, r1,   c7           ; 頂点色に環境色を加算                    \n"C, &
    "mov  oD0.w, c8.w                 ; マテリアルのアルファを設定              \n"C, &
    "mov oT0, v4                       // テクスチャー座標のコピー              \n"C/
!----------------------------------------------------------------------------
data VertexShader4/ &
    "vs.1.1                            // シェーダ バージョン 1.1               \n"C, &
    "mul r1,v2.zyxw,c0.wwww            // Geforce3 で UBYTE4 がないのを補う     \n"C, &
!
    "dp3 r0.w,v1.xyz,c0.xxx            // 最後の係数はウェイトの合計が1から算出 \n"C, &
    "add r0.w,-r0.w,c0.x                                                        \n"C, &
!
    "mov a0.x,r1.x                     // 1 つめの行列を設定                    \n"C, &
    "m4x3 r4,v0,c[a0.x + 9]                                                     \n"C, &
    "m3x3 r5,v3,c[a0.x + 9]                                                     \n"C, &
    "mul r4,r4,v1.xxxx                 // 係数をかけて合成する                  \n"C, &
    "mul r5,r5,v1.xxxx                                                          \n"C, &
!
    "mov a0.x,r1.y                     // 2 つめの行列を設定                    \n"C, &
    "m4x3 r2,v0,c[a0.x + 9]                                                     \n"C, &
    "m3x3 r3,v3,c[a0.x + 9]                                                     \n"C, &
    "mad r4,r2,v1.yyyy,r4              // 係数をかけて合成する                  \n"C, &
    "mad r5,r3,v1.yyyy,r5                                                       \n"C, &
!
    "mov a0.x,r1.z                     // 3 つめの行列を設定                    \n"C, &
    "m4x3 r2,v0,c[a0.x + 9]                                                     \n"C, &
    "m3x3 r3,v3,c[a0.x + 9]                                                     \n"C, &
    "mad r4,r2,v1.zzzz,r4              // 係数をかけて合成する                  \n"C, &
    "mad r5,r3,v1.zzzz,r5                                                       \n"C, &
!
    "mov a0.x,r1.w                     // 4 つめの行列を設定                    \n"C, &
    "m4x3 r2,v0,c[a0.x + 9]                                                     \n"C, &
    "m3x3 r3,v3,c[a0.x + 9]                                                     \n"C, &
    "mad r4,r2,r0.wwww,r4              // 係数をかけて合成する                  \n"C, &
    "mad r5,r3,r0.wwww,r5                                                       \n"C, &
!
    "mov r4.w,c0.x                     // 座標変換                              \n"C, &
    "m4x4 oPos,r4,c2                                                            \n"C, &
!
    "dp3  r1, -r5, c1                 ; 法線と光源ベクトルの内積                \n"C, &
    "mad  r1,  r1, c0.y, c0.y         ; r1 = r1 * 0.5 + 0.5 (0.0〜1.0に補正)    \n"C, &
    "mul  r1,  r1, r1                 ; r1 = r1 * r1                            \n"C, &
    "mul  r1,  r1, c8                 ; ライト・マテリアル色を積算              \n"C, &
    "add  oD0.xyz, r1,   c7           ; 頂点色に環境色を加算                    \n"C, &
    "mov  oD0.w, c8.w                 ; マテリアルのアルファを設定              \n"C, &
    "mov oT0, v4                       // テクスチャー座標のコピー              \n"C/
!----------------------------------------------------------------------------
!   インデックススキニングのための頂点シェーダーの生成
data IxVtx1/#20000000,#40020000,#40040002,#40020003,#40010004,#FFFFFFFF/
data IxVtx2/#20000000,#40020000,#40000001,#40040002,#40020003,#40010004,#FFFFFFFF/
data IxVtx3/#20000000,#40020000,#40010001,#40040002,#40020003,#40010004,#FFFFFFFF/
data IxVtx4/#20000000,#40020000,#40020001,#40040002,#40020003,#40010004,#FFFFFFFF/
    if (m_IxVtxShader(1) /= -1) return  ! シェーダ作成済み?

    IndxVtx(1) = LOC(IxVtx1)
    IndxVtx(2) = LOC(IxVtx2)
    IndxVtx(3) = LOC(IxVtx3)
    IndxVtx(4) = LOC(IxVtx4)
	
    shader_program(1) = LOC(VertexShader1)
    shader_program(2) = LOC(VertexShader2)
    shader_program(3) = LOC(VertexShader3)
    shader_program(4) = LOC(VertexShader4)

    iSize(1) = SIZEOF(VertexShader1)
    iSize(2) = SIZEOF(VertexShader2)
    iSize(3) = SIZEOF(VertexShader3)
    iSize(4) = SIZEOF(VertexShader4)

!   アドレスレジスタが使えないならソフトウェアT&L
!   D3DRS_SOFTWAREVERTEXPROCESSINGメンバがTRUEのとき,D3DUSAGE_SOFTWAREPROCESSING
!   フラグを設定する。
    ibUseSW = 0
    if (gcaps%VertexShaderVersion < D3DVS_VERSION) then 
      ibUseSW = D3DUSAGE_SOFTWAREPROCESSING
    end if

    do j = 1,4
!     シェーダプログラムの読み込み
      iRes = D3DXAssembleShader(shader_program(j),iSize(j),0,NULL,LOC(lpCode),NULL)
!     頂点シェーダの生成
      call XGetBufferPointer(lpCode,lpFunc)
      call XCreateVertexShader(lpdv,IndxVtx(j),lpFunc,LOC(m_IxVtxShader(j)),ibUseSW,iRes)
      call XRelease(lpCode,iRes)
    end do
    return
!============================================================================
!  デバイスが変更されたときと最後に実行
!============================================================================
entry CSkinModel_ClsVtxShader(iRes)
    if (m_IxVtxShader(1) == -1) return  ! シェーダは作成されてない
    do j = 1,4
      if (m_IxVtxShader(j) /= 0) then
        call XDeleteVertexShader(m_pD3DDev,m_IxVtxShader(j),iRes)
        m_IxVtxShader(j) = -1
      end if
    end do
    return
end

(6) フレームの読み込み

 読み込んだデータは確保したフレームに登録します。データのタイプを調べ,タイプに応じてそれぞれの処理を行います。このサブルーチンは再帰的に動作します。

!****************************************************************************
!   SFrame_LoadFrames
!   フレームの読み込み
!****************************************************************************
recursive subroutine SFrame_LoadFrames(lpframe,lpxofobjCur,lpde,model)
use WINCOM
type (T_GUID), PARAMETER :: TID_D3DRMFrame =             &
    T_GUID(Z'3d82ab46',Z'62da',Z'11cf',                  &
    CHAR(Z'ab')//CHAR(Z'39')//CHAR(Z'00')//CHAR(Z'20')// &
    CHAR(Z'af')//CHAR(Z'71')//CHAR(Z'e4')//CHAR(Z'33'))

type (T_GUID), PARAMETER :: TID_D3DRMMesh =              &
    T_GUID(Z'3d82ab44',Z'62da',Z'11cf',                  &
    CHAR(Z'ab')//CHAR(Z'39')//CHAR(Z'00')//CHAR(Z'20')// &
    CHAR(Z'af')//CHAR(Z'71')//CHAR(Z'e4')//CHAR(Z'33'))

type (T_GUID), PARAMETER :: TID_D3DRMAnimationSet =      &
    T_GUID(Z'3d82ab50',Z'62da',Z'11cf',                  &
    CHAR(Z'ab')//CHAR(Z'39')//CHAR(Z'00')//CHAR(Z'20')// &
    CHAR(Z'af')//CHAR(Z'71')//CHAR(Z'e4')//CHAR(Z'33'))

type (T_GUID), PARAMETER :: TID_D3DRMAnimation =         &
    T_GUID(Z'3d82ab4f',Z'62da',Z'11cf',                  &
    CHAR(Z'ab')//CHAR(Z'39')//CHAR(Z'00')//CHAR(Z'20')// &
    CHAR(Z'af')//CHAR(Z'71')//CHAR(Z'e4')//CHAR(Z'33'))

type (T_GUID), PARAMETER :: TID_D3DRMFrameTransformMatrix = &
    T_GUID(Z'f6f23f41',Z'7686',Z'11cf',                  &
    CHAR(Z'8f')//CHAR(Z'52')//CHAR(Z'00')//CHAR(Z'40')// &
    CHAR(Z'33')//CHAR(Z'35')//CHAR(Z'94')//CHAR(Z'a3'))

type (T_GUID), PARAMETER :: IID_IDirectXFileData =       &
    T_GUID(Z'3d82ab44',Z'62da',Z'11cf',                  &
    CHAR(Z'ab')//CHAR(Z'39')//CHAR(Z'00')//CHAR(Z'20')// &
    CHAR(Z'af')//CHAR(Z'71')//CHAR(Z'e4')//CHAR(Z'33'))

type (SFrameA) sw,ck
pointer (lpframe,sw),(lpframeCur,ck)

real matNew(4,4)
pointer (lpmatNew,matNew)

type (T_GUID) utype
pointer (lputype, utype)

    lpxofobjChild = 0    ! LPDIRECTXFILEDATA
    lpxofChild    = 0    ! LPDIRECTXFILEOBJECT

!   タイプを調べて,種類に応じたロードを行う
    call XGetType(lpxofobjCur,LOC(lputype),iRes)
    if (iRes /= 0) return
    if (memcmp(lputype,LOC(TID_D3DRMMesh),16) == 0) then
!     ☆メッシュファイル(頂点情報)
      call SFrame_LoadMesh(lpframe,lpxofobjCur,model)

    else if (memcmp(lputype,LOC(TID_D3DRMFrameTransformMatrix),16) == 0) then
!     ☆姿勢行列(各骨に関連する行列データ) トランスフォーム行列の取得
      call XGetData(lpxofobjCur,NULL,LOC(iSize),LOC(lpmatNew),iRes)
      if (iRes == 0) then           ! 親フレームに行列を設定
        sw%matRot     = matNew      ! (lpmatNew,matNew)
        sw%matRotOrig = matNew      ! (lpframe,sw) : SFrameA
      end if

    else if (memcmp(lputype,LOC(TID_D3DRMAnimationSet),16) == 0) then 
!     ☆アニメーションセット(その中にアニメーションデータを含む)
      call SFrame_LoadAnimationSet(lpframe,lpxofobjCur,lpde,model)

    else if (memcmp(lputype,LOC(TID_D3DRMAnimation),16) == 0) then 
!     ☆アニメーション(1つのアニメーションデータ)
      call SFrame_LoadAnimation(lpframe,lpxofobjCur,lpde,model)

    else if (memcmp(lputype,LOC(TID_D3DRMFrame),16) == 0) then
!     ☆子フレームの情報領域確保
      call SFrameInt(lpframeCur)    ! (lpframeCur,ck)

!     名前があれば読み込む
      call XGetName(lpxofobjCur,NULL,LOC(NameLen),iRes)
      if (NameLen > 0) then
        ck%lszName = LOC(ck%Name)   ! (lpframeCur,ck)
        call XGetName(lpxofobjCur,ck%lszName,LOC(NameLen),iRes)
      end if

!     フレームの追加登録
      call SFrame_AddFrame(lpframe,lpframeCur)

!     子供のデータを読み込む
      do
!       次のオブジェクトを取得
        call XGetNextObject(lpxofobjCur,LOC(lpxofChild),iRes)
        if (iRes /= 0) exit
!       子供に FileData を要求して,FileData があったら再起的に読み込む
        call XQueryInterface(lpxofChild,LOC(IID_IDirectXFileData),LOC(lpxofobjChild),iRes)
        if (iRes == 0) then
          call SFrame_LoadFrames(lpframeCur,lpxofobjChild,lpde,model)
          call XRelease(lpxofobjChild,iRes)
        end if
        call XRelease(lpxofChild,iRes)
      end do
    end if
    return
end

(7) アニメーションデータを読み込んだときは,それが含まれるアニメーションセットにリンクを張り,アニメーションデータのタイプに応じて保存先を決めます。Xファイルのアニメーションデータのタイプ(キー)には,回転,拡大・縮小,平行移動の他にこれらを合成した行列の4タイプがあります。本章の美人モデルには行列キーしか存在していません。

!****************************************************************************
!   アニメデータのロード
!****************************************************************************
subroutine SFrame_LoadAnimation(lpframe,lpxofobjCur,lpde,model)
use WINCOM

type (T_GUID), PARAMETER :: IID_IDirectXFileDataReference =  &
    T_GUID(Z'3d82ab45',Z'62da',Z'11cf',                      &
    CHAR(Z'ab')//CHAR(Z'39')//CHAR(Z'00')//CHAR(Z'20')//     &
    CHAR(Z'af')//CHAR(Z'71')//CHAR(Z'e4')//CHAR(Z'33'))

type (T_GUID), PARAMETER :: IID_IDirectXFileData =       &
    T_GUID(Z'3d82ab44',Z'62da',Z'11cf',                  &
    CHAR(Z'ab')//CHAR(Z'39')//CHAR(Z'00')//CHAR(Z'20')// &
    CHAR(Z'af')//CHAR(Z'71')//CHAR(Z'e4')//CHAR(Z'33'))

type (T_GUID), PARAMETER :: TID_D3DRMFrame =             &
    T_GUID(Z'3d82ab46',Z'62da',Z'11cf',                  &
    CHAR(Z'ab')//CHAR(Z'39')//CHAR(Z'00')//CHAR(Z'20')// &
    CHAR(Z'af')//CHAR(Z'71')//CHAR(Z'e4')//CHAR(Z'33'))

type (T_GUID), PARAMETER :: TID_D3DRMAnimationKey =      &
    T_GUID(Z'10dd46a8',Z'775b',Z'11cf',                  &
    CHAR(Z'8f')//CHAR(Z'52')//CHAR(Z'00')//CHAR(Z'40')// &
    CHAR(Z'33')//CHAR(Z'35')//CHAR(Z'94')//CHAR(Z'a3'))

type (SDrawElementA) de
pointer (lpde,de)

type (SFrameA) sw,ck
pointer (lpframe,sw),(lpframeCur,ck)

type (T_GUID) utype
pointer (lputype,utype)

integer Data(1)
pointer (lpData,Data)

!   Xファイルの情報
    TYPE SMatrixKeyXFile
      sequence
      integer dwTime
      integer dwFloats
      real mat(4,4)
    END TYPE SMatrixKeyXFile

type (SMatrixKeyXFile) MatrixKeyXFile(1)
pointer (lpFileMatrixKey,MatrixKeyXFile)

    TYPE SRotateKeyXFile
      sequence
      integer dwTime
      integer dwFloats
      real    w,x,y,z
    END TYPE SRotateKeyXFile

type (SRotateKeyXFile) RotateKeyXFile(1)
pointer (lpFileRotateKey,RotateKeyXFile)

    TYPE SPositionKeyXFile
      sequence
      integer dwTime
      integer dwFloats
      type (D3DXVECTOR3) vPos
    END TYPE SPositionKeyXFile

type (SPositionKeyXFile) PositionKeyXFile(1)
pointer (lpFilePosKey,PositionKeyXFile)

    TYPE SScaleKeyXFile
      sequence
      integer dwTime
      integer dwFloats
      type (D3DXVECTOR3) vScale
    END TYPE SScaleKeyXFile

type (SScaleKeyXFile) ScaleKeyXFile(1)
pointer (lpFileScaleKey,ScaleKeyXFile)
!------------------------------------------
!   メモリに格納されるときのXファイルの情報
    type SMatrixKey
      sequence
      integer dwTime
      real mat(4,4)
    end type SMatrixKey

type (SMatrixKey) MatrixKey(1)
pointer (lpMatrixKey,MatrixKey)

    TYPE D3DXQUATERNION
      SEQUENCE
      REAL*4 :: x,y,z,w
    END TYPE D3DXQUATERNION

    TYPE SRotateKey
      sequence
      integer dwTime
      type (D3DXQUATERNION) quatRotate
    END TYPE SRotateKey

type (SRotateKey) RotateKey(1)
pointer (lpRotateKey,RotateKey)

    TYPE SPositionKey
      sequence
      integer dwTime
      type (D3DXVECTOR3) vPos
    END TYPE SPositionKey

type (SPositionKey) PositionKey(1)
pointer (lpPositionKey,PositionKey)

    TYPE SScaleKey
      sequence
      integer dwTime
      type (D3DXVECTOR3) vScale
    END TYPE SScaleKey

type (SScaleKey) ScaleKey(1)
pointer (lpScaleKey,ScaleKey)

!   アニメーションフレーム領域の確保
    call SFrameInt(lpframeCur)    ! (lpframeCur,ck)
!   フレームを追加
    call SFrame_AddFrame(lpframe,lpframeCur)

!   アニメーションセットのSFrameA(lpframeAnimNext)からリンクされていたフレームに割り込んでリンク
    ck%lpframeAnimNext = sw%lpframeAnimNext
    sw%lpframeAnimNext = lpframeCur

!   読める限りのデータを読んで,アニメーションのデータをロードする
    do
      call XGetNextObject(lpxofobjCur,LOC(lpxofChild),iRes)
      if (iRes /= 0) exit
!     参照をしらべる
      call XQueryInterface(lpxofChild,LOC(IID_IDirectXFileDataReference),LOC(lpxofobjChildRef),iRes)
      if (iRes == 0) then
!       データ参照を解決する
        call XResolve(lpxofobjChildRef,LOC(lpxofobjChild),iRes)
        if (iRes == 0) then
          call XGetType(lpxofobjChild,LOC(lputype),iRes)   ! タイプの取得
          if (iRes == 0) then
            if (memcmp(lputype,LOC(TID_D3DRMFrame),16) == 0) then
              if (ck%lpframeToAnimate /= NULL) exit        ! (lpframeCur,ck)
!             名前を読み込む
              call XGetName(lpxofobjChild,NULL,LOC(NameLen),iRes)
              if (NameLen > 0) then
                ck%lszName = LOC(ck%Name)
                call XGetName(lpxofobjChild,ck%lszName,LOC(NameLen),iRes)
!               同じ名前のフレームを検索し,リンクを張る
                ck%lpframeToAnimate = IFrame_FindFrame(de%lpframeRoot,ck%lszName,0)
              end if
            end if
          end if
          call XRelease(lpxofobjChild,iRes)
        end if
        call XRelease(lpxofobjChildRef,iRes)
      else
!       参照がなければデータを調べる
        call XQueryInterface(lpxofChild,LOC(IID_IDirectXFileData),LOC(lpxofobjChild),iRes)
        if (iRes == 0) then
!         タイプを調べて,種類に応じた処理を行う
          call XGetType(lpxofobjChild,LOC(lputype),iRes)   ! タイプの取得
          if (iRes == 0) then
            if (memcmp(lputype,LOC(TID_D3DRMFrame),16) == 0) then
!             フレームデータ
              call SFrame_LoadFrames(lpframeCur,lpxofobjChild,lpde,model)
            else if (memcmp(lputype,LOC(TID_D3DRMAnimationKey),16) == 0) then 
!             アニメーションのキーフレームデータ
              call XGetData(lpxofobjChild,NULL,LOC(iSize),LOC(lpData),iRes)
              iKeyType = Data(1)     ! アニメ属性
              iKeys    = Data(2)     ! 全コマ数
              select case(iKeyType)
              case (0) !  回転        
                kSize = SIZEOF(RotateKey(1))*iKeys
                lpRotateKey = MALLOC(kSize)        ! (lpRotateKey,RotateKey(1))
                ck%m_pRotateKeys = lpRotateKey     ! (lpframeCur,ck)
                ck%m_cRotateKeys = iKeys
                lpFileRotateKey  = lpData + 8      ! (lpFileRotateKey,RotateKeyXFile(1))
                do k = 1,iKeys
                  RotateKey(k)%dwTime       = RotateKeyXFile(k)%dwTime
                  RotateKey(k)%quatRotate%x = RotateKeyXFile(k)%x
                  RotateKey(k)%quatRotate%y = RotateKeyXFile(k)%y
                  RotateKey(k)%quatRotate%z = RotateKeyXFile(k)%z
                  RotateKey(k)%quatRotate%w = RotateKeyXFile(k)%w
                end do
              case (1) !  拡大縮小
                kSize = SIZEOF(ScaleKey(1))*iKeys
                lpScaleKey = MALLOC(kSize)         ! (lpScaleKey,ScaleKey(1))
                ck%m_pScaleKeys = lpScaleKey       ! (lpframeCur,ck)
                ck%m_cScaleKeys = iKeys
                lpFileScaleKey  = lpData + 8       ! (lpFileScaleKey,ScaleKeyXFile(1))
                do k = 1,iKeys
                  ScaleKey(k)%dwTime = ScaleKeyXFile(k)%dwTime
                  ScaleKey(k)%vScale = ScaleKeyXFile(k)%vScale
                end do
              case (2) !  平行移動
                kSize = SIZEOF(PositionKey(1))*iKeys
                lpPositionKey = MALLOC(kSize)      ! (lpPositionKey,PositionKey(1))
                ck%m_pPositionKeys = lpPositionKey ! (lpframeCur,ck)
                ck%m_cPositionKeys = iKeys
                lpFilePosKey       = lpData + 8    ! (lpFilePosKey,PositionKeyXFile(1))
                do k = 1,iKeys
                  PositionKey(k)%dwTime = PositionKeyXFile(k)%dwTime
                  PositionKey(k)%vPos   = PositionKeyXFile(k)%vPos
                end do
              case (4) ! 行列
                kSize = SIZEOF(MatrixKey(1))*iKeys
                lpMatrixKey = MALLOC(kSize)        ! (lpMatrixKey,MatrixKey(1))
                ck%m_pMatrixKeys = lpMatrixKey     ! (lpframeCur,ck)
                ck%m_cMatrixKeys = iKeys
                lpFileMatrixKey  = lpData + 8      ! (lpFileMatrixKey,MatrixKeyXFile(1))
                do k = 1,iKeys
                  MatrixKey(k)%dwTime = MatrixKeyXFile(k)%dwTime
                  MatrixKey(k)%mat    = MatrixKeyXFile(k)%mat
                end do
              end select
            end if
            call XRelease(lpxofobjChild,iRes)
          end if
        end if
      end if
      call XRelease(lpxofChild,iRes)
    end do
    return
!===========================================================================
!   アニメーション行列の解放
!===========================================================================
entry SFrame_FreeAnimation(lframe)
    lpframeCur = lframe          ! (lpframeCur,ck) : SFrameA
    if(ck%m_pRotateKeys /= 0) then
      call FREE(ck%m_pRotateKeys)
      ck%m_pRotateKeys = 0
    end if
    if(ck%m_pScaleKeys /= 0) then
      call FREE(ck%m_pScaleKeys)
      ck%m_pScaleKeys = 0
    end if
    if(ck%m_pPositionKeys /= 0) then
      call FREE(ck%m_pPositionKeys)
      ck%m_pPositionKeys = 0
    end if
    if(ck%m_pMatrixKeys /= 0) then
      call FREE(ck%m_pMatrixKeys)
      ck%m_pMatrixKeys = 0
    end if
    return
end

 再帰呼び出し

 Fortran90/95では,サブルーチンの再帰呼び出しが可能になりました。再帰処理は,リスト構造やツリー構造のデータ処理に効果を発揮します。数学的処理では,例えば,多重積分のように積分関数の定義の中で同じ積分ルーチンを引用するなどの利用方法が考えられます。 本プログラムの中では,フレームデータを読み込むときや変換行列を合成するときに再帰呼び出しを行っています。

10.5 fragMOTIONによる骨入れとモーションの作成

 次にモーションをどうやって作成するかという説明をします。
プログラムができても,サンプルのtinyを表示するだけでは物足りません。自分の好みのモデルを作成してこそ意味があるといえるでしょう。モデルをアニメートするのにfragMOTIONというソフトを使用しました。

 1) fragMOTIONを起動したら,まずEditメニューのCustomize User Interfaceを選択し,ViewPreferencesのRendererの項目をrenderD3D9.dllに変更します。

 2) 用意しておいた既存のBVH型式又はX型式のアニメーション付きデータをimportし,骨とアニメーションだけを抽出します。アニメーション付きデータは,ネットで探すと見つかります。最初はできるだけ簡単な動作のものを選びます。骨を一から作成するのは骨が折れるので他から拝借する方が賢明です。

 3) Metasequoiaで作成したモデルのX型式のデータをimportします。
  importする前に,X型式のデータは本プログラム(Direct3D07)を用いて最適化しておきます。最適化しないで用いると一部のデータが骨の動きに追随できない場合が生じます。
  fragMOTIONは右手座標系を使用しています。そのため,Metasequoia等左手座標系で作成したXデータは左右逆になって表示されます。ミラー機能を用いると左右を反転させることができます。
 また,fragMOTIONにimportすると,法線が再計算されてしまい,元のデータが失われてしまいますので,必要に応じてXデータを直接編集して法線データを置き換えてやります。

 4) 用意しておいた骨とアニメーションデータをimportし,メッシュモデルにマージします。モデルにぴったり合うように骨の関節位置や大きさを調節します。

 5) 頂点と骨の関連付けを行います。先ずは,Auto Assign Verticesの機能を用いて自動で関連付けを行います。関連付けができたら一度動かしてみて様子を見ます。この操作は一回ではうまくいかないので,何度も骨の位置を調節してできるだけ骨の動きに頂点が自然に追随するように調整します。

 6) 自動ではうまくいかない部分を手動で関連付けします。頂点を選択し,骨のどの部分に関連付けるかを決めます。関節に近い部分は2つの骨に関連付けし,その影響の度合いをWeight値で設定します。最大4つの骨に関連付けできます。ただし,Weight値を 0 で関連付けするとローディング時にエラーになることがあるので,注意が必要です。

図10.4 fragMOTIONで骨を入れた例

 6) 骨入れがうまくいけば,歩行動作などは試行錯誤が必要ですが,簡単なモーションであればKeyFrameEditorを用いて比較的容易に付けることができます。

図10.5 KeyFrameEditorでモーションを付ける例

 7) 最後に作成したアニメーションデータと伴にXファイル形式でExportします。

10.6 プログラムのサンプル

 サンプルプログラムのダウンロード(5340KB) 06/01/02 

10.7 まとめ

 今回のアニメーションでは,予め用意したいくつかのモーションを表示させることができました。今後は,これらのモーションを組み合わせて状況に応じた動作をとれるようにすることかと思います。