Lekce 37 - Cel-Shading

Cel-Shading je druh vykreslování, při kterém výsledné modely vypadají jako ručně kreslené karikatury z komiksů (cartoons). Rozličné efekty mohou být dosaženy miniaturní modifikací zdrojového kódu. Cel-Shading je velmi úspěšným druhem renderingu, který dokáže kompletně změnit duch hry. Ne ale vždy... musí se umět a použít s rozmyslem.

Článek o teorii Cel-Shadingu napsal Sami "MENTAL" Hamlaoui a umístil ho na GameDev.net. Poté, co byl jeho článek publikován, zavalili čtenáři Samiho emaily, ve kterých se dotazovali po zdrojovém kódu. Napsal tedy další článek, tentokrát pro NeHe, který už ale popisuje pouze zdrojový kód. Tato česká verze je složena z obou článků - teoretického i praktického.

Teoretická část

Předtím než půjdete dál, měli byste mít dostatečné znalosti z následujících oblastí:

Pokud něčemu z těchto čtyř položek nerozumíte, neznamená to, že byste nutně neporozuměli Cel-Shadingu, ale určitě budete mít obrovské potíže s psaním vlastních programů.

Základní rendering

Začneme opravdu jednoduchými věcmi. Žádná světla, žádné obrysy, pouze ploché cartoon modely. Budeme potřebovat jenom dva druhy dat - pozici a barvu každého vertexu. Před kreslením vždy vypneme osvětlení a blending. Co by se stalo? Při zapnutých světlech by objekty vypadaly normálně. Nedosáhli bychom plochého cartoon efektu. Blending vypínáme, aby se jednotlivé vertexy nesmíchaly s ostatními.

Shrnuto

Základní osvětlení (směrové)

Každý vertex bude potřebovat i další data. Kromě původní pozice a barvy budeme používat i normálový vektor a intenzitu osvětlení (jedna float hodnota). Tyto nové proměnné použijeme pro renderování se základním osvětlením.

Světelné mapy (lighting maps)

Nechci vás poplést, pod lightmapami si nepředstavujte simulaci světel na objektech typu Quake 1 a Quake 2. Podívejte se na stěny, abyste pochopili, co mám na mysli. Nepředstavujte si oblasti, které jsou osvětleny/ztmaveny specifickými místy map. To, co budeme používat zde, je kompletně novou formou lightmap - 1D textury.

Zkuste si najít nějakou animaci (Cartoon Network je vždy dobrým zdrojem) a podívejte se na osvětlení postav. Všimli jste si, že nejsou hladké jako v reálném životě? Světlo se rozděluje do jednotlivých plošek. Nikdy jsem neslyšel žádný termín nebo pojmenování pro tento efekt, takže mu budeme říkat Sharp lighting. Abychom ho vytvořili potřebujeme definovat 1D texturu, která bude ukládat požadované hodnoty.

1D textura

Toto je textura 1x16 pixelů (velmi zvětšená). Používáme hodnoty stupňů šedi, protože budou zkombinovány s barvou vertexu. Můžete si všimnout, že v lightmapě jsou pouze 3 barvy, které mají podobnou intenzitu, jaká se používá v animovaných filmech. Díky tomu, že používáme velikost právě 16 pixelů, můžeme snadno modifikovat hodnoty, abychom vytvořili rozličné efekty. Pokud chcete, můžete také použít obyčejnou černobílou texturu, ale nedoporučuje se to. Nikdy byste neměli použít 100% černou, protože tato barva vytváří vyzdvižení a okraje, které vypadají dost špatně.

Jakmile máte vytvořenu svou texturu, nahrajte ji do API, které používáte (OpenGL, DX, software). Vrátíme se k ní za chvíli.

Počítání osvětlení

Teď přijdou vhod znalosti ohledně softwarového osvětlení. Pokusím se vše vysvětlit jednoduchým jazykem. Vždy se ujistěte, že máte normalizovaný směrový vektor světla! Vše, co potřebujeme udělat, je spočítání skalárního součinu vektoru světla s normálou vertexu.

