• Nem Talált Eredményt

A textúra elkészítése

In document 3D megjelenítési technikák (Pldal 184-190)

technikák optikai és interferometriai háttere

5. Első lépések az OpenGL-ben

6.10. Árnyalások

6.11.1. A textúra elkészítése

A textúrát egy pontjaival meghatározott kép alapján készíthetjük el a

void glTexImage2D (GLenum target, GLint level, GLint components, GLsizei width, GLsizei height, GLint border,

GLenum format, GLenum type, const GLvoid *pixels);

függvény segítségével. A függvény target paramétere GL_TEXTURE_2D érték kell legyen. A level paraméter a mip-map-ek számát adja meg, ha csak egy kép van, akkor értéke 0. A components paraméterben a színkomponensek számát definiálhatjuk. A kép méreteit a width, height, keretét a border paraméter tartalmazza.

A format paraméter definiálja a használt színmodellt (pl. GL_RGB, GL_RGBA stb.). A type paraméter a színadatok típusát tárolja (GL_INT, GL_FLOAT stb.) és végül a pixels a pontokat definiáló színeket tartalmazó tömb kezdőcíme.

Az alábbi függvények a textúra-kép használatát szabályozzák:

void glTexParameterf(GLenum target, GLenum pname, GLfloat param);

void glTexParameteri(GLenum target, GLenum pname, GLint param);

A target paraméter értéke mindkét esetben GL_TEXTURE_2D kell, legyen. A pname paraméter nevezi meg azt a tulajdonságot, melyet a textúrára vonatkozóan be szeretnénk állítani, a param pedig a beállító értéket tartalmazza. Ha a pname értéke például a GL_TEXTURE_WRAP_S, vagy GL_TEXTURE_WRAP_T, akkor a param segítségével a különböző irányokban előírhatjuk, hogy a kép a felületre nyújtva (GL_CLAMP), vagy eredeti méretben ismétlődésekkel tölti ki a felületet (GL_REPEAT ez az alapérték). Ha a pname értéke GL_TEXTURE_MIN_FILTER vagy GL_TEXTURE_MAG_FILTER, akkor a textúraelem pixelre történő kicsinyítésének, illetve nagyításának módját írhatjuk elő. A paramGL_NEAREST értéke esetén a több leképzett képpont közül a pixelhez legközelebbi pont színe a meghatározó, a GL_LINEAR (alapérték) esetben pedig a szóba kerülő pontok színe átlagolódik.

A textúra leképezés során azt, hogy a felület színe és a képpontok színe milyen módon kerül összevetésre a glTexEnvtipus() függvénnyel szabályozhatjuk:

void glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GLfloat param);

Az első két paraméter a megadott konstans kell, legyen. A param paraméter GL_DECAL beállításkor a kép színe kerül a felületre, mintha matricaként rátennénk, egyébként (GL_MODULATE, GL_BLEND) a textúra keveredik a háttérszínnel. Az ilyen módon meghatározott textúra megjelenik minden olyan felületen, amely a glEnable(GL_TEXTURE_2D) beállítással jött létre.

Az alábbi programrészletben egy legfeljebb MERET*MERET nagyságú bitkép adataival töltjük fel a textúrát. A bitképek egyszerű kezelése érdekében – CLI-t használunk.

private: System::Void Form1_Load(System::Object^ sender, glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);

glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);

// A pixel textúra elemre való nagyításának, // kicsinyítésének módja

glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

// A mintázat matricaként kerül a felületre takarva azt.

glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);

OpenGL

glEnable(GL_TEXTURE_2D);

// Használat után letöröljük a bitkép objektumot delete Bm;

}

catch (...) { }

}

4.36. ábra - Textúra

7. 3D-s objektumok

7.1. 3D-s objektumok megadása

A 3D-s objektumok megadása is csúcspont és attribútumaik segítségével történik. Ezeket kiszámolhatjuk kézzel is, mint ahogy eddig tettük, vagy egy 3D modellező programban megrajzolhatjuk az objektumokat, amelyeket betölt a programunk.

Az objektumok betöltéséhez ismerni kell az objektumot leíró fájl formátumát. Ilyen formátum pl. a szöveges Wavefront OBJ (.obj), a bináris 3D Studio (.3ds), vagy az XML alapú Collada (.dae). A fájl betöltéséhez az Open Asset Import Library (Assimp) könyvtárat fogjuk használni. Az Assimp-et C++-ban írták, sok különböző típusú formátumot képes kezelni, amelyekhez egy egységes API-n keresztül lehet hozzáférni.

Gyakran előfordul, hogy egy modell különböző háromszögeinek ugyanaz a csúcspont is a része, ilyenkor felesleges újra eltárolni a vertexet. Ehelyett egy tömbben fogjuk tárolni az összes csúcspontot, és az attribútumokat, és egy külön tömbben tároljuk a háromszögeket alkotó vertexek indexeit (4.37. ábra - Vertex és index puffer).

4.37. ábra - Vertex és index puffer

3D grafikánál az árnyaláshoz a pozíciók mellett meg kell adni a csúcsokban lévő normál vektorokat is. A normál vektor egy egység hosszú vektor, amely merőleges a felületre. Definiáljunk két típust: egy 3-elemű vektort, amellyel a pozíciókat és normál vektorokat tudjuk leírni, illetve egy struktúrát amely egy oldal indexeit fogja tartalmazni.

struct vec3 { float x, y, z;

vec3(float x, float y, float z) : x(x), y(y), z(z) { } } ;

struct Face {

unsigned int index[3];

} ;

