Kurz DirectX (30.)

St°edem pozornosti v dneÜnφ novΘ lekci bude t°φda CTerrain a XCamera. S druhou jmenovanou t°φdou jsme ji₧ pracovali v minulΘ a p°edminulΘ lekci, dnes ji tedy pouze doplnφme, abychom mohli o°ezßvat viditelnou scΘnu. Naopak t°φdu CTerrain budeme vytvß°et ·pln∞ od zaΦßtku.

30.1. Frustum

O Frustum jsme si pov∞d∞li, ₧e je to komol² jehlan, kter² vymezuje prostor. VÜechny objekty, kterΘ jsou uvnit° tohoto prostoru, by se m∞ly vykreslit, proto₧e budou vid∞t. U ostatnφch objekt∙ je jistota, ₧e vid∞t nebudou a tφm pßdem nßs nezajφmajφ. D∙le₧itΘ je tedy zajistit, abychom vykreslovali jen ty objekty, kterΘ uvnit° jsou a zbyteΦn∞ nepl²tvali v²kon t∞mi ostatnφmi. Abychom takov² test mohli v∙bec provΘst, je t°eba mφt Frustum spoΦφtanΘ a to je ·kol tΘto Φßsti.

Op∞t budeme pou₧φvat hojn∞ pojm∙ z analytickΘ geometrie. Zßklad je struktura CLIPVOLUME, kterß pomocφ obecn²ch rovnic rovin p°esn∞ definuje Frustum:

typedef struct _CLIPVOLUME
{
   D3DXPLANE pLeft, pRight;
   D3DXPLANE pTop, pBottom;
   D3DXPLANE pNear, pFar;
} CLIPVOLUME;

D3DXPLANE urΦuje Φty°mi koeficienty a, b, c a d rovnici roviny ve tvaru:

ax + by + cz + d = 0

Co₧ je obecnß rovnice roviny v prostoru. Koeficienty a, b a c tedy urΦujφ normßlov² vektor roviny. Atributy pLeft a pRight urΦujφ boky, pTop a pBottom je strop a podlaha a nakonec pNear a pFar jsou p°ednφ a zadnφ st∞na.


Obr. 1: Frustum

┌kolem je tedy urΦit vÜech Üest rovin co nejp°esn∞ji. Vypadß to jako nep°ekonateln² problΘm, ale ve skuteΦnosti je to trochu analytickΘ geometrie ze st°ednφ Ükoly. Nßsleduje novß metoda t°φdy XCamera, kterß urΦφ Frustum naÜφ kamery:

void XCamera::ComputeClipVolume()
{

FLOAT dist, t1, t2;
D3DXVECTOR3 p, pt[8];
D3DXVECTOR3 v1, v2, n;
D3DXVECTOR3 vUpDir = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
D3DXVECTOR3 vDir = m_vLookAtPoint - m_vEyePt ;
D3DXVECTOR3 vCross;
D3DXVec3Cross(&vCross, &vUpDir, &vDir);
D3DXVec3Cross(&vUpDir, &vDir, &vCross);
D3DXVec3Normalize(&vDir, &vDir);
D3DXVec3Normalize(&vCross, &vCross);
D3DXVec3Normalize(&vUpDir, &vUpDir);

for(INT i = 0; i < 8; i++)
{
    dist = (i & 0x4) ? FAR_CLIP : NEAR_CLIP;
    pt[i] = dist * vDir;
    t1 = dist * tanf(FOV/2);
    t1 = (i & 0x2) ? t1 : -t1;
    pt[i] += vUpDir * t1;
    t2 = dist * tanf(FOV/2) * ASPECT; // take into account screen proportions
    t2 = (i & 0x1) ? -t2 : t2;
    pt[i] += vCross * t2;
    pt[i] += m_vEyePt;
}

//compute the near plane
v1 = pt[2] - pt[0];
v2 = pt[1] - pt[0];
D3DXVec3Cross(&n, &v2, &v1);
D3DXVec3Normalize(&n, &n);
m_CV.pNear.a = n.x;
m_CV.pNear.b = n.y;
m_CV.pNear.c = n.z;
m_CV.pNear.d = -(n.x * pt[0].x + n.y * pt[0].y + n.z * pt[0].z);

//compute the far plane
v1 = pt[5] - pt[4];
v2 = pt[6] - pt[4];
D3DXVec3Cross(&n, &v2, &v1);
D3DXVec3Normalize(&n, &n);
m_CV.pFar.a = n.x;
m_CV.pFar.b = n.y;
m_CV.pFar.c = n.z;
m_CV.pFar.d = -(n.x * pt[4].x + n.y * pt[4].y + n.z * pt[4].z);// - 10.0f;

//compute the top plane
v1 = pt[6] - pt[2];
v2 = pt[3] - pt[2];
D3DXVec3Cross(&n, &v2, &v1);
D3DXVec3Normalize(&n, &n);
m_CV.pTop.a = n.x;
m_CV.pTop.b = n.y;
m_CV.pTop.c = n.z;
m_CV.pTop.d = -(n.x * pt[2].x + n.y * pt[2].y + n.z * pt[2].z);

//compute the bottom plane
v1 = pt[1] - pt[0];
v2 = pt[4] - pt[0];
D3DXVec3Cross(&n, &v2, &v1);
D3DXVec3Normalize(&n, &n);
m_CV.pBottom.a = n.x;
m_CV.pBottom.b = n.y;
m_CV.pBottom.c = n.z;
m_CV.pBottom.d = -(n.x * pt[0].x + n.y * pt[0].y + n.z * pt[0].z);

//compute the left plane
v1 = pt[3] - pt[1];
v2 = pt[5] - pt[1];
D3DXVec3Cross(&n, &v2, &v1);
D3DXVec3Normalize(&n, &n);
m_CV.pLeft.a = n.x;
m_CV.pLeft.b = n.y;
m_CV.pLeft.c = n.z;
m_CV.pLeft.d = -(n.x * pt[1].x + n.y * pt[1].y + n.z * pt[1].z);

//compute the right plane
v1 = pt[4] - pt[0];
v2 = pt[2] - pt[0];
D3DXVec3Cross(&n, &v2, &v1);
D3DXVec3Normalize(&n, &n);
m_CV.pRight.a = n.x;
m_CV.pRight.b = n.y;
m_CV.pRight.c = n.z;
m_CV.pRight.d = -(n.x * pt[0].x + n.y * pt[0].y + n.z * pt[0].z);

}

