Jak vyzrát na efekty 1.díl - Světla - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:



C/C++

Jak vyzrát na efekty 1.díl - Světla

13. března 2002, 00.00 | V tomto článku si ukážeme, jak naprogramovat jednoduché čočkové efekty, které vylepší náš 3D engine..

Tak jsem tu zase. Dnes Vás nebudu otravovat nějakými kolizemi, ale ukážeme si, jak udělat něco, co je opravdu na programu na první pohled vidět. Každý z Vás asi vidí o kolo každého světla záři. Pokud budeme mít dosti intenzivní zdroj světla, jakým je například Slunce, můžeme při jeho natáčení na kameru padnout na další zajímavý efekt a tím je jakýsi kruh okolo středu a série dalších kruhů a prstenců různých barev, které jsou rozmístěny na jedné přímce směrem od světla ke středu objektivu. Tyto efekty budeme souhrně nazývat čočkové, protože vznikají díky chybám čoček.

Co budeme potřebovat?
Nejdříve si musíme světlo umístit do prostoru a budeme s ním zacházet jako se světlem bodovým(světlo se šíří v kuloplochách směrem od středu).

typedef struct
{
  float r,g,b,a;
} T_COLOR_RGBA;

typedef struct
{
  float r,g,b;
} T_COLOR;

class C_LIGHT : public C_OBJECT
{
public:
  T_COLOR_RGBA glow_color;

  float position[3]; // this is for easiest manipulation of light coordinates
  foat glow_size; // size of light glow (in world size no in window size !!!)
  unsigned int flags;

C_LIGHT(); 
~C_LIGHT();

int Set_Position(float X,float Y,float Z); // set position of the light

int Set_Glow_Color(float r,float g,float b,float a);
int Set_Glow_Color(const T_COLOR_RGBA& color);

int Set_Glow_Size(float new_size); // set size of the light

int Add_Flag(unsigned int flag); // add new fx flag
int Clear_Flags(void); // set flags to 0x0000000 -> no fx

int Draw(C_CAMERA * camera); // draw teh light
int Draw_Glow(void); // draw glow :-)

int Move(); // this place the light into it's position
};
 
Co znamená proměnná flags? Do ní uložíme parametry světla jako Glow,Start,Ring (ale to až dále). Do této proměnné zapisujeme pomocí funkce Add_Flag(flag). Tato funkce provede asi toto : flags=flags | flag; (přidá ji do seznamu). Proto doporučuji při inicializaci světla spustit funkci Clear_Flags(), která nastaví proměnnou flags na 0x00000 (0). Zjištění jestli je položka v seznamu je velice jednoduché if(flags & flag == 1)...je v seznamu.

Nadefinujme si tedy základní flagy :
#define LIGHT_FX_RING 0x00000001 //prstenec okolo středu
#define LIGHT_FX_STAR 0x00000010 //paprsky
#define LIGHT_FX_GLOW 0x00000100 // záře
#define LIGHT_FX_ASEC 0x00001000 //automatic secondaries

Záře
Teď jsme si tedy nadefinovali, jak bude vypadat základ našeho objektu SVĚTLO. Postupně budeme připisovat jednotlivé metody a proměnné. S takto definovaným světlem jsme již schopni vykreslit základní záři se středem v position[3](x,y,z), barvě glow_color a velikosti glow_size (jde samozřejmě o kružnici, tudíž o poloměr). Nejlepším řešením je si onu záři uložit jako bitmapu a do scénu ji následně plácat pomocí blendingu.

Druhé bitmapky si zatím nebudeme všímat, k ní se dostaneme později. Jelikož je bitmapa v odstínech šedi, umožňuje nám měnit libovolně její barvu pomocí příkazu glColor4fv(glow_color). Pochopitelně musíme nejdříve nastavit požadovanou barvu. Někteří z Vás si teď možná řeknou, že sice vykreslíme plošku v prostoru, která bude mít na sebe "naplácnutou" tuto texturu, ale jakmile se kamera otočí jiným směrem, otočí se i tato ploška. Jak zařídit, aby normála plošky byla stále orientována směrem ke kameře? Je to relativně jednoduché. Nadefinujme si matici4x4 float Matrix4x4[16]; Do ní můžeme nahrát MODEL_VIEW_MATRIX z OpenGL : glGetFloatv(GL_MODELVIEW_MATRIX,Matrix4x4); Když z této matice, která vyjadřuje umístění objektů v prostoru z pohledu kamery zbavíme posunutí(kamery) a transponujeme ji(prohodíme sloupce za řádky), dostaneme matici, která nám defakto "zruší" natočení kamery a ploška bude stále stejně orientována k vektoru, kterým se kamera kouká (tzn. stále to bude čtverec) :-)