Skalární součin je matematická funkce, která spočítá úhel mezi dvěma vektory a vrátí ho jako kosinus úhlu. Invertujeme-li kosinus získáme úhel. Namísto kosinu však považujte číslo za texturovací koordinátu. Texturovací koordináty jsou čísla od nuly do jedné. Kosinus je sice v rozmezí -1 až 1, ale pokud bude číslo záporné můžeme mu přiřadit nulu. Skalární součin vektoru světla a normály vertexu můžeme tedy považovat za texturovací koordináty!

Rendering objektu

Nyní máme texturovací koordináty každého vertexu, je čas vykreslit objekt. Stejně jako minule vypneme světla i blending, ale zapneme 1D texturování. Vykreslíme objekt stejně jako minule, ale před tím, než umístíme vertex, specifikujeme texturové koordináty (simulace světla).

Shrnuto

Umístitelná světla

Tato metoda je pouhou modifikací minulého postupu. Umístitelné světlo nabízí mnohem více flexibility než směrové osvětlení, protože může být libovolně posunováno po scéně. Dynamicky osvětlované polygony jsou více realistické, ale použitá matematika je delší. Ne komplikovanější, pouze delší.

Spočítání Sharp koordinátů světla

U směrového osvětlení jsme potřebovali získat skalární součin směrového vektoru světla s normálou vertexu. Nyní, protože umístitelná světla nemají směrový vektor (emitují světlo do všech směrů), bude mít každý vertex svůj paprsek, který září skrz něj. Nejdříve potřebujeme určit vektor směřující z pozice světelného zdroje k pozici vertexu. Normalizujeme ho, takže bude mít jednotkovou délku. tím jsme získali směr světla k vertexu. Vypočítáme skalární součin mezi vektorem světla a normálou vertexu. Vše opakujeme pro každý vertex ve scéně. Těmito nadbytečnými výpočty se však sníží FPS. Pojďme se podívat na rychlejší metodu, která redukuje celkový počet osvětlených vertexů.

Testování vzdálenosti od světla

Ke snížení počtu osvětlených vertexů přiřadíme každému světlu poloměr kam paprsky dosahují. Před počítáním hodnot osvětlení (viz. výše) zkontrolujeme, jestli je vertex v kouli, určené poloměrem světla. Pokud ano, aplikujeme na něj světla. Je to základní detekce kolizí s koulí, na kterou existují spousty článků a tutoriálů.

Rendering

Objekt vykreslíme stejně jako u směrového osvětlení. Specifikujeme barvu, texturovací koordináty a pozici.

Shrnuto

Obrysy a zvýraznění

Obrysy a zvýraznění jsou tenké černé linky reprezentující tahy tužkou, které zdůrazňují okraje. Můžou znepokojit, ale jejich vytvoření je mnohem jednodušší než si myslíte.

Výpočet kde zvýrazňovat

Pravidlo je jednoduché: vykreslit linku na okraji, který má jeden přivrácený a jeden odvrácený polygon. Zní to hloupě, ale zkuste se podívat například na klávesnici. Všimli jste si, že nemůžete vidět zadní části kláves? To proto, že jsou odvrácené. Na rozhraní vykreslíme čáru, abychom zvýraznili, že tam je okraj.

Možná, že si to ani neuvědomujete, ale nikde jsem se nezmínil o našem vlastním cullingu polygonů. To proto, že vše za nás udělá API, ve kterém programujeme.

Rendering zvýraznění

Klasicky vykreslíme objekt a pak nastavíme šířku čáry na dva až tři pixely. Můžeme také zapnout antialiasing. Změníme mód cullingu, aby odstraňoval přivrácené polygony. Přepneme do drátěného modelu, takže se budou vykreslovat pouze okrajové hrany polygonů. Vykreslíme je, ale nepotřebujeme specifikovat barvu a texturovací koordináty. Tím vykreslíme drátěný model objektu z relativně širokých linek. Nicméně... cullingem jsou linky přivrácených polygonů odstraněny a depth bufferem se vyřadí všechny linky, které jsou hlouběji než přivrácené (tedy ty zadní). Zdálo by se, že tedy nevykreslíme nic. Ale díky šířce čáry zasahují linky okrajových polygonů až za okraje objektu. Právě ty se vykreslí. Z toho plyne, že tato metoda nebude pracovat při tloušťce čáry nastavené na jeden pixel.

