Lekce 22 - Bump Mapping & Multi Texturing

Pravý čas vrátit se zpátky na začátek a začít si opakovat. Nováčkům v OpenGL se absolutně nedoporučuje! Pokud, ale máte odvahu, můžete zkusit dobrodružství s nadupanou grafikou. V této lekci modifikujeme kód z šesté lekce, aby podporoval hardwarový multi texturing přes opravdu skvělý vizuální efekt nazvaný bump mapping.

Při překladu této lekce jsem zvažoval zda mám některé termíny překládat do češtiny. Ale vzhledem k tomu, že jsou to většinou názvy, které se běžně v oboru počítačové grafiky objevují, rozhodl jsem se nechat je v původním znění. Aby však i ti, kteří se s nimi setkávají poprvé, věděli o čem je řeč, tak je zde v rychlosti vysvětlím:

OpenGL extension je funkce, která není v běžné specifikaci OpenGL dostupná, ale kvůli novým možnostem grafických akcelerátorů a novým postupům při programování byla do OpenGL dodatečně přidána. Tyto funkce ve svém názvu obsahují EXT nebo ARB. Firmy se samozřejmě snaží, aby jejich akcelerátor podporoval těchto rozšíření co nejvíce, protože mnohé z nich zrychlují práci, přidávají nové možnosti nebo zvyšují výkon.

Bumpmapa je textura, která obsahuje informace o reliéfu. Většinou bývá ve stupních šedi, kde tmavá místa udávají vyvýšeniny a světlá rýhy, nebo naopak - to záleží na programátorovi.

Emboss bumpmapping je postup vytváření reliéfovaných textur, u kterých se zdá, že jsou tvarované i do hloubky - hlavní téma této lekce.

Alpha kanál je poslední složka RGBA barvy, která obsahuje informace o průhlednosti. Pokud je alpha maximální (255 nebo 1.0f), tak není objekt vůbec průhledný. Pokud je alpha nulová je objekt neviditelný.

Blending je míchání alpha kanálu s barevnou texturou. Dociluje se jím průhlednosti.

Artefakt je nějaký vizuální prvek, který by se v renderované scéně neměl objevovat. Nicméně vzhledem k tomu, že postupy, které by je nezanechávaly jsou většinou velmi pomalé, musí se používat jiné, které na úkor kvality zvýší rychlost renderování.

Další názvy typu vertex, pipeline, ... by měly být dobře známé z předchozích tutoriálů.

Doufám, že Vám překlad i téma budou srozumitelné a že Vám pomohou vytvářet kvalitní OpenGL aplikace. Pokud byste narazili na nějaký problém, není nic jednoduššího než poslat emailem dotaz. Rád Vám na všechny otázky odpovím, případně opravím nedostatky v textu.

Tato lekce byla napsána Jensem Schneiderem. Volně vychází z 6. lekce, i když vzniklo mnoho změn. Naučíte se zde:

Nejméně tři z výše uvedených bodů mohou být považovány za "pokročilé renderovací techniky". Měli byste mít již základní představu o tom, jak funguje renderovací pipeline OpenGL. Měli byste znát většinu příkazů užitých v tutoriálu a měli byste být obeznámeni s vektorovou matematikou. Sekce, které začínají slovy "začátek teorie(...)" a končí slovy "konec teorie(...)", se snaží vysvětlit problematiku uvedenou v závorkách. Tohle je zde jen pro jistotu. Pokud danou problematiku znáte, můžete tyto části jednoduše přeskočit. Pokud budete mít problémy s porozuměním kódu, zvažte návrat zpět k teoretickým částem textu. Poslední, ale neméně důležité: Tato lekce obsahuje více než 1 200 řádek kódu a velká část z nich je nejen nudná, ale i dobře známá těm, kteří četli předchozí tutoriály. Proto nebudu komentovat každý řádek, ale jen podstatu této lekce. Pokud narazíte na něco jako >-<, znamená to, že zde byly vynechány nějaké nepodstatné řádky kódu.

Takže, jdeme na to:

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

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

#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 "glext.h"// Hlavičkový soubor pro multitexturing

#include <string.h>// Hlavičkový soubor pro řetězce

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

GLfloat MAX_EMBOSS udává "sílu" bumpmappingu. Vyšší hodnoty hodně zvýrazní efekt, ale stejně tak sníží kvalitu obrazu tím, že zanechávají v rozích ploch takzvané "artefakty".

#define MAX_EMBOSS (GLfloat)0.008f// Maximální posunutí efektem

Fajn, připravíme se na použití GL_ARB_multitexture. Je to celkem jednoduché:

Většina grafických akcelerátorů má dnes více než jednu texturovací jednotku. Abychom mohli této výhody využít, musíme prověřit, zda akcelerátor podporuje GL_ARB_multitexture, který umožňuje namapovat dvě nebo více textur na jeden útvar při jednom průchodu pipeline. Nezní to příliš významně, ale opak je pravdou! Skoro vždy když něco programujete, přidáním další textury na objekt, razantně zvýšíte jeho vizuální kvalitu. Dříve bylo nutno použít dvě prokládané textury při vícenásobném vykreslování geometrie, což může vést k velkému poklesu výkonu. Dále v tutoriálu bude multitexturing ještě podrobněji popsán.

Teď zpět ke kódu: __ARB_ENABLE je užito pro určení toho, zda chceme využít multitexturingu, když bude dostupný. Pokud chcete poznat vaší kartou podporovaná OpenGL rozšíření, pouze odkomentujte #define EXT_INFO. Dále chceme prověřit podporu extensions při běhu programu, abychom zajistili přenositelnost kódu. Proto potřebujeme místo pro pár řetězců. Dále chceme rozlišovat mezi možností používat extensions a samotným používáním. Nakonec potřebujeme vědět, kolik texturovacích jednotek máme k dispozici (použijeme ale pouze dvě). Alespoň jedna texturovací jednotka je vždy přítomna na akcelerátoru podporujícím OpenGL, takže nastavíme maxTexelUnits na hodnotu 1.

#define __ARB_ENABLE true// Použito pro vyřazení multitexturingu

// #define EXT_INFO// Odkomentujte, pokud chcete při startu vidět podporovaná rozšíření OpenGL

#define MAX_EXTENSION_SPACE 10240// Místo pro řetězce s OpenGL rozšířeními

#define MAX_EXTENSION_LENGTH 256// Maximum znaků v jednom řetězci s rozšířením

bool multitextureSupported = false;// Indikátor podpory multitexturingu

bool useMultitexture = true;// Použit multitexturing?

GLint maxTexelUnits = 1;// Počet texturovacích jednotek - nejméně 1

