DirectX9 - 4. návod - Světla


DirectX9 - 4. návod - Světla

4. Světla

 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
 SetupLights - Nastaví materiál a světla scény
 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

Vykreslování 3D geometrie je mnohem zajímavější, když jsou do scény přidány dynamické světla. Abychom ale použili taková světla v D3D, musíme provést několik věcí. Tak nejprve musíme objektu nastavit materiál (ten udává barvu povrchu, barvu odlesků, lesklost povrchu, atd). Poté musí geometrie objektu obsahovat tzv. normálové vektory jsou to kolmice k jednotlivým místům povrchu objektu), díky kterým se dá velmi rychle spočítat úhel dopadajícího světla a tudíž i míru osvětlení. Daný normálový vektor se váže k danému vertexu.
Světla mohou být těchto typů: Směrové (directional, prochází celou scénou v jednom směru, např slunce), Bodové (point, světlo vychází z jednoho konkrétního bodu a šíří se všemi směry, např svíčka) a Kuželové (spotlight, světlo se šíří z bodu jedním směrem nejsilněji jinými směry postupně slaběji, např baterka). Posledním druhem světla, které stojí už trochu mimo, je ambientní (osvětluje všechno ve scéně bez ohledu na vzdálenost, úhel dopadu atd.)

 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 a připravíme si globální proměnné.
#include <d3dx9.h>

// Globální proměnné
LPDIRECT3D9             g_pD3D       = NULL; // Nutné k vytvoření D3DDevice
LPDIRECT3DDEVICE9       g_pd3dDevice = NULL; // Naše vykreslovací zařízení
LPDIRECT3DVERTEXBUFFER9 g_pVB        = NULL; // Buffer na uložení vertexů

// Struktura pro náš konkrétní vertex. K pozici v prostoru potřebujeme 
// ještě onen normálový vektor
struct CUSTOMVERTEX
{
    D3DXVECTOR3 position; // 3D pozice pro daný vertex
    D3DXVECTOR3 normal;   // Normálový vektor v daném místě povrchu
};

// Naše FVF, které popisuje výše zmíněnou strukturu vertexu
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL)

 nahoru

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

HRESULT InitD3D( HWND hWnd )
V této funkci, na rozdíl od jejích verzí v předchozích příkladech, vytvoříme ještě z-buffer a na jejím konci ho také zapneme. Mimoto také vypneme culling, abychom viděli obě strany povrchu.
    // Vytvoříme D3D objekt
    if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
        return E_FAIL;

    // Nastavíme strukturu k vytvoření D3DDevice. Protože jíž budeme používat
  	// komplexnější geometrii, vytvoříme nově i z-buffer
    D3DPRESENT_PARAMETERS d3dpp;
    ZeroMemory( &d3dpp, sizeof(d3dpp) );
    d3dpp.Windowed = TRUE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // Vytvoříme D3DDevice
    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                      &d3dpp, &g_pd3dDevice ) ) )
    {
        return E_FAIL;
    }

    // Vypneme culling (potřebujeme totiž vidět obě strany povrchu objektu)
    g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );

    // A zapneme z buffer
    g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
    
    return S_OK;

 nahoru

InitGeometry - Vytvoření geometrie scény

HRESULT InitGeometry() 
V této funkci jsme si připravili v minulém příkladu jeden polygon. Nyní si již vytvoříme o něco komplexnější objekt, který ale ještě neumíme načítat ze souboru a proto si ho zatím pouze vygenerujeme algoritmem. Tím objektem bude pro jednoduchost válec. Při generování si krom pozice vypočítáme už i normálové vektory.
    // Nejprve si vytvoříme vertex buffer
    if( FAILED( g_pd3dDevice->CreateVertexBuffer( 50*2*sizeof(CUSTOMVERTEX),
                                                  0, D3DFVF_CUSTOMVERTEX,
                                                  D3DPOOL_DEFAULT, &g_pVB, 
                                                  NULL )))
    {
        return E_FAIL;
    }

	  // Nyní ho naplníme. Zde je pro jednoduchost zvoleno algoritmické 
	  // vygenerování válce, včetně normálových vektorů. Jak načíst již 
	  // připravené modely si ukážeme příště.
    CUSTOMVERTEX* pVertices;
    if( FAILED( g_pVB->Lock( 0, 0, (void**)&pVertices, 0 ) ) )
        return E_FAIL;
    for( DWORD i=0; i<50; i++ )
    {
        FLOAT theta = (2*D3DX_PI*i)/(50-1);
        pVertices[2*i+0].position = D3DXVECTOR3(sinf(theta),-1.0f,cosf(theta));
        pVertices[2*i+0].normal   = D3DXVECTOR3(sinf(theta), 0.0f,cosf(theta));
        pVertices[2*i+1].position = D3DXVECTOR3(sinf(theta), 1.0f,cosf(theta));
        pVertices[2*i+1].normal   = D3DXVECTOR3(sinf(theta), 0.0f,cosf(theta));
    }
    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. A oproti tomu minulému se nezměnilo opravdu nic.
    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()