Shrnuto

To je z teorie asi všechno. Nyní se ji pokusíme převést do praxe.

Praktická část

Na začátku bych se chtěl omluvit za volbu použitého modelu, ale v poslední době si hodně hraji s Quake 2...

#include <windows.h>// Hlavičkový soubor pro Windows

#include <gl\gl.h>// Hlavičkový soubor pro OpenGL32 knihovnu

#include <gl\glu.h>// Hlavičkový soubor pro Glu32 knihovnu

#include <gl\glaux.h>// Hlavičkový soubor pro Glaux knihovnu

#include <math.h>// Hlavičkový soubor pro matematickou knihovnu

#include <stdio.h>// Hlavičkový soubor pro standardní vstup/výstup

#include "NeHeGL.h"// Hlavičkový soubor pro NeHeGL

Nadefinujeme pár struktur, které nám pomohou při ukládání dat. První z těchto struktur je tagMATRIX. Pokud se na ni podíváte, zjistíte, že ukládá matici jako jednorozměrné pole 16ti floatů místo toho, aby to bylo dvourozměrné pole 4x4. To je proto, že OpenGL pracuje taky s jednorozměrným polem. Pokud bychom použili 4x4, hodnoty by byly ve špatném pořadí.

typedef struct tagMATRIX// Ukládá OpenGL matici

{

float Data[16];// Matice ve formátu OpenGL

}

MATRIX;

Další strukturou je vektor, který ukládá jednotlivé x, y, z složky na osách.

typedef struct tagVECTOR// Struktura vektoru

{

float X, Y, Z;// Složky vektoru

}

VECTOR;

Třetí je vertexová struktura. Každý vertex se bude skládat z pozice a normály (žádné texturovací koordináty). Složky struktury musí být v uvedeném pořadí, jinak se při loadování stane něco OPRAVDU strašného (sám jsem kvůli tomu rozsekal celý tento kód, abych našel chybu).

typedef struct tagVERTEX// Struktura vertexu

{

VECTOR Nor;// Normála vertexu

VECTOR Pos;// Pozice vertexu

}

VERTEX;

Nakonec struktura polygonu. Vím, že toto není nejlepší způsob, jak ukládat vertexy, ale pro jednoduchost to stačí. Normálně bych použil pole vertexů a pole polygonů obsahujících indexy vertexů tvořících polygon, ale my to uděláme jinak - vše pro jednoduchost.

typedef struct tagPOLYGON// Struktura polygonu

{

VERTEX Verts[3];// Pole tří vertexů

}

POLYGON;

Nádherně jednoduchá část kódu. Přečtěte si komentář ke každé proměnné a budete vědět, proč jsme ji deklarovali.

bool outlineDraw = true;// Flag pro vykreslování obrysu

bool outlineSmooth = false;// Flag pro vyhlazování čar

float outlineColor[3] = { 0.0f, 0.0f, 0.0f };// Barva čar

float outlineWidth = 3.0f;// Tloušťka čar

VECTOR lightAngle;// Směr světla

bool lightRotate = false;// Flag oznamující zda rotujeme světlem

float modelAngle = 0.0f;// Úhel natočení objektu na ose y

bool modelRotate = false;// Flag na otáčení modelem

POLYGON* polyData = NULL;// Data polygonů

int polyNum = 0;// Počet polygonů

GLuint shaderTexture[1];// Místo pro jednu texturu

Model je uložen úplně nejjednodušším způsobem. Prvních pár bajtů obsahuje počet polygonů tvořících objekt a zbytek souboru je pole tagPOLYGON struktur. Proto může následující funkce data přímo načíst bez jakéhokoliv dalšího upravování.

BOOL ReadMesh()// Načte obsah souboru model.txt