void Uprav_Matici(float * matice[])
{
float temp[16];
int x,y;

//odstranime pounuti
matice[3] = 0.0f;
matice[3] = 0.0f;
matice[7] = 0.0f;
matice[11] = 0.0f;
matice[12] = 0.0f;
matice[13] = 0.0f;
matice[14] = 0.0f;
matice[15] = 1.0f; 

// transponujeme matici (prohodime radky za sloupce)

for(x = 0; x < 4; x++)
for(y = 0; y < 4; y++)
{
temp[x,y] = matice[y,x];
}

// upravime nasi matici
memcpy(matice,temp,sizeof(float)*16);
};

Z důvodu úspory počítám tuto matici při posunutí kamery (C_CAMERA), která obsahuje model_view_matrix a transponovanou model_view_matri, dále svoje souřadnice. Pokud chcete celou její třídu, stáhněte si demo ze článku Jak vyzrát na kolize 3. Zde ji z úsporných důvodů znovu uvádět nebudu.

Teď jsme úspěšně matici transponovali a již s ní stačí pouze vynásobit současnou model_view_matrix, aby jsme zrušili ono otočení. Následně vykreslíme čtverec s texturou záře.

glPushMatrix();  //ulozime model_view_matrix

glTranslatef(position[0],position[1],position[2]); //umistime svetlo do prostoru
glMultMatrixd(Matrix4x4); //vynasobime transponovanou matici

if(flags & LIGHT_FX_GLOW) Draw_Glow(); //pokud chceme u svetla kreslit zari, tak ji vykreslime

glPopMatrix(); //vratime model_view_matrix do puvodniho stavu

return FCE_OK;
};

Na monitoru to pak vypadá asi takto : (světlo je nastaveno na [1,1,1,1])

Paprsky
U paprsků postupujeme stejně jako u záře (nastavíme barvu, velikost), až na jeden menší detail. Pokud překryjeme nějakým objektem střed světla, budeme chtít, aby se paprsky nevykreslily, docílíme tím docela pěkného efektu při průletu objektu před světlem, paprsky zmizí, záře je stále vidět, pokud objekt nezakrývá celé světlo. K zjištění viditelnosti světla použijeme funkci OpenGL gluProject(), které zadáme souřadnice x,y,z, model_view_matrix, projekční matici a funkce nám vrátí x,y,z souřadnice v okně (z... je hloubka v okně, shodná se z-bufferem). Matice model_view_matrix, projection_matrix a parametry viewportu viewport načteme pomocí fce. glGetFloatv(). Objekt C_CAMERA obsahuje tyto informace po celou doby jednoho snímku, nemusí se proto nahrávat pro každé světlo, zdrojové kódy naleznete ve článku Jak vyzrát na kolize 3. (3D_camera.h/cpp a 3D_init.h/cpp)
int C_LIGHT::Is_Center_Visible(C_CAMERA * camera)
{
  float dist;  // aktualni hodnota v Z-Bufferu

  gluProject(x,y,z,model_view_amtrix,projection_matrix,view_port,&wnd_x,&wnd_y,&wnd_z);  // načteme souřadnice světla v okně

  glReadPixels((int)wnd_x,(int)wnd_y,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,&dist);  // zjsitime aktualni hodnotu Z-Bufferu na souřadnicich světla

  if(dist >= wnd_z) return TRUE; else return FALSE;  //pokud je hodnota Z-Bufferu vetší nebo rovna pozici světla v okne, je střed viditelny
};

Logicky nám do objektu C_CAMERA přibyla metoda Is_Center_Visible() a proměnné wnd_x,wnd_y,wnd_z (souřadnice světla v okně). Do metody Draw() můžeme nyní připsat pár řádek :

if(Is_Center_Visible(camera) == TRUE)
{
  if(flags & LIGHT_FX_STAR) Draw_Star(); // pokud chceme kreslit paprsky, tak je vykreslime
}

Na monitoru to pak vypadá asi takto : (paprsky maji barvu [0.9,0.8,0.0,1.0], střed světla není překrytý)

Prstenec
Tady už nebudeme potřebovat texturu, protože použijeme opět jednu z funkcí OpenGL a tou je gluDrawDisk(). K tomu, aby se něco vykreslilo, musíme vytvořit GLUquadricObj * light pomocí funkce light = gluNewQuadric(); Všechna světla mohou tuto proměnnou sdílet, tak ji není třeba vkládat do třídy C:LIGHT. Přidáme tedy fnkci Draw_Ring();
int C_LIGHT::Draw_Ring(void)
{
glColor4f(ring_color[0],ring_color[1],ring_color[2],ring_color[3]); // nastavime barvu
gluDisk(light,vnitrni_polomer,vnejsi_polomer,pocet_segmentu,1); // vykreslime prstenec
return FCE_OK;
};

