Előadás Home Introduction Environment setup Best homeworks

Poligon rajzolás

Az OpenGL mindent egy 3D-s koordinátarendszerben definiál, viszont a képernyő és a window egy 2D pixel rácsként értelmezhető. Ezért a grafikus pipeline egyik fő feladata a 3D koordináták transzformálása a megfelelő 2D koordinátává, majd a 2D koordináta transzformálása a megfeleő 2D színes pixel koordinátává.

Grafikus pipeline
A grafikus pipeline több egymás utáni lépés sorozata, ahol minden egyes lépés az előző kimenetét fogadja bemenetként. Minden egyes lépés jól meghatározott és könnyen végrehajtható párhuzamos módon. A grafikus pipeline egyes lépéseihez kis shader programokat definiálhatunk, melyeket a GPU számos shader processzorával párhuzamosan futtathatunk.

Ezen shader-ek nagy részét a fejlesztő maga tudja megírni és ezáltal sokkal jobban ki tudja használni a GPU erőforrásait. Shader kódokat az OpenGL Shading Language (GLSL) segítségével írhatunk, amely nagyban hasonlít a C nyelvre.

A következő ábra a grafikus pipeline főbb lépéseit reprezentálja. A kék ábrák azokat a részeket jelentik, ahol definiálni tudjuk saját shader kódunkat.

title

A grafikus pipeline bemenete egy 3D koordinátákat tartalmazó tömböt (vertex data) vár. A vertex data vertexek összessége, ahol a vertex egy 3D koordináta attribútumait és esetleg valamilyen szín információt tartalmaz. (Az OpenGL szempontjából, a primitíveken (point, line, triangle) keresztül definiálnunk kell, hogy hogyan szeretnénk renderelni a vertex data-t.)

A grafikus pipeline első része a vertex shader, amely bemenetként egy vertex adatot vár és a fő célja, hogy a 3D koordinátákat transzformálja, továbbá lehetőséget biztosít, hogy különböző műveleteket végezhessünk a vertex egyes attribútumain.

A primitive(shape) assembly a vertex shader kimenetét, azaz az összes vertex adatot várja és a megfelelő primitívbe rendezi azokat.

A primitive assembly kimenetét a geometry shader fogadja. A geometry shader lehetőséget biztosít, hogy módosítsuk, a vertex data struktúránkat, új elemek felvételével/törlésével ezáltal módosíthatjuk az adott alakzatot vagy új primitíveket hozhatunk létre.

A következő lépés a rasterization ahol az egyes primitívek és a képernyő 2D pixel koordinátái közötti megfeleltetés számolódik ki, mely művelet eredménye a fragment-ek. A fragment shader futása előtt, megtörténik a vágás, ami eldobja az összes olyan fragment-et ami kívül esik a látómezőn, ezzel felgyorsítva a további folyamatokat.

A fragment shader fő célja, hogy minden egyes pixelhez kiszámolja a végleges színt, továbbá itt történik a legtöbb körszerű OpenGL effekt. A fragment shader adatokat tartalmaz a 3D scene-ről, hogy ki számolja a fényeket, az árnyékokat és az egyes fényviszonyokhoz tartozó színeket az adott anyag tulajdonság függvényeiben.

Miután a fragment shader meghatározta a színeket, a shader kimenete még keresztül mehet néhány állapoton. Ilyen az alpha test és a blending. Itt ellenőrízhetjük az adott fragment-ek mélységét és attól függően, hogy egy fragment takarásban van e más objektumok által eliminálhatjuk a megjelenítésből. Továbbá az alpha érték segítségével tudjuk vizsgélni egy adott fragment áttetszőségének a mértékét és ettől függően változtatjuk a színét, figyelembe véve a mögötte lévő objektum színét.

A modern OpenGL-ben az alkalmazás működéséhez mindenképpen szükséges legalább egy vertex és egy fragment shader-t definiálnunk. A geometry shader opcionális.


Vertex input