{

FILE *In = fopen("Data\\model.txt", "rb");// Otevře soubor

if (!In)// Kontrola chyby otevření

return FALSE;

fread(&polyNum, sizeof(int), 1, In);// Načte hlavičku souboru (počet vertexů)

polyData = new POLYGON[polyNum];// Alokace paměti

fread(&polyData[0], sizeof(POLYGON) * polyNum, 1, In);// Načte všechna data

fclose(In);// Zavře soubor

return TRUE;// Loading objektu úspěšný

}

Funkce DotProduct() spočítá úhel mezi dvěma vektory nebo rovinami. Funkce Magnitude() spočítá délku vektoru a funkce Normalize() upraví vektor na jednotkovou délku.

inline float DotProduct(VECTOR &V1, VECTOR &V2)// Spočítá odchylku dvou vektorů

{

return V1.X * V2.X + V1.Y * V2.Y + V1.Z * V2.Z;// Vrátí úhel

}

inline float Magnitude(VECTOR &V)// Spočítá délku vektoru

{

return sqrtf(V.X * V.X + V.Y * V.Y + V.Z * V.Z);// Vrátí délku vektoru

}

void Normalize(VECTOR &V)// Vytvoří jednotkový vektor

{

float M = Magnitude(V);// Spočítá aktuální délku vektoru

if (M != 0.0f)// Proti dělení nulou

{

V.X /= M;// Normalizování jednotlivých složek

V.Y /= M;

V.Z /= M;

}

}

Funkce RotateVector() pootočí vektor podle zadané matice. Všimněte si, že vektor pouze otočí, ale už nic nedělá s jeho pozicí. Funkce se používá pro otáčení normál, aby zajistila, že normály budou při počítání osvětlení ukazovat správným směrem.

void RotateVector(MATRIX &M, VECTOR &V, VECTOR &D)// Rotace vektoru podle zadané matice

{

D.X = (M.Data[0] * V.X) + (M.Data[4] * V.Y) + (M.Data[8] * V.Z);// Otočení na x

D.Y = (M.Data[1] * V.X) + (M.Data[5] * V.Y) + (M.Data[9] * V.Z);// Otočení na y

D.Z = (M.Data[2] * V.X) + (M.Data[6] * V.Y) + (M.Data[10] * V.Z);// Otočení na z

}

První významnější funkcí tohoto enginu je Initialize(), která provádí to, co je z jejího názvu zjevné - inicializaci.

BOOL Initialize (GL_Window* window, Keys* keys)// Uživatelská a OpenGL inicializace