Take upravime fci. Draw()
...
if(Is_Center_Visible() == TRUE)
{
...
if(flags & LIGHT_FX_RING) Draw_Ring();
...
}

V praxi to vypadá asi takto:

Kamerový čočkový efekt
Toto byl asi nejlepší název, který mě napadl, například v 3D Studiu se mu říká Manual Secondaries. K vidění je převážně na filmech a to v případě, že kamera zabírá slunce(ale ne na každém filmu). Celý systém spočívá v tom, že vezmeme přímku, která prochází středem světla a středem kamery a na ni umístíme různě barevné kruhy a prstence. Aby jsme zabránili zmenšování kruhů v závislosti na vzdálenosti světla od kamery, přepneme v okamžiku vykreslování z perspektivy do ortografického zobrazení a budeme kreslit rovnou na "průmětnu" kamery. Aby byl celý systém variabilní, uložíme si informace o jednotlivých segmentech do libovolného pole, do kterého hodnoty načteme např. ze souboru.
typedef struct
{
int size;  // prumer
T_COLOR_RGBA color;  // barva
int distance;  // vzdalenost od svetla (po přímce)
char disk;  //je to prstenec nebo ne
float inner_radius; // vnitrni radius v procentech vnejsiho 
} T_ASEC_ELEMENT;  // jeden segment

// Do light.cpp pridame

T_ASEC_ELEMENT asec_data[asec_count];

inv C_LIGHT::Draw_ASec(void);
{
int i; // cyklus
float wp_c_x,wp_c_y; //stred view_portu
float wp_w,wp_h; //sirka a vyska view_portu
float wp_len,li_len; //uhlopricka viewportu a vzdalenost svetla od stredu
C_VECTOR direction; // vektor primku (svetlo->kamera)
C_VECTOR position; //kam mam vykreslit kruh?

wp_w = (float)engine.renderer.Get_ViewPort_Width(); // ziskame sirky viewportu, tek lze pomoci glGetFloatv(GL_VIEPORT,xxx)
wp_h = (float)engine.renderer.Get_ViewPort_Height(); // ---||---
wp_len =(float) sqrt(sqr(wp_w / 2) + sqr(wp_h / 2)); // vypocitame uhlopricku

wp_c_x = wp_w / 2.0f; // urcime stred
wp_c_y = wp_h / 2.0f; // urcime stred

direction.Set((float)(wnd_x - wp_c_x),float(wnd_y - wp_c_y),0.0f); // urcime vektor
li_len = direction.Compute_Length(); // zjistime vzdalenost svetla od stredu

direction.Normalize(); // normalizujeme vektor
direction = direction * (li_len / wp_len) * 2; //zmensime ho v pomeru vzdalenost_od _stredu : delka_uhlopricky

glMatrixMode(GL_PROJECTION); // Zvolime projkcni matici
glPushMatrix(); // Ulozime ji
glLoadIdentity(); // Vumazeme ji
glOrtho(0,wp_w,0,wp_h,-1,1); // Nastavime ortograficke promitani

glDisable(GL_TEXTURE_2D); // zakazemem textury

glMatrixMode(GL_MODELVIEW); // vybereme model_view matici
glPushMatrix(); // ulozim ji 

for(i = 0; i < asec_count; i++) // projedeme vsechny segmenty
{
glLoadIdentity(); // Vymazeme matici

position = direction * (float)-asec_data[i].distance; // napocitame x,y pozici segmentu

glTranslated(wnd_x + position.vx,wnd_y + position.vy,0); // umistime do prostoru

glColor4f(asec_data[i].color[0],asec_data[i].color[1],asec_data[i].color[2],asec_data[i].color[3]); // nastavime barvu

gluDisk(light_quadric,asec_data[i].size * asec_data[i].inner_radius,asec_data[i].size,engine.info.light_asec_divisions,1); // vykreslime disk nebo prstenec
}

glMatrixMode(GL_PROJECTION); // Vybereme projekcni matici
glPopMatrix(); // Nahrajeme puvodni matici

glEnable(GL_TEXTURE_2D); // zapneme textury

glMatrixMode(GL_MODELVIEW); // Vybereme model_view matici
glPopMatrix(); // Nahrajeme puvodni

return FCE_OK;
};

Vim, že to asi vypadá dosti hnusně, ale je to velice jednoduché, stačí si to nakreslit. Do Draw() nám přirozeně přibude :

...
if(Is_Center_Visible() == TRUE)
{
...
if(flags & LIGHT_FX_ASEC) Draw_ASec();
...

A co na to praxe?

No a to je vše! Zdrojové kódy naleznete ZDE

Těším se nashledanou u dalšího dílu, budeme se zabývat částicovými systémy.


Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » C/C++  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: