DirectX (29.)

KoneΦn∞ je zde novß lekce programovßnφ 3D grafiky. Minule jsme na¥ukli optimalizaΦnφ metody, dnes budeme implementovat p°φklad terΘnu, kter² ukß₧e, ₧e tyto metody jsou skuteΦn∞ pot°eba. Bude se jednat o terΘn slo₧en² z polφΦek, informace o terΘnu budeme Φφst bu∩ ze souboru bmp, pak se jednß o techniku heightmap nebo pou₧ijeme jeden z mnoha algoritm∙ na generovßnφ nßhodnΘ krajiny.

29.1. Tvorba terΘnu

Existuje mnoho algoritm∙ na tvorbu terΘnu slo₧en²ch ze Φtvercov²ch polφΦek. Tyto polφΦka jsou slo₧ena ze dvou troj·helnφk∙. Sφ¥ takovΘho terΘnu pak m∙₧e vypadat takto:

ProblΘm tedy je, jak sprßvn∞ nastavit z-tovou sou°adnici jednotliv²ch vrchol∙, aby to ve v²sledku vypadalo jako terΘn. Nßhodn∞ p°i°azenΘ hodnoty asi nebudou to pravΘ, pak vznikl velice "ostrß" plocha. Je jasnΘ, ₧e hodnota jednoho vrcholu musφ n∞jak ovliv≥ovat hodnoty vrchol∙ kolem. Je mnoho algoritm∙, jak tuto ·lohu °eÜit a ka₧d² mß mnoho modifikacφ s mφrn∞ liÜφcφmi se v²sledky. V naÜφ lekci pou₧ijeme dv∞ metody tvorby terΘnu.

1) Tou prvnφ bude metoda zvanß heightmap neboli mapa v²Üek. Jednß se o 2D pole o rozm∞rech vytvß°enΘho terΘnu, ve kterΘm jsou ulo₧eny z-tovΘ sou°adnice ka₧dΘho vertexu. Jak ale toto pole naplnit? NejjednoduÜÜφ je pou₧φt bitmapu, kde barevnß informace nese sprßvnou v²Üku. Pokud pou₧ijete monochromatickou bitmapu, je jedno jakou slo₧ku barvy berete v ·vahu (R = G = B). U barevnΘ je to trochu slo₧it∞jÜφ. V naÜem p°φpad∞ jsem pou₧il odliÜn² zp∙sob. Barevnß informace bitmapy nese barvu danΘho vertexu a informaci o v²Üce nese alpha kanßl vytvo°en² nap°φklad ve Photoshopu a ulo₧en² spolu s bitmapou. PotΘ bitmapu naΦteme jako texturu a pomocφ metod LockRect() a UnlockRect() naΦteme pot°ebnou informaci. Jako p°φklad uvedu zdrojovou bitmapu terΘnu a jeho alpha kanßl, kter² je pou₧it pro z-tovou sou°adnici vertex∙:

2) Za druhΘ pou₧ijeme velice primitivnφ (aΦkoliv dost pomal²) algoritmus "kopc∙". Je to iteraΦnφ algoritmus, kter² v₧dy vypoΦφtß nßhodn² st°ed "kopce" a potΘ provede v tomto mφst∞ vyboulenφ terΘnu pomocφ funkce kosinus. Tento postup opakuje mnohokrßt, tak₧e se na terΘnu vytvß°enφ nßhodn∞ vysokΘ hrbolky. ProblΘm je, ₧e po ka₧dΘm novΘm kopci se musφ projet celΘ pole vertex∙ a modifikovat p°φsluÜn∞ jejich v²Üku (u v∞tÜiny navφc neprovßdφme ₧ßdnou zm∞nu!), co₧ trvß docela dlouho (kdy₧ se to mß provΘst 1000x). V²sledek popsanΘho algoritmu je vid∞t na dalÜφm obrßzku:

29.2. SystΘm rozhranφ

Dßle jsem implementoval do naÜeho enginu systΘm rozhranφ. Zßkladem jsou funkce CreateDisplayObject() Φi CreateInputObject(). Kdo znß technologii COM, vÜe rychle pochopφ. Tento systΘm toti₧ pracuje na podobnΘm principu. Ka₧d² objekt, kter² chceme exportovat (v∞tÜinou se jednß o t°φdy) neexportujeme p°φmo, ale exportujeme pouze jeho rozhranφ (interface), kterΘ obsahuje pouze metody, tedy rozhranφ nikdy nemß atributy! Toto rozhranφ mß jen Φist∞ virtußlnφ metody, je tedy abstraktnφ a nem∙₧eme z n∞j vytvo°it objekt. Je t°eba zd∞dit dalÜφ t°φdu, kterß ovÜem nebude exportovanß a navφc bude obsahovat implementaci metod rozhranφ (dokonce musφ, aby Üel z tΘto t°φdy vytvo°it objekt) a m∙₧e mφt vlastnφ atributy. PotΘ vytvo°φme jak²si mana₧er objekt∙, kter² nßm bude vytvß°et objekty, tedy ji₧ nebude pot°eba alokovat pam∞¥ pomocφ new, to za nßs provede mana₧er. VÜe si ukß₧eme na p°φkladu. m∞jme rozhranφ IDisplay:

class DISPLAY_API IDisplay
 {
  public:
    virtual HRESULT Init(HWND hWnd) = 0;
    virtual HRESULT UpdateBackground() = 0;
    virtual HRESULT Present() = 0;
    virtual HRESULT RestoreDisplay() = 0;
    virtual D3DXVECTOR2* GetResolution() = 0;
    virtual ICamera * GetCamera() = 0;
    virtual LPDIRECT3DDEVICE8 GetDevice() = 0;
    virtual IResourceManager* GetResourceManager() = 0;
    virtual D3DFORMAT GetTextureFormat() = 0;
    virtual HRESULT EnableLight(int iIndex, BOOL bEnable = TRUE) = 0;
    virtual HRESULT SetLight(int iIndex, D3DLIGHT8 * pLight) = 0;
 public:
   // interface functions
    virtual HRESULT AddRef() = 0;
    virtual HRESULT Release() = 0;
 };

Vidφme pouze Φist∞ virtußlnφ metody b²valΘ t°φdy XDisplay. Abychom toto rozhranφ mohli pou₧φt, je t°eba ho exportovat pomocφ DISPLAY_API. T°φda XDisplay z∙stane zachovßna, ale nynφ mß zßkladnφ t°φdu prßv∞ IDisplay a jejφ metody ji₧ nejsou ΦistΘ a musφ b²t tedy implementovßny:

class XDisplay : public IDisplay
 {
    // D3D objects
    IDirect3D8 * m_lpD3DObject;
    IDirect3DDevice8* m_lpD3DDevice;
    D3DPRESENT_PARAMETERS m_d3dDeviceParam;
    DWORD m_dwRef;
    //
    // Display settings

    WORD m_wFlags;
    // Display parameters
    D3DXVECTOR2 m_sRes;
    UINT m_iDepth;
    D3DDEVTYPE m_typeDeviceType;
    int m_iAdapterOrdinal;
    D3DFORMAT m_formatScreenDepth;
    D3DFORMAT m_formatTextureDepth;
    D3DTEXTUREFILTERTYPE m_tftMagTextureFilter;
    D3DTEXTUREFILTERTYPE m_tftMinTextureFilter;
    D3DTEXTUREFILTERTYPE m_tftMipTextureFilter;
    //
    // FPS atributes
 
   float m_fStartTime;
    float m_fStopTime;
    FLOAT m_Frames;
    float m_fCurrentFPS;
    float m_fFrameRate;
    int m_iDesiredFPS;
    char m_szFPSString[50];
    char m_szInfoString[512];
    //
    // Camera
    ICamera * m_lpCamera;
    I3DFont * m_lpFPSFont;
    I3DFont * m_lpInfoFont;
    IResourceManager* m_lpResManager;
    //
    // Time between two frames
    float m_fTime;
    // Time from app start
    float m_fTotalTime;
    // Background color
    D3DCOLOR m_dwBackgroundColor;
    int m_iMaxLights;

 private:
    int UpdateFPS();
    void LimitFPS();
    void Clean(void);
    void BuildUpMatrices(D3DXVECTOR3 *pvEyePt, D3DXVECTOR3 *pvLookAtPt);

 public:
    virtual HRESULT Init(HWND hWnd);
    virtual HRESULT UpdateBackground();
    virtual HRESULT Present();
    virtual HRESULT RestoreDisplay();
    virtual D3DXVECTOR2* GetResolution() { return &m_sRes;}
    virtual ICamera * GetCamera() {return m_lpCamera;}
    virtual IResourceManager* GetResourceManager() {return m_lpResManager; }
    virtual LPDIRECT3DDEVICE8 GetDevice() {return m_lpD3DDevice;}
    virtual D3DFORMAT GetTextureFormat() {return m_formatTextureDepth;}
    virtual HRESULT EnableLight(int iIndex, BOOL bEnable = TRUE);
    virtual HRESULT SetLight(int iIndex, D3DLIGHT8 * pLight);

 public:
    virtual HRESULT AddRef();
    virtual HRESULT Release();

    XDisplay(void);
    ~XDisplay(void);
 };