Az OpenGL egy 3D grafikus library, ezért 3D koordinátákat kell használnuk, továbbá csak azokat a koordinátákat veszi figyelembe, melyeknek mind az x, y és z értékei -1 és 1 között vannak. Az ezen kívüli koordináták nem fognak a képernyőn megjelenni. A -1 és 1 közötti koordinátákat normalized device koordinátáknak nevezzük.

Ha egy háromszöget szeretnénk rajzolni, akkor 3db 3D vertex-et kell definiálnunk, melyek normalized device koordinátákkal adottak:

In [2]:
float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

Mivel mi most egy 2D háromszöget szeretnénk rajzolni, ezért a vertex-ek z koordinátáit 0.0 értékre állítjuk.

title

Az OpenGL koordináta rendszere közepe a (0, 0, 0) pont, ahol az x, y és z koordináták -1 és 1 között értelmezettek. A fenti ábra az átláthatóság miatt nem jeleníti meg a z tengelyt, ami a mélységet reprezentálja. A glViewport függvény ebből a 3D koordináta rendszerből képez le a képernyő pixel koordinátáira, melyek az adott window szélesség és magasság tartományán belül értelmezettek.
A definiált vertex adat (háromszög csúcsai jelen esetben) lesz a grafikus pipeline bemeneti adata, melyet a vertex shader fog fogadni. Ehhez először megfelelő méretű memóriát kell foglalnuk a GPU memóriában, ahol a vertex adatot fogjuk tárolni, majd meg kell mondanunk az OpenGL-nek, hogy hogyan értelmezze a memóriát és hogy hogyan küldje azt a GPU-nak. Végül a vertex shader a grafikus pipeline első lépéseként feldolgozza a vertex adatokat.

Ezeket a memória műveleteket egy vertex buffer objects (VBO)-en keresztül tudjuk kezelni. A VBO nagy számú vertex-et tud tárolni a GPU memóriájában. A VBO előnye, hogy nagy mennyiségű adatot tudunk egyszerre a GPU memóriájába küldeni és ott tárolni, anélkül, hogy folyamatosan kis mennyiségű adatot (egy db vertex-et) kellene küldenünk. A CPU-ról a GPU-ra adatot küldeni lassú (költséges) folyamat, ezért egy lépésben annyi adatot küldünk, amennyit csak lehet, mert miután az adat a GPU memóriájába került, onnan a vertex shader gyakorlatilag instant eléri és ez extrém módon felgyorsítja a további feldolgozást.

Ahogy minden OpenGL objektum esetén a VBO buffer objektumhoz is tartozik egy egyedi id, ami az adott buffer objektumot hivatkozza. A glGenBuffers függvény segítségével generálhatunk egy buffert egy egyedi id-val.

In [ ]:
unsigned int VBO;
glGenBuffers(1, &VBO);
Az OpenGL számos beépített buffer objektum típussal rendelkezik és a vertex buffer az egy GL_ARRAY_BUFFER-nek felel meg. Az OpenGL lehetővé teszi, hogy egyszerre számos buffer objektumot használjunk (bind), amíg az egyes buffer objektumok különböző buffer objektum típussal rendelkeznek.

A létrehozott VBO buffer objektumot a glBindBuffer függvénnyel bind-olhatjuk, mint egy GL_ARRAY_BUFFER target.
Biding: Egy egyedi azonosítóval rendelkező OpenGL objektumot a context egy cél objektumához rendelünk.
In [ ]:
glBindBuffer(GL_ARRAY_BUFFER, VBO);

A glBufferData függvénnyel másolhatjuk a vertex adatot a buffer memóriájába:

In [ ]:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
A függvény első paramétere határozza meg a típusát a buffernek, amibe az adatot másoljuk, a második argument a másolandó adat mérete byte-ban, amit a bufferbe szeretnénk tölteni, a harmadik paraméter pedig az aktuális adat, amit a buffer-be szeretnénk küldeni.

A negyedik paraméter határozza meg, hogy a GPU hogyan kezelje az adatot, mely a következő három módon történhet:
  • GL_STATIC_DRAW: az adat nem vagy csak nagyon ritkán változik.
  • GL_DYNAMIC_DRAW: az adat sokat változik.
  • GL_STREAM_DRAW: az adat folyamatosan változik.
A mi mostani példánkban a háromszög pozíciója nem fog változni, ezért a GL_STATIC_DRAW verziót használjuk. Most már a GPU memóriájában tároljuk az adatot, melyet egy VBO nevű vertex buffer objektumon keresztül érünk el. A következő lépésben egy vertex és egy fragment shader objektumot hozunk létre, amik az adat tényleges feldolgozását fogják elvégezni.

Vertex shader

Ha renderelni szeretnénk valamit az OpenGL-el, akkor legalább egy vertex és egy fragment shader-t létre kell hoznunk. Most egy hármoszög rajzolásához szükséges egyszerű példát nézünk meg, majd a későbbi tutorialok során egyre jobban belemegyünk a részletekbe.

Első lépésben a GLSL nyelv segítségével megírunk egy egyszerű vertex shader-t, majd lefordítjuk (compile), hogy használni tudjuk az applikációban. A vertex shader kód:

In [ ]:
#version 400 core
layout (location = 0) in vec3 _pos;

void main() {
    gl_Position = vec4(_pos.x, _pos.y, _pos.z, 1.0);
}

Minden shader az OpenGL verzióval kezdődik és azt is kimondjuk explicit, hogy a core profile-t szeretnénk használni. Utána az in kulcsszó segítségével deklarálunk minden bemeneti (input) vertex attribútumot. Most csak egy vertex attribútumot hozunk létre, ami a háromszög csúcsainak a pozícióját fogja fogadni. Továbbá a layout kulcsszó segítségével beállítjuk az input változó "lokációját", melyről később lesz szó. Az input változó típusa vec3, de a gl_Position egy vec4 típusú vektort vár, ahol a negyedik attribútum a perspective division. A későbbi tutoriálokban erről is lesz szó részletesen.

A vertex shader kimenetét (output) a gl_Position beépített OpenGL változón keresztül állíthatjuk be, azaz a gl_Position-nak értékül adjuk a bemeneti vec3 változót, melyet vec4 típusú vektorrá bővítünk.

Ebben az egyszerű vertex shaderben egyszerűen csak tovább adjuk a bemenetet a pipeline további részeinek, anélkül, hogy bármilyen műveletet elvégeznénk a bemeneten. Viszont ha a bemeneti koordináták nem normalizáltak, akkor transzformálnunk kell a bemeneti koordinátákat az OpenGL megfelelő koordináta rendszerébe és utána állítjuk be a shader kimenetét.


A shader fordítása (compile)

A megírt vertex shader forrás kódját tárolhatjuk egy C string-ként magában a fő program forrás kódjában, de be is olvashatjuk egy külső fájlból. Viszont ahhoz, hogy használhassuk az OpenGL-ben dinamikusan kell fordítanunk futás időben. Ehhez először létre kell hoznunk egy shader objektumot, melyre szintén egy objektum id-val fogunk hivatkozni. A glCreateShader függvény segítségével hozhatjuk létre a shader objektumot:
In [ ]:
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);

A GL_VERTEX_SHADER enum segítségével beállítjuk, hogy a glCreateShader egy vertex shader típusú objektumot hozzon létre. A következő lépésben csatoljuk a shader forrás kódját a shader objektumot és lefordítjuk:

In [ ]:
glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
glCompileShader(vertexShader);
A glShaderSource függvénynek első paramétereként a shader objektumot kel megadni, a második paraméter jelzi, hogy a forráskódot hány string-ben adjuk át, a harmadik paraméter pedig maga a string, ami tárolja a forrás kódot. A glGetShaderiv függvény ellenőrzi, hogy a shader fordítása sikeres volt e. Ha a fordítás sikertelen, akkor a glGetShaderInfoLog függvénnyel lekérdezhetjük a hiba okát:
In [ ]:
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);

if(!success) {
    glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
    std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}

Fragment shader

A háromszög rendereléséhez egy fragment shader-t is létre kell hoznunk, mely a színt fogja hozzárendelni a pixel értékekhez. Jelen példában egy kék színű háromszöget fogunk rajzolni. Az OpenGL-ben egy színt négy érték ír le: a három RGB értéken kívül az objektum átlátszóságát is be kell állítanunk. A fragment shader kódja a következő:
In [ ]:
#version 400 core
out vec4 FragColor;

void main() {
    FragColor = vec4(0.0f, 0.0f, 1.0f, 1.0f);
}
A shader elején az out kulcsszó segítségével definiálunk egy kimeneti változót, mely a színt fogja tárolni. Most egyszerűen csak kékre állítjuk és beállítjuk, hogy egyáltalán ne legyen átlátszó. Bonyolult esetekben ezt a színt számoljuk a shader elején a megvilágítás, anyagfelület és egyéb tulajdonságok alapján.

A fragment shader fordítási folyamata hasonló a vertex shader-éhez, de a típust most GL_FRAGMENT_SHADER-re állítjuk:

In [ ]:
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShader);

Most, hogy mind a vertex, mind a fragment shader-t lefordítottuk, a keletkezett shader objektumot össze kell linkelni egy shader program-má, melyet a rendereléshez használhatunk.


Shader program

Ahhoz, hogy a shader objektumokat használni tudjuk, először hozzá kell őket linkelni a shader program objektumhoz, majd amikor renderelni szeretnénk aktiváljuk a shader programot. Amikor a shader-eket egy program objektumhoz kötjük (link), akkor minden shader kimenetét (output) a pipeline-ban következő shader bemenetéhez (input) kötjük.

Egy program objektumot a következő képpen hozunk létre:

In [ ]:
unsigned int shaderProgram;
shaderProgram = glCreateProgram();

A következő lépésben a glAttachShader függvénnyel hozzácsatoljuk shader objektumokat a program objektumhoz, majd a glLinkProgram-mal össze linkeljük őket:

In [ ]:
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

Majd megvizsgáljuk, hogy sikeres volt e a linkelés:

In [ ]:
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success) {
    glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}

Ha renderelni szeretnénk valamit, akkor a glUseProgram segítségével aktiváljuk a megfelelő program objektumot (azaz a megfelelő shader-eket):

In [ ]:
glUseProgram(shaderProgram);

Miután egy programhoz linkeltük a shader objektumokat, utána töröljük őket, mert már nincs rájuk szükségünk:

In [ ]:
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
A vertex adatot feltöltöttük a GPU memóriájába és meghatároztuk, hogy a GPU hogyan dolgozza fel a vertex és a fragment shader-eken keresztül. Következő lépésben definiálnunk kell, hogy az OpenGL hogyan értelmezze a GPU memóriában tárolt adatokat és, hogy hogyan küldje azokat a shaderek megfelelő bemeneti attribútumjainak.

Vertex attribútumok összekötése

A vertex shader-ben számos bemeneti attribútumot definiálhatunk, ami nagy rugalmasságot biztosít, viszont manuálisan kell megmondanunk, hogy az input adat mely része mely attribútumhoz tartozzon.

Jelenlegi példánkban (háromszög) a vertex buffer adat a következő képpen néz ki:

title

  • A háromszög csúcsának a pozíció adatai float típusúak, azaz 32 bit-en (4 byte) tárolódnak.
  • Minden csúcs 3 float típusú attribútumot tárol
  • Egy tömbben (array) tároljuk ezeket a csúcs pontokat, ezért a memóriában egymás mellett helyezkednek el.

A fenti tudás ismeretében a glVertexAttribPointer függvényen keresztül definiálhatjuk az OpenGL-nek, hogy hogyan értelmezze az adatot:

In [ ]:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

A glVertexAttribPointer függvény paraméterei:

  • Az első paraméter határozza meg, hogy melyik vertex attribútot konfiguráljuk be. A vertex shader-ben a bemeneti (in) _pos attribútumot a layout (location = 0) értékre állítottuk, tehát az első paraméter ezért 0.
  • A második paraméter az adott attribútum mérete. (vec3)
  • A harmadik paraméter a típust határozza meg. (A vec3 float típusokat tartalmaz.)
  • A következő paraméterrel lehet állítani, hogy szeretnénk e normalizálni az adatot.
  • Az ötödik paraméter megadja az eltolást (stride) az egymást követő háromszög csúcsok között. (Egy vec3 3 db float értéket tartalmaz.)
  • Az utolsó paraméter határozza meg, hogy a _pos data hova kerül a bufferbe (offset).

Azt, hogy a GPU memóriából az adat, hogyan kerül a megfelelő vertex shader attribútumba a VBO szabályozza. De mivel több VBO is lehet ezért a megfelelőt VBO-t hozzá kell adni (bind) a glVertexAttribPointer-hez.

A következő lépésben a glEnableVertexAttribArray függvénnyel engedélyeznünk kell a vertex attribútumot a lokációjának átadásával:

In [ ]:
// 0. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. then set the vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);  
// 2. use our shader program when we want to render an object
glUseProgram(shaderProgram);
// 3. now draw the object 
someOpenGLFunctionThatDrawsOurTriangle();
Ezt minden egyes rajzolásnál meg kell ismételnünk, de ha pl. van 5 vertex attribútumunk és több száz objektumunk akkor nagyon macerás lenne mindig a megfelelő vertex buffer objektumot betölteni (bind) majd az attribútumait felkonfigurálni. Ezért ezeket az állapot konfigurációkat eltároljuk egy objektumban és az adott vertex buffer objektum betöltésekor a megfelelő állapotot csak egyszerűen betöltjük.

Vertex array objektum

A vertex array objektum-ot (VAO) ugyan úgy aktiválhatjuk (bind) mint a VBO-t és az egyes vertex attribútumok konfigurációját tároljuk benne. Így a vertex attribótum pointer-eket csak egyszer kell beállítani, majd amikor használni akarjuk őket csak betöltjük a megfelelő VAO-t. A Core OpenGL megköveteli, hogy definiáljunk egy VAO-t ami tartalmazza, hogy az egyes vertex input-okkal mit kell csinálni.

A VAO a következő dolgokat tárolja:

  • A glVertexAttribPointer-en keresztül tárolja a vertex attribútum konfigurációkat.
  • Meghívhatjuk rajta keresztül a glEnableVertexAttribArray és a glDisableVertexAttribArray függvényeket.

title

A VAO létrehozása nagyon hasonló a VBO létrehozásához:

In [ ]:
unsigned int VAO;
glGenVertexArrays(1, &VAO);

A glBindVertexArray függvénnyel aktiváljuk (bind) a VAO-t, majd be kell töltenünk és konfigurálnunk a VBO-t és az attribútum pointer-t majd deaktiváljuk (unbind) a VBO-t és a VAO-t. Amikor rajzolni szeretnénk csak újra betöltjük (bind) a megfelelő VAO-t:

In [ ]:
// ..:: Initialization code (done once (unless your object frequently changes)) :: ..
// 1. bind Vertex Array Object
glBindVertexArray(VAO);
// 2. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. then set our vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);  

// the call to glVertexAttribPointer registered VBO as the vertex attribute's 
// bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 0); 

// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. 
// VAOs requires a call to glBindVertexArray.
glBindVertexArray(0); 

// ..:: Drawing code (in render loop) :: ..
// 4. draw the object
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();

A VAO tárolja a vertex attribútumok konfigurációját és hogy melyik VBO-t kell használni. Ha több objektumot szeretnénk rajzolni, először lőtrehozzuk az összes VAO-t és a megfelelő VBO-kat, majd mindig azt a VAO-t aktiváljuk amelyiket használni szeretnénk.

A glDrawArrays függvény a megfelelő primitíveket fogja rajzolni az aktív shader felhasználásával:

In [ ]:
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);

A glDrawArrays első paraméterében meghatározzuk a primitív típusát amit rajzolni szeretnénk, majd a második paraméter meghatározza a vertex array kezdő indexét, végül az utolsó paraméter, hogy hány vertex-et szeretnénk rajzolni.

A háromszöget rajzoló forráskód: Code