Vypadß to mo₧nß d∞siv∞, ale nynφ si vÜe podrobn∞ vysv∞tlφme. Pomohou nßm nßsledujφcφ obrßzky. V hornφ Φßsti je vid∞t vztah mezi t°emi d∙le₧it²mi vektory: vDir, vUpDir a vCross. To se takΘ t²kß prvnφ Φßsti v k≤du, kde od sebe odeΦteme LookAtPoint a EyePoint a tφm zφskßme vektor kamery vDir. Dßle pot°ebujeme vektor vCross, kter² je kolm² k vDir, ale zßrove≥ musφ b²t kolm² k vektoru vUpDir. Pou₧ijeme tedy vektorov² souΦin. Potφ₧ je v tom, ₧e vUpDir nemusφ mφ°it p°φmo nahoru, ale bude naklon∞n tak, aby byl kolm² na vDir, kter² bude v naÜem p°φpad∞ mφ°it spφÜ k terΘnu. Musφme tedy zp∞tn∞ vyu₧φt vektor vCross a spoΦφtat sprßvn² svisl² vektor (op∞t pomocφ vektorovΘho souΦinu). Nakonec vÜechny tyto vektory znormalizujeme, aby m∞ly jednotkovou velikost.


Obr. 2: Frustum v detailu

