Lekce 3 - 3D grafika v OpenGL a C++ - Základy vykreslování
V minulé lekci, 3D grafika v OpenGL a C++ - Vytvoření okna, jsme si otevřeli naše první okno. Pojďme si do něj něco vykreslit.
Vykreslování trojúhelníku
Konečně se dostáváme k samotnému vykreslování. OpenGL vidí všechny modely jako trojúhelníky, proto náš první model bude právě trojúhelník. OpenGL obsahuje mnoho pokročilých struktur (VAO, VBO, shadery atd...), proto je ze začátku i obyčejné vykreslení trojúhelníku složitá práce. Nakonec zjistíte, že je to naopak docela jednoduchá záležitost.
Shadery a dodatečný kód
Ještě před psaním kódu si stáhněte zdrojové kódy a přidejte si k projektu následující soubory:
NacitacShaderu.h
NacitacShaderu.cpp
bodovy_shader.vert
pixelovy_shader.frag
První dva soubory si rovnou přidáme do projektu skrz Visual
Studio a NacitacShaderu.h
naincludujeme do našeho souboru z
minulé lekce:
#include <GL/glew.h> #include <GLFW/glfw3.h> #include "NacitacShaderu.h" int main(void) { // kód z předchozí lekce }
Zbylé soubory .vert
a .frag
si přidejme někam k
našemu projektu, nejlépe ke knihovně glew32.dll
.
pozn.: Shadery nemají specifickou příponu souboru, proto si můžete
zvolit příponu libovolnou, klidně i .txt
. Silně ale doporučuji
shader soubory segregovat podle typu, aby s nimi bylo možné rozumně
pracovat.
OpenGL využívá při vykreslování tzv. shadery, což
jsou programy ovlivňující celý proces vykreslování. O shaderech si povíme
více v příští lekci. Prozatím nám ale stačí vědět, že shadery jsou
nutné pro vykreslování a že budeme používat 2 hlavní
typy shaderů, vrcholový (vertex) a
pixelový (fragment) shader. Implementace shaderů je v OpenGL
individuální, proto v seriálu budeme používat funkce z
NacitacShaderu
.
Body trojúhelníku
Přesuňme se nyní do funkce main()
pod inicializaci GLFW a
GLEW. Abychom vykreslili trojúhelník, potřebujeme k tomu souřadnice jeho
tří vrcholů. První bod bude vlevo dole, druhý bude
nahoře uprostřed a třetí bude vpravo
dole:
// inicializace GLEW a GLFW /* GLfloat je pouze standartní float, má ale speciální deklaraci, aby se OpenGL vyhnulo kompilačním problémům, takto fungují i jiné typy */ GLfloat pole_bodu_data[] = { -1.0f, -1.0f, 0.0f, // první bod 0.0f, 1.0f, 0.0f, // druhý bod 1.0f, -1.0f, 0.0f // třetí bod };
Body nám odpovídají souřadnicím v našem okně podle tvaru
X, Y, Z
. Souřadnici Z
si necháme na hodnotě
0.0f
, protože vykreslujeme 2D. Teoreticky bychom si ji mohli
vypustit, ale museli bychom upravit bodový shader.
Načítání shaderů
Pro použití shaderů si nejdříve musíme vytvořit shader program. To jsou již zkompilované shadery v jednom kusu, které se nachází na grafické kartě. Při vykreslování nám stačí zvolit daný shader program a všechny shadery k němu připojené se automaticky použijí.
Nejdříve si tedy vytvoříme náš shader program:
GLuint shader_program = glCreateProgram(); // vytvoříme si na GPU nový shader program
OpenGL nám vrátí (stejně jako u samotných shaderů a ostatních
konstrukcí) místo na GPU, kde se program nachází (nemůže nám vrátit
přímý ukazatel, protože GPU je jiné zařízení). Dále k
programu připojíme naše shadery, bodovy_shader
a
pixelovy_shader
, které rovnou zkompilujeme:
GLuint bodovy_shader = pripojShaderKProgramu("bodovy_shader.vert", GL_VERTEX_SHADER, shader_program); // přidáme k němu bodový shader GLuint pixelovy_shader = pripojShaderKProgramu("pixelovy_shader.frag", GL_FRAGMENT_SHADER, shader_program); // přidáme k němu pixelový shader
Jako poslední propojíme náš program s příslušným shaderovým procesorem a zkontrolujeme případné chyby:
bool uspech_linkovani = napojProgram(shader_program); // připojíme program k GPU // pokud kompilace nebo linkování shaderů selže, terminujeme program if (bodovy_shader == 0 || pixelovy_shader == 0 || !uspech_linkovani) { glfwTerminate(); return -1; }
Vertex Array Object a Vertex Buffer Object
Jak již bylo zmíněno, GPU (grafický procesor) je úplně jiné zařízení, proto s ním nelze pracovat stejně jako se CPU (procesorem) a RAM. Naše data musíme nejdříve předat našemu GPU do video-paměti.
Data se neskládají pouze z bodů modelu, ale také z barev nebo souřadnic textur. Určitě by bylo vhodné, kdyby byla všechna data pohromadě a všechno se vykreslilo jedním příkazem.
VBO neboli Vertex Buffer Object je struktura, která skladuje data nějakého typu. Mohou to být například body modelů (vrcholy), barvy nebo souřadnice textur.
VAO neboli Vertex Array Object je struktura, která skladuje VBO a jejich nastavení pohromadě. Můžeme k němu připojit třeba vrcholy našeho modelu a souřadnice textur (např. model keře i s jeho texturou), nastavit způsob čtení bufferů a napojit indexy shaderů. Poté stačí při vykreslování jen načíst náš VAO a všechno se vykreslí v jednom příkazu.
Jako první si vygenerujeme nové VAO a nastavíme ho jako aktivní:
GLuint VAO_trojuhelnik; // založíme si náš VAO pro trojúhelník glGenVertexArrays(1, &VAO_trojuhelnik); // řekneme OpenGL ať ho vygeneruje (chceme pouze jeden) glBindVertexArray(VAO_trojuhelnik); // nastavíme ho jako aktivní VAO
Nyní si můžeme vytvořit VBO, do kterého budeme kopírovat vrcholy trojúhelníku:
GLuint VBO_vrcholy; // založíme si náš VBO pro vrcholy trojúhelníku glGenBuffers(1, &VBO_vrcholy); // řekneme OpenGL ať ho vygeneruje (chceme pouze jeden) glBindBuffer(GL_ARRAY_BUFFER, VBO_vrcholy); // připojíme ho k aktuálnímu VAO (tedy našemu trojúhelníku) glBufferData(GL_ARRAY_BUFFER, sizeof(pole_bodu_data), pole_bodu_data, GL_STATIC_DRAW); // překopírování dat vrcholů do našeho VBO
Před kopírováním dat nesmíme zapomenout na napojení VBO k aktuálnímu VAO. Po napojení VBO můžeme data úspěšně kopírovat. Nakonec už jenom potřebujeme nastavit formát dat:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); // nastavíme správný formát dat, aby OpenGL vědělo, jak tento buffer číst glEnableVertexAttribArray(0); // umožníme našemu shaderu používat data na indexu 0
Jako první hodnotu nastavíme index, na kterém se
nachází buffer s vrcholy. Pro nás to bude index 0 (nemusí být nutně 0, ale
musí se shodovat s indexem v bodovy_shader.vert). Data jsou typu
float
, normalizovat nechceme a offset/skok je 0. Následně už
musíme náš index pouze zapnout.
Pro bezpečnostní účely je dobrý postup všechny aktivní struktury odbindovat, abychom se vyhnuli chybám:
// pro bezpečnost musíme přepnout aktivní VBO a VAO na nulovou hodnotu glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0);
Všechny operace, které jsme doposud provedli (založení VAO/VBO, načtení dat, kompilace shaderů, nastavení formátu dat...) patří do inicializační části programu a stačí je provést pouze jednou (pokud je v průběhu nebudeme upravovat). Při jejich dalším použití je již stačí zvolit jako aktivní struktury a vše se vykreslí pomocí jednoho příkazu. Bez použití VAO by se všechna nastavení musela aplikovat při každém vykreslovacím cyklu, což by bylo výpočtově neefektivní.
Vykreslení bodů
Přesuňme se nyní do naší smyčky, kam přidáme trochu vykreslovacího kódu:
while (!glfwWindowShouldClose(okno)) { glClearColor(0.0, 0.1, 0.0, 1.0); // nastavení barvy pozadí glClear(GL_COLOR_BUFFER_BIT); // vyčištění okna glUseProgram(shader_program); // nastavení našeho shader programu jako aktivního glBindVertexArray(VAO_trojuhelnik); // nastavení našeho VAO jako aktivního glDrawArrays(GL_TRIANGLES, 0, sizeof(pole_bodu_data) / (sizeof(float) * 3)); // vykreslení našich bodů // bezpečnostní "odbindování" našich shaderů a našeho trojúhelníku glBindVertexArray(0); glUseProgram(0); glfwSwapBuffers(okno); // OpenGL vymění buffery glfwPollEvents(); // získání všech eventů (stisk klávesy atd.) }
Nejdříve nastavíme náš shader program a VAO jako aktivní a spustíme vykreslovací funkci. Ta má nastaveno, že vykresluje trojúhelníky, začíná na offsetu 0 a vykresluje 3 vrcholy (vypočítali jsme z velikosti dat). Zde můžete vidět, že nám pouze stačí nabindovat náš VAO a všechna jeho data/nastavení se při vykreslování použijí. Nakonec už pouze odbindujeme aktivní struktury.
Při ukončení nesmíme zapomenout na vyčištění aktuálně používaných zdrojů na GPU:
// vyčištění zdrojů a terminování GLFW glDeleteShader(bodovy_shader); glDeleteShader(pixelovy_shader); glDeleteProgram(shader_program); glDeleteBuffers(1, &VBO_vrcholy); glDeleteVertexArrays(1, &VAO_trojuhelnik); glfwTerminate(); return 0;
V našem případě bychom to mohli vynechat, protože při ukončení programu se všechno automaticky vyčistí. U větších projektů by to ovšem činilo veliký problém.
Konečný výstup programu by mohl vypadat nějak takto:
Pokud vám něco nefungovalo, zdrojové kódy jsou níže ke stažení.
V další lekci, 3D grafika v OpenGL a C++ - Shadery, se podíváme blíže na shadery a přidáme našemu trojúhelníku barvy za běhu programu.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 33x (4.77 kB)
Aplikace je včetně zdrojových kódů v jazyce C++