title


Element buffer objektum

Az element buffer objektum (EBO) szerepét egy példán keresztül mutatjuk be, amiben egy négyszöget fogunk rajzolni. Egy négyszöget kirajzolhatunk két háromszög rajzolásával:

In [ ]:
float vertices[] = {
    // first triangle
     0.5f,  0.5f, 0.0f,  // top right
     0.5f, -0.5f, 0.0f,  // bottom right
    -0.5f,  0.5f, 0.0f,  // top left 
    // second triangle
     0.5f, -0.5f, 0.0f,  // bottom right
    -0.5f, -0.5f, 0.0f,  // bottom left
    -0.5f,  0.5f, 0.0f   // top left
};
Látható, hogy a definiált csúcsok között van átfedés (bottom rifht ugyanaz, mint top left). Ha például egy olyan alakzatot szeretnénk definiálni, ami több ezer háromszögre bontható, akkor ezek a többször definiált csúcsok nagy mértékben növelik a redundanciát és a felesleges műveleteket. A megoldás, hogy csak az egyedi csúcsokat definiáljuk és meghatározzuk, hogy milyen sorrendben szeretnénk kirajzolni azokat. A négyszög esetén így csak négy csúcsot kell definiélnunk a hat helyett, továbbá meg kell adnunk a bejrásuk sorrendjét.

Az EBO egy buffer, amiben az OpenGL a megfelelő sorrendben tárolja az indexeit, a kirajzolandó csúcsoknak. Ezt a módszert index drawing-nak nevezzük. Először definiáljuk a négyszög egyedi (unique) csúcsait (vertex), majd a kirajzolandó indexeket:
In [ ]:
float vertices[] = {
     0.5f,  0.5f, 0.0f,  // top right
     0.5f, -0.5f, 0.0f,  // bottom right
    -0.5f, -0.5f, 0.0f,  // bottom left
    -0.5f,  0.5f, 0.0f   // top left 
};
unsigned int indices[] = {  // note that we start from 0!
    0, 1, 3,   // first triangle
    1, 2, 3    // second triangle
};

A következő lépésben létrehozzuk az EBO-t:

In [ ]:
unsigned int EBO;
glGenBuffers(1, &EBO);
Hasonlóan, mint a VBO esetén a aktiváljuk az (bind) EBO-t, majd a glBufferData függvény hívásával a bufferbe másoljuk az indexeket. Ahogy eddig is, a bind után elvégezzük a műveleteket, majd az unbind paranccsal deaktiváljuk az EBO-t, csak ebben az esetben a GL_ELEMENT_ARRAY_BUFFER konstanst használjuk:
In [ ]:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

Viszont most a glDrawArrays parancs helyett a glDrawElements parancsot kell használnunk, ami az index buffert használja a rajzoláshoz. A rajzolás előtt a megfelelő EBO-t aktiválnunk kell:

In [ ]:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

Ha sok mindent kell rajzolnunk, akkor ez is nehézges lehet, ezért a VAO az EBO konfigurációkat is tárolja. Így a rajzolásnál a megfelelő VAO-t kell betölteni, ami automatikusan be fogja tölteni a megfelelő EBO-t is.

title

A végső inicializáló és rajzoló kód a következő képpen néz ki:

In [ ]:
// ..:: Initialization code :: ..
// 1. bind Vertex Array Object
glBindVertexArray(VAO);
// 2. copy our vertices array in a vertex buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. copy our index array in a element buffer for OpenGL to use
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. then set the vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glBindBuffer(GL_ARRAY_BUFFER, 0); 

glBindVertexArray(0); 

// ..:: Drawing code (in render loop) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
glBindVertexArray(0);

A rajzolás eredménye, továbbá ha wireframe módban rajzolunk látható a két háromszög, ami a négyszöget definiálja:

title title

Ha wireframe módban szeretnék rajzolni, akkor a következő paranccsal engedélyezhetjük és, amíg vissza nem váltunk, az összes rajzolás ebbe a módba fog történni.

In [ ]:
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
A teljes forráskód: Code