Navφc t°φda obsahuje soukromΘ atributy a vÜimn∞te si, ₧e nenφ exportovßna z knihovny! K Φemu jsou metody AddRef() a Release()? Uvnit° t°φdy naleznete atribut m_dwRef, ve kterΘm je ulo₧en poΦet referencφ na tento objekt, tedy poΦet u₧ivatel∙ pou₧φvajφcφ tento objekt. Kdykoliv zavolßte funkci CreateDisplayObject() je toto poΦφtadlo inkrementovßno metodou AddRef() a kdy₧ u₧ivatel ji₧ nepot°ebuje objekt, uvolnφ jeho rozhranφ metodou Release(), kterß snφ₧φ reference. Zßrove≥ pokud poΦet referencφ klesne na 0, Φili objekt ji₧ nenφ pou₧φvßn, objekt se sßm zruÜφ. Je tedy dule₧itΘ p°i zφskßnφ rozhranφ ho takΘ uvolnit, jinak objekt z∙stane viset v pam∞ti. Rozhranφ jsou ulo₧ena v souboru Interfaces.h. Jak ale vypadß ona zßhadnß funkce CreateDisplayObject()? Ve skuteΦnosti je velice primitivnφ:

// Call This function to get desired interface
 DISPLAY_API HRESULT CreateDisplayObject(DISIID InterfaceID, void ** ppv)
 {
   // unique objects
   static IDisplay * g_theDisplay = NULL;
   static ICamera * g_theCamera = NULL;
   static IResourceManager * g_theResourceManager = NULL;
   if(!ppv)
   {
      return ERROR_INVALID_PARAMETER;
   }
   // get pointer according IID
   switch(InterfaceID) {
      case DISIID_IDisplay:
         if(!g_theDisplay)
         {
            *ppv = g_theDisplay = new XDisplay;
         }
         else {
            *ppv = g_theDisplay;
         }
         ((IDisplay*)(*ppv))->AddRef();
         break;
      case DISIID_ICamera:
         if(!g_theCamera)
         {
            *ppv = g_theCamera = new XCamera;
         }
         else {
            *ppv = g_theCamera;
         }
         ((ICamera*)(*ppv))->AddRef();
         break;
      case DISIID_IResourceManager:
         if(!g_theResourceManager)
         {
            *ppv = g_theResourceManager = new XResourceManager;
         }
         else {
            *ppv = g_theResourceManager;
         }
         ((IResourceManager*)(*ppv))->AddRef();
         break;
      case DISIID_IVertexBuffer:
         *ppv = new XVertexBuffer;
         ((IVertexBuffer*)(*ppv))->AddRef();
         break;
      case DISIID_IIndexBuffer:
         *ppv = new XIndexBuffer;
         ((IIndexBuffer*)(*ppv))->AddRef();
         break;
      case DISIID_IMesh:
         *ppv = new XMesh;
         ((IMesh*)(*ppv))->AddRef();
         break;
      case DISIID_ITexture:
         *ppv = new XTexture;
         ((ITexture*)(*ppv))->AddRef();
         break;
      case DISIID_I3DObject:
         *ppv = new X3DObject;
         ((I3DObject*)(*ppv))->AddRef();
         break;
      case DISIID_I3DFont:
         *ppv = new XFont;
         ((I3DFont*)(*ppv))->AddRef();
         break;
      default:
         return E_NOINTERFACE;
   }
   return S_OK;
 }

Mß dva parametry, prvnφm urΦφme, jakΘho objektu chceme rozhranφ a do druhΘ se ulo₧φ p°φmo ukazatel na po₧adovanΘ rozhranφ. Pokud u₧ivatel po₧ßdß o rozhranφ, kterΘ nenφ podporovßno, funkce vracφ E_NOINTERFACE. Prvnφ t°i objekty jsou zvlßÜtnφ tφm, ₧e jsou unikßtnφ, Φili funkce je vytvo°φ p°i prvnφm volßnφ, potΘ vracφ jen ukazatel a inkrementuje reference. Ukazatele jsou uchovßny v podob∞ statick²ch prom∞nn²ch p°φmo ve funkci. DalÜφ objekty jsou v₧dy nov∞ vytvo°eny. Identifikßtory rozhranφ jsou definovßny v souboru manager.h jako typ enum:

// Interface ids
enum DISIID
{
   DISIID_IDisplay,
   DISIID_ICamera,
   DISIID_IIndexBuffer,
   DISIID_IVertexBuffer,
   DISIID_IMesh,
   DISIID_IResourceManager,
   DISIID_ITexture,
   DISIID_I3DObject,
   DISIID_I3DFont
};