Na dalÜφch obrßzcφch je vid∞t Frustum sm∞rem od kamery a shora. FOV je ·hel pohledu, kter² mßme definovan² jako PI/4, tedy 45 stup≥∙. V cyklu inicializujeme osm bod∙ komolΘho jehlanu (Φφsla bod∙ jsou vyznaΦena na obrßzku: 0-7). Nejprve poΦφtßme p°ednφ st∞nu, potΘ zadnφ, pou₧ijeme tedy bu∩ konstantu NEAR_CLIP nebo FAR_CLIP. PotΘ protßhneme vektor vDir o vzdßlenost st∞ny od kamery, tφm se dostaneme do st°edu danΘ st∞ny. Prom∞nnΘ t1 a t2 p°edstavujφ posun ve vertikßlnφm resp. horizontßlnφm sm∞ru. SpoΦφtßme je ·pln∞ jednoduÜe pomocφ tangenty ·hlu FOV/2 (protilehlß ku p°ilehlΘ, protilehlß je zde po₧adovanß prom∞nnß t1/t2 a p°ilehlß je vzdßlenost od kamery - tedy bu∩ NEAR_CLIP nebo FAR_CLIP. ProΦ bereme jen polovinu uhlu FOV je vid∞t s obrßzku. U t2 navφc musφme poΦφtat s tφm, ₧e Frustum nemß Φtvercovou podstavu, ale ₧e hrany jsou v pom∞ru 4:3 (konstanta ASPECT). Podle bodu, kter² zrovna poΦφtßme bude t1/t2 bu∩ kladnΘ nebo zßpornΘ a posuneme bod ve sm∞ru vDirUp a vCross. Na zßv∞r jen posuneme Frustum do skuteΦnΘ pozice kamery.

Nynφ u₧ bude hraΦka urΦit st∞ny Frustum, nebo¥ mßme hned Φty°i body, kterΘ le₧φ v ka₧dΘ rovin∞ (dokonce by staΦily t°i). Ji₧ jsem zmφnil, ₧e koeficienty a, b, c obecnΘ rovnice roviny w, jsou slo₧ky normßlovΘho vektoru n. UrΦφme tedy normßlovΘ vektory. To provedeme zcela jednoduÜe. Vybereme si libovolnΘ t°i body u ka₧dΘ st∞ny a spoΦφtßme z nich dva sm∞rovΘ vektory s1 a s2, kterΘ le₧φ v rovin∞ w. PotΘ pou₧ijeme vektorov² souΦin k urΦenφ vektoru kolmΘho na oba sm∞rovΘ vektory s1 a s2. To jsme ale zφskali vektor kolm² na rovinu w, proto₧e s1 a s2 le₧ely v rovin∞ w a zφskali jsme tudφ₧ po₧adovan² vektor n. Zb²vß umφstit rovinu v prostoru, tedy urΦit koeficient d. Ten zφskßme zp∞tn²m dosazenφm libovolnΘho bodu, kter² le₧φ v rovin∞, do stßvajφcφ rovnice. Po ·pravßch p°edchozφ rovnice dostaneme:

d = -(ax + by + cz)

a, b a c ji₧ mßme. x, y a z urΦujφ bod v rovin∞ (m∙₧eme si vybrat libovoln² z t∞ch Φty°, kterΘ znßme). Tyto v²poΦty provedeme pro ka₧dou st∞nu Frustum zvlßÜ¥.

30.2. T°φda CTerrain

V dalÜφ Φßsti se budeme zab²vat t°φdou CTerrain, kterß zapouzd°φ to, co jsme d∞lali v minulΘ lekci, to znamenß tvorbu terΘnu a navφc k tomu p°idßme podporu optimalizace pomocφ quad tree. O stromech se dozvφte vφce v p°φÜtφ lekci Kurzu o datov²ch strukturßch. Nßm ale budou staΦit ·plnΘ zßklady. Navφc teorii jsme ji₧ probrali minule a implementace nenφ p°φliÜ slo₧itß (pokud znßte rekurzi).

ZaΦneme tedy postupn∞. Zßklad bude struktura QTNode, kterß reprezentuje jeden uzel, pop°φpad∞ list stromu.

struct QTNode {
   //
   // Type of node - NODE, LEAF

   NODETYPE ntType;
   //
   // Bounding points

   BoundingPoint arBounds[4];
   //
   // Each node has four branches/children/kids
   // LEAF has not any kids

   QTNode *pBranches[4];
   //
   // leaf's tiles, each LEAF has 25 vertices (2 triangles per tile -> 32 triangles per LEAF)
   // NODE has not any vertices

   IVertexBuffer *pVB;
   BOOL bVis;
};

Struktura obsahuje typ uzlu (bu∩ se jednß o obyΦejn² uzel nebo o list - tedy uzel bez potomk∙). Typ NODETYPE je definovßn takto:

typedef enum NODETYPE {

   NODE_TYPE,
   LEAF_TYPE,
};

NODE_TYPE tedy p°edstavuje obyΦejn² uzel a LEAF_TYPE list. Dßle uchovßvßme okrajovΘ body uzlu. Tato informace se nßm bude hodit p°i urΦovßnφ, zda-li je uzel uvnit° nebo vn∞ Frustum. Prom∞nna pBraches je pole Φty° ukazatel∙ na potomky danΘho uzlu. Uzly na nejni₧Üφ ·rovni naz²vßme listy, to nenφ nic novΘho a listy takΘ uchovßvajφ informaci o terΘnu. V naÜem p°φpad∞ se jednß p°φmo o Vertex buffer, co₧ nenφ zcela ideßlnφ, ale lΘpe to vy°eÜφme v p°φÜtφ lekci. Poslednφ atribut nßm jen °φkß, zda-li je list vid∞t Φi nikoliv. Tuto vlastnost bychom ani nepot°ebovali, ale chceme vykreslovat mapu viditeln²ch list∙ (viz. dßle). Na dalÜφm obrßzku vidφte v²znam v∞tÜiny prom∞nn²ch:


Obr. 3: Detail QTNode

ZelenΘ kuliΦky v rozφch p°edstavujφ body ulo₧enΘ v arBounds.

P°ejd∞me k samotnΘ t°φd∞ CTerrain:

class CTerrain
{
   DWORD m_dwFlags;
   // Common IB, heightmap and material
   IIndexBuffer *m_pTerrainIB;
   ITexture *m_pTerrainSurface;
   ITexture *m_pHeightMap;
   // Terrain dimensions
   int m_iTerrainTilesX;
   int m_iTerrainTilesY;
   int m_iTerrainVerticesX;
   int m_iTerrainVerticesY;
   // Current terrain method
   TERRAIN_METHOD m_iTerrainMethod;
   I3DFont *m_pDebugFont;

   std::vector <QTNode*> m_arLeaves;
   std::vector <QTNode*> m_arVisQuads;
   //
   // Root of the quad tree

   QTNode *m_pRoot;
   //
   // Vertices...

   VERTEX **m_arTerrain;
   //
   // Common inicialization

   HRESULT InternalInit();
   int CreateNode(QTNode *pNode);
   int FillLeaves();
   int DestroyNode(QTNode *pNode);
   int CircleAlgorithm();
   int ComputeNormals();
   int CreateQuadTree();
   // Fills queue of the visible quads
   HRESULT CullTerrain(QTNode *pNode, CLIPVOLUME& cv);

public:
   HRESULT GenerateTerrain(int x, int y, TERRAIN_METHOD tmMethod = TM_CIRCLE);
   HRESULT GenerateTerrainFromFile(LPCSTR szFile);
   HRESULT DestroyTerrain();
   int GetTitlesX() {return m_iTerrainTilesX;}
   int GetTitlesY() {return m_iTerrainTilesY;}

   void SetFlag(DWORD dwFlag) {m_dwFlags |= dwFlag;}
   void UnsetFlag(DWORD dwFlag) {m_dwFlags &= ~dwFlag;}
   void InvertFlag(DWORD dwFlag) { if(dwFlag & m_dwFlags) { UnsetFlag(dwFlag); } else {                                     SetFlag(dwFlag);}}
   void ResetFlags() {m_dwFlags = 0;}

   HRESULT Render();
   HRESULT Restore();

   CTerrain(void);
   ~CTerrain(void);
};

V nßsledujφcφ tabulce najdete popis metod:

Nßzev
Popis
GenerateTerrain(int x, int y, TERRAIN_METHOD tmMethod); Generuje nßhodn² terΘn po₧adovanou metodou o dan²ch rozm∞rech. Inicializuje prom∞nnΘ m_iTerrainTilesX, m_iTerrainTilesY, m_iTerrainVerticesX, m_iTerrainVerticesY a vytvo°φ pole m_arTerrain. Uklßdß metodu terΘnu: m_iTerrainMethod.
GenerateTerrainFromFile(LPCSTR szFile); Generuje terΘn z heightmapy danΘ parametrem. Inicializuje prom∞nnΘ m_iTerrainTilesX, m_iTerrainTilesY, m_iTerrainVerticesX, m_iTerrainVerticesY a vytvo°φ pole m_arTerrain. Dßle naΦte heightmapu a ukazatel ulo₧φ do m_pHeightMap. Uklßdß metodu terΘnu: m_iTerrainMethod.
DestroyTerrain(); Odstranφ terΘn z pam∞ti (uvolnφ se vÜechny pomocnΘ objekty, textury, VB a IB).
GetTitlesX() Vracφ velikost terΘnu ve sm∞ru x.
GetTitlesY() Vracφ velikost terΘnu ve sm∞ru y.
SetFlag(DWORD dwFlag) Nastavφ dan² p°φznak. Modifikuje prom∞nnou m_dwFlags.
UnsetFlag(DWORD dwFlag) Vynuluje p°φznak. Modifikuje prom∞nnou m_dwFlags.
InvertFlag(DWORD dwFlag) Invertuje p°φznak. Modifikuje prom∞nnou m_dwFlags.
ResetFlags() Vynuluje vÜechny p°φznaky. Modifikuje prom∞nnou m_dwFlags.
Render() Provede o°φznutφ viditeln²ch list∙ a vykreslφ terΘn, p°φpadn∞ mapu. Metoda by m∞la b²t volßna v bloku BeginScene()-EndScene().
Restore() Obnovφ vÜechny zdroje po ztrßt∞ za°φzenφ (textury, IB a VB jednotliv²ch list∙).
InternalInit() Alokuje n∞kterΘ zdroje spoleΦnΘ pro oba typy tvorby terΘnu. Jednß se o texturu povrchu: m_pTerrainSurface, index buffer list∙ m_pTerrainIB a font m_pDebugFont.
CreateNode(QTNode *pNode) Rekurzivnφ metoda vytvß°ejφcφ strukturu quad tree.
FillLeaves() Naplnφ listy stromu daty, zkopφruje tedy vrcholy terΘnu do dφlΦφch VB.
DestroyNode(QTNode *pNode) Zcela uvolnφ pam∞¥ stromu tj. vyma₧e vÜechny uzle a listy i s jejich VB.
CircleAlgorithm() Aplikuje algoritmus kopeΦk∙ (viz. minulß lekce)).
ComputeNormals() SpoΦφtß normßly celΘho terΘnu. Op∞t jsme tento postup probφrali v minulΘ lekci.
CreateQuadTree() Vytvo°φ cel² quad tree. Nejprve tedy zinicializuje ko°en stromu a potΘ spustφ metody CreateNode().
CullTerrain(QTNode *pNode, CLIPVOLUME& cv) Zajistφ o°ezßvßnφ terΘnu tzn. naplnφ pole viditeln²ch list∙.