Následující řádky slouží k tomu, aby spojily rozšíření s voláním funkcí v C++. Pouze využijeme PNF-kdo-to-kdy-přečetl jako předdefinovaného datového typu schopného popsat volání funkcí. Zpočátku není jisté, zda získáme přístup k těmto prototypům funkcí, tudíž je nastavíme na NULL. Příkazy glMultiTexCoordifARB odkazují na dobře známé příkazy glTexCoordif(), udávající i-rozměrné souřadnice textury. Všimněte si, že proto mohou úplně nahradit příkazy glTexCoordif. Dříve jsme používali pouze verzi pro typ GLfloat, my potřebujeme pouze prototypy k příkazům končícím na "f" - ostatní jsou potom taky dostupné (fv, i, ...). Poslední dva prototypy slouží k určení texturovací jednotky, která bude přijímat informace o textuře (glActiveTextureARB()) a k určení, která texturovací jednotka je asociována s příkazem ArrayPointer (glClientActiveTextureARB). Mimochodem: ARB je zkratkou "Architectural Review Board". Rozšíření s ARB v názvu nejsou vyžadovány pro implementaci kompatibilní s OpenGL, ale jsou široce využívány a podporovány.

PFNGLMULTITEXCOORD1FARBPROC glMultiTexCoord1fARB = NULL;

PFNGLMULTITEXCOORD2FARBPROC glMultiTexCoord2fARB = NULL;

PFNGLMULTITEXCOORD3FARBPROC glMultiTexCoord3fARB = NULL;

PFNGLMULTITEXCOORD4FARBPROC glMultiTexCoord4fARB = NULL;

PFNGLACTIVETEXTUREARBPROC glActiveTextureARB = NULL;

PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = NULL;

Potřebujeme globální proměnné:

GLuint filter=1;// Jaký filtr použít

GLuint texture[3];// Místo pro tři textury

GLuint bump[3];// Naše bumpmapy

GLuint invbump[3];// Invertované bumpmapy

GLuint glLogo;// Místo pro OpenGL Logo

GLuint multiLogo;// Místo pro logo s multitexturingem

GLfloat LightAmbient[] = { 0.2f, 0.2f, 0.2f};// Barva ambientního světla je 20% bílá

GLfloat LightDiffuse[] = { 1.0f, 1.0f, 1.0f};// Difúzní světlo je bílé

GLfloat LightPosition[] = { 0.0f, 0.0f, 2.0f};// Pozice je někde uprostřed scény

GLfloat Gray[] = { 0.5f, 0.5f, 0.5f, 1.0f };// Barva okraje textury

bool emboss = false;// Jenom Emboss, žádná základní textura

bool bumps = true;// Používat bumpmapping?

GLfloat xrot;// X rotace

GLfloat yrot;// Y rotace

GLfloat xspeed;// Rychlost x rotace

GLfloat yspeed;// Rychlost y rotace

GLfloat z = -5.0f;// Hloubka v obrazovce

Další část kódu obsahuje souřadnice kostky sestavené z GL_QUADS. Každých pět čísel reprezentuje jednu sadu 2D texturovacích souřadnic a jednu sadu 3D vertexových souřadnic bodu. Data jsou uvedena v poli kvůli snazšímu vykreslování ve for smyčkách. Během jednoho renderovacího cyklu budeme tyto souřadnice potřebovat vícekrát.

GLfloat data[] =

{

// Přední stěna

0.0f, 0.0f, -1.0f, -1.0f, +1.0f,

1.0f, 0.0f, +1.0f, -1.0f, +1.0f,

1.0f, 1.0f, +1.0f, +1.0f, +1.0f,

0.0f, 1.0f, -1.0f, +1.0f, +1.0f,

// Zadní stěna

1.0f, 0.0f, -1.0f, -1.0f, -1.0f,

1.0f, 1.0f, -1.0f, +1.0f, -1.0f,

0.0f, 1.0f, +1.0f, +1.0f, -1.0f,

0.0f, 0.0f, +1.0f, -1.0f, -1.0f,

// Horní stěna

0.0f, 1.0f, -1.0f, +1.0f, -1.0f,

0.0f, 0.0f, -1.0f, +1.0f, +1.0f,

1.0f, 0.0f, +1.0f, +1.0f, +1.0f,

1.0f, 1.0f, +1.0f, +1.0f, -1.0f,

// Dolní stěna

1.0f, 1.0f, -1.0f, -1.0f, -1.0f,

0.0f, 1.0f, +1.0f, -1.0f, -1.0f,

0.0f, 0.0f, +1.0f, -1.0f, +1.0f,

1.0f, 0.0f, -1.0f, -1.0f, +1.0f,

// Pravá stěna

1.0f, 0.0f, +1.0f, -1.0f, -1.0f,

1.0f, 1.0f, +1.0f, +1.0f, -1.0f,

0.0f, 1.0f, +1.0f, +1.0f, +1.0f,

0.0f, 0.0f, +1.0f, -1.0f, +1.0f,

// Levá stěna

0.0f, 0.0f, -1.0f, -1.0f, -1.0f,

1.0f, 0.0f, -1.0f, -1.0f, +1.0f,

1.0f, 1.0f, -1.0f, +1.0f, +1.0f,

0.0f, 1.0f, -1.0f, +1.0f, -1.0f

};

Další část kódu rozhoduje o použití OpenGL extensions za běhu programu.

Předpokládejme, že máme dlouhý řetězec obsahující názvy všech podporovaných rozšíření oddělených znakem nového řádku -'\n'. Potřebujeme vyhledat znak nového řádku a tuto část začít porovnávat s hledaným řetězcem, dokud nenarazíme na další znak nového řádku, nebo dokud nalezený řetězec neodpovídá tomu hledanému. V prvním případě vrátíme true, v druhém případě vezmeme další sub-řetězec dokud nenarazíme na konec řetězce. Budeme si muset dát pozor na to, zda řetězec nezačíná znakem nového řádku.

Poznámka: Kontrola podpory rozšíření by se měla VŽDY provádět až za běhu programu.

bool isInString(char *string, const char *search)

{

int pos = 0;

int maxpos = strlen(search)-1;

int len = strlen(string);

char *other;

for (int i=0; i<len; i++)

{

if ((i==0) || ((i>1) && string[i-1]=='\n'))// Nové rozšíření začíná zde

{

other = &string[i];

pos=0;// Začít nové hledání

while (string[i]!='\n')// Hledání celého řetězce jména rozšíření

{

if (string[i]==search[pos])

pos++;// Další znak

if ((pos>maxpos) && string[i+1]=='\n')

return true; // A máme to!

i++;

}

}

}

return false;// Smůla, nic jsme nenašli!

}

