Lekcija 8: Meshevi i .x datoteke
Često je potrebno u directX-u nacrtati složene objekte, npr. likove u nekoj igri, model čajnika, stola, ili neku prostoriju. Ručno utipkavanje svake točke takvih objektata je jednostavno nezamisliv posao. U tu svrhu, directX omogućava učitavanje složenih objekata kreiranih u nekom od programa za 3D modeliranje kao što su 3D Studio Max, Lightwave, Blender ili slični. Za te programe postoje pluginovi koji mogu eksportirati objekte u .x datoteke. Mesh je u principu skup verteksa, tekstura, i materijala koji zajedno čine neki objekt u 3D prostoru. X datoteke su najčešće u binarnom obliku, no ja sam ostavio smiley.x u ascii formatu, tako da u notepadu možete vidjeti njen sadržaj.
Manipulacije mesh objektima u ovom programu vrši se preko Cmesh klase koja je opisana u mesh.cpp i mesh.h datotekama.
Unutar DX-a mesh je predstavljen pomoću četiri varijable:
|
DWORD m_dwNumMaterials; broj materijala u meshu LPD3DXMESH m_pMesh; klasa D3DXMESH sadrži vertekse D3DMATERIAL9* m_pMeshMaterials; ova klasa sadrži materijale LPDIRECT3DTEXTURE9* m_pMeshTextures;klasa sadrži teksture |
Što su materijali, te koja je njihova svrha biti će objašnjeno u idučoj lekciji, zato sada prema njima pristupite jednostavno kao dijelu mesha.
Učitavanje ovih vrijednosti vrši se u konstruktoru CMesh klase:
|
CMesh::CMesh(LPDIRECT3DDEVICE9 pD3DDevice, LPSTR pFilename) {//u konstruktoru se učitava mesh iz .x datoteke. //mesh je skup materijava, vertexa i tekstura zapisanih u datoteku pomoću nekog programa za 3D modeliranje //Složene objekte uvjek prikazujemo meshevima i njihovim teksturama. LPD3DXBUFFER pMaterialsBuffer = NULL; LPD3DXMESH pMesh = NULL; //podešavanje matrice transformacije na jedinične(default) D3DXMatrixIdentity(&scale); D3DXMatrixIdentity(&translation); D3DXMatrixIdentity(&rotation); m_pD3DDevice = pD3DDevice;
if(FAILED(D3DXLoadMeshFromX(pFilename, D3DXMESH_SYSTEMMEM, m_pD3DDevice, NULL, &pMaterialsBuffer,NULL, &m_dwNumMaterials, &pMesh))) { m_pMesh = NULL; m_pMeshMaterials = NULL; m_pMeshTextures = NULL; return; } D3DXMATERIAL* matMaterials = (D3DXMATERIAL*)pMaterialsBuffer->GetBufferPointer();
m_pMeshMaterials = new D3DMATERIAL9[m_dwNumMaterials]; m_pMeshTextures = new LPDIRECT3DTEXTURE9[m_dwNumMaterials]; for(DWORD i = 0; i < m_dwNumMaterials; i++) { m_pMeshMaterials[i] = matMaterials[i].MatD3D; m_pMeshMaterials[i].Ambient = m_pMeshMaterials[i].Diffuse; if(FAILED(D3DXCreateTextureFromFile(m_pD3DDevice, matMaterials[i].pTextureFilename, &m_pMeshTextures[i]))) { m_pMeshTextures[i] = NULL; } } SafeRelease(pMaterialsBuffer);
pMesh->CloneMeshFVF(D3DXMESH_MANAGED, MESH_D3DFVF_CUSTOMVERTEX, m_pD3DDevice, &m_pMesh); SafeRelease(pMesh); //radi eventualnog sjenčanja potrebno je izračunati normale D3DXComputeNormals(m_pMesh,NULL); } |
Ključni dijelovi gornjeg koda su poziv D3DXLoadMeshFromX funkcije koja vrača mesh (vertekse), buffer materijala i imena datoteka sa teksturama, te njihov broj. Poslije toga potrebno je buffere materijala razdijeliti na pojedine te učitati teksture. Na kraju se računaju normale pojedinih verteksa unutar mesha radi eventualnog sjenčanja (iduća lekcija).
Radi lakše manipulacije CMesh klasa sadrži i matrice transformacija. Bitno je kako množimo matrice prije renderiranja samog podseta mesha. Najbolje je prvo napraviti rotaciju, zatim skaliranje, a tek na kraju translaciju, jer u suprotnom mogli bi dobiti neželjene efekte.
|
D3DXMATRIXA16 out; D3DXMatrixMultiply(&out,&rotation,&scale); D3DXMatrixMultiply(&out,&out,&translation); m_pD3DDevice->SetTransform(D3DTS_WORLD,&out); |
Renderiranje mesha vrši se po podsetovima verteksa, tako da se za svaki podset podešava odgovarajući materijal i tekstura.
|
for(DWORD i = 0; i < m_dwNumMaterials; i++) { m_pD3DDevice->SetMaterial(&m_pMeshMaterials[i]); if(m_pMeshTextures[i]!=NULL) m_pD3DDevice->SetTexture(0, m_pMeshTextures[i]);
m_pMesh->DrawSubset(i); } m_pD3DDevice->SetMaterial(NULL);//da ne crta objekte iza sa tim materijalom m_pD3DDevice->SetTexture(0,NULL);//da ne crta objekte sa zastarjelom teksturom |
Nakon renderiranja postavljamo materijal i teksturu na NULL vrijednost, tako da nebi dobili crtanje nekog objekta iza sa zastrajelim materijalom i teksturom.
Podešavanje matrica transformacija vrši se preko Set metoda klase CMesh.
|
void SetPosition(float x,float y,float z);//podešava položaj void SetRotate(float x,float y,float z);//podešava kuteve prema x,y i z osi void SetScale(float x,float y,float z);//podešava velicinu objekta |
Metoda GetPosition vrača položaj objekta direktno iz matrice translacije, što nemora uvjek biti savršeno točno, jer je sama matrica translacije često podložna raznim matematičkim operacijama, no u ovom slučaju zadovoljava.
Također je omogućeno kopiranje mesha, tako da objekt ostaje isti, no smješta se u novu CMesh klasu. Ovo je mnogo bolje i brže od kreiranja novog objekta iz datoteke. Kopija CMesh objekta dobiva se pozivom MakeCopy metode originalnog objekta. Samo kopiranje izvedeno je u overloadanom (pretovarenom) konstruktoru klase CMesh CMesh(CMesh *original);.
Overloadanje, ili kako ga hrvatski programeri nazivaju, pretovarivanje funkcija i operatora je dodjeljivanje novih funkcionalnosti starim operatorima ili metodama. Vidi se iz primjera da postoje dva konstruktora klase CMesh, no oni ne primaju iste parametre, te ih po tome compiler razlikuje. Ako pozovemo konstruktor sa parametrom tipa CMesh* program će izvršiti konstuktor CMesh(CMesh *original), a ako pozovemo sa parametrima (ID3DDevice *,LPSTR) onda poziva drugi konstruktor.
Iz programa se vidi da veliki smajlić uvjek gleda prema položaju miša, a mali smajlići gledaju prema velikom smajliću.
Male smajliće stvaramo pritiskom na lijevu tipku miša, i oni će se pojaviti kada tipku odpustimo.
|
if(CDInput::MouseLeftReleased) {//ako je odpuštena lijeva tipka miša, kreiraj je dnog smajlića CMesh *m=meshes[0]->MakeCopy(); m->SetPosition(CDInput::MouseX,CDInput::MouseY,1.0); m->SetScale(0.5,0.5,0.5); m->SetRotate(-anglex,-angley,-anglez); meshes.push_back(m); } |
Kreiranje se vrši u Render metodi CScene objekta. Računanje kuta pogleda vrši se iz pozicije miša prema velikom smajliću, ili pojedinih pozicija malih samjlića prema velikome.
Računanje kuta velikog smajlića:
|
float deltay=50-100*sin(2*D3DX_PI*(timeGetTime()%5000/5000.));//veliki smajlić ide gore dolje po sinusu. //izračunaj kut velikog smajlića tako da gleda prema mišu float distance=sqrt(((CDInput::MouseX-400)*(CDInput::MouseX-400))+((CDInput::MouseY-300-deltay)*(CDInput::MouseY-300-deltay)));
float angley=atan2((CDInput::MouseX-400),distance); float anglex=D3DX_PI-atan2((CDInput::MouseY-300-deltay),distance); float anglez=0; meshes[0]->SetRotate(anglex,angley,anglez); meshes[0]->SetPosition(400,300+deltay,10); |
Veliki smajlić vrši translaciju po sinusu, čija relativna koordinata je prikazana sa deltay varijablom. Nakon toga računaju se pojedini kutovi zrake, iz centra smajlića prema mišu, prema x, y i z osi. Kut je jednak arkustangensu udljenosti miša od smajlića, podjeljenom sa udaljenošću po osi. Kut oko y osi ovisi o pomaku po x osi, a kut oko y osi o pomaku na x osi. Atan2 funkcija prima dva parametra, prvi predstavlja y, a drugi x, te računa Atan(y/x), s time da ispravno računa kut u svim kvadrantima, što kod atan funkcije nije tako. Rotacija oko x osi je pomaknuta za PI, tako da smiley ne gleda "iza", nego "napred". Ista stvar računa se za male smajliće, samo ne prema mišu, nego prema velikom smiliću.
|
D3DXVECTOR3 v=meshes[0]->GetPosition(); D3DXVECTOR3 vec; for(int br=1; br<meshes.size();br++) {//izračunaj kuteve malih samjlića tako da gledaju prema velikom vec=meshes[br]->GetPosition(); float distance1=sqrt(((v.x-vec.x)*(v.x-vec.x))+((v.x-vec.y)*(v.x-vec.y)));
float angley1=atan2((v.x-vec.x),distance); float anglex1=D3DX_PI-atan2((v.y-vec.y),distance); float anglez1=0; meshes[br]->SetRotate(anglex1,angley1,anglez1); } |
Svi meshevi su smješteni u vektor, te se iz glavne Render metode crtaju sljedećim programskim odsječkom:
|
for(br=0;br<meshes.size();br++) {//renderira sve mesheve meshes[br]->Render(); } |
Izgled osme aplikacije
Made by: Mll