{

int i;// Řídící proměnná cyklů

Následující 3 proměnné jsou použity pro načtení shader souboru. Line obsahuje jeden řádek řetězce a pole shaderData ukládá hodnoty pro shading. Používáme 96 hodnot namísto 32, protože potřebujeme převést stupně šedi na hodnoty RGB, aby s nimi mohlo OpenGL pracovat. Můžeme sice hodnoty uložit jako stupně šedi, ale bude jednodušší když při nahrávání textury použijeme stejné hodnoty pro jednotlivé složky RGB.

char Line[255];// Pole 255 znaků

float shaderData[32][3];// Pole 96 shader hodnot

FILE *In = NULL;// Ukazatel na soubor

Klasické nastavení enginu a OpenGL...

g_window = window;

g_keys = keys;

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Perspektivní korekce

glClearColor(0.7f, 0.7f, 0.7f, 0.0f);// Světle šedé pozadí

glClearDepth(1.0f);// Nastavení hloubkového bufferu

glEnable(GL_DEPTH_TEST);// Zapne testování hloubky

glDepthFunc(GL_LESS);// Typ testování hloubky

Při vykreslování čar chceme, aby byly pěkně vyhlazené. Implicitně je tato funkce vypnuta, ale stiskem klávesy 2 ji můžeme zapínat a vypínat podle libosti.

glShadeModel(GL_SMOOTH);// Jemné stínování

glDisable(GL_LINE_SMOOTH);// Vypne vyhlazování čar

Zapneme ořezávání vnitřních stěn objektu, které stejně nejsou vidět a vypneme OpenGL světla, protože potřebné výpočty provedeme po svém.

glEnable(GL_CULL_FACE);// Zapne face culling (ořezávání stěn)

glDisable(GL_LIGHTING);// Vypne světla

V další části kódu načteme shader soubor. Obsahuje pouze 32 desetinných čísel uložených, pro jednoduchou modifikaci, v ASCII formátu, každé na samostatném řádku.

In = fopen("Data\\shader.txt", "r");// Otevření shader souboru

if (In)// Kontrola, zda je soubor otevřen

{

for (i = 0; i < 32; i++)// Projde všech 32 hodnot ve stupních šedi

{

if (feof(In))// Kontrola konce souboru

break;

fgets(Line, 255, In);// Získání aktuálního řádku

Přeměníme načtené stupně šedi na RGB, jak jsme si popsali výše.

// Zkopíruje danou hodnotu do všech složek barvy

shaderData[i][0] = shaderData[i][1] = shaderData[i][2] = float(atof(Line));

}

fclose(In);// Zavře soubor

}

else

{

return FALSE;// Neúspěch

}

Nahrajeme texturu přesně tak, jak je. Bez použití filtrování, jinak by výsledek vypadal opravdu hnusně, přinejmenším. Použijeme GL_TEXTURE_1D, protože jde o jednorozměrné pole hodnot.

glGenTextures(1, &shaderTexture[0]);// Získání ID textury

glBindTexture(GL_TEXTURE_1D, shaderTexture[0]);// Přiřazení textury; od teď je 1D texturou

// Nikdy nepoužívejte bi-/trilinearní filtrování!

glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, 32, 0, GL_RGB , GL_FLOAT, shaderData);// Upload dat

Nastavíme směr dopadání světla na objekt ze směru kladné části osy z. Ve svém důsledku to znamená, že světlo bude zepředu svítit na model.

lightAngle.X = 0.0f;// Nastavení směru x

lightAngle.Y = 0.0f;// Nastavení směru y

lightAngle.Z = 1.0f;// Nastavení směru z

Normalize(lightAngle);// Normalizování vektoru světla

Načtení tvaru ze souboru (funkce popsána výše).

return ReadMesh();// Vrátí návratovou hodnotu funkce ReadMesh()

}

Funkce Deinitialize() je pravým opakem předchozí funkce. Smaže texturu a data polygonů nahraná pomocí funkcí Initialize() a ReadMesh().

void Deinitialize(void)// Deinicializace

{

glDeleteTextures(1, &shaderTexture[0]);// Smaže shader texturu

delete [] polyData;// Uvolní data polygonů

}

Funkce Update() se periodicky volá v hlavní smyčce tohoto dema. Jedinou její funkcí je zpracování vstupu z klávesnice.

void Update(DWORD milliseconds)// Aktualizace scény (objektu)

{

if (g_keys->keyDown [VK_ESCAPE] == TRUE)// Klávesa ESC

{

TerminateApplication (g_window);// Ukončení programu

}

if (g_keys->keyDown [VK_F1] == TRUE)// Klávesa F1

{

ToggleFullscreen(g_window);// Přepnutí módů fullscreen/okno

}

if (g_keys->keyDown [' '] == TRUE)// Mezerník

{

modelRotate = !modelRotate;// Zapne/vypne rotaci objektu

g_keys->keyDown [' '] = FALSE;

}

if (g_keys->keyDown ['1'] == TRUE)// Klávesa čísla 1

{

outlineDraw = !outlineDraw;// Zapne/vypne vykreslování obrysu

g_keys->keyDown ['1'] = FALSE;

}

if (g_keys->keyDown ['2'] == TRUE)// Klávesa číslo 2

{

outlineSmooth = !outlineSmooth;// Zapne/vypne anti-aliasing

g_keys->keyDown ['2'] = FALSE;

}

if (g_keys->keyDown [VK_UP] == TRUE)// Šipka nahoru

{

outlineWidth++;// Zvětší tloušťku čáry

g_keys->keyDown [VK_UP] = FALSE;

}

if (g_keys->keyDown [VK_DOWN] == TRUE)// Šipka dolů

{

outlineWidth--;// Zmenší tloušťku čáry

g_keys->keyDown [VK_DOWN] = FALSE;

}

if (modelRotate)// Je rotace zapnutá

modelAngle += (float)(milliseconds) / 10.0f;// Aktualizace úhlu natočení v závislosti na FPS

}

Funkce, na kterou už určitě netrpělivě čekáte. Draw() provádí většinu nejdůležitější práce v tomto tutoriálu - počítá hodnoty stínu, renderuje daný tvar a renderuje obrys.

void Draw(void)// Vykreslování

{

int i, j;// Řídící proměnné cyklů

Proměnná TmpShade se použije na uložení dočasné hodnoty stínu pro aktuální vertex. Všechna data týkajícího se jednoho vertexu jsou spočítána ve stejném čase, což znamená, že můžeme použít jen jednu proměnnou, kterou postupně použijeme pro všechny vertexy. Struktury TmpMatrix, TmpVector a TmpNormal jsou také použity pro spočítání dat jednoho vertexu. TmpMatrix se nastaví vždy jednou při startu funkce Draw() a nezmění se až do jejího dalšího startu. TmpVector a TmpNormal se liší vertex od vertexu.

float TmpShade;// Dočasná hodnota stínu

MATRIX TmpMatrix;// Dočasná MATRIX struktura

VECTOR TmpVector, TmpNormal;// Dočasné VECTOR struktury

Po deklaraci proměnných vymažeme buffery a data OpenGL matice.

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Vymaže buffery

glLoadIdentity();// Reset matice

Nejdříve zkontrolujeme zda chceme obrys vyhlazený. Když ano, zapneme anti-aliasing. Když ne, tak ho vypneme. Jak jednoduché...

if (outlineSmooth)// Chce uživatel vyhlazené čáry?

{

glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);// Použije nejkvalitnější výpočty

glEnable(GL_LINE_SMOOTH);// Zapne anti-aliasing

}

else// Nechce

{

glDisable(GL_LINE_SMOOTH);// Vypne anti-aliasing

}

Posunutím kamery o 2 jednotky dozadu nastavíme pohled, potom model pootočíme o daný úhel. Poznámka: protože jsme nejdříve pohnuli s kamerou, model se bude točit na místě. Pokud bychom to udělali opačně, model by rotoval kolem kamery.

glTranslatef(0.0f, 0.0f, -2.0f);// Posun do hloubky

glRotatef(modelAngle, 0.0f, 1.0f, 0.0f);// Rotace objektem na ose y

Získáme nově vytvořenou OpenGL matici a uložíme ji do TmpMatrix.

glGetFloatv(GL_MODELVIEW_MATRIX, TmpMatrix.Data);// Získání matice

Kouzla začínají... Povolíme 1D texturování a použijeme texturu stínu. Potom nastavíme barvu modelu. Vybral jsem bílou, protože na ní jde lépe vidět světlo a stín než na ostatních barvách. Nejméně vhodná je zcela určitě černá.

// Kód Cel-Shadingu

glEnable(GL_TEXTURE_1D);// Zapne 1D texturování

glBindTexture(GL_TEXTURE_1D, shaderTexture[0]);// Zvolí texturu

glColor3f(1.0f, 1.0f, 1.0f);// Nastavení barvy modelu (bílá)

Začneme s kreslením trojúhelníků. Projdeme všechny polygony v poli a všechny vertexy každého z těchto polygonů. Nejdříve zkopírujeme normálu do dočasné struktury. Díky tomu můžeme hodnotami normály otáčet bez toho, že bychom ztratili původní data (bez průběžné degradace).

glBegin(GL_TRIANGLES);// Začátek kreslení trojúhelníků

for (i = 0; i < polyNum; i++)// Prochází jednotlivé polygony