Teď musíme získat řetězec obsahující názvy extensions a převést ho tak, aby jednotlivé názvy byly odděleny znakem nového řádku. Pokud najdeme sub-řetězec "GL_ARB_multitexture", tak je tato funkce podporovaná. Ale my jí použijeme, jen když je __ARB_ENABLE nastaveno na true. Ještě potřebujeme zjistit podporu GL_EXT_texture_env_combine. Toto rozšíření zavádí nový způsob interakce s texturovacími jednotkami. My to potřebujeme, protože GL_ARB_multitexture pouze přenáší výstup z jedné texturovací jednotky do další s vyšším číslem. Než abychom používali další komplexní rovnice pro výpočet blendingu (které by ale mohly mít odlišný efekt), raději zajistíme podporu tohoto rozšíření. Pokud jsou všechna rozšíření podporována, zjistíme kolik texturovacích jednotek máme k dispozici a hodnotu uložíme do maxTexelUnits. Pak musíme spojit funkce s našimi jmény. To provedeme pomocí funkce wglGetProcAdress() s parametrem obsahujícím název funkce.

bool initMultitexture(void)

{

char *extensions;

extensions = strdup((char *) glGetString(GL_EXTENSIONS));// Získání řetězce s rozšířeními

int len = strlen(extensions);// Délka řetězce

for (int i = 0; i<len; i++)// Rozdělit znakem nového řádku místo mezery

if (extensions[i] == ' ')

extensions[i] = '\n';

#ifdef EXT_INFO

MessageBox(hWnd,extensions,"supported GL extensions",MB_OK | MB_ICONINFORMATION);

#endif

if (isInString(extensions,"GL_ARB_multitexture")// Je multitexturing podporován?

&& __ARB_ENABLE// Příznak pro povolení multitexturingu

&& isInString(extensions,"GL_EXT_texture_env_combine"))// Je podporováno texture-environment-combining?

{

glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, &maxTexelUnits);

glMultiTexCoord1fARB = (PFNGLMULTITEXCOORD1FARBPROC)wglGetProcAddress("glMultiTexCoord1fARB");

glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");

glMultiTexCoord3fARB = (PFNGLMULTITEXCOORD3FARBPROC)wglGetProcAddress("glMultiTexCoord3fARB");

glMultiTexCoord4fARB = (PFNGLMULTITEXCOORD4FARBPROC)wglGetProcAddress("glMultiTexCoord4fARB");

glActiveTextureARB = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");

glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress("glClientActiveTextureARB");

#ifdef EXT_INFO

MessageBox(hWnd,"The GL_ARB_multitexture extension will be used.","feature supported!",MB_OK | MB_ICONINFORMATION);

#endif

return true;

}

useMultitexture = false;// Nemůžeme to používat, pokud to není podporováno!

return false;

}

InitLights() pouze inicializuje osvětlení. Je volána funkcí initGL().

void initLights(void)

{

glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);// Načtení informace o světlech do GL_LIGHT1

glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);

glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);

glEnable(GL_LIGHT1);

}

V této lekci vytvoříme hodně textur. Nyní k naší načítací funkci. Nejdříve loadujeme základní bitmapu a připravíme z ní tři filtrované textury (GL_NEAREST, GL_LINEAR, GL_LINEAR_MIPMAP_NEAREST). Použijeme pouze jednu datovou strukturu na uložení bitmap. Navíc zavedeme novou strukturu nazvanou alpha, která bude obsahovat informace o alpha kanálu (průhlednosti) textury. Proto uložíme RGBA obrázky jako dvě bitmapy: jednu 24 bitovou RGB a jednu osmi bitovou ve stupních šedi pro alpha kanál. Aby fungovalo načítání správně, musíme po každém načtení smazat Image, jinak nebudeme upozorněni na případné chyby při nahrávání textur.

Textura Base

Také je u specifikace typu textury vhodné uvést místo čísla 3 proměnnou GL_RGB8, a to kvůli lepší kompatibilitě s dalšími verzemi OpenGL. Tato změna je označena v kódu takto.

int LoadGLTextures()// Loading bitmapy a konverze na texturu

{

bool status=true;// Indikuje chyby

AUX_RGBImageRec *Image=NULL;// Ukládá bitmapu

char *alpha=NULL;

if (Image = auxDIBImageLoad("Data/Base.bmp"))// Nahraje bitmapu

{

glGenTextures(3, texture);// Generuje tři textury

// Vytvoření nelineárně filtrované textury

glBindTexture(GL_TEXTURE_2D, texture[0]);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image->data);

// Vytvoření lineárně filtrované textury

glBindTexture(GL_TEXTURE_2D, texture[1]);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image->data);

// Vytvoření mipmapované textury

glBindTexture(GL_TEXTURE_2D, texture[2]);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);

gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, Image->sizeX, Image->sizeY, GL_RGB, GL_UNSIGNED_BYTE, Image->data);

}

else

status = false;

if (Image)// Pokud obrázek existuje

{

if (Image->data)// Pokud existují data obrázku

delete Image->data;// Uvolní data obrázku

delete Image;// Uvolní strukturu obrázku

Image = NULL;// Nastaví ukazatel na NULL

}

Načteme bumpmapu. Z důvodů uvedených níže musí mít pouze 50% intenzitu, takže ji musíme nějakým způsobem ztmavit. Já jsem se rozhodl použít funkci glPixelTransferf(), která udává jakým způsobem budou bitmapy převedeny na textury. My tuto funkci použijeme na ztmavení jednotlivých RGB kanálů bitmapy na 50% původní intenzity. Pokud dosud nepoužíváte rodinu funkcí glPixelTransfer(), měli byste se na ně podívat - jsou celkem užitečné.

Textura Bumpmapy

// Loading bumpmap

if (Image = auxDIBImageLoad("Data/Bump.bmp"))

{

glPixelTransferf(GL_RED_SCALE,0.5f);// Snížení intenzity RGB na 50% - poloviční intenzita

glPixelTransferf(GL_GREEN_SCALE,0.5f);

glPixelTransferf(GL_BLUE_SCALE,0.5f);

Další problém je, že nechceme, aby se bitmapa v textuře pořád opakovala, chceme ji namapovat pouze jednou na texturovací souřadnice od (0.0f,0.0f) do (1.0f,1.0f). Vše kolem nich by mělo být namapováno černou barvou. Toho dosáhneme zavoláním dvou funkcí glTexParameteri(), které není třeba popisovat.

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);// Bez wrappingu (zalamování)

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);

glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR,Gray);// Barva okraje textury

glGenTextures(3, bump);// Vytvoří tři textury

// Vytvoření nelineárně filtrované textury

>-<

// Vytvoření lineárně filtrované textury

>-<

// Vytvoření mipmapované textury

>-<

Nyní musíme vytvořit ještě invertovanou bumpmapu, o které jsme již psali a jejíž význam bude vysvětlen dále. Odečtením barvy každého bodu bumpmapy od bílé barvy {255, 255, 255} získáme obrázek s invertovanými barvami. Předtím nesmíme nastavit intenzitu zpět na 100% (než jsem na to přišel strávil jsem nad tím asi 3 hodiny), invertovaná bitmapa musí být tedy také ztmavená na 50%.

for (int i = 0; i < 3 * Image->sizeX * Image->sizeY; i++)// Invertování bumpmapy

Image->data[i] = 255 - Image->data[i];

glGenTextures(3, invbump);// Vytvoří tři textury

// Vytvoření nelineárně filtrované textury

>-<

// Vytvoření lineárně filtrované textury

>-<

// Vytvoření mipmapované textury

>-<

glPixelTransferf(GL_RED_SCALE,1.0f);// Vrácení intenzity RGB zpět na 100%

glPixelTransferf(GL_GREEN_SCALE,1.0f);

glPixelTransferf(GL_BLUE_SCALE,1.0f);

}

else

status = false;

if (Image)// Pokud obrázek existuje

{

if (Image->data)// Pokud existují data obrázku

delete Image->data;// Uvolní data obrázku

delete Image;// Uvolní strukturu obrázku

Image = NULL;// Nastaví ukazatel na NULL

}

Načítání bitmap log je velmi jednoduché až na zkombinování RGB-A kanálů, nicméně kód by měl být dostatečně jasný. Všimněte si, že tato textura je vytvořena z dat alpha, nikoliv z dat Image. Bude zde použit pouze jeden filtr.

Textura OpenGL_ALPHA

// Načte bitmapy log

if (Image = auxDIBImageLoad("Data/OpenGL_ALPHA.bmp"))

{

alpha = new char[4*Image->sizeX*Image->sizeY];// Alokuje paměť pro RGBA8-Texturu

for (int a=0; a < Image->sizeX * Image->sizeY; a++)

alpha[4*a+3] = Image->data[a*3];// Vezme pouze červenou barvu jako alpha kanál

Textura OpenGL

if (!(Image = auxDIBImageLoad("Data/OpenGL.bmp")))

status = false;

for (a = 0; a < Image->sizeX * Image->sizeY; a++)

{

alpha[4*a]=Image->data[a*3];// R

alpha[4*a+1]=Image->data[a*3+1];// G

alpha[4*a+2]=Image->data[a*3+2];// B

}

glGenTextures(1, &glLogo);// Vytvoří jednu texturu

// Vytvoří lineárně filtrovanou RGBA8-Texturu

glBindTexture(GL_TEXTURE_2D, glLogo);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Image->sizeX, Image->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, alpha);

delete alpha;// Uvolní alokovanou paměť

}

else

status = false;

if (Image)// Pokud obrázek existuje

{

if (Image->data)// Pokud existují data obrázku

delete Image->data;// Uvolní data obrázku

delete Image;// Uvolní strukturu obrázku

Image = NULL;// Nastaví ukazatel na NULL

}

Textura Extension Enabled ALFA

if (Image = auxDIBImageLoad("Data/multi_on_alpha.bmp"))

{

alpha = new char[4*Image->sizeX*Image->sizeY];// Alokuje paměť pro RGBA8-Texturu

for (int a = 0; a < Image->sizeX * Image->sizeY; a++)

alpha[4*a+3]=Image->data[a*3];// Vezme pouze červenou barvu jako alpha kanál

Textura Extension Enabled

if (!(Image=auxDIBImageLoad("Data/multi_on.bmp")))

status = false;

for (a=0; a < Image->sizeX * Image->sizeY; a++)

{

alpha[4*a] = Image->data[a*3];// R

alpha[4*a+1] = Image->data[a*3+1];// G

alpha[4*a+2] = Image->data[a*3+2];// B

}

glGenTextures(1, &multiLogo);// Vytvoří jednu texturu

// Vytvoří lineárně filtrovanou RGBA8-Texturu

glBindTexture(GL_TEXTURE_2D, multiLogo);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Image->sizeX, Image->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, alpha);

delete alpha;

}

else

status = false;

if (Image)// Pokud obrázek existuje

{

if (Image->data)// Pokud existují data obrázku

delete Image->data;// Uvolní data obrázku

delete Image;// Uvolní strukturu obrázku

Image = NULL;// Nastaví ukazatel na NULL

}

return status;// Vrátí status

}

Následuje funkce doCube(), která kreslí krychli spolu s normálami. Všimněte si, že tato verze zatěžuje pouze texturovací jednotku #0, glTexCoord(s, t) pracuje stejně jako glMultiTexCoord(GL_TEXTURE0_ARB, s, t). Krychle může být taky vykreslena pomocí prokládaných polí, to ale teď nebudeme řešit. Nemůže však být uložena na display listu, ty používají pravděpodobně přesnost různou od GLfloat, což vede k nepěkným vedlejším efektům.

void doCube(void)

{

int i;

glBegin(GL_QUADS);

// Přední stěna

glNormal3f( 0.0f, 0.0f, +1.0f);

for (i=0; i<4; i++)

{

glTexCoord2f(data[5*i],data[5*i+1]);

glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

}

// Zadní stěna

glNormal3f( 0.0f, 0.0f,-1.0f);

for (i=4; i<8; i++)

{

glTexCoord2f(data[5*i],data[5*i+1]);

glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

}

// Horní stěna

glNormal3f( 0.0f, 1.0f, 0.0f);

for (i=8; i<12; i++)

{

glTexCoord2f(data[5*i],data[5*i+1]);

glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

}

// Spodní stěna

glNormal3f( 0.0f,-1.0f, 0.0f);

for (i=12; i<16; i++)

{

glTexCoord2f(data[5*i],data[5*i+1]);

glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

}

// Pravá stěna

glNormal3f( 1.0f, 0.0f, 0.0f);

for (i=16; i<20; i++)

{

glTexCoord2f(data[5*i],data[5*i+1]);

glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

}

// Levá stěna

glNormal3f(-1.0f, 0.0f, 0.0f);

for (i=20; i<24; i++)

{

glTexCoord2f(data[5*i],data[5*i+1]);

glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);

}

glEnd();

}