SvΘ rozhranφ mß ka₧dß t°φda, kterou chceme pou₧φt i vn∞ dynamickΘ knihovny. Tento systΘm je zaveden i v knihovn∞ Input. Nynφ m∙₧ete odkudkoliv zavolat funkci CreateDisplayObject(DISIID_IDisplay, (void**) pDis) a zφskat tak rozhranφ unikßtnφho objektu XDisplay. Mßte zaruΦeno, ₧e funkce vrßtφ rozhranφ na tent²₧ objekt (kter² byl vytvo°en p°i prvnφm volßnφ s t∞mito parametry), ale je t°eba ho sprßvn∞ uvolnit metodou Release() po ukonΦenφ prßce s tφmto rozhranφm. Po tΘto ·prav∞ se nßm velice zjednoduÜφ ₧ivot.

29.3. PoΦφtßnφ normßlov²ch vektor∙ vrchol∙

Abychom mohli pou₧φt osv∞tlenφ Direct3D, je t°eba ka₧dΘmu vrcholu terΘnu p°i°adit sprßvn² normßlov² vektor, tedy vektor kolm² na plochu. Princip je docela jednoduch² a kdo mß za sebou vektorovou algebru, jist∞ pro n∞j nebude problΘm uveden² algoritmus implementovat. Pot°ebujeme urΦit rovinu, ve kterΘ le₧φ dan² vrchol a normßla tΘto roviny je i normßla vrcholu. Tuto rovinu urΦφme ze sousednφch vrchol∙. Budeme-li nßroΦnφ, budeme poΦφtat normßlu ze vÜech 4 sousednφch vrchol∙. V naÜem p°φpad∞ se ale spokojφme s jednoduÜÜφm °eÜenφm a postaΦφ nßm pouze dva sousednφ vrcholy. Na nßsledujφcφm obrßzku je to vÜechno rozkreslenΘ:

     

Na obrßzku je v0 vrchol, pro kter² chceme spoΦφtat normßlu, v1 a v2 jsou dva sousedi, pomocφ kter²ch urΦφme rovinu, s1 a s2 jsou dva sm∞rovΘ vrcholy le₧φcφ v rovin∞, k nφ₧ je normßlov² vektor n0 kolm². Vektory s1 a s2 spoΦφtßme velice jednoduÜe, proto₧e znßme poΦßteΦnφ i koncovΘ body, pak s1 = v0 - v1 a s2 = v0 - v2. Dßle pou₧ijeme vektorov² souΦin, kter² p°esn∞ vypoΦte vektor n0 (vektorov² souΦin dvou vektor∙ vrßtφ vektor kolm² k ob∞ma vektor∙m). Je t°eba si dßt pozor, aby vektory s1 a s2 m∞ly sprßvnou orientaci, proto₧e v opaΦnΘm p°φpad∞ by vektor n0 m∞l opaΦnou orientaci, tudφ₧ pod terΘn. V²sledkem by byla tma. MenÜφ problΘm nastßvß na hranßch terΘnu nebo¥ krajnφ vrcholy nemajφ ty sprßvnΘ sousedy, tak₧e vektor spoΦφtßme ze soused∙, kterΘ jsou blφ₧e k poΦßtku.


for(int y = 0; y < g_pHeightMap->Height(); y++)
{
   for(int x = 0; x < g_pHeightMap->Width(); x++)
   {
      v0 = arTerrain[x][y].vecPos;
      if(x < g_pHeightMap->Width()-1)
      {
         z1 = arTerrain[x+1][y].vecPos.z;
      }
      else
      {
         z1 = arTerrain[x-1][y].vecPos.z;
      }

      if(y < g_pHeightMap->Height()-1)
      {
         z2 = arTerrain[x][y+1].vecPos.z;
      }
      else
      {
         z2 = arTerrain[x][y-1].vecPos.z;
      }
      v1 = D3DXVECTOR3(float(x+1), float(y), z1);
      v2 = D3DXVECTOR3(float(x), float(y+1), z2);
      s1 = v0 - v1;
      s2 = v0 - v2;

      // create normal vector
      D3DXVec3Cross(&n0, &s1, &s2);
      D3DXVec3Normalize(&n0, &n0);
      arTerrain[x][y].vecNormal = n0;
   }
}

Knihovna D3DX naÜt∞stφ obsahuje plno podp∙rn²ch funkcφ pro prßci s vektory, tak₧e v²poΦet bude docela jednoduch². VypoΦet provedeme pro ka₧d² vrchol v terΘnu, do v0 ulo₧φme pozici danΘho vrcholu a potΘ nalezneme v²Üku dvou soused∙. U krajnφch vertex∙ je t°eba pou₧φt jinΘ dva sousedy. Dßle spoΦteme pozici soused∙ (ji₧ vφme v²Üku) a urΦφme sm∞rovΘ vektory s1 a s2 (zde vyu₧φvßme p°etφ₧en² operßtor - pro objekt D3DXVECTOR3). Nakonec pomocφ funkce D3DXVec3Cross() spoΦteme vektorov² souΦin vektor∙ s1 a s2 a tento vektor normalizujeme. Vektor bude mφt po normalizaci velikost 1. Na zßv∞r vektor p°i°adφme danΘmu vrcholu.

Pokud by vßm takov²to v²poΦet nestaΦil, pou₧ijte druh² zmφn∞n² zp∙sob (asi sprßvn∞jÜφ), kdy vypoΦtete normßlu z ka₧d²ch dvou sousednφch soused∙ danΘho vektoru, tak zφskßte 4 normßly, ze kter²ch ud∞lßte pr∙m∞rn² v²sledn² vektor n0. Tento zp∙sob je popsßn na dalÜφm obrßzku:

29.4. ╚tenφ barevnΘ informace z textury

Dßle se zam∞°φme na p°eΦtenφ pot°ebn²ch informacφ ze zdrojovΘ bitmapy. Z obrßzku vytvo°φme klasickou texturu:

// set parameters according terrain method
dwRet = CreateDisplayObject(DISIID_ITexture, (void**)&g_pHeightMap);
if(dwRet == S_OK)
{
   dwRet = g_pHeightMap->LoadTextureFromFile("default.bmp");
   if(dwRet != S_OK)
   {
      XException exp("Cannot load texture for heightmap!", dwRet);
      THROW(exp);
   }
   //set dim of the terrain according dim of the height map
   g_iTerrainTilesX = g_pHeightMap->Width() - 1;
   g_iTerrainTilesY = g_pHeightMap->Height() - 1;
   g_iTerrainVerticesX = g_pHeightMap->Width();
   g_iTerrainVerticesY = g_pHeightMap->Height();
}

A rovn∞₧ si ulo₧φme rozm∞ry terΘnu danΘ velikostφ textury, tφm pßdem jsme omezenφ na textury o rozm∞rech 128x128, 128x64 atd. V prom∞nnΘ g_iTerrainTilesX je poΦet polφΦek ve sm∞ru osy X, v g_iTerrainVerticesX je poΦet vrchol∙ ve sm∞ru osy X. Vytvo°enφ vertex a index bufferu si nechßme na pozd∞ji, ale urΦit∞ vytvo°φme funkci FillTerrainBuffers(), kterß naΦte data z heightmapy a aplikuje v²Üe popsanΘ algoritmy. PotΘ jen spoΦφtß normßly, p°esype data do vertex bufferu a vypoΦte obsah index bufferu. Pro nßs te∩ bude d∙le₧itß prvnφ Φßst:

VERTEX **arTerrain;
arTerrain = new VERTEX*[g_iTerrainVerticesX];
for(i = 0; i < g_iTerrainVerticesX; i++)
{
   arTerrain[i] = new VERTEX[g_iTerrainVerticesY];
}

D3DLOCKED_RECT lr;
g_pHeightMap->GetTexture()->LockRect(0, &lr, NULL, D3DLOCK_READONLY);
TEXTURE_PIXEL * data = (TEXTURE_PIXEL*)lr.pBits;
i = 0;
for(int y = 0; y < g_iTerrainVerticesY; y++)
{
   for(int x = 0; x < g_iTerrainVerticesX; x++)
   {
      arTerrain[x][y].vecPos = D3DXVECTOR3(float(x), float(y), float(data[i].a)/255.0f*10.0f);

      arTerrain[x][y].dwDiffuse = D3DCOLOR_ARGB(255,data[i].r,data[i].g,data[i].b);
      arTerrain[x][y].tu1 = (x % 2) ? 1.0f : 0.0f;
      arTerrain[x][y].tv1 = (y % 2) ? 1.0f : 0.0f;
      arTerrain[x][y].vecNormal = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
      i++;
   }
}
g_pHeightMap->GetTexture()->UnlockRect(0);

Zde nejprve vytvo°φme dynamickΘ 2D pole vertex∙. Pak volßme metodu LockRect() textury. Tato metoda uzamkne textutu heightmapy a vrßtφ strukturu D3DLOCKED_RECT, ve kterΘ je ukazatel na data textury. Je to pole o stejn²ch rozm∞rech jako textura. V tomto poli jsou ulo₧eny barevnΘ informace textury a samoz°ejm∞ takΘ alpha kanßl. Data jsou rozd∞lena po 4 bytech, kde prvnφ byte je modrß slo₧ka, druhß je zelenß, t°etφ je Φervenß a Φtvrtß je hodnota alpha kanßlu, kter² pou₧ijeme pro v²poΦet v²Üky vertexu v danΘm mφst∞ textury (ka₧dΘmu vertexu p°φsluÜφ jeden pixel textury). Pro tento ·Φel pou₧ijeme strukturu TEXTURE_PIXEL:

struct TEXTURE_PIXEL
{
   BYTE b;
   BYTE g;
   BYTE r;
   BYTE a;
};

Kterß obsahuje barevnΘ slo₧ky a alpha kanßl pixelu na danΘm mφst∞ textury. Hodnota alpha se m∙₧e m∞nit v rozmezφ 0-255, proto toto Φφslo pod∞lφme 255.0, abychom dostali hodnotu v rozmezφ 0-1.0 a dßle pracujeme s tφm rozsahem (v naÜem p°φpad∞ ho pouze vynßsobφme 10.0). NormßlovΘ vektory zinicializujeme do neÜkodnΘho stavu, to je sm∞r vzhur∙. TakΘ musφme sprßvn∞ nastavit texturovΘ sou°adnice. Pro lichΘ vertexy nastavujeme hodnotu 0.0f, pro sudΘ 1.0f a to ve sm∞ru X i Y. Na zßv∞r je t°eba texturu odemknout metodou UnlockRect().

29.5. V²poΦet index∙

Nynφ p°ejdeme k vlastnφ podstat∞ terΘnu. Ji₧ jsem uvedl, ₧e terΘn je slo₧en z polφΦek, kde ka₧dΘ polφΦko obsahuje 4 vrcholy a tedy dva troj·helnφky:

Nap°φklad prvnφ polφΦko je slo₧eno z troj·helnφk∙ v0, v1, v4 a v1, v5, v4. A v tomto po°adφ takΘ budou ulo₧eny indexy t∞chto vrchol∙: 0,1,4,1,5,4. V²poΦet index∙ tedy u₧ budeme provßd∞t pro ka₧dΘ polφΦko (nikoliv pro ka₧d² vrchol):

WORD *pIndices;
dwRet = g_pTerrainIB->GetBuffer()->Lock(0, 0, (BYTE**)&pIndices, 0);
i = 0;
for(int y = 0; y < g_iTerrainTilesY; y++)
{
   for(int x = 0; x < g_iTerrainTilesX; x++)
   {
      pIndices[i + 0] = x + y * g_iTerrainVerticesX;
      pIndices[i + 1] = (x+1) + y * g_iTerrainVerticesX;
      pIndices[i + 2] = x + (y+1) * g_iTerrainVerticesX;
      i += 3;
      pIndices[i + 0] = (x+1) + y * g_iTerrainVerticesX;
      pIndices[i + 1] = (x+1) + (y+1) * g_iTerrainVerticesX;
      pIndices[i + 2] = x + (y+1) * g_iTerrainVerticesX;
      i += 3;
   }
}
dwRet = g_pTerrainIB->GetBuffer()->Unlock();

Nejd°φve samoz°ejm∞ musφme zamknout index buffer. V ka₧dΘm cyklu inicializujeme indexy pro jedno polφΦko, je vid∞t, ₧e pro jedno polφΦko budeme pot°ebovat 6 index∙ (z toho zßrove≥ plyne po₧adavek na velikost index bufferu). Prvnφ t°i °ßdky jsou indexy pro prvnφ troj·helnφk, potΘ se p°esuneme na druh² (jednß se o vypoΦet index∙ ve 2D poli vrchol∙). Na zßv∞r nezapome≥te buffer odemknout.

Nynφ tedy vφme, jak velkΘ majφ b²t vertex a index buffery. Ty m∙₧eme vytvo°it hned po naΦtenφ heightmapy:

dwRet = CreateDisplayObject(DISIID_IVertexBuffer, (void**) &g_pTerrainVB);
if(dwRet == S_OK)
{
   g_dwTerrainVBSize = g_iTerrainVerticesX * g_iTerrainVerticesY * sizeof(VERTEX);
   g_dwVerticesCount = g_iTerrainVerticesX * g_iTerrainVerticesY;
   dwRet = g_pTerrainVB->Create(g_dwTerrainVBSize, D3DUSAGE_WRITEONLY, VERTEXFORMAT, D3DPOOL_DEFAULT);
   if(dwRet != S_OK)
   {
      XException exp("Cannot create VB for terrain!", dwRet);
      THROW(exp);
   }
}
dwRet = CreateDisplayObject(DISIID_IIndexBuffer, (void**) &g_pTerrainIB);
if(dwRet == S_OK)
{
   g_dwTerrainIBSize = g_iTerrainTilesX * g_iTerrainTilesY * 6 * sizeof(WORD);
   g_dwIndicesCount = g_iTerrainTilesX * g_iTerrainTilesY * 6;
   dwRet = g_pTerrainIB->Create(g_dwTerrainIBSize, D3DUSAGE_WRITEONLY, D3DFMT_INDEX16, D3DPOOL_DEFAULT);
   if(dwRet != S_OK)
   {
      XException exp("Cannot create IB for terrain!", dwRet);
      THROW(exp);
   }
}

Velikost vertex bufferu je p°φmo danß poΦtem pixel∙ textury. Metoda Create() rozhranφ IVertexBuffer ovÜem po₧aduje velikost v bytech, tak₧e hodnotu musφme vynßsobit velikostφ struktury VERTEX. U index bufferu jsme si °ekli, ₧e pro ka₧dΘ polφΦko budeme pot°ebovat 6 index∙. PoΦet polφΦek je g_iTerrainTilesX * g_iTerrainTilesY. Op∞t je zde po₧adovßna velikost v bytech a tudφ₧ je t°eba nßsobit hodnotu velikostφ WORDu (indexy jsou 16-ti bitovΘ).

Po p°esypanφ dat z 2D pole, vytvo°enΘho na zaΦßtku funkce FillTerrainBuffers(), ji₧ nenφ toto pole pot°eba a proto ho zcela vyma₧eme z pam∞ti.

29.7. Algoritmus kopeΦk∙

V tΘto podkapitole struΦn∞ popφÜu jak pracuje algoritmus kopeΦk∙. Jak jsem uvedl na zaΦßtku lekce, algoritmus vytvß°φ na povrchu jakΘsi kopeΦky o danΘm polom∞ru circle size. Tyto kopeΦky majφ n∞kolik parametr∙, kterΘ lze libovoln∞ m∞nit, t°eba i nßhodn∞:

Vertexy uvnit° tohoto polom∞ru jsou vyboulenΘ podle funkce kosinus, vertexy vn∞ z∙stanou nezm∞n∞ny. Na nßsledujφcφm obrßzku vidφme jeden kopeΦek:

Na zaΦßtku nastavφme v²Üku vÜech vertex∙ na urΦitou zßkladnφ hodnotu. Dßle opakovan∞ vytvß°φme kopeΦky na nßhodn²ch mφstech mapy a s nßhodn²mi parametry. Slo₧itost terΘnu zßvisφ na poΦtu iteracφ hlavnφho cyklu, teda na poΦtu kopeΦk∙. V²pis algoritmu:

void CircleAlgorithm(VERTEX **arVertices, int dimX, int dimY)
{
   int iCircleX, iCircleY;
   char per[50];
   double pd, circlesize, distance, disp, fPer = 0.0f;
   int iIterCount = cmnReadSetupInt(_S_TERRAIN, _S_ITERCOUNT);
   int iDisplacement = cmnReadSetupInt(_S_TERRAIN, _S_DISPLACEMENT);
   int iMaxHeight = cmnReadSetupInt(_S_TERRAIN, _S_MAXHEIGHT) * 10.0f;
   // reset terrain
   for(int y = 0; y < dimY; y++)
   {
      for(int x = 0; x < dimX; x++)
      {
         arVertices[x][y].vecPos.z = -10.0f;
      }
   }
   for(int i = 0; i < iIterCount; i++)
   {
      // choose random circle center
      iCircleX = rand() % dimX;
      iCircleY = rand() % dimY;

      circlesize = (rand() % iMaxHeight) / 10.0f + 10.0f;
      disp = (rand() % iDisplacement) / 10.0f;

      // apply displacement
      for(int y = 0; y < dimY; y++)
      {
         for(int x = 0; x < dimX; x++)
         {
            // compute distance current point and center point
            distance = sqrt(pow(arVertices[iCircleX][iCircleY].vecPos.x - arVertices[x][y].vecPos.x, 2) +                            pow(arVertices[iCircleX][iCircleY].vecPos.y - arVertices[x][y].vecPos.y, 2));

            pd = distance * 2 / circlesize;

            if(fabs(pd) <= 1.0f)
            {
               arVertices[x][y].vecPos.z += float(disp / 2 + cos(pd * D3DX_PI) * disp / 2);
            }
         }
      }
      fPer += (100.0f/iIterCount);
      g_pDisplay->UpdateBackground();
      g_pDisplay->GetDevice()->BeginScene();
      sprintf(per, "Generating terrain: %3.1lf%%", fPer);
      g_pKeys->Draw(per, 0, 50, D3DCOLOR_ARGB(255,255,255,0));
      g_pDisplay->GetDevice()->EndScene();
      g_pDisplay->Present();
   }
}