╚erven∞ jsou oznaΦeny soukromΘ metody. Nynφ si ukß₧eme jednotlivΘ metody podrobn∞ji.

Tvorba terΘnu:

HRESULT CTerrain::GenerateTerrainFromFile(LPCSTR szFile)
{

DWORD dwRet = S_FALSE;
int i = 0, x, y;
//
// Init common object (texture, font etc.)

InternalInit();
//
// Load heightmap texture

dwRet = CreateDisplayObject(DISIID_ITexture, (void**)&m_pHeightMap);
if(dwRet == S_OK)
{
   dwRet = m_pHeightMap->LoadTextureFromFile(szFile);
   if(dwRet != S_OK)
   {
      XException exp("Cannot load texture for heightmap!", dwRet);
      THROW(exp);
   }
   if(m_pHeightMap->GetFormat() != D3DFMT_A8R8G8B8 && m_pHeightMap->GetFormat() !=          D3DFMT_X8R8G8B8)
   {
      XException exp("Invalid heightmap format! Must be 32-bit with alpha channel.");
   THROW(exp);
   }
   //set dim of the terrain according dim of the height map
   m_iTerrainTilesX = m_pHeightMap->Width() - 1;
   m_iTerrainTilesY = m_pHeightMap->Height() - 1;
   m_iTerrainVerticesX = m_pHeightMap->Width();
   m_iTerrainVerticesY = m_pHeightMap->Height();
}
m_arTerrain = new VERTEX*[m_iTerrainVerticesX];
for(i = 0; i < m_iTerrainVerticesX; i++)
{
   m_arTerrain[i] = new VERTEX[m_iTerrainVerticesY];
}
//
// Open heightmap

D3DLOCKED_RECT lr;
m_pHeightMap->GetTexture()->LockRect(0, &lr, NULL, D3DLOCK_READONLY);
TEXTURE_PIXEL * data = (TEXTURE_PIXEL*)lr.pBits;

int p = lr.Pitch/4;
// Init basic terrain parameters
for(y = 0; y < m_iTerrainVerticesY; y++)
{
   for(x = 0; x < m_iTerrainVerticesX; x++)
   {
      TEXTURE_PIXEL tp = data[y*p + x];

      m_arTerrain[x][y].vecPos = D3DXVECTOR3(float(x), float(y),                                  float(tp.a)/255.0f*20.0f-10.0f);
      m_arTerrain[x][y].dwDiffuse = D3DCOLOR_ARGB(128, tp.r, tp.g, tp.b);
      m_arTerrain[x][y].tu1 = (x % 2) ? 1.0f : 0.0f;
      m_arTerrain[x][y].tv1 = (y % 2) ? 1.0f : 0.0f;
      m_arTerrain[x][y].vecNormal = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
   }
}
m_pHeightMap->GetTexture()->UnlockRect(0);

ComputeNormals();
//
// Create quad tree

CreateQuadTree();

return dwRet;

}

Jak jsem ji₧ zmφnil, tato metoda vytvo°φ terΘn z heightmapy. Prakticky shrnuje vÜe, co jsme napsali v minulΘ lekci. Nejprve vytvo°φme spoleΦnΘ objekty jako je font nebo textura povrchu, pak naΦteme texturu heightmapy do pam∞ti, vytvo°φme 2D pole terΘnu a naplnφme ho daty podle textury. Nakonec spoΦteme normßly a vytvo°φme strom.

HRESULT CTerrain::GenerateTerrain(int x, int y, TERRAIN_METHOD tmMethod /*= TM_CIRCLE*/)
{

DWORD dwRet = S_FALSE;
int i = 0;
//
// Init common object (texture, font etc.)

InternalInit();

m_iTerrainTilesX = x;
m_iTerrainTilesY = y;
m_iTerrainVerticesX = x + 1;
m_iTerrainVerticesY = y + 1;

m_arTerrain = new VERTEX*[m_iTerrainVerticesX];
for(i = 0; i < m_iTerrainVerticesX; i++)
{
   m_arTerrain[i] = new VERTEX[m_iTerrainVerticesY];
}
//
// Init basic terrain parameters

for(y = 0; y < m_iTerrainVerticesY; y++)
{
   for(x = 0; x < m_iTerrainVerticesX; x++)
   {
      m_arTerrain[x][y].vecPos = D3DXVECTOR3(float(x), float(y), 0.0f);
      m_arTerrain[x][y].dwDiffuse = D3DCOLOR_ARGB(128,128,255,128);
      m_arTerrain[x][y].tu1 = (x % 2) ? 1.0f : 0.0f;
      m_arTerrain[x][y].tv1 = (y % 2) ? 1.0f : 0.0f;
      m_arTerrain[x][y].vecNormal = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
   }
}
//
// Apply circle algorithm on the terrain

CircleAlgorithm();
ComputeNormals();
//
// Create quad tree

CreateQuadTree();

return dwRet;

}

Tato metoda je podobnß, op∞t se volß metoda InternalInit() a pak se vytvo°φ 2D pole vertex∙, kterΘ se naplnφ daty. Pou₧ije se algoritmus kopeΦk∙ a spoΦφtajφ se normßly. Na zßv∞r postavφme strom. V p°edchozφch metodßch takΘ uklßdßme velikost terΘnu. V prvnφm p°φpad∞ se bere podle velikost textury heightmapy. Ve druhΘm je pak dßna dv∞ma parametry.

Proto₧e s p°φpravou terΘnu ·zce souvisφ metoda InternalInit(), podφvejme se te∩ na nφ:

HRESULT CTerrain::InternalInit()
{

DWORD dwRet = S_FALSE;
DWORD dwTerrainIBSize, dwIndicesCount;
IDisplay *pDis;
CreateDisplayObject(DISIID_IDisplay, (void**) &pDis);
//
// Create font
if(S_OK == (dwRet = CreateDisplayObject(DISIID_I3DFont, (void**) &m_pDebugFont)))
{
   m_pDebugFont->CreateFont(15, 8, "Arial", TRUE, D3DCOLOR_ARGB(255,150,255,100));
}
else
{
   TRACE("Cannot create font: %d", dwRet);
   return dwRet;
}
//
// Create common index buffer for all quad leaves
dwRet = CreateDisplayObject(DISIID_IIndexBuffer, (void**) &m_pTerrainIB);
if(dwRet == S_OK)
{
   dwTerrainIBSize = LEAF_I_SIZE * LEAF_I_SIZE * 6 * sizeof(WORD);
   dwIndicesCount = LEAF_I_SIZE * LEAF_I_SIZE * 6;
   dwRet = m_pTerrainIB->Create(dwTerrainIBSize, D3DUSAGE_WRITEONLY, D3DFMT_INDEX16,    D3DPOOL_DEFAULT);
   if(dwRet != S_OK)
   {
      XException exp("Cannot create IB for terrain!", dwRet);
      THROW(exp);
   }
}

//
// Load terrain texture
dwRet = CreateDisplayObject(DISIID_ITexture, (void**)&m_pTerrainSurface);
if(dwRet == S_OK)
{
   dwRet = m_pTerrainSurface->LoadTextureFromFileEx("grass64.bmp", 6,    pDis->GetTextureFormat());
   if(dwRet != S_OK)
   {
      XException exp("Cannot load texture for terrain surface!", dwRet);
      THROW(exp);
   }
}

SAFE_RELEASE(pDis);

return dwRet;

}

Nejednß se o nic slo₧itΘho, proto₧e metoda mß jen za ·kol vytvo°it pßr objekt∙: font, kter²m pφÜeme informace o terΘnu na obrazovku, index buffer jednoho listu, kter² vyu₧ijeme p°i vykreslovßnφ a textura povrchu terΘnu - naÜe znßmß trßva.

Veled∙le₧itou metodou a vlastn∞ podstatou je CreateNode(). Tato metoda je rekurzivnφ tzn. ₧e volß sama sebe pro ka₧d² uzel stromu. ZaΦφnßme od ko°ene v metod∞ CreateQuadTree():

int CTerrain::CreateQuadTree()
{

//
// Check terrain validity

if(!m_arTerrain)
{
   XException exp("Terrain is not initialized. You cannot apply any algorithm.");
   THROW(exp);
}
if(m_iTerrainVerticesX >= LEAF_I_SIZE && m_iTerrainVerticesY >= LEAF_I_SIZE)
{
   m_pRoot = new QTNode;
   // Init ROOT
   m_pRoot->ntType = NODE_TYPE;
   m_pRoot->pVB = NULL;
   m_pRoot->arBounds[0].x = m_arTerrain[0][0].vecPos.x;
   m_pRoot->arBounds[0].y = m_arTerrain[0][0].vecPos.y;
   m_pRoot->arBounds[0].z = m_arTerrain[0][0].vecPos.z;
   m_pRoot->arBounds[1].x = m_arTerrain[m_iTerrainVerticesX-1][0].vecPos.x;
   m_pRoot->arBounds[1].y = m_arTerrain[m_iTerrainVerticesX-1][0].vecPos.y;
   m_pRoot->arBounds[1].z = m_arTerrain[m_iTerrainVerticesX-1][0].vecPos.z;
   m_pRoot->arBounds[2].x = m_arTerrain[0][m_iTerrainVerticesY-1].vecPos.x;
   m_pRoot->arBounds[2].y = m_arTerrain[0][m_iTerrainVerticesY-1].vecPos.y;
   m_pRoot->arBounds[2].z = m_arTerrain[0][m_iTerrainVerticesY-1].vecPos.z;
   m_pRoot->arBounds[3].x = m_arTerrain[m_iTerrainVerticesX-1]
                            [m_iTerrainVerticesY-1].vecPos.x;
   m_pRoot->arBounds[3].y = m_arTerrain[m_iTerrainVerticesX-1]
                            [m_iTerrainVerticesY-1].vecPos.y;
   m_pRoot->arBounds[3].z = m_arTerrain[m_iTerrainVerticesX-1]
                            [m_iTerrainVerticesY-1].vecPos.z;

   CreateNode(m_pRoot);
   //
   // Fill index buffer and leaves with data

   Restore();
}
else
{
   return S_FALSE;
}
return S_OK;

}

Zde tedy p°ipravφme prvnφ uzel - ko°en - a zavolßme CreateNode(). Na zßv∞r naplnφme vÜechny listy daty pomocφ metody Restore(). VÜimn∞te si inicializace okrajov²ch bod∙. JednoduÜe pou₧ijeme krajnφ body pole m_arTerrain, proto₧e ko°en je p°es cel² terΘn, uzel s nejv∞tÜφ rozlohou.

