CX's Hatena Blog

はてなブログを使ってみるテスト

DirectX で地球を回してみるテスト

○○ で地球を回してみるシリーズ。

今回日程 シリーズ構成言語
1日目OpenGL で地球を回してみるテスト C/C++
2日目DirectX で地球を回してみるテスト C/C++
3日目Java 3D で地球を回してみるテスト Java
4日目WebGL で地球を回してみるテスト JavaScript
5日目Three.js で地球を回してみるテスト JavaScript
6日目Babylon.js で地球を回してみるテストJavaScript
7日目gl.enchant.js で地球を回してみるテストJavaScript
8日目PhiloGL で地球を回してみるテストJavaScript
9日目CubicVR.js で地球を回してみるテストJavaScript
最終日Away3D で地球を回してみるテストJavaScript
追加+1Flash 版 Away3D で地球を回してみるテストActionScript
追加+2WPF で地球を回してみるテストC# + XAML
追加+3JavaFX 3D で地球を回してみるテストJava

昨日は OpenGL で地球を回してみましたが、今回は、DirectX で同様のことを実現したいと思います。

f:id:cx20:20140121013440p:plain

0. 事前準備

以下のコンパイラとライブラリを用意します。

Windows 8.1SDK に含まれる DirectX のライブラリはストアアプリ用の為、デスクトップ用に DirectX の開発をするには「DirectX SDK(June 2010)」をインストールする必要があるようです。

1. Visual C++ 2013 を起動する

f:id:cx20:20140120001921p:plain

2. 新しいプロジェクトを作成する

新しいプロジェクト名は「earth」としておきます。

f:id:cx20:20140121013441p:plain

f:id:cx20:20140121013443p:plain

f:id:cx20:20140121013444p:plain

3. プロジェクトのプロパティでインクルードファイルとライブラリのパスを追加する

f:id:cx20:20140121013445p:plain

f:id:cx20:20140121013446p:plain

f:id:cx20:20140121013447p:plain

4. ソースを修正する

f:id:cx20:20140121013448p:plain

ウィザードで生成された上記の「earth.cpp」を以下のコードに置き換えます。

#include "stdafx.h"
#include "earth.h"

#define R      50.0f  // 球半径
#define VIEW_R 150.0f // カメラ位置

#include <windows.h>
#include <d3dx9.h>
#include <tchar.h>
#include <mmsystem.h>

#pragma once
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib,"winmm.lib")

#define SAFE_RELEASE(p)         { if (p) { (p)->Release(); (p)=NULL; } }
#define FVF_TLVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)

LPDIRECT3D9             g_pD3D      = NULL;
LPDIRECT3DDEVICE9       g_pDev      = NULL;
LPD3DXMESH              g_pMeshApp  = NULL;
LPD3DXMESH              g_pMeshApp2 = NULL;
LPDIRECT3DTEXTURE9      g_pTextures = NULL;

DWORD start;    // プログラム開始時のシステム時刻

HRESULT InitD3D(HWND hWnd);
HRESULT CreateSphere();
void  SetupMatrices();
void Render();
void Cleanup();
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// 頂点フォーマットの定義
struct  TLVERTEX{
    float       x, y, z;
    float       tu, tv;
};

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int)
{
    LPCTSTR lpszClassName = _T("helloWindow");
    LPCTSTR lpszWindowName = _T("Hello, DirectX World!");

    WNDCLASSEX wc;
    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = CS_CLASSDC;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0L;
    wc.cbWndExtra    = 0L;
    wc.hInstance     = hInst;
    wc.hIcon         = LoadIcon(hInst, MAKEINTRESOURCE(IDI_APPLICATION));
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = lpszClassName;
    wc.hIconSm       = LoadIcon(hInst, MAKEINTRESOURCE(IDI_APPLICATION));

    RegisterClassEx(&wc);

    HWND hWnd = CreateWindow(
        lpszClassName, 
        lpszWindowName,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 450, 450,
        NULL, NULL, hInst, NULL);

    InitD3D(hWnd);
    CreateSphere();

    start = timeGetTime();

    ShowWindow(hWnd, SW_SHOWDEFAULT);
    UpdateWindow(hWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

void convert(float x, float y, float z, float r, float& u, float& v)
{
    float q;
    float q2;
    q = atan2(z, x);

    u = q / (2.0f * 3.1415f);
    q2 = asin(y / r);
    v = (1.0f - q2 / (3.1415f / 2.0f)) / 2.0f;
    if (u>1.0)
        u = 1.0;
}

// デバイス/モード等の初期化
HRESULT InitD3D(HWND hWnd)
{
    HRESULT     hr;

    g_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
    if (g_pD3D == NULL)
    {
        return E_FAIL;
    }

    // D3DDeviceオブジェクトの作成
    D3DPRESENT_PARAMETERS   d3dpp;
    d3dpp.BackBufferWidth            = 0;
    d3dpp.BackBufferHeight           = 0;
    d3dpp.BackBufferFormat           = D3DFMT_UNKNOWN;
    d3dpp.BackBufferCount            = 0;
    d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;
    d3dpp.MultiSampleQuality         = 0;
    d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow              = NULL;
    d3dpp.Windowed                   = TRUE;
    d3dpp.EnableAutoDepthStencil     = 0;
    d3dpp.AutoDepthStencilFormat     = D3DFMT_UNKNOWN;
    d3dpp.Flags                      = 0;
    d3dpp.FullScreen_RefreshRateInHz = 0;
    d3dpp.PresentationInterval       = 0;

    hr = g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
        D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &g_pDev);

    if (FAILED(hr))
    {
        return E_FAIL;
    }

    return  S_OK;
}

HRESULT CreateSphere()
{
    HRESULT     hr;

    // 球メッシュの生成
    D3DXCreateSphere(g_pDev, R, 32, 32, &g_pMeshApp, NULL);

    g_pMeshApp->CloneMeshFVF(NULL, FVF_TLVERTEX, g_pDev, &g_pMeshApp2);
    LPDIRECT3DVERTEXBUFFER9 pVB = NULL;
    TLVERTEX* pVer = NULL;
    g_pMeshApp2->GetVertexBuffer(&pVB);
    pVB->Lock(0, 0, (VOID**)&pVer, 0);
    DWORD n;
    for (n = 0; n < g_pMeshApp2->GetNumVertices(); n++){
        float u, v;
        convert(pVer[n].x, pVer[n].y, pVer[n].z, R, u, v);
        pVer[n].tu = u;
        pVer[n].tv = v;
    }
    pVB->Unlock();

    hr = D3DXCreateTextureFromFile(g_pDev, _T("Texture.jpg"), &g_pTextures);
    if (FAILED(hr)){
        MessageBox(NULL, _T("Texture Load Error"), _T("Error"), MB_OK);
        return E_FAIL;
    }

    return  S_OK;
}

// 描画環境の設定
void  SetupMatrices()
{
    D3DXMATRIX      matView;
    D3DXMATRIX      matProj;
    D3DXVECTOR3     ViewForm;

    float rt = (float(timeGetTime() - start) / 10.0f);   // システムタイムより回転角を算出する
    ViewForm.x = (float)(VIEW_R*cos(rt / 180 * 3.14));
    ViewForm.y = 0.0f;
    ViewForm.z = (float)(VIEW_R*sin(rt / 180 * 3.14));

    D3DXMatrixLookAtLH(&matView, &ViewForm, &D3DXVECTOR3(0.0f, 0.0f, 0.0f), &D3DXVECTOR3(0.0f, 1.0f, 0.0f));
    g_pDev->SetTransform(D3DTS_VIEW, &matView);
    D3DXMatrixPerspectiveFovLH(&matProj, D3DXToRadian(45.0f), 1.0f, 100, 1000);
    g_pDev->SetTransform(D3DTS_PROJECTION, &matProj);

    g_pDev->SetRenderState(D3DRS_WRAP0, D3DWRAPCOORD_0);

    g_pDev->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); // 反時計回りの面を消去
    g_pDev->SetRenderState(D3DRS_LIGHTING, FALSE);           // ライティングしない
    g_pDev->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);      // Zバッファ使わない

}

// 描画処理
void Render()
{
    g_pDev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
    if (SUCCEEDED(g_pDev->BeginScene())){
        g_pDev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);
        SetupMatrices();
        g_pDev->SetFVF(FVF_TLVERTEX);

        g_pDev->SetTexture(0, g_pTextures);
        g_pMeshApp2->DrawSubset(0);
        g_pDev->EndScene();
    }
    g_pDev->Present(NULL, NULL, NULL, NULL);
}

// オブジェクトの開放
void Cleanup()
{
    SAFE_RELEASE(g_pTextures);
    SAFE_RELEASE(g_pMeshApp);
    SAFE_RELEASE(g_pMeshApp2);
    SAFE_RELEASE(g_pDev);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg){
    case WM_CLOSE:
    case WM_DESTROY:
        Cleanup();
        PostQuitMessage(0);
        return 0L;
    case WM_PAINT:
        Render();
        return 0;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

上記、コードは、

■ 球にテクスチャーを張り視点を回転させる(DirectX9 32/64bit)

http://yamatyuu.net/computer/program/directx9/Sphere_Texture/index.html

のものを、一部、修正したものになります。

5. コンパイルする

[F5] キーでコンパイルします。

f:id:cx20:20140121013449p:plain

「正常終了」が出れば、とりあえず、コンパイル完了です。

6. 出力先フォルダにテクスチャファイルをコピーする

テクスチャに使用する画像ファイルは、「Texture.jpg」としてください。

f:id:cx20:20140120002054p:plain

7. 実行する

f:id:cx20:20140121013440p:plain

地球が回れば、成功です。

参考

■ 球にテクスチャーを張り視点を回転させる(DirectX9 32/64bit)

http://yamatyuu.net/computer/program/directx9/Sphere_Texture/index.html

■ CX's Hello, World! » Hello, DirectX(C++) World!

http://cx20.main.jp/blog/hello/2012/07/23/hello-directx-cpp-world/

■ 2013年にDirectXでゲームを作りたいときに知っておくべきこと - arveltの技術メモ

http://arvelt.hatenablog.com/entry/2013/09/05/105903