C/C++ & Visual C++

Kurz DirectX (32.)

Ve 32. lekci programovßnφ v Direct3D postoupφme dßl a zbavφme se oÜkliv²ch Φern²ch okraj∙ kolem terΘnu. V dalÜφ Φßsti Φlßnku se podφvßme jak v Direct3D pracuje mlha. Nakonec jeÜt∞ upravφme t°φdu XMesh, aby se automaticky poΦφtaly normßlovΘ vektory.

32.1. Obloha a zakonΦenφ terΘnu

Objekt, kter² ohraniΦuje nßÜ sv∞t naz²vßme skybox, co₧ je doslova krychle, kter² mß na vnit°nφ stran∞ texturu okolφ (v naÜem p°φpad∞ to bude nebe, ale m∙₧e to byt t°eba horizont apod.). Cel² nßÜ sv∞t je uvnit° tΘto velkΘ krychle, tak₧e se zdß, ₧e sv∞t nemß ₧ßdnΘ viditelnΘ hranice. Proto₧e jsem nevymyslel lepÜφ ΦeskΘ slovo, budu pou₧φvat anglick² v²raz skybox.

NßÜ skybox vlastn∞ nebude ani box, proto₧e je to p°i omezenφ kamery zcela zbyteΦnΘ. StaΦφ nßm jen vlastn∞ okraje kolem terΘnu (Φili svislΘ st∞ny krychle, kterΘ vlastn∞ nebudou svislΘ, viz. dßle). Okraje rozÜφ°φme, abychom zabrßnili vypadnutφ kamery z prostoru (kamera m∙₧e ve skuteΦnosti zcela opustit plochu terΘnu!) - Φili ud∞lßme takov² "trycht²°". NicmΘn∞ budeme pot°ebovat 8 vrchol∙, stejn∞ jako kdybychom pou₧ili krychli. Za to ale nebudeme definovat seznam troj·helnφk∙ a vystaΦφme si s pßsem (triangle strip), kter² se renderuje rychleji, aΦkoli zde se to samoz°ejm∞ neprojevφ.

Skybox bude reprezentovat objekt ID3Object:

I3DObject *m_pSkyBox;

Tomuto objektu musφme vytvo°it vertex i index buffer:

// horni rozsireni oblohy
float dif;
// Create sky
dwRet = CreateDisplayObject(DISIID_I3DObject, (void**)&m_pSkyBox);
if(dwRet == S_OK)
{
   m_pSkyBox->Init(NULL, 0, (D3DFORMAT)0, 8*sizeof(VERTEX), 10*sizeof(WORD), VB_WRITEONLY|IB_WRITEONLY);
   m_pSkyBox->GetVB()->GetBuffer()->Lock(0,0, (BYTE**) &pVertices, 0);
   for(i = y = 0; y < 2; y++)
   {
      for(x = 0; x < 2; x++)
      {
         pVertices[i].vecPos.x = float(x * (m_iTerrainTilesX));
         pVertices[i].vecPos.y = float(y * (m_iTerrainTilesY));
         pVertices[i].vecPos.z = float(0.0f);
         pVertices[i].dwDiffuse = D3DCOLOR_ARGB(255, 47,205,251);
         // horni vertex
         pVertices[i].tu1 = (i & 0x1) ? 1.0f : 0.0f;
         pVertices[i].tv1 = ((i & 0x6) == 0x6 || (~i & 0x6) == 0x6) ? 0.0f : 1.0f;
         i++;

         dif = (i & 0x2) ? 200.0f : -200.0f;
         pVertices[i].vecPos.x = float(x * m_iTerrainVerticesX + dif);
         dif = (i & 0x4) ? 200.0f : -200.0f;
         pVertices[i].vecPos.y = float(y * m_iTerrainVerticesY + dif);
         pVertices[i].vecPos.z = float(40.0f);
         pVertices[i].dwDiffuse = D3DCOLOR_ARGB(255, 11,1,152);
         // horni vertex
         pVertices[i].tu1 = (i & 0x1) ? 1.0f : 0.0f;
         pVertices[i].tv1 = ((i & 0x6) == 0x6 || (~i & 0x6) == 0x6) ? 0.0f : 1.0f;
         i++;
      }
   }
   m_pSkyBox->GetVB()->GetBuffer()->Unlock();

   m_pSkyBox->GetIB()->GetBuffer()->Lock(0,0, (BYTE**) &pIndices, 0);
   pIndices[0] = 0;pIndices[1] = 1;
   pIndices[2] = 2;pIndices[3] = 3;
   pIndices[4] = 6;pIndices[5] = 7;
   pIndices[6] = 4;pIndices[7] = 5;
   pIndices[8] = 0;pIndices[9] = 1;
   m_pSkyBox->GetIB()->GetBuffer()->Unlock();
}

K≤d vypadß slo₧it∞ a nep°ehledn∞ a je lepÜφ, kdy₧ si objekt nakreslφte na papφr. OΦφslujte si hrany podle tabulky:

╚φslo Binßrnφ k≤d Poloha (x, y, z) Textura (u, v)
0 000 0,0,0 0,0
1 001 0,0,1 1,0
2 010 1,0,0 0,1
3 011 1,0,1 1,1
4 100 0,1,0 0,1
5 101 0,1,1 1,1
6 110 1,1,0 0,0
7 111 1,1,1 1,0

èedivΘ °ßdky oznaΦujφ "hornφ" vrcholy, bφlΘ dolnφ. VÜimn∞te si, ₧e tyto °ßdky majφ v k≤du na prvnφm bitu jedniΦku. Tφmto zp∙sobem p°i°azujeme vlastnosti vrchol∙m.

Nejprve se tedy vytvo°φ objekt I3DObject, v dalÜφm kroku se naplnφ vertex a index buffer. Index∙ je t°eba jen 10, proto₧e chceme pou₧φt triangle strip (na prvnφ troj·helnφk pot°ebujeme 3 vrcholy, na dalÜφ jen jeden, 3 + 7 * 1 = 10). Inicializaci VB provßdφme ve zdvojenΘ smyΦce for, a v ka₧dΘm cyklu vytvo°φme dva vrcholy, kterΘ jsou pod sebou (Φili majφ stejnΘ x a y sou°adnice, ale z se liÜφ) - je to v₧dy dvojice °ßdk∙ v tabulce, bφl² a Üed². Dolnφ vrchol je jednoduch², znßme velikost terΘnu a tento vrchol "pasuje" na roh terΘnu. HorÜφ je to s texturov²mi sou°adnicemi (v naÜem p°φkladu by nebyly pot°eba, proto₧e texturu nepou₧φvßm). VÜimn∞te si, ₧e u-sou°adnice je kopiφ prvnφho bitu k≤du vrcholu. Tento °ßdek kopii provede:

pVertices[i].tu1 = (i & 0x1) ? 1.0f : 0.0f;

U sou°adnice v je to slo₧it∞jÜφ. Kdy₧ se ovÜem podφvßme do tabulky, op∞t je vid∞t, ₧e sou°adnice v je 1, pokud se 2. a 3. bit k≤du liÜφ. Pokud jsou tyto bity stejnΘ (0 nebo 1), je sou°adnice nulovß. Nßsledujφcφ °ßdek provede popsanou operaci:

pVertices[i].tv1 = ((i & 0x6) == 0x6 || (~i & 0x6) == 0x6) ? 0.0f : 1.0f;

Tilda p°ed prom∞nnou i znamenß negaci. Prom∞nnß dif, urΦuje rozÜφ°enφ objektu. ProblΘm je, ₧e u ka₧dΘho vrcholu mß tato prom∞nnß jinΘ znamΘnko. Zde se problΘm rozd∞lφ na x a y sou°adnice (pro ka₧dou sou°adnici d∞lßme samostatn² test). Pokud je x-sou°adnice 0, je t°eba parametr dif odeΦφst, v opaΦnΘm p°φpad∞ p°iΦφst. A x-sou°adnice je jedniΦkovß, prßv∞ kdy₧ je jedniΦkov² 2. bit k≤du:

dif = (i & 0x2) ? 200.0f : -200.0f;
pVertices[i].vecPos.x = float(x * m_iTerrainVerticesX + dif);

Pro sou°adnici y platφ totΘ₧, jen se dφvßme na 3. bit:

dif = (i & 0x4) ? 200.0f : -200.0f;
pVertices[i].vecPos.y = float(y * m_iTerrainVerticesY + dif);

TexturovΘ sou°adnice druhΘho vrcholu poΦφtßme ·pln∞ stejn∞.

Index buffer je tak mal², ₧e jsem ho naplnil v²Φtem hodnot z pole. Kdy₧ si objekt nakreslφte, bude vßm jasnΘ, proΦ jsou vrcholy spojeny zrovna takto. Je toti₧ d∙le₧itΘ, aby normßlovΘ vektory ploch sm∞°ovaly dovnit° - kamera je uvnit° objektu.

JeÜt∞ si krßtce povφme n∞co o zakonΦenφ terΘnu. ZakonΦenφ znamenß, ₧e mezi hranou skyboxu a hranou terΘnu nebude dφra. Tento problΘm se dß °eÜit tak, ₧e p°idßte dalÜφ troj·helnφky mezi zmφn∞nΘ hrany, co₧ je ale pom∞rn∞ nßroΦnΘ (kv∙li optimalizaci byste museli tyto troj·helnφky p°i°adit okrajov²m list∙m stromu). Jß jsem to vy°eÜil ·pln∞ jednoduÜe, staΦφ vÜem krajnφm vertex∙m p°i°adit z-sou°adnici 0.0:

if(x == 0 || x == m_iTerrainVerticesX-1)
{
   m_arTerrain[x][y].vecPos.z = 0.0f;
   m_arTerrain[x][y].dwDiffuse = 0;
}
if(y == 0 || y == m_iTerrainVerticesY-1)
{
   m_arTerrain[x][y].vecPos.z = 0.0f;
   m_arTerrain[x][y].dwDiffuse = 0;
}

Tento k≤d provedeme ji₧ p°i pln∞nφ pole m_arTerrain v metod∞ GenerateTerrainFromFile(). St∞na na hran∞ terΘnu sice nebude zcela svislß, ale to zas tolik nevadφ (je to tak zanedbatelnΘ, ₧e to ani nenφ vid∞t).

32.2. Mlha v Direct3D

V Direct3D existujφ dva typy mlhy - pixelovß a vertexovß. Hlavnφ rozdφl mezi nimi je, ₧e vertexovß mlha je poΦφtßna b∞hem fßze transformacφ a osv∞tlovßnφ pro ka₧d² vertex, naproti tomu pixelovou mlhu poΦφtß ovladaΦ za°φzenφ pro ka₧d² pixel. V dokumentaci MSDN mßte podrobn∞ popsßno, jak²m zp∙sobem se mlha poΦφtß. Pro naÜe ·Φely bude staΦit, kdy₧ vßm ukß₧i, jak se prakticky mlha pou₧φvß.

V naÜem p°φkladu jsem pou₧il pixelovou mlhu. Co musφme ud∞lat proto, abychom mlhu spustili? Je to velice snadnΘ.

1) Nastavφme pomocφ SetRenderState() parametr D3DRS_FOGENABLE na hodnotu TRUE. Tφm mlhu zapneme.
2) Dßle nastavφme barvu mlhy pomocφ hodnoty D3DRS_FOGCOLOR.
3) Dßle typ mlhy s hodnotou D3DRS_FOGTABLEMODE. Tento parametr urΦφ, ₧e chceme pixelovou mlhu a zßrove≥ nastavφme typ mlhy. U toho se trochu zastavφm, proto₧e dalÜφ parametry jsou zßvislΘ prßv∞ na typu. Existujφ vφce typ∙, my si zde uvedeme t°i nejd∙le₧it∞jÜφ.

D3DFOG_EXP - exponencißlnφ mlha prvnφho typu. "Hustota" mlhy se zvyÜuje nep°φmo ·m∞rn∞ vzdßlenosti od kamery.
D3DFOG_EXP2 - exponencißlnφ mlha prvnφho typu. "Hustota" mlhy se zvyÜuje se vzdßlenostφ od kamery, ale efekt je jeÜt∞ umocn∞n.
D3DFOG_LINEAR - lineßrnφ mlha - zde definujeme dv∞ reßlnΘ hodnoty, kter²m °φkßme, kde mlha zaΦφnß a kde konΦφ.

U exponencißlnφ mlhy definujeme pouze jeden reßln² parametr a tφm je hustota mlhy.

VÜe si samoz°ejm∞ ukß₧eme na jednoduchΘm p°φkladu:

float fDensity = 0.005f;
pDis->GetDevice()->SetRenderState(D3DRS_FOGDENSITY, *(DWORD *)(&fDensity));
pDis->GetDevice()->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_EXP);
pDis->GetDevice()->SetRenderState(D3DRS_FOGCOLOR, D3DCOLOR_ARGB(128, 200, 200, 100));
pDis->GetDevice()->SetRenderState(D3DRS_FOGENABLE, TRUE);

Tφmto nastavφme exponencißlnφ lehkou mlhu ₧lutΘ barvy, nakonec mlhu zapneme. Pokud bychom pou₧ili lineßrnφ mlhu, je t°eba definovat dva parametry:

float fFogStart = 1.0f;
float fFogEnd = 100.0f;
pDis->GetDevice()->SetRenderState(D3DRS_FOGSTART, *((DWORD*) (&fFogStart)));
pDis->GetDevice()->SetRenderState(D3DRS_FOGEND, *((DWORD*) (&fFogEnd)));
pDis->GetDevice()->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_LINEAR);
pDis->GetDevice()->SetRenderState(D3DRS_FOGCOLOR, D3DCOLOR_ARGB(128, 200, 200, 100));
pDis->GetDevice()->SetRenderState(D3DRS_FOGENABLE, TRUE);

V p°φkladu z lekce pou₧φvßme prvnφ typ, ale dobrΘ je vytvo°it metodu t°φdy XDisplay pro nastavenφ vÜech typ∙ mlhy, vΦetn∞ vertexovΘ. O tom si ovÜem povφme p°φÜt∞.

32.3. Modifikace t°φdy XMesh

Kdy₧ exportujete model nap°φklad z 3D Studia Max, m∙₧ete k modelu p°idat i normßlovΘ vektory, kterΘ jsou pot°eba pro v²poΦet osv∞tlenφ. V tomto p°φpad∞ nenφ co °eÜit, proto₧e Direct3D tyto vektory automaticky naΦte spolu s ostatnφmi atributy vrchol∙. Nynφ si ale p°edstavte, ₧e model normßly neobsahuje. Mesh bez normßl se zobrazφ bu∩ Φern² anebo bφl² pokud vypnete osv∞tlenφ. Abychom tento problΘm vy°eÜili, pou₧ijeme funkci knihovny D3DX D3DXComputeNormals(). To vypadß nßramn∞ jednoduÜe, ale tak snadnΘ to nenφ. Uvedenß funkce p°edpoklßdß, ₧e ve formßtu vrchol∙ meshe bude slo₧ka normßl (D3DFVF_NORMAL), v opaΦnΘm p°φpad∞ se toti₧ nic nestane. Tak₧e nejprve musφme "p°eformßtovat" mesh do vlastnφho formßtu. To znamenß, ₧e vÜem vrchol∙m meshe urΦφme vlastnφ formßt s tφm, ₧e spoleΦnΘ vlastnosti se kopφrujφ. Proto jsem vytvo°il nov² typ MESH_VERTEX a formßt tohoto typu MESH_FORMAT:

struct MESH_VERTEX
{
   D3DXVECTOR3 vecPos;
   D3DXVECTOR3 vecNormal;
   DWORD dwDiffuse;
   float tu1, tv1;
};

#define MESH_FORMAT D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1|D3DFVF_DIFFUSE

VÜimn∞te si, ₧e vrchol obsahuje to samΘ co VERTEX. Nynφ pou₧ijeme metodu CloneMeshFVF(), abychom mesh p°eformßtovali. Tato metoda vytvo°φ identickou kopii zdrojovΘho meshe, ale vrcholy majφ pochopiteln∞ po₧adovan² formßt.

m_lpMesh->CloneMeshFVF(m_lpMesh->GetOptions(), MESH_FORMAT, m_lpDevice, &lpMesh);

V dalÜφm kroku zjistφme, zda-li p∙vodnφ mesh ji₧ obsahoval normßly, v tomto p°φpad∞ nemß smysl generovat novΘ. Jinak pou₧ijeme v²Üe uvedenou funkci D3DXComputeNormals().

if(!(m_lpMesh->GetFVF() & D3DFVF_NORMAL))
{
   // Compute normals
   dwRet = D3DXComputeNormals(lpMesh, NULL);
   if(dwRet != D3D_OK)
   {
      XException exp("Cannot compute normals.", dwRet);
   THROW(exp);
   }
}

Dßle jsem p°idal novou metodu t°φd∞ XMesh, kterß nastavφ barvu urΦitΘho podobjektu (subset) meshe. K tomu, abychom mohli p°istupovat k vrchol∙m jednotliv²m podobjekt∙m je t°eba mφt k dispozici tabulku atribut∙. Tato tabulka po vytvo°enφ meshe neexistuje a je t°eba ji vytvo°it metodou Optimize() s parametrem D3DXMESHOPT_ATTRSORT.

lpMesh->Optimize(D3DXMESHOPT_ATTRSORT, NULL, NULL, NULL, NULL, &m_lpMesh);

Nynφ ji₧ je tabulka k dispozici a m∙₧eme pou₧φt metodu GetAtrributeTable().

int XMesh::SetSubsetColor(int iSubSet, D3DCOLOR dwColor)
{

MESH_VERTEX *pVertices;
DWORD dwSize, dwRet;
int i, v;
D3DXATTRIBUTERANGE *pAtr;
if(m_lpDevice && int(m_dwNumMat) > iSubSet && m_meshType == CUSTOM)
{
    dwRet = m_lpMesh->GetAttributeTable(NULL, &dwSize);
   if(dwRet != S_OK)
   {
      XException exp("Cannot get atr table size!", dwRet);
      THROW(exp);
   }
   pAtr = new D3DXATTRIBUTERANGE[dwSize];
   dwRet = m_lpMesh->GetAttributeTable(pAtr, &dwSize);
   if(dwRet != S_OK)
   {
      XException exp("Cannot get atr table!", dwRet);
      THROW(exp);
   }
   if(int(dwSize) > iSubSet)
   {
      dwRet = m_lpMesh->LockVertexBuffer(0, (BYTE**) &pVertices);
      if(dwRet != S_OK)
      {
         XException exp("Cannot lock mesh VB!", dwRet);
         THROW(exp);
      }
      // Find first vertex for this subset
      v = pAtr[iSubSet].VertexStart;
      for(i = 0; i < int(pAtr[iSubSet].VertexCount); i++)
      {
         // Modify color

         pVertices[v].dwDiffuse = dwColor;
         v++;
      }

      dwRet = m_lpMesh->UnlockVertexBuffer();
      if(dwRet != S_OK)
      {
        XException exp("Cannot unlock mesh VB!", dwRet);
         THROW(exp);
      }
   }
   SAFE_DELETE_ARRAY(pAtr);

      }
   return -1;
}

V metod∞ nejprve zφskßme tabulku atribut∙, v prvnφm kroku je t°eba zφskat velikost tabulky (v tomto p°φpad∞ zadßvßme hodnotu NULL), dßle zφskßme vlastnφ tabulku opakovan²m volßnφm. V tabulce jsou mimojinΘ ulo₧eny offsety ve vertex bufferu pro jednotlivΘ podobjekty a poΦet vrchol∙. PotΘ VB zamkneme a modifikujeme vybran²m vrchol∙m barvu. Na zßv∞r VB op∞t odemkneme.

32.1. Zßv∞r

P°φÜt∞ se jeÜt∞ podrobn∞ji vrßtφme k mlze, proto₧e v Direct3D je mo₧nostφ mnoho. Nakonec si jeÜt∞ trochu pohrajeme s kamerou, aby se nemohla dostat pod terΘn.

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

 

Ond°ej BuriÜin a Ji°φ Formßnek