int CTerrain::CreateNode(QTNode *pNode)
{

UINT uiWidth, uiHeight;
//
// Check terrain validity
if(!m_arTerrain)
{
   XException exp("Terrain is not initialized. You cannot apply any algorithm.");
   THROW(exp);
}
if(pNode)
{

//
// Compute node width and height

uiWidth = int(pNode->arBounds[1].x - pNode->arBounds[0].x);
uiHeight = int(pNode->arBounds[2].y - pNode->arBounds[0].y);
//
// Get node type

if(uiWidth == LEAF_I_SIZE)
{
   pNode->ntType = LEAF_TYPE;
   // Create vertex buffer
   if(S_OK == CreateDisplayObject(DISIID_IVertexBuffer, (void**) &pNode->pVB))
   {
      pNode->pVB->Create(LEAF_V_SIZE*LEAF_V_SIZE*sizeof(VERTEX),
                        D3DUSAGE_WRITEONLY, VERTEXFORMAT, D3DPOOL_DEFAULT);
      pNode->bVis = FALSE;
      m_arLeaves.push_back(pNode);
   }
   return 1;
}
else
{

pNode->ntType = NODE_TYPE;
//
// Create 4 children

// First
pNode->pBranches[0] = new QTNode;
pNode->pBranches[0]->pVB = NULL;
// Init new bounding box
pNode->pBranches[0]->arBounds[0] = pNode->arBounds[0];

pNode->pBranches[0]->arBounds[1].x = pNode->arBounds[1].x - uiWidth / 2;
pNode->pBranches[0]->arBounds[1].y = pNode->arBounds[1].y;
pNode->pBranches[0]->arBounds[1].z = m_arTerrain[int(pNode->pBranches[0]->arBounds[1].x)]
[int(pNode->pBranches[0]->arBounds[1].y)].vecPos.z;

pNode->pBranches[0]->arBounds[2].x = pNode->arBounds[2].x;
pNode->pBranches[0]->arBounds[2].y = pNode->arBounds[2].y - uiHeight / 2;
pNode->pBranches[0]->arBounds[2].z = m_arTerrain[int(pNode->pBranches[0]->arBounds[2].x)](
[int(pNode->pBranches[0]->arBounds[2].y)].vecPos.z;

pNode->pBranches[0]->arBounds[3].x = pNode->arBounds[3].x - uiWidth / 2;
pNode->pBranches[0]->arBounds[3].y = pNode->arBounds[3].y - uiHeight / 2;
pNode->pBranches[0]->arBounds[3].z = m_arTerrain[int(pNode->pBranches[0]->arBounds[3].x)]
[int(pNode->pBranches[0]->arBounds[3].y)].vecPos.z;

// Second
pNode->pBranches[1] = new QTNode;
pNode->pBranches[1]->pVB = NULL;
// Init new bounding box
pNode->pBranches[1]->arBounds[0].x = pNode->arBounds[0].x + uiWidth / 2;
pNode->pBranches[1]->arBounds[0].y = pNode->arBounds[0].y;
pNode->pBranches[1]->arBounds[0].z = m_arTerrain[int(pNode->pBranches[1]->arBounds[0].x)]
[int(pNode->pBranches[1]->arBounds[0].y)].vecPos.z;

pNode->pBranches[1]->arBounds[1] = pNode->arBounds[1];

pNode->pBranches[1]->arBounds[2].x = pNode->arBounds[2].x + uiWidth / 2;
pNode->pBranches[1]->arBounds[2].y = pNode->arBounds[2].y - uiHeight / 2;
pNode->pBranches[1]->arBounds[2].z = m_arTerrain[int(pNode->pBranches[1]->arBounds[2].x)]
[int(pNode->pBranches[1]->arBounds[2].y)].vecPos.z;

pNode->pBranches[1]->arBounds[3].x = pNode->arBounds[3].x;
pNode->pBranches[1]->arBounds[3].y = pNode->arBounds[3].y - uiHeight / 2;
pNode->pBranches[1]->arBounds[3].z = m_arTerrain[int(pNode->pBranches[1]->arBounds[3].x)]
[int(pNode->pBranches[1]->arBounds[3].y)].vecPos.z;

// Third
pNode->pBranches[2] = new QTNode;
pNode->pBranches[2]->pVB = NULL;
// Init new bounding box
pNode->pBranches[2]->arBounds[0].x = pNode->arBounds[0].x;
pNode->pBranches[2]->arBounds[0].y = pNode->arBounds[0].y + uiHeight / 2;
pNode->pBranches[2]->arBounds[0].z = m_arTerrain[int(pNode->pBranches[2]->arBounds[0].x)]
[int(pNode->pBranches[2]->arBounds[0].y)].vecPos.z;

pNode->pBranches[2]->arBounds[1].x = pNode->arBounds[1].x - uiWidth / 2;
pNode->pBranches[2]->arBounds[1].y = pNode->arBounds[1].y + uiHeight / 2;
pNode->pBranches[2]->arBounds[1].z = m_arTerrain[int(pNode->pBranches[2]->arBounds[1].x)]
[int(pNode->pBranches[2]->arBounds[1].y)].vecPos.z;

pNode->pBranches[2]->arBounds[2] = pNode->arBounds[2];

pNode->pBranches[2]->arBounds[3].x = pNode->arBounds[3].x - uiWidth / 2;
pNode->pBranches[2]->arBounds[3].y = pNode->arBounds[3].y;
pNode->pBranches[2]->arBounds[3].z = m_arTerrain[int(pNode->pBranches[2]->arBounds[3].x)]
[int(pNode->pBranches[2]->arBounds[3].y)].vecPos.z;

// Fourth
pNode->pBranches[3] = new QTNode;
pNode->pBranches[3]->pVB = NULL;
// Init new bounding box
pNode->pBranches[3]->arBounds[0].x = pNode->arBounds[0].x + uiWidth / 2;
pNode->pBranches[3]->arBounds[0].y = pNode->arBounds[0].y + uiHeight / 2;
pNode->pBranches[3]->arBounds[0].z = m_arTerrain[int(pNode->pBranches[3]->arBounds[0].x)]
[int(pNode->pBranches[3]->arBounds[0].y)].vecPos.z;

pNode->pBranches[3]->arBounds[1].x = pNode->arBounds[1].x;
pNode->pBranches[3]->arBounds[1].y = pNode->arBounds[1].y + uiHeight / 2;
pNode->pBranches[3]->arBounds[1].z = m_arTerrain[int(pNode->pBranches[3]->arBounds[1].x)]
[int(pNode->pBranches[3]->arBounds[1].y)].vecPos.z;

pNode->pBranches[3]->arBounds[2].x = pNode->arBounds[2].x + uiWidth / 2;
pNode->pBranches[3]->arBounds[2].y = pNode->arBounds[2].y;
pNode->pBranches[3]->arBounds[2].z = m_arTerrain[int(pNode->pBranches[2]->arBounds[2].x)]
[int(pNode->pBranches[3]->arBounds[2].y)].vecPos.z;

pNode->pBranches[3]->arBounds[3] = pNode->arBounds[3];

// Create children of the children
CreateNode(pNode->pBranches[0]);
CreateNode(pNode->pBranches[1]);
CreateNode(pNode->pBranches[2]);
CreateNode(pNode->pBranches[3]);

}
return 0;

}
return -1;

}

Metoda vypadajφcφ na prvnφ pohled slo₧it∞. Na druh² je to ·pln∞ jednoduchΘ. Nejprve pot°ebujeme spoΦφtat velikost aktußlnφho uzlu (tj. uzlu, pro kter² byla metoda volßna). Otec tohoto uzlu ovÜem pro nßs spoΦφtal okrajovΘ body, staΦφ tedy od sebe odeΦφst ty sprßvnΘ dva a dostaneme tak Üφ°ku uiWidth a "v²Üku" uiHeight uzlu. Dßle testujeme, zda-li jsme ji₧ nedosßhli nejni₧Üφ "listovΘ" ·rovn∞. Velikost listu definuje konstanta LEAF_I_SIZE. Pokud ano, aktußlnφ uzel je list a provedeme t°i operace: uzel si musφ pamatovat, ₧e je list, vytvo°φ se pro n∞j vertex buffer (zatφm se neplnφ) a vlo₧φ se do pole list∙ - v tomto bod∞ je metoda ukonΦena a vracφme se o ·rove≥ v²Ü.
Pokud je ale uzel jeÜt∞ p°φliÜ velk², je t°eba ho znovu rozΦtvrtit a pro ka₧dou Φtvrtinu zavolat CreateNode(). Tak₧e vytvo°φme Φty°i novΘ uzly a nastavφme jim novΘ krajnφ body tak, aby pokryly p°edchßzejφcφ uzel. Tohle si urΦit∞ nakreslete na kousek papφru!

Listy mßme ulo₧eny v poli m_arLeaves a nynφ je musφme naplnit daty z hlavnφho pole celΘho terΘnu m_arTerrain:

int CTerrain::FillLeaves()
{
   QTNode *pNode;
   VERTEX *pVertices;
   int v, y, x, i;
   for(i = 0; i < (int)m_arLeaves.size(); i++)
   {
      pNode = (QTNode*)m_arLeaves[i];
      pNode->pVB->GetBuffer()->Lock(0, 0, (BYTE**) &pVertices, 0);

      v = 0;
      for(y = (int)pNode->arBounds[0].y; y <= (int)pNode->arBounds[2].y; y++)
      {
         for(x = (int)pNode->arBounds[0].x; x <= (int)pNode->arBounds[1].x; x++)
         {
            pVertices[v] = m_arTerrain[x][y];
            v++;
         }
      }
      pNode->pVB->GetBuffer()->Unlock();
   }
   return -1;
}

Zb²vß metoda pro napln∞nφ a obnovu Vertex a Index bufferu. P°i ztrßt∞ za°φzenφ (a i p°i inicializaci) se automatick² volß metoda Restore(), kterß naplnφ IB a volß metodu pro napln∞nφ vÜech list∙:

HRESULT CTerrain::Restore()
{
   WORD *pIndices;
   DWORD dwRet = S_FALSE;
   int x, y, i = 0;

   if(m_arTerrain)
   {
      TRACE("Restoring terrain surface...");
      dwRet = m_pTerrainIB->GetBuffer()->Lock(0, 0, (BYTE**)&pIndices, 0);
      for(y = 0; y < LEAF_I_SIZE; y++)
      {
         for(x = 0; x < LEAF_I_SIZE; x++)
         {
            pIndices[i + 0] = x + y * LEAF_V_SIZE;
            pIndices[i + 1] = (x+1) + y * LEAF_V_SIZE;
            pIndices[i + 2] = x + (y+1) * LEAF_V_SIZE;
            i += 3;
            pIndices[i + 0] = (x+1) + y * LEAF_V_SIZE;
            pIndices[i + 1] = (x+1) + (y+1) * LEAF_V_SIZE;
            pIndices[i + 2] = x + (y+1) * LEAF_V_SIZE;
            i += 3;
         }
      }
      dwRet = m_pTerrainIB->GetBuffer()->Unlock();
      dwRet = FillLeaves();
   }
   return dwRet;
}

V Index bufferu jsou odkazy v rßmci jednoho listu, proto₧e listy budeme vykreslovat postupn∞. Princip naleznete v minulΘ lekci - je to jako kdybychom m∞li mal² povrch o rozm∞rech jednoho listu.

Nynφ si ukß₧eme jak o°ezßvat listy, kterΘ jsou mimo Frustum. O°ezßvßnφ se provßdφ v metod∞ CullTerrain(). Jednß se op∞t o rekurzivnφ metodu (vÜimn∞te si, ₧e pokud pracujeme se stromem, rekurze nßm podstatn∞ zjednoduÜuje ₧ivot). V²sledek metody je seznam viditeln²ch list∙:

// Fills queue of the visible quads
HRESULT CTerrain::CullTerrain(QTNode *pNode, CLIPVOLUME& cv)
{
   DWORD zones[4] = {0, 0, 0, 0};
   FLOAT x, y, z;
   float temp;

   for(int i = 0; i < 4; i++)
   {
      x = pNode->arBounds[i].x;
      y = pNode->arBounds[i].y;
      z = pNode->arBounds[i].z;

      temp = cv.pNear.a * x + cv.pNear.b * y + cv.pNear.c * z + cv.pNear.d;
      if (temp > CULL_TOLERANCE) {
         zones[i] |= 0x01;
      }
      else {
            temp = cv.pFar.a * x + cv.pFar.b * y + cv.pFar.c * z + cv.pFar.d;
            if (temp > CULL_TOLERANCE) {
               zones[i] |= 0x02;
            }
      }

      temp = cv.pLeft.a * x + cv.pLeft.b * y + cv.pLeft.c * z + cv.pLeft.d;
      if (temp > CULL_TOLERANCE) {
         zones[i] |= 0x04;
      }
      else {
           temp = cv.pRight.a * x + cv.pRight.b * y + cv.pRight.c * z + cv.pRight.d;
           if (temp > CULL_TOLERANCE) {
             zones[i] |= 0x08;
           }
      }

      temp = cv.pTop.a * x + cv.pTop.b * y + cv.pTop.c * z + cv.pTop.d;
      if (temp > CULL_TOLERANCE+2.0f) {
         zones[i] |= 0x10;
      }
      else
      {
         temp = cv.pBottom.a * x + cv.pBottom.b * y + cv.pBottom.c * z + cv.pBottom.d;
         if (temp > CULL_TOLERANCE) {
            zones[i] |= 0x20;
         }
      }
   }

   //if all of the corners are outside of the boundaries
   // this node is excluded, so stop traversing

   DWORD res = zones[0] & zones[1] & zones[2] & zones[3];
   if(res)
   {
      return -1;
   }

   // if this is a leaf add the triangle lists to the render queue
   if (pNode->ntType == LEAF_TYPE)
   {
      pNode->bVis = TRUE;
      m_arVisQuads.push_back(pNode);
      return 1;
   }
   else
   {
      //this is not a leaf traverse deeper
      for(i = 0; i < 4; i++) {
         CullTerrain(pNode->pBranches[i], cv);
      }
   }
   return 0;
}

Princip metody spoΦφvß v tom, ₧e zkoumßme vÜechny Φty°i krajnφ body ka₧dΘho uzlu a testujeme, zda-li jsou uvnit° Frustum nebo ne. Pokud je alespo≥ jeden bod uvnit°, pova₧ujeme uzel za viditeln² a pokud se jednß o list, p°idßme ho do pole m_arVisQuads. V opaΦnΘm p°φpad∞ volßme metody CullTerrain() pro vÜechny potomky.
JeÜt∞ se vrßtφm k testu. Zde koneΦn∞ vyu₧ijeme naÜφ novou strukturu CLIPVOLUME. Postupn∞ vezmeme vÜechny st∞ny Frustum a dosazujeme krajnφ body uzlu. Pokud by bod le₧el p°esn∞ v rovin∞ (co₧ se ve sv∞te float∙ prakticky nem∙₧e stßt), vyÜel by v²sledek 0. Nßs ale zajφmß situace, kdy je bod venku (uvnit°). Pak vyjde v²sledek kladn² (v naÜem p°φpad∞ pou₧φvßme konstantu CULL_TOLERANCE, pomocφ kterΘ m∙₧eme °φdit p°esah) a to zaznamenßme v poli zones, kam se postupn∞ p°idßvß informace o bodech, kterΘ jsou mimo. Pokud jsou mimo vÜechny, metoda se ukonΦφ.

Na zßv∞r si jeÜt∞ ukß₧eme vykreslovacφ metodu Render().

HRESULT CTerrain::Render()
{

IDisplay *pDis;
CLIPVOLUME cv;
PIXEL v;
int i;
char szInfo[255];
CreateDisplayObject(DISIID_IDisplay, (void**) &pDis);

if(m_dwFlags & TF_DRAWQUADSMAP)
{
   for(i = 0; i < (int)m_arLeaves.size(); i++)
   {
      ((QTNode*)m_arLeaves[i])->bVis = FALSE;
   }
}

m_arVisQuads.clear();
pDis->GetCamera()->GetClipVolume(cv);
CullTerrain(m_pRoot, cv);

pDis->GetDevice()->SetVertexShader(VERTEXFORMAT);
pDis->GetDevice()->SetIndices(m_pTerrainIB->GetBuffer(), 0);
pDis->GetDevice()->SetTexture(0, m_pTerrainSurface->GetTexture());

if(m_dwFlags & TF_USEQUADS)
{
   for(i = 0; i < (int)m_arVisQuads.size(); i++)
   {
      pDis->GetDevice()->SetStreamSource(0,((QTNode*)m_arVisQuads[i])->pVB->GetBuffer(),
                                        sizeof(VERTEX));
      pDis->GetDevice()->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,LEAF_V_SIZE*LEAF_V_SIZE,0,
                                              LEAF_I_SIZE*LEAF_I_SIZE * 2);
   }
   sprintf(szInfo, "Using quad tree culling\nTotal leaves: %d\nTotal faces: %d",
          (int)m_arVisQuads.size(),(int)m_arVisQuads.size() * LEAF_I_SIZE * LEAF_I_SIZE * 2);
}
else
{
   for(i = 0; i < (int)m_arLeaves.size(); i++)
   {
      pDis->GetDevice()->SetStreamSource(0, ((QTNode*)m_arLeaves[i])->pVB->GetBuffer(),
                                           sizeof(VERTEX));
      pDis->GetDevice()->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,LEAF_V_SIZE*LEAF_V_SIZE,0,                                           LEAF_I_SIZE*LEAF_I_SIZE * 2);
   }
   sprintf(szInfo, "Without quad tree culling\nTotal leaves: %d\nTotal faces: %d",
          (int)m_arLeaves.size(), (int)m_arLeaves.size() * LEAF_I_SIZE * LEAF_I_SIZE * 2);
}

