NOVINKA! E-learningové kurzy umělé inteligence. Nyní AI za nejlepší ceny. Zjisti více:
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

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:

První trojúhelník v OpenGL - OpenGL - 3D grafika v C++

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++

 

Předchozí článek
3D grafika v OpenGL a C++ - Vytvoření okna
Všechny články v sekci
OpenGL - 3D grafika v C++
Přeskočit článek
(nedoporučujeme)
3D grafika v OpenGL a C++ - Shadery
Článek pro vás napsal Richard Bleier
Avatar
Uživatelské hodnocení:
2 hlasů
Aktivity