Ezeket felhasználva készítjük el a példaprogramot! A 3D-s objektumokon is bemutatjuk az eddig megismert ábrázolásokat. Definiáljunk egy 3D-s objektumot leíró osztályt! Ez tartalmazni fogja a megfelelő puffereket, amelyeket tömb helyett a Standard Library-ben lévő std::vector generikus típusú objektumban fogunk tárolni. Ennek előnye a hagyományos tömbkezeléssel szemben, hogy a memóriát magától fel fogja szabadítani az objektum megszűnésekor. Emellett definiáljunk két metódust, az egyik betölti az objektumot, a másik

void load(const char* filename);

void render();

} ;

Írjuk meg a load() metódust, amely betöltet az Assimp-el egy 3D-s fájlt, majd ebből a szükséges attribútumokat átalakítja a saját adatszerkezetünkre. Az Assimp használatához szükség van néhány fejállományra, illetve hozzá kell szerkeszteni az assimp.lib fájlt a programhoz.

void Mesh::load(const char* filename) {

// mesh betöltés

Assimp::Importer importer;

const aiScene* pScene = importer.ReadFile(filename, aiProcess_Triangulate);

if (!pScene || pScene->mNumMeshes == 0) return;

// mesh

const aiMesh* pMesh = pScene->mMeshes[0];

Az Assimp::Importer osztályon keresztül lehet betölteni egy fájlt, amelyre vissza ad egy const aiScene pointert. Ez az adatstruktúra tartalmazza többek között az objektumokat (mesh). Emellett tartalmazhatja a színteret is, azaz, hogy az egyes mesh-ek milyen hierarchikus viszonyban vannak egymással, és hol helyezkednek el. A ReadFile() metódusnak megadtuk az aiProcess_Triangulate flaget, amivel elérjük, hogy az Assimp a sok pontból álló poligonokat alakítsa át háromszögekké.

A példában egy .obj fájlból töltünk be egy teáskannát, és feltételezzük, hogy csak egy mesh lesz a fájlban.

Ezután a saját adatszerkezetünkre alakítjuk át az aiMesh-ben lévő információt.

// memóriafoglalás

vertexes.reserve(pMesh->mNumVertices);

normals.reserve(pMesh->mNumVertices);

indexes.reserve(pMesh->mNumFaces);

// vertexek másolása

for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) vertexes.push_back(vec3(

pMesh->mVertices[i].x, pMesh->mVertices[i].y, pMesh->mVertices[i].z));

// normálisok másolása

for (unsigned int i = 0; i < pMesh->mNumVertices; ++i) normals.push_back(vec3(

pMesh->mNormals[i].x, pMesh->mNormals[i].y, pMesh->mNormals[i].z));

OpenGL

// indexek másolása

for (unsigned int i = 0; i < pMesh->mNumFaces; ++i) { Face face;

face.index[0] = pMesh->mFaces[i].mIndices[0];

face.index[1] = pMesh->mFaces[i].mIndices[1];

face.index[2] = pMesh->mFaces[i].mIndices[2];

indexes.push_back(face);

} }

Az std::vector magától nagyobb memóriát foglal, ha betelik az előre lefoglalt terület, azonban ezt elkerülhetjük, mert tudjuk, hogy pontosan hány elem fog belekerülni. Ezután az aiMesh struktúrából átmásoljuk a számunkra érdekes részeket.

for (std::vector<Face>::const_iterator it = indexes.begin();

it != indexes.end();

A main() függvény a szokásos módon néz ki, létrehoz egy ablakot mélység pufferrel, és dupla puffereléssel.

int main(int argc, char* argv[]) {

glutInit(&argc, argv);

glutInitWindowSize(640, 480);

glutInitWindowPosition(0, 0);

glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);

glutCreateWindow(”Hello 3D”); töltsük be a modellt. Később forgatni fogjuk a modellt, amihez deklaráljunk még 2 globális változót!

Mesh teapot;

float angle = 0.0f; // forgatás mértéke fokokban int lastTime = 0;

void onInit()

{

glClearColor(0.1f, 0.2f, 0.3f, 0.0f);

glShadeModel(GL_SMOOTH); egy perspektivikus vetítést a már ismert gluPerspective() függvénnyel.

void onResize(int width, int height) {

glViewport(0, 0, width, height);

if (height == 0) height = 1;

double aspect = static_cast<double>(width) / static_cast<double>(height);

glMatrixMode(GL_PROJECTION);

glLoadIdentity();

gluPerspective(60.0, aspect, 0.1, 100.0);

glMatrixMode(GL_MODELVIEW);

}

Az onIdle() függvény a szokásos módon méri az utolsó hívás óta eltelt időt, és frissíti a forgatáshoz használt angle nevű változót.

void onIdle() {

// idő mérés

int now = glutGet(GLUT_ELAPSED_TIME);

float dt = (now - lastTime) / 1000.0f;

lastTime = now;

angle += 36.0 * dt;

glutPostRedisplay();

}

Végül már csak az onDisplay()-t kell megírni.

void onDisplay()

Ezután beállítunk egy nézeti transzformációt a gluLookAt() függvénnyel, amelynek meg kell adni a kamera pozícióját (első 3 paraméter), azt, hogy melyik pontba néz (második 3 paraméter), illetve a felfele irányt (harmadik 3 paraméter). A felfele irány megadásával lehet a kamerát az optikai tengelye mentén forgatni. Majd kirajzoljuk az objektumot.

4.38. ábra - 3D-s objektum

OpenGL

In document 3D megjelenítési technikák (Pldal 184-190)