if(m_dwFlags & TF_DRAWQUADSMAP)
{
   pDis->GetDevice()->SetVertexShader(PIXELFORMAT);
   pDis->GetDevice()->SetRenderState(D3DRS_LIGHTING, FALSE);
   pDis->GetDevice()->SetTexture(0, NULL);
   for(i = 0; i < (int)m_arLeaves.size(); i++)
   {
      QTNode *pNode = (QTNode*)m_arLeaves[i];
      v.vecPos.x = pNode->arBounds[0].x/LEAF_I_SIZE +
                   pDis->GetResolution()->x - m_iTerrainTilesX/LEAF_I_SIZE - 10;
      v.vecPos.y = pNode->arBounds[0].y/LEAF_I_SIZE + 10;
      v.vecPos.z = 0.0f;
      v.rhw = 1.0f;
      if(pNode->bVis)
      {
         v.dwDiffuse = D3DCOLOR_ARGB(255,255,255,0);
      }
      else
      {
         v.dwDiffuse = D3DCOLOR_ARGB(255,128,128,128);
      }
      pDis->GetDevice()->DrawPrimitiveUP(D3DPT_POINTLIST, 1, &v, sizeof(PIXEL));
   }
   pDis->GetDevice()->SetRenderState(D3DRS_LIGHTING, TRUE);
}