Parametrem disp urΦφme mφru "vyboulenosti" kopeΦku. Tuto hodnotu volφme nßhodn∞, ale krajnφ hodnotu naΦφtßme z konfiguraΦnφho souboru. Vyboulenφ je zßvislΘ na vzdßlenosti st°edovΘho vertexu a vertexu, pro kter² poΦφtßme v²Üku. Tuto vzdßlenost spoΦφtßme jako vzdßlenost dvou bod∙ v rovin∞ (druhß odmocnina souΦtu druh²ch mocnin rozdφl∙ x-ov²ch a y-ov²ch sou°adnic obou bod∙). Prom∞nnß pd je menÜφ ne₧ 1.0 pokud je vertex v okolφ st°edu kopce a v tomto p°φpad∞ aplikujeme vyboulenφ. V opaΦnΘm p°φpad∞ s vertexem nebudeme h²bat, proto₧e je mimo dosah vytvß°enΘho kopce. Poslednφch 7 °ßdku jen vypφÜe informaci o pr∙b∞hu vytvß°enφ terΘnu, proto₧e to je Φasov∞ docela nßroΦnΘ. Proto₧e jeÜt∞ neb∞₧φ obnovovacφ smyΦka, je t°eba prohazovat buffery um∞le.

Algoritmus umo₧≥uje mnoho modifikacφ a to nejen zm∞nou parametr∙, ale m∙₧ete nap°φklad jinou funkci ne₧ kosinus a vytvß°et tak jinΘ tvary kopc∙.

29.8. Vykreslenφ terΘnu

Na zßv∞r lekce si povφme, jak terΘn vykreslit. Zde nastßvß mal² problΘm kv∙li omezenφ poΦtu vykreslovan²ch troj·helnφk∙. V∞tÜina grafick²ch karet umo₧≥uje vykreslovat pouze 65536 primitiv b∞hem jednoho volßnφ metody DrawIndexedPrimitive(). Pokud mßme terΘn o rozm∞rech 128x128 je vÜechno v po°ßdku, proto₧e pak budeme mφt pouze 32258 troj·helnφk∙. Pokud ale zkusφte terΘn 256x256 narazφte, proto₧e 130050 troj·helnφku najednou prost∞ nevykreslφte. Tak₧e nejv∞tÜφ terΘn, kter² m∙₧eme vytvo°it na jedno volßnφ DrawIndexedPrimitive() je 128x256 s 64770 troj·helnφky. ╪eÜenφ tohoto problΘmu je celkem prostΘ, volat vykreslovacφ funkci vφckrßt s jin²mi parametry. My se s tφm vÜak nebudeme zab²vat, proto₧e terΘn beztak nebudeme vykreslovat najednou (dnes tedy ano, ale dnes takΘ nepou₧φvßme ₧ßdnou optimalizaΦnφ metodu a spolΘhßme pouze na hrub² v²kon grafickΘ karty a procesoru). Dßme tedy do programu test na sprßvnou velikost textury.

g_pDisplay->GetDevice()->BeginScene();
g_pDisplay->GetDevice()->SetVertexShader(VERTEXFORMAT);
g_pDisplay->GetDevice()->SetStreamSource(0, g_pTerrainVB->GetBuffer(), sizeof(VERTEX));
g_pDisplay->GetDevice()->SetIndices(g_pTerrainIB->GetBuffer(), 0);
g_pDisplay->GetDevice()->SetTexture(0, g_pTerrainSurface->GetTexture());

g_pDisplay->GetDevice()->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, g_dwVerticesCount, 0, g_dwIndicesCount / 3);

g_pDisplay->GetDevice()->EndScene();

Na samotnΘm vykreslenφ nenφ nic, co bychom neprobφrali v minul²ch lekcφch. Volßme metodu pro p°ipravenφ scΘny BeginScene(), dßle nastavujeme formßt vertex∙, zdrojov² vertex buffer, index buffer a texturu traviΦky, pak vykreslφme cel² terΘn volßnφm DrawIndexedPrimitive(), na zßv∞r ukonΦφme scΘnu metodou EndScene(). Prom∞nnß g_dwIndicesCount obsahuje poΦet index∙, ji₧ vφme, ₧e na ka₧d² troj·helnφk p°ipadajφ t°i indexy, staΦφ tedy tuto hodnotu pod∞lit t°emi a zφskßme poΦet troj·helnφk∙.

29.9. Zßv∞r

VÜimn∞te, ₧e pokud vytvo°φte v∞tÜφ terΘn, vaÜe grafickß karta p°estane stφhat vykreslovat terΘn s rozumn²m FPS. Ono takΘ vykreslovat vÜe nenφ to pravΘ a v p°φÜtφ lekci si tedy ukß₧eme jak tento NEDOSTATEK vylepÜit.

T∞Üφm se p°φÜt∞ nashledanou.

Ji°φ Formßnek