Tato funkce byla již také vysvětlena. Starala se o nastavení tří matic (světa, pohledu a projekce) a to dělá stále. Konkrétně v tomto případě otáčí matici světa (tedy objekt) podle osy X, matici pohledu (tedy kameru) má stále na stejném místě a matice projekce je nastavená na 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.
    // Nastavení matice světa (resp. kam a jak umístit objekt)
    D3DXMATRIXA16 matWorld;
    D3DXMatrixIdentity( &matWorld );
    D3DXMatrixRotationX( &matWorld, timeGetTime()/500.0f );
    g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

	  // Nastavíme matici pohledu (resp. kamery)
    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 );

	  // A nastavíme matici projekce (tedy jak se 3D scéna převede na 2D 
	  // souřadnice okna)
    D3DXMATRIXA16 matProj;
    D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f );
    g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

 nahoru

SetupLights - Nastaví materiál a světla scény

VOID SetupLights()
Zcela nová funkce. Tato má za úkol nastavit vše ohledně světel a metariálů ve scéně. Nejprve si připravíme materiál. V tomto případě má pouze ambientní a difuzní složku a je čistě bílý. Může být v jednu chvíli použit pouze jeden materiál. Pokud Vás teď napadá množství textur a barevných polygonů, je vždy třeba vykreslování takových objektů rozdělit na vykreslení jednotlivých sad polygonů se stejnými parametry. Později si to vysvětlíme, ale pro tentokrát se zatím musíme spokojit s tímto jednoduchým případem bez textur a rozličných materiálů.
Poté si přidáme dvě světla (žluté a červené). Obě jsou směrová, tedy svítí skrze celou scénu ve stejném směru a intenzitě. Druhé světlo si přidáme proto, abyste měli představu, jak se více světel ve scéně vytvoří. Můžeme mít až 8 světel naráz. Pokud jich potřebujeme více, musíme již použít pixel-shadery, ale to nás čeká až někdy v budoucnu.
S touto funkcí doporučuji hodně experimentovat, získáte tak o světlech lepší povědomí.
	  // Nastavení materiálu
    D3DMATERIAL9 mtrl;
    ZeroMemory( &mtrl, sizeof(D3DMATERIAL9) );
    mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
    mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
    mtrl.Diffuse.b = mtrl.Ambient.b = 1.0f;
    mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
    g_pd3dDevice->SetMaterial( &mtrl );

    // Nastavíme žluté směrové světlo. A posledním řádkem ho zapneme.
    D3DLIGHT9 light;
    ZeroMemory( &light, sizeof(D3DLIGHT9) );
    light.Type       = D3DLIGHT_DIRECTIONAL;
    light.Diffuse.r  = 1.0f;
    light.Diffuse.g  = 1.0f;
    light.Diffuse.b  = 0.0f;
    light.Direction = D3DXVECTOR3(0.707f, 0.707f, 0.0f);
	  g_pd3dDevice->SetLight( 0, &light );
    g_pd3dDevice->LightEnable( 0, TRUE );
    
    // Ještě přidáme jedno červené z jiného směru
    light.Type       = D3DLIGHT_DIRECTIONAL;
    light.Diffuse.r  = 1.0f;
    light.Diffuse.g  = 0.0f;
    light.Diffuse.b  = 0.0f;
    light.Direction = D3DXVECTOR3(-0.707f, 0.0f, 0.707f);
    g_pd3dDevice->SetLight( 1, &light );
    g_pd3dDevice->LightEnable( 1, TRUE );

	  // Nakonec zapneme hardwarové osvětlování a stínování
	  g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );

    // Nakonec přidáme ještě složku ambientního světla
    g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x00202020 );

 nahoru

Render - Funkce pro vykreslení

VOID Render()
Již známá funkce. Je zde navíc čištění z-bufferu a zavolání funkce SetupLights, která nastaví materiály a světla ve scéně.
    // Vyčistíme back buffer (do něho budeme kreslit) a ještě z buffer
    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
                         D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );

    // Začneme vykreslování
    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
    {
        // Nastavíme světla a materiály
        SetupLights();

        // Nastavíme matice
        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, 2*50-2 );

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

    // Nyní už jen zobrazíme obsah back bufferu na "obrazovku" (front bufferu)
    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 4", NULL };
    RegisterClassEx( &wc );

    // Vytvoření okna aplikace
    HWND hWnd = CreateWindow( "Dx9 Tutorial 4", "Dx9 4 - Světla",
                              WS_OVERLAPPEDWINDOW, 100, 100, 300, 300,
                              NULL, NULL, wc.hInstance, NULL );

    // Inicializujeme Direct3D
    if( SUCCEEDED( InitD3D( hWnd ) ) )
    {
        // Vytvoříme geometrii
        if( SUCCEEDED( InitGeometry() ) )
        {
            // Zobrazíme okno
            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();
            }
        }
    }

    // Odregistrujeme třídu okna
    UnregisterClass( "Dx9 Tutorial 4", 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ý | 5.7.2007
Zde můžete obsah této stránky ohodnotit:
hodnotilo:76    


© 2008 Jan Zelený | monade.cz