m_pDebugFont->Draw(szInfo, 0, 300);

SAFE_RELEASE(pDis);
return 0;

}

Pokud mßme aktivnφ mapu, je t°eba vymazat viditelnost vÜech list∙. V opaΦnΘm p°φpad∞ atribut bVis v∙bec k niΦemu nepou₧φvßme, tak₧e tuto smyΦku m∙₧eme p°eskoΦit. Dßle pou₧ijeme objekt CLIPVOLUME z naÜφ kamery a provedeme o°ezßnφ metodou CullTerrain().

P°ed vykreslenφm musφme nastavit znßmΘ parametry jako formßt vertex∙, texturu a index bufferu. PotΘ se rozhoduje, jak²m zp∙sobem budeme listy vykreslovat. Pokud je zapnutß optimalizace, vykreslujφ se pouze viditelnΘ listy z pole m_arVisQuads. V opaΦnΘm p°φpad∞ se vykreslφ vÜechny listy z pole m_arLeaves.

V poslednφ Φßsti vykreslujeme mapu (pokud o to u₧ivatel stojφ). Mapa je tvo°ena v pravΘm hornφm rohu a zatφm ji vykreslujeme po pixelech. ka₧d² pixel p°edstavuje list. ViditelnΘ jsou ₧lutΘ, ostatnφ ÜedΘ. Pou₧φvßme k tomu transformovanΘ vrcholy PIXEL, u nich₧ nastavujeme pouze polohu (p°φmo v sou°adnicφch obrazovky) a barvu.

Na zßv∞r vykreslφme informace o terΘnu. Vypisuje se pouze poΦet viditeln²ch list∙ a celkov² poΦet polygon∙.

30.3. Zßv∞r

A co nßs Φekß v p°φÜtφ lekci? Dßle budeme zdokonalovat nßÜ jednoduch² engine. Nap°φklad p°idßme oblohu a podφvßme se, jak dßle upravit a zrychlit vykreslovanφ terΘnu (optimalizovat lze tΘm∞° donekoneΦna). Dßle bych cht∞l vylepÜit podporu sv∞tel. Budou to tedy spφÜe takovΘ "kosmetickΘ" zm∞ny ne₧ revoluce, kterou jste vid∞li dnes.

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