Přichází čas na inicializaci OpenGL. Vše je jako v lekci 06, kromě toho, že zavoláme funkci initLights(), místo toho, abychom světla nastavovali zde. A ještě samozřejmě voláme nastavení případného multitexturingu.

int InitGL(GLvoid)// Všechno nastavení OpenGL

{

multitextureSupported = initMultitexture();

if (!LoadGLTextures())// Vytvoření textur

return false;

glEnable(GL_TEXTURE_2D);// Zapne texturové mapování

glShadeModel(GL_SMOOTH);// Zapne smooth shading

glClearColor(0.0f, 0.0f, 0.0f, 0.5f);// Černé pozadí

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

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

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

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Kvalitní výpočty perspektivy

initLights();// Inicializace světel

return true;// Vše v pořádku

}


Začátek teorie (Emboss Bump Mapping)

Zde je asi 95% práce. Vše u čeho bylo napsáno, že bude vysvětleno později, je v následující teoretické sekci. Jedná se o přepsání prezentace v PowerPointu do HTML.

Emboss Bump Mapping

Michael I. Gold - NVidia Corporation

Bump Mapping

Skutečný bump mapping používá per-pixel osvětlení.

Emboss Bump Mapping

Emboss Bump Mapping je pouze náhražka.

Výpočet difúzního osvětlení

C = (L * N) x Dl x Dm

Přibližný stupeň rozptylu L * N

Textura reprezentuje výškovou mapu

Přibližné odvození

Zohlednění přibližných údajů

Spočítání reliéfu

1) Původní reliéf (H0).

Původní reliéf (H0)

2) Původní reliéf (H0) proložený druhým (H1), který je mírně posunutý směrem ke světlu.

Původní reliéf (H0) proložený druhým (H1), který je mírně posunutý směrem ke světlu

3) Odečtení původního od posunutého reliéfu (H0-H1) - vede ke vzniku světlých (B) a tmavých (D) ploch.

Odečtení původního od posunutého reliéfu (H0-H1) - vede ke vzniku světlých (B) a tmavých (D) ploch

Výpočet osvětlení

Určíme hodnotu barvy (Cf) dané plochy

Je to vše? Takhle jednoduché?!

Ještě nejsme úplně hotoví. Stále musíme:

Tvorba textury

Uchovávejte textury!

Výpočet offsetu textury

Pootočení vektoru světla

Výpočet offsetu textury (pokračování)

Použijte pro posunutí vektor světla normálního prostoru

Implementace na TNT

Spočítejte vektory, texturovací souřadnice na hostiteli

Implementace na TNT (pokračování)

Nastavení alpha kanálu na combineru

Konec teorie (Emboss Bump Mapping)


My to ale uděláme trochu jinak než podle TNT implementace, abychom umožnili našemu programu běžet na VŠECH akcelerátorech. Zde se můžeme přiučit dvě nebo tři věci. Jedna z nich je, že bumpmapping je více fázový algoritmus na většině karet (ne na TNT, kde se to dá nahradit jednou dvou-texturovací fází). Už byste si měli být schopni představit, jak hezký multitexturing ve skutečnosti je. Nyní implementujeme 3-fázový netexturovací algoritmus, který pak může být (a bude) vylepšen na 2 fázový texturovací algoritmus.

Teď byste si měli uvědomit, že musíme udělat nějaké násobení matice maticí (a násobení vektoru maticí). Ale to není nic čeho bychom se měli obávat: OpenGL zvládne násobení matice maticí za nás a násobení vektoru maticí je celkem jednoduché: funkce VMatMult(M,v) vynásobí matici M s vektorem v a výsledek uloží zpět ve v: v = M * v. Všechny matice a vektory předané funkci musejí mít stejný tvar: matice 4x4 a 4-rozměrné vektory. To je pro zajištění kompatibility s OpenGL.

void VMatMult(GLfloat *M, GLfloat *v)

{

GLfloat res[3];

res[0] = M[0]*v[0]+M[1]*v[1]+M[ 2]*v[2]+M[ 3]*v[3];

res[1] = M[4]*v[0]+M[5]*v[1]+M[ 6]*v[2]+M[ 7]*v[3];

res[2] = M[8]*v[0]+M[9]*v[1]+M[10]*v[2]+M[11]*v[3];

v[0]=res[0];

v[1]=res[1];

v[2]=res[2];

v[3]=M[15];// Homogenní souřadnice

}


Začátek teorie (algoritmy pro Emboss Bump Mapping)

Zde se zmíníme o dvou odlišných algoritmech. První popisuje program, který se jmenuje GL_BUMP a napsal ho Diego Tártara v roce 1999. I přes pár nevýhod velmi pěkně implementuje bumpmapping. Teď se na tento algoritmus podíváme:

  1. Všechny vektory musí být BUĎ v prostoru objektu NEBO v prostoru scény
  2. Spočítání vektoru v z aktuální pozice vertexu vzhledem ke světlu
  3. Normalizace v
  4. Promítnutí v do tangenoidního prostoru. (To je plocha, která se dotýká daného vertexu. Pokud pracujete s rovnými plochami, tak je to zpravidla plocha samotná.)
  5. Posuneme souřadnice (s,t) o složky x,y vektoru v

To nevypadá špatně! V podstatě je to algoritmus popsaný Michaelem I. Goldem výše. Má však zásadní nevýhodu: Támara používá projekci pouze pro rovinu xy. To pro naše potřeby nestačí, protože zjednodušuje promítací krok pouze na složky x a y a se složkou z vektoru v vůbec nepočítá.

Ale tato implementace vytvoří rozptýlené světlo stejným způsobem, jako ho budeme dělat my: s použitím v OpenGL zabudované podpory osvětlení. Takže nemůžeme použít metodu kombinerů, jakou navrhuje Gold (Chceme, aby naše programy běžely i na jiných než TNT kartách!), nemůžeme uložit stupeň rozptylu do alpha kanálu. Tak již máme problém s 3 fázovým netexturovaním a 2 fázovým texturováním, proč na poslední průchod nepoužít OpenGL-Lighting, aby za nás dodělal ambientní světlo a barvy? Je to možné (a výsledek vypadá celkem dobře), ale jen proto, že nyní nepoužíváme složitou geometrii. Tohle byste si měli zapamatovat. Pokud budete chtít renderovat několik tisíc bumpmapovaných trojúhelníků, zkuste objevit něco jiného.

Navíc, používá multitexturing (jak můžeme vidět) ne tak jednoduše jako my s ohledem na tento speciální případ.

Ale teď k naší implementaci. Vypadá podobně jako algoritmus předtím, kromě projekční fáze, kde použijeme vlastní postup:

Proč je to dobré?

Nevýhody:

Tento náčrtek ukazuje, kde se nacházejí jednotlivé vektory. Můžete jednoduše získat t a s odečtením jesnosti jednotlivých vektorů, ale ujistěte se, že jsou správně natočené a normalizované. Modrý bod označuje vertex, kde je namapován texCoord2f(0.0f, 0.0f).

Lokace vektorů

Konec teorie (algoritmy pro Emboss Bump Mapping)


Teď se podívejme na generátor posunutí textury. Tato funkce se jmenuje SetUpBumps().

// Funkce nastaví posunutí textury

// n : normála k ploše, musí mít délku 1

// c : nějaký bod na povrchu

// l : pozice světla

// s : směr texturovacích souřadnic s (musí být normalizován!)

// t : směr texturovacích souřadnic t (musí být normalizován!)

void SetUpBumps(GLfloat *n, GLfloat *c, GLfloat *l, GLfloat *s, GLfloat *t)

{

GLfloat v[3];// Vertex z aktuální pozice ke světlu

GLfloat lenQ;// Použito při normalizaci

// Spočítání v z aktuálního vertexu c ke světlu a jeho normalizace

v[0] = l[0] - c[0];

v[1] = l[1] - c[1];

v[2] = l[2] - c[2];

lenQ = (GLfloat) sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);

v[0] /= lenQ;

v[1] /= lenQ;

v[2] /= lenQ;

// Zohlednění v tak, abychom dostali texturovací souřadnice

c[0] = (s[0]*v[0] + s[1]*v[1] + s[2]*v[2]) * MAX_EMBOSS;

c[1] = (t[0]*v[0] + t[1]*v[1] + t[2]*v[2]) * MAX_EMBOSS;

}

Nepřipadá vám to tak komplikované jako předtím? Teorie je ale důležitá, abyste pochopili jak efekt funguje a jak ho ovládat. Během psaní tutoriálu jsem se to sám naučil :-]

Vždycky jsem chtěl zobrazit logo při běhu ukázkového programu. My teď taky dvě zobrazíme. Zavoláme funkci doLogo(). Ta vyresetuje GL_MODELVIEW matici, která musí být při posledním průchodu zavolána.

Tato funkce zobrazí dvě loga: OpenGl logo a logo multitexturingu, pokud je povolen. Loga jsou zčásti průhledná. Protože mají alpha kanál, smícháme je pomocí GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA podle OpenGL dokumentace. Obě dvě jsou ploché, nemáme pro ně souřadnici z. Čísla použitá pro hrany jsou zjištěny "empiricky" (pokus-chyba), tak aby loga padla pěkně do rožků. Musíme zapnout blending a vypnout světla, abychom se vyhli chybným efektům. Abychom zajistili, že loga budou vždy vepředu, vyresetujeme GL_MODELVIEW matici a nastavíme funkci na testování hloubky na GL_ALWAYS.

void doLogo(void)// MUSÍ SE ZAVOLAT AŽ NAKONEC!!! Zobrazí dvě loga

{

glDepthFunc(GL_ALWAYS);

glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

glEnable(GL_BLEND);

glDisable(GL_LIGHTING);

glLoadIdentity();

glBindTexture(GL_TEXTURE_2D,glLogo);

glBegin(GL_QUADS);

glTexCoord2f(0.0f,0.0f); glVertex3f(0.23f, -0.4f,-1.0f);

glTexCoord2f(1.0f,0.0f); glVertex3f(0.53f, -0.4f,-1.0f);

glTexCoord2f(1.0f,1.0f); glVertex3f(0.53f, -0.25f,-1.0f);

glTexCoord2f(0.0f,1.0f); glVertex3f(0.23f, -0.25f,-1.0f);

glEnd();

if (useMultitexture)

{

glBindTexture(GL_TEXTURE_2D,multiLogo);

glBegin(GL_QUADS);

glTexCoord2f(0.0f,0.0f); glVertex3f(-0.53f, -0.4f,-1.0f);

glTexCoord2f(1.0f,0.0f); glVertex3f(-0.33f, -0.4f,-1.0f);

glTexCoord2f(1.0f,1.0f); glVertex3f(-0.33f, -0.3f,-1.0f);

glTexCoord2f(0.0f,1.0f); glVertex3f(-0.53f, -0.3f,-1.0f);

glEnd();

}

glDepthFunc(GL_LEQUAL);

}

Teď přichází funkce na bumpmapping bez texturingu. Je to tří-průchodová implementace. Jako první GL_MODELVIEW matice se převrátí pomocí aplikace všech provedených kroků v opačném pořadí a obráceně na matici dané identity. Výsledkem je matice, která při aplikaci na objekt "vrací" GL_MODELVIEW. My jí jednoduše získáme funkcí glGetFloatv(). Pamatujte, že matice musí být pole s 16 prvky a že je tato matice "přesunuta"!

Mimochodem: Když přesně nevíte, jak se s maticí manipuluje, zvažte použití globálních souřadnic, protože převracení matice je složité a náročné na čas. Ale pokud používáte mnoho vertexů, převracení matice může být daleko rychlejší.

bool doMesh1TexelUnits(void)

{

GLfloat c[4] = {0.0f, 0.0f, 0.0f, 1.0f};// Aktuální vertex

GLfloat n[4] = {0.0f, 0.0f, 0.0f, 1.0f};// Normalizovaná normála daného povrchu

GLfloat s[4] = {0.0f, 0.0f, 0.0f, 1.0f};// Směr texturovacích souřadnic s, normalizováno

GLfloat t[4] = {0.0f, 0.0f, 0.0f, 1.0f};// Směr texturovacích souřadnic t, normalizováno

GLfloat l[4];// Pozice světla, která bude transformována do prostoru objektu

GLfloat Minv[16];// Převrácená modelview matice

int i;

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Smaže obrazovku a hloubkový buffer

// Sestavení převrácené modelview matice; nahradí funkce Push a Pop jednou funkcí glLoadIdentity()

// Jednoduché sestavení tím, že všechny transformace provedeme opačně a v opačném pořadí

glLoadIdentity();

glRotatef(-yrot,0.0f,1.0f,0.0f);

glRotatef(-xrot,1.0f,0.0f,0.0f);

glTranslatef(0.0f,0.0f,-z);

glGetFloatv(GL_MODELVIEW_MATRIX,Minv);

glLoadIdentity();

glTranslatef(0.0f,0.0f,z);

glRotatef(xrot,1.0f,0.0f,0.0f);

glRotatef(yrot,0.0f,1.0f,0.0f);

// Transformace pozice světla do souřadnic objektu:

l[0] = LightPosition[0];

l[1] = LightPosition[1];

l[2] = LightPosition[2];

l[3] = 1.0f;// Homogení souřadnice

VMatMult(Minv,l);

První fáze:

Tohle vyrenderuje krychli pouze z bumpmap.

glBindTexture(GL_TEXTURE_2D, bump[filter]);

glDisable(GL_BLEND);

glDisable(GL_LIGHTING);

doCube();

Druhá fáze:

Tohle vyrendruje krychli se správným emboss bumpmappingem, ale bez barev.

Mohli bychom ušetřit čas rotací vektoru světla opačným směrem. To však nefunguje úplně správně, tak to uděláme jinou cestou: otočíme každou normálu a prostřední bod stejně jako naši geometrii.

glBindTexture(GL_TEXTURE_2D,invbump[filter]);

glBlendFunc(GL_ONE,GL_ONE);

glDepthFunc(GL_LEQUAL);

glEnable(GL_BLEND);

glBegin(GL_QUADS);

// Přední stěna

n[0] = 0.0f;

n[1] = 0.0f;

n[2] = 1.0f;

s[0] = 1.0f;

s[1] = 0.0f;

s[2] = 0.0f;

t[0] = 0.0f;

t[1] = 1.0f;

t[2] = 0.0f;

for (i=0; i<4; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Zadní stěna

n[0] = 0.0f;

n[1] = 0.0f;

n[2] = -1.0f;

s[0] = -1.0f;

s[1] = 0.0f;

s[2] = 0.0f;

t[0] = 0.0f;

t[1] = 1.0f;

t[2] = 0.0f;

for (i=4; i<8; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Horní stěna

n[0] = 0.0f;

n[1] = 1.0f;

n[2] = 0.0f;

s[0] = 1.0f;

s[1] = 0.0f;

s[2] = 0.0f;

t[0] = 0.0f;

t[1] = 0.0f;

t[2] = -1.0f;

for (i=8; i<12; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Spodní stěna

n[0] = 0.0f;

n[1] = -1.0f;

n[2] = 0.0f;

s[0] = -1.0f;

s[1] = 0.0f;

s[2] = 0.0f;

t[0] = 0.0f;

t[1] = 0.0f;

t[2] = -1.0f;

for (i=12; i<16; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Pravá stěna

n[0] = 1.0f;

n[1] = 0.0f;

n[2] = 0.0f;

s[0] = 0.0f;

s[1] = 0.0f;

s[2] = -1.0f;

t[0] = 0.0f;

t[1] = 1.0f;

t[2] = 0.0f;

for (i=16; i<20; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Levá stěna

n[0] = -1.0f;

n[1] = 0.0f;

n[2] = 0.0f;

s[0] = 0.0f;

s[1] = 0.0f;

s[2] = 1.0f;

t[0] = 0.0f;

t[1] = 1.0f;

t[2] = 0.0f;

for (i=20; i<24; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

glEnd();

Třetí fáze:

Tohle dokončí renderování krychle s osvětlením. Nejdříve musíme nastavit texture environment na GL_MODULATE. Můžeme zapínat a vypínat multitexturing. Tuto fázi provedeme, jen pokud uživatel nechce vidět pouze emboss.

if (!emboss)

{

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

glBindTexture(GL_TEXTURE_2D,texture[filter]);

glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);

glEnable(GL_LIGHTING);

doCube();

}

Poslední fáze:

xrot += xspeed;

yrot += yspeed;

if (xrot > 360.0f)

xrot -= 360.0f;

if (xrot < 0.0f)

xrot += 360.0f;

if (yrot > 360.0f)

yrot -= 360.0f;

if (yrot < 0.0f)

yrot += 360.0f;

doLogo();// Nakonec loga

return true;

}

Další funkce udělá tohle všechno ve dvou fázích s podporou multitexturingu. Použijeme dvě texturovací jednotky. Více by bylo extrémně obtížné vzhledem k blendingovým rovnicím. Lépe použít TNT. Všimněte si, že se funkce liší od doMesh1TexelUnits() jen tím, že posíláme dvě sady texturovacích souřadnich na každý vertex!

bool doMesh2TexelUnits(void)

{

GLfloat c[4] = {0.0f,0.0f,0.0f,1.0f};// Aktuální vertex

GLfloat n[4] = {0.0f,0.0f,0.0f,1.0f};// Normalizovaná normála povrchu

GLfloat s[4] = {0.0f,0.0f,0.0f,1.0f};// Směr texturovacích souřadnic s, normalizováno

GLfloat t[4] = {0.0f,0.0f,0.0f,1.0f};// Směr texturovacích souřadnic t, normalizováno

GLfloat l[4];// Pozice světla k převedení na souřadnice objektu

GLfloat Minv[16];// Převrácená modelview matice

int i;

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Smaže obrazovku a hloubkový buffer

// Sestavení převrácené modelview matice, tohle nahradí funkce Push a Pop jednou funkcí glLoadIdentity()

// Jednoduché sestavení tím, že všechny transformace provedeme opačně a v opačném pořadí

glLoadIdentity();

glRotatef(-yrot,0.0f,1.0f,0.0f);

glRotatef(-xrot,1.0f,0.0f,0.0f);

glTranslatef(0.0f,0.0f,-z);

glGetFloatv(GL_MODELVIEW_MATRIX,Minv);

glLoadIdentity();

glTranslatef(0.0f,0.0f,z);

glRotatef(xrot,1.0f,0.0f,0.0f);

glRotatef(yrot,0.0f,1.0f,0.0f);

// Transformace pozice světla na souřadnice objektu:

l[0] = LightPosition[0];

l[1] = LightPosition[1];

l[2] = LightPosition[2];

l[3] = 1.0f;// Homogení souřadnice

VMatMult(Minv,l);

První fáze:

Nastavení texture combineru 0 na

Nastavení texture combineru 1 na

Tohle vyrenderuje krychli skládající se z šedých map.

// TEXTUROVACÍ JEDNOTKA #0:

glActiveTextureARB(GL_TEXTURE0_ARB);

glEnable(GL_TEXTURE_2D);

glBindTexture(GL_TEXTURE_2D, bump[filter]);

glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);

glTexEnvf (GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE);

// TEXTUROVACÍ JEDNOTKA #1:

glActiveTextureARB(GL_TEXTURE1_ARB);

glEnable(GL_TEXTURE_2D);

glBindTexture(GL_TEXTURE_2D, invbump[filter]);

glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);

glTexEnvf (GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);

// Obecné přepínače

glDisable(GL_BLEND);

glDisable(GL_LIGHTING);

Teď pouze vyrenderujeme stěny jednu po druhé jako v doMesh1TexelUnits(). Pouze jedna novinka: používá glMultiTexCoordfARB() místo glTexCoord2f(). Všimněte si, že v prvním parametru je uvedeno, které texturovací jednotce přísluší souřadnice. Parametr musí být GL_TEXTUREi_ARB, kde i je v intervalu od 0 do 31.

glBegin(GL_QUADS);

// Přední stěna

n[0] = 0.0f;

n[1] = 0.0f;

n[2] = 1.0f;

s[0] = 1.0f;

s[1] = 0.0f;

s[2] = 0.0f;

t[0] = 0.0f;

t[1] = 1.0f;

t[2] = 0.0f;

for (i=0; i<4; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glMultiTexCoord2fARB(GL_TEXTURE0_ARB, data[5*i], data[5*i+1]);

glMultiTexCoord2fARB(GL_TEXTURE1_ARB, data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Zadní stěna

n[0] = 0.0f;

n[1] = 0.0f;

n[2] = -1.0f;

s[0] = -1.0f;

s[1] = 0.0f;

s[2] = 0.0f;

t[0] = 0.0f;

t[1] = 1.0f;

t[2] = 0.0f;

for (i=4; i<8; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);

glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Horní stěna

n[0] = 0.0f;

n[1] = 1.0f;

n[2] = 0.0f;

s[0] = 1.0f;

s[1] = 0.0f;

s[2] = 0.0f;

t[0] = 0.0f;

t[1] = 0.0f;

t[2] = -1.0f;

for (i=8; i<12; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);

glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Dolní stěna

n[0] = 0.0f;

n[1] = -1.0f;

n[2] = 0.0f;

s[0] = -1.0f;

s[1] = 0.0f;

s[2] = 0.0f;

t[0] = 0.0f;

t[1] = 0.0f;

t[2] = -1.0f;

for (i=12; i<16; i++)

{

>c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);

glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Pravá stěna

n[0] = 1.0f;

n[1] = 0.0f;

n[2] = 0.0f;

s[0] = 0.0f;

s[1] = 0.0f;

s[2] = -1.0f;

t[0] = 0.0f;

t[1] = 1.0f;

t[2] = 0.0f;

for (i=16; i<20; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);

glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

// Levá stěna

n[0] = -1.0f;

n[1] = 0.0f;

n[2] = 0.0f;

s[0] = 0.0f;

s[1] = 0.0f;

s[2] = 1.0f;

t[0] = 0.0f;

t[1] = 1.0f;

t[2] = 0.0f;

for (i=20; i<24; i++)

{

c[0] = data[5*i+2];

c[1] = data[5*i+3];

c[2] = data[5*i+4];

SetUpBumps(n,c,l,s,t);

glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);

glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);

glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);

}

glEnd();

Druhá fáze:

Tohle vyrenderuje celou bumpmapovanou krychli.

glActiveTextureARB(GL_TEXTURE1_ARB);

glDisable(GL_TEXTURE_2D);

glActiveTextureARB(GL_TEXTURE0_ARB);

if (!emboss)

{

glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

glBindTexture(GL_TEXTURE_2D,texture[filter]);

glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);

glEnable(GL_BLEND);

glEnable(GL_LIGHTING);

doCube();

}

Poslední fáze:

xrot += xspeed;

yrot += yspeed;

if (xrot>360.0f)

xrot -= 360.0f;

if (xrot<0.0f)

xrot += 360.0f;

if (yrot>360.0f)

yrot -= 360.0f;

if (yrot<0.0f)

yrot += 360.0f;

doLogo();// Nakonec loga

return true;

}

Konečně funkce na renderování bez bumpmappingu - abychom mohli vidět ten rozdíl!

bool doMeshNoBumps(void)

{

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Smaže obrazovku a hloubkový buffer

glLoadIdentity();// Reset matice

glTranslatef(0.0f,0.0f,z);

glRotatef(xrot,1.0f,0.0f,0.0f);

glRotatef(yrot,0.0f,1.0f,0.0f);

if (useMultitexture)

{

glActiveTextureARB(GL_TEXTURE1_ARB);

glDisable(GL_TEXTURE_2D);

glActiveTextureARB(GL_TEXTURE0_ARB);

}

glDisable(GL_BLEND);

glBindTexture(GL_TEXTURE_2D,texture[filter]);

glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);

glEnable(GL_LIGHTING);

doCube();

xrot += xspeed;

yrot += yspeed;

if (xrot>360.0f)

xrot -= 360.0f;

if (xrot<0.0f)

xrot += 360.0f;

if (yrot>360.0f)

yrot -= 360.0f;

if (yrot<0.0f)

yrot += 360.0f;

doLogo();// Nakonec loga

return true;

}

Vše co musí drawGLScene() udělat je rozhodnout jakou doMesh funkci zavolat.

bool DrawGLScene(GLvoid)// Všechno kreslení

{

if (bumps)

{

if (useMultitexture && maxTexelUnits > 1)

return doMesh2TexelUnits();

else

return doMesh1TexelUnits();

}

else

return doMeshNoBumps();

}

Hlavní funkce Windows, přidány některé klávesy:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

// Začátek zůstává nezměněn

if (keys['E'])

{

keys['E']=false;

emboss=!emboss;

}

if (keys['M'])

{

keys['M']=false;

useMultitexture=((!useMultitexture) && multitextureSupported);

}

if (keys['B'])

{

keys['B']=false;

bumps=!bumps;

}

if (keys['F'])

{

keys['F']=false;

filter++;

filter%=3;

}

if (keys[VK_PRIOR])

{

z-=0.02f;

}

if (keys[VK_NEXT])

{

z+=0.02f;

}

if (keys[VK_UP])

{

xspeed-=0.01f;

}

if (keys[VK_DOWN])

{

xspeed+=0.01f;

}

if (keys[VK_RIGHT])

{

yspeed+=0.01f;

}

if (keys[VK_LEFT])

{

yspeed-=0.01f;

}

// Konec také nezměněn

}

Teď když jsme zvládli tento tutoriál, pár slov o generování textur a bumpmapových objektů. Předtím, než začnete programovat ambiciózní hry a budete se divit, proč bumpmapping není tak rychlý a nevypadá tak dobře, přečtěte si toto:

Poděkování:

napsal: Jens Schneider <schneide (zavináč) pool.informatik.rwth-aachen.de>
přeložil: Václav Slováček - Wessan <horizont (zavináč) host.sk>

Lekce 22

<<< Lekce 21 | Lekce 23 >>>