{

for (j = 0; j < 3; j++)// Prochází jednotlivé vertexy

{

// Zkopírování aktuální normály do dočasné struktury

TmpNormal.X = polyData[i].Verts[j].Nor.X;

TmpNormal.Y = polyData[i].Verts[j].Nor.Y;

TmpNormal.Z = polyData[i].Verts[j].Nor.Z;

Otočíme vektor o matici, kterou jsme získali od OpenGL a normalizujeme ho.

RotateVector(TmpMatrix, TmpNormal, TmpVector);// Otočí vektor podle matice

Normalize(TmpVector);// Normalizace normály

Spočítáme odchylku pootočené normály a směru světla. Potom hodnotu dáme do rozmezí 0-1 (z původního -1 až 1).

TmpShade = DotProduct(TmpVector, lightAngle);// Spočítání hodnoty stínu

if (TmpShade < 0.0f)// Pokud je TmpShade menší než nula bude se rovnat nule

TmpShade = 0.0f;

Předáme tuto hodnotu OpenGL jako texturovací souřadnici. Potom předáme pozici vertexu a opakujeme. A opakujeme. A opakujeme. Myslím, že podstatu už chápete.

glTexCoord1f(TmpShade);// Nastavení texturovací souřadnice na hodnotu stínu

glVertex3fv(&polyData[i].Verts[j].Pos.X);// Pošle pozici vertexu

}

}

glEnd();// Konec kreslení

glDisable(GL_TEXTURE_1D);// Vypne 1D texturování

Přesuneme se k obrysům. Obrys můžeme definovat jako hranu, kde je jeden polygon přivrácen směrem k nám a druhý od nás. Použijeme pro OpenGL běžné testování hloubky - méně nebo stejně (GL_LEQUAL) a také nastavíme vyřazování všech polygonů otočených k nám. Také použijeme blending, aby to trochu vypadalo.

Nastavíme OpenGL tak, aby polygony čelem od nás vyrenderoval jako čáry. Vyřadíme všechny polygony čelem k nám a nastavíme testování hloubky na menší nebo stejné na aktuální ose Z. Potom ještě nastavíme barvu čar, projdeme všechny polygony a vykreslíme jejich rohy. Stačí zadat pozici. Nemusíme zadávat normálu a stíny, protože chceme jenom obrys.

// Kód pro vykreslení obrysů

if (outlineDraw)// Chceme vůbec kreslit obrys?

{

glEnable(GL_BLEND);// Zapne blending

glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);// Mód blendingu

glPolygonMode(GL_BACK, GL_LINE);// Odvrácené polygony se stanout pouze obrysovými čarami

glLineWidth(outlineWidth);// Nastavení šířky čáry

glCullFace(GL_FRONT);// Nerenderovat přivrácené polygony

glDepthFunc(GL_LEQUAL);// Mód testování hloubky

glColor3fv(&outlineColor[0]);// Barva obrysu (černá)

glBegin(GL_TRIANGLES);// Začátek kreslení trojúhelníků

for (i = 0; i < polyNum; i++)// Prochází jednotlivé polygony

{

for (j = 0; j < 3; j++)// Prochází jednotlivé vertexy

{

glVertex3fv(&polyData[i].Verts[j].Pos.X);// Pošle pozici vertexu

}

}

glEnd();// Konec kreslení

Na konci už jenom vrátíme nastavení do původního stavu a ukončíme funkci i tutoriál.

glDepthFunc(GL_LESS);// Testování hloubky na původní nastavení

glCullFace(GL_BACK);// Nastavení ořezávání na původní hodnotu

glPolygonMode(GL_BACK, GL_FILL);// Normální vykreslování

glDisable(GL_BLEND);// Vypne blending

}

}

napsal: Sami "MENTAL" Hamlaoui <disk_disaster (zavináč) hotmail.com>
teoretickou část přeložil: Michal Turek - Woq <WOQ (zavináč) seznam.cz>
praktickou část přeložil: Václav Slováček - Wessan <horizont (zavináč) host.sk>

Zdrojové kódy

Lekce 37

<<< Lekce 36 | Lekce 38 >>>