DirectX9 - 3. návod - Matice


DirectX9 - 3. návod - Matice

3. Matice

 Cíl

 Příprava
 InitD3D - Vytvoření vykreslovacího zařízení
 InitGeometry - Vytvoření geometrie scény
 Cleanup - Uvolnění alokovaných proměnných
 SetupMatrices - Nastaví transformační matice
 Render - Funkce pro vykreslení
 MsgProc - Zachytávání a zpracování zpráv okna
 WinMain - Vstupní bod naší aplikace

 Poznámka na závěr
 Příklad je zde ke stažení

Cíl

Nyní když už víme jak vytvořit D3DDevice a vykreslit díky němu nějaké 2D vertexy, můžeme jít o krůček dál a vykreslit 3D geometrii. Abychom se ale s 3D prostorem vypořádali, potřebujeme zasvětit do používání 4x4 transformačních matic pro posun, rotaci, zvětšení a projekci do 2D. Zní to náročně, ale práce s těmito maticemi je po krátké době velmi intuitivní.
Geometrie je definována v modelovém prostoru. Můžeme objekt posouvat (translation), otáčet (rotation), natahovat (scaling) atd, nebo rovnou aplikovat kombinaci těchto operací. To vše pomocí "World" matice. O geometrii se poté říká, že je ve world (modelovém) prostoru. Další věcí je umístění kamery a natočení směru pohledu na objekt. Další transformační maticí tedy bude "View" matice, tedy matice pohledu na scénu. Díky ní můžeme posouvat, natáčet a nebo třeba rotovat pohledem kamery. Po aplikaci i této matice říkáme, že objekt je ve "View" prostoru, česky něco jako v prostoru pohledu kamery. Poslední, tedy třetí transformací, je "Projection" matice, tedy matice finální projekce z 3D prostoru před kamerou do 2D prostoru okna.
Všimněme si, že v tomto tutoriálu namísto knihovny D3D9 používáme D3DX9. Rozdíl je ten, že X verze obsahuje navíc spoustu užitečných nástrojů, mimo jiné i pro práci s maticemi. Pro použití D3DX9 knihovny stačí namísto d3d9.h includovat d3dx9.h a slinkovat s knihovnou d3dx9.lib

 nahoru

Příprava

Založíme si projekt (viz. Nultý tutorial). A můžeme začít psát zdrojový text... Nejdříve naincludujeme potřebnou DirectX hlavičku, tentokrát to namísto d3d9.h bude d3dx9.h
#include <d3dx9.h>
Nyní si připravíme globální proměnné, které budeme potřebovat. Změnou oproti minulému tutoriálu je to, že nedefinujeme již transformovanou pozici každého vertexu do 2D, ale poctivě ještě ve 3D. Je to tedy poprvé, kdy opravdu programujeme DirectX 3D aplikaci.
LPDIRECT3D9             g_pD3D       = NULL; // Používá se k vytvoření D3DDevice
LPDIRECT3DDEVICE9       g_pd3dDevice = NULL; // Naše vykreslovací zařízení
LPDIRECT3DVERTEXBUFFER9 g_pVB        = NULL; // Buffer k uchování vertexů

// Struktura pro náš uživatelsky definovaný vertex
struct CUSTOMVERTEX
{
    FLOAT x, y, z;      // Netransformovaná 3D pozice pro vertex
    DWORD color;        // Barva
};

// Uživatelský FVF (flexibilní vertex-formát),který popisuje naši strukturu
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)

 nahoru

InitD3D - Vytvoření vykreslovacího zařízení

HRESULT InitD3D( HWND hWnd ) 
Tato funkce je téměř stejná jako ta v předchozím návodu, tedy nejprve klasicky vytvoříme D3D objekt a z něho poté vykreslovací zařízení d3dDevice.
    // Vytvoříme D3D objekt
    if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
        return E_FAIL;

    // Nastavíme strukturu použitou pro vytvoření D3DDevice
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory( &d3dpp, sizeof(d3dpp) );
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;

    // Vytvoříme D3DDevice (vykreslovací zařízení)
    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                      &d3dpp, &g_pd3dDevice ) ) )
    {
        return E_FAIL;
    }
Nyní ale oproti předchozímu tutoriálu konečně nastavíme alespoň dva parametry pro naše vykreslovací zařízení. Potřebujeme totiž vypnout kontrolu natočení polygonů (culling), díky které bychom neviděli zadní stranu polygonu. Déle ještě vypneme hardwarové osvětlování scény, protože máme barvy jednotlivých vertexů dány napevno.
    // Vypneme "culling", díky tomu uvidíme i zadní stranu polygonu
    g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );

    // Vypneme D3D osvětlování, protože už barvu vertexu máme pevně danou
    g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );

    return S_OK;

 nahoru

InitGeometry - Vytvoření geometrie scény

HRESULT InitGeometry() 
Tato funkce se bude nyní objevovat namísto předešlé InitVB (inicializace vertex bufferu) Její obsah bude podobný, tedy vytvoření vertex bufferu a naplnění ho hodnotami. Později se ale naučíme získávat geometrii ze souborů a Vertex buffer nahradí Mesh.
Protentokrát je ale obsah opravdu velmi podobný, vytvoříme si tři vertexy (tentokrát ve 3D), poté Vertex buffer a nakonec ho těmito vertexy naplníme.
    // Inicializujeme tři vertexy pro náš trojúhelník
    CUSTOMVERTEX g_Vertices[] =
    {
        { -1.0f,-1.0f, 0.0f, 0xffff0000, },
        {  1.0f,-1.0f, 0.0f, 0xff0000ff, },
        {  0.0f, 1.0f, 0.0f, 0xffffffff, },
    };

    // Vytvoříme vertex buffer
    if( FAILED( g_pd3dDevice->CreateVertexBuffer( 3*sizeof(CUSTOMVERTEX),
                                                  0, D3DFVF_CUSTOMVERTEX,
                                                  D3DPOOL_DEFAULT, &g_pVB, 
                                                  NULL ) ) )
    {
        return E_FAIL;
    }

    // Naplníme vertex buffer
    VOID* pVertices;
    if( FAILED( g_pVB->Lock( 0, sizeof(g_Vertices), (void**)&pVertices, 0 ) ) )
        return E_FAIL;
    memcpy( pVertices, g_Vertices, sizeof(g_Vertices) );
    g_pVB->Unlock();

    return S_OK;

 nahoru

Cleanup - Uvolnění alokovaných proměnných

VOID Cleanup()
I tato funkce byla podrobněji nastíněna v 1. příkladu. Jedinou změnou zde je nutnost krom d3dDevice (vykreslovacího zařízení) a D3D (Instance Direct3D) uvolnit také vertex buffer.
    if( g_pVB != NULL )        
        g_pVB->Release();

    if( g_pd3dDevice != NULL ) 
        g_pd3dDevice->Release();

    if( g_pD3D != NULL )
        g_pD3D->Release();

 nahoru

SetupMatrices - Nastaví transformační matice

VOID SetupMatrices()
Nová funkce, která nám při každém snímku přenastaví všechny tři matice (world, view, projection). První z nich (world) v podstatě znamená umístění objektu - nastavujeme tzv. modelový prostor. V našem případě budeme touto maticí otáčet kolem osy y s dobou periody 1s a tedy se nám v důsledku bude otáčet objekt. Druhá matice (view) nám udává v podstatě umístění kamery. Tato se měnit nebude, kamera se bude stále dívat z pozice 5 jednotek zpět po ose z a 3 jednotky vzhůru a směřovat bude na počátek (0,0,0). Třetí a poslední maticí (projection) nastavíme způsob promítnutí do 2D plochy našeho okna. Zde nastavíme zorný úhel Pi/4 radiánů, poměr stran 1:1, a vykreslovat budeme vše ve vzdálenosti od 1.0 do 100.0
    // Pro World matici bude stačit rotace kolem osy y
    D3DXMATRIXA16 matWorld;
    UINT  iTime  = timeGetTime() % 1000;
    FLOAT fAngle = iTime * (2.0f * D3DX_PI) / 1000.0f;
    D3DXMatrixRotationY( &matWorld, fAngle );
    g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

    // Nastavíme View matici (kameru)
    D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
    D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
    D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
    D3DXMATRIXA16 matView;
    D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
    g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

    // Nyní ještě Projection matici
    D3DXMATRIXA16 matProj;
    D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f );
    g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

 nahoru

Render - Funkce pro vykreslení

VOID Render()
Již známá funkce. Jediným rozdílem je, že po zahájení vykreslovaní (není nutností) a ještě před tím, než něco skutečně vykreslíme, zavoláme funkci SetupMatrices(), která se postará o nastavení všech tří matic.
    // Vyčistíme back buffer na černou barvu
    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, 
                         D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );

    // Zahájíme vykreslování
    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
    {
        // Nastavíme world, view a projection matici
        SetupMatrices();

        // Vykreslíme obsah vertex bufferu
        g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof(CUSTOMVERTEX) );
        g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
        g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 1 );

        // Ukončíme vykreslování
        g_pd3dDevice->EndScene();
    }

    // Zobrazíme vše na obrazovku
    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );

 nahoru

MsgProc - Zachytávání a zpracování zpráv okna

LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
Na konec klasicky dvě funkce pro režii Windows. Tentokrát se zde opravdu ničeho nového nedočkáme a proto jen uvádím jejich zdrojový text.
    switch( msg )
    {
        case WM_DESTROY:
            Cleanup();
            PostQuitMessage( 0 );
            return 0;
    }

    return DefWindowProc( hWnd, msg, wParam, lParam );

 nahoru

WinMain - Vstupní bod pro naši aplikaci

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
Poslední a hlavní funkce, po spuštění se volá právě ona.
    // Registrace třídy okna
    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
                      GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
                      "Dx9 Tutorial 3", NULL };
    RegisterClassEx( &wc );

    // Vytvoření okna aplikace
    HWND hWnd = CreateWindow( "Dx9 Tutorial 3", "Dx9 3 - Matice",
                              WS_OVERLAPPEDWINDOW, 100, 100, 256, 256,
                              NULL, NULL, wc.hInstance, NULL );

    // Inicializace Direct3D
    if( SUCCEEDED( InitD3D( hWnd ) ) )
    {
        // Vytvoření geometrie scény
        if( SUCCEEDED( InitGeometry() ) )
        {
            // Zobrazení okna
            ShowWindow( hWnd, SW_SHOWDEFAULT );
            UpdateWindow( hWnd );

            // Vstoupíme do smyčky zpracování zpráv
            MSG msg;
            ZeroMemory( &msg, sizeof(msg) );
            while( msg.message!=WM_QUIT )
            {
                if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
                {
                    TranslateMessage( &msg );
                    DispatchMessage( &msg );
                }
                else
                    Render();
            }
        }
    }

    UnregisterClass( "Dx9 Tutorial 3", wc.hInstance );
    return 0;

 nahoru

Poznámka na závěr

Tento text je téměř přesným opisem výsledného zdrojového textu. Vynechány byly částečně komentáře, které nahrazuje tento text. Zdrojový text ke stažení na konci této stránky ale ony komentáře obsahuje a umožní Vám to lepší orientaci i bez stálého připojení na tyto stránky.
Dále byly vynechány složené závorky jednotlivých těl funkcí.

 nahoru

Příklad je zde ke stažení


 nahoru



Jan Zelený | 22.3.2007
Zde můžete obsah této stránky ohodnotit:
hodnotilo:104    


© 2008 Jan Zelený | monade.cz