Jak vyzrát na efekty 4.díl - SkyBoxy, mlhoviny, atd. - 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 4.díl - SkyBoxy, mlhoviny, atd.

effekt

26. dubna 2002, 00.00 | Toto je poslední díl seriálu Jak vyzrát na efekty. Dnes se pokusím popsat pár jednoduchých efektů pro vytváření pozádí ve 3D hrách.

Dnes se naposledy setkáváme v našem seriálu Jak vyzrát na efekty. V minulých dílech jsem se Vám pokusil vysvětlit systém tvoření základních efektů, které zkrášlí váš prográmek. Jde o efekty jednoduché, ale silné. Pomocí nich můžete vytvořit spoustu velice zajímavých efektů od hořícího ohně, průletu rakety atmosférou, efekty zásahu, jiskry, kouřové stopy za raketami, laserové zbraně po efekt vady čočky. Ale ještě jeden důležitý efekt, nebo spíše techniku jsem tu nezmínil. Tato technika je velice jednoduchá a výsledný efekt opravdu stojí za to.

SKY-BOX
Pokud děláte jakoukoliv hru, je dobré mít nějaké pozadí, přeci jen sebelepší 3D střílečka nebude vypadat dobře, pokud na pozadí nebude např. město, hory, atd. Vezměme si opět hru Half-Life (nebo Sin, atd.). Představte si, jaký by na vás tvořil dojem onen kaňon, kdyby na pozadí nebyly hory, ale jen černá barva?! Technika, kterou autoři těchto her používají se nazývá SKY-BOX. Jak jsem již říkal, celý systém je velice jednoduchý. K vytvoření pozadí nám postačí krychle o rozměrech w,h,d (šířka, hloubka, výška), se středem o souřadnicích kamery a 6 texturami.

Nadefinujme si tedy základní třídu C_SKY_BOX

class C_SKY_BOX
{
private:
  C_CAMERA * camera;
  int tex_ID[6];
public:
  C_SKY_BOX();
  int Change_Camera(C_CAMERA * to);
  int Draw(float w,float h,float d);
  int Load_Texture(char file[],int to);
};

Opět zde máme objekt / třídu C_CAMERA, tu můžeme nahradit souřadnicemi jejího středu (x,y,z), ale takhle je to myslím srozumitelnější. Fce. Change_Camera() nastaví aktuální kameru, Draw() vykreslí SKY-BOX o daných rozměrech a fce. Load_Texture() nahraje texturu pro stěnu to. Pro snažší práci by jsme si měli nadefinovat pár proměnných / konstant
#define SBOX_FRONT 0
#define SBOX_RIGHT 1
#define SBOX_BACK 2
#define SBOX_LEFT 3
#define SBOX_TOP 4
#define SBOX_BOTTOM 5
Tak, toto jsou čísla jednotlivých stěn, myslím si, že když je pojmenujeme, bude mít lepší představu o tom, jakou stěnu právě nahráváme :-) Nahrávací fci. pro textury zde rozepisovat nebudu, pokud programujete pod OpenGL je definice celé třídy správná (int tex_ID[6] je pole čísel, která vrací fce. glGenTextures(); ) u Direct3D to bude asi trochu jinak. Jediné na co si musíme dát opravdu pozor jsou texturové koordináty pro jednotlivé stěny a při vykreslování je dobré vědět jak jsme nastavili perspektivu, pokud máme near_clip nastaven na 1.0, nemá smysl vykreslovat SKYBOX o rozměrech (1,1,1), poněvadž se stěny "oříznou" a cokoliv co bude blíže jak 1.0 se prostě nezobrazí, v tomto případě je lepší vykreslovat SKYBOX o rozměrech (2,2,2) a výše. Také musíme vypnout osvětlování (GL_LIGHTING), depth-testing (GL_DEPTH_TEST), ořezávání neviditelných polygonů (GL_CULL_FACE) a doporučuji i mlhu (GL_FOG). SKYBOX vždy vykreslujeme jako první!!! Co se týká hodnoty tex_ID[] 0..max (textura je nahrána), -1 (textura není nahrána). Díky tomuto můžeme vykreslit např. jednu stěnu s obrázkem mlhoviny, atd.

int C_SKY_BOX::Draw(float w,float h,float d)
{
int tex_tiling = 1;  // kolikrat chceme kreslit texturu vedle sebe
int fog_used = FALSE; //byla mlha zapnuta?

glPushMatrix();  //ulozime zobrazovaci matici (GL_MODELVIEW_MATRIX)

if(camera == NULL) return FCE_ERROR; //pokud není kamera, nic se kreslit nebude

glTranslatef(camera->x,camera->y,camera->z); // posuneme střed skyboxu do pozice kamery

glColor4f(1.0,1.0,1.0,1.0);  // nastavime barvu na bílou (to je jen taková formalitka, ale může nám ušetřit pár šedin
glDisable(GL_CULL_FACE); //vypneme orezávání neviditelných stěn
glDisable(GL_LIGHTING); //vypneme osvětlování
glDepthMask(GL_FALSE); //vypneme depth buffer (zde pouze nezapisujeme, ale v podstate je to to samé jako kdyz ho vypneme

if(glIsEnabled(GL_FOG) == GL_TRUE) // pokud byla zapnuta mlha, vypneme ji, ale potom ji musíme zase zapnout
{
fog_used = TRUE; //ano, mlha byla pouzita
glDisable(GL_FOG);  // vypenem mlhu
};

glEnable(GL_TEXTURE_2D);  // zapneme texturovani

if(tex_ID[2] != -1) // pokud textura je, vykreslíme stěnu krychle
{
Texture_Bind(GL_TEXTURE_2D,tex_ID[2]);  // nabindujeme texturu (to same jako glBindTexture(), ale ja textury ukladam trochu jinak, tak mam i jine fce.
glBegin(GL_QUADS);
glTexCoord2d(0,0); glVertex3d(-w, d, h);
glTexCoord2d(1,0); glVertex3d( w, d, h); 
glTexCoord2d(1,1); glVertex3d( w, d,-h);
glTexCoord2d(0,1); glVertex3d(-w, d,-h);
glEnd();
}

if(tex_ID[0] != -1)  // nabindujeme texturu (to same jako glBindTexture(), ale ja textury ukladam trochu jinak, tak mam i jine fce.
{
Texture_Bind(GL_TEXTURE_2D,tex_ID[0]);
glBegin(GL_QUADS);
glTexCoord2d(0,0); glVertex3d( w,-d, h);
glTexCoord2d(1,0); glVertex3d(-w,-d, h); 
glTexCoord2d(1,1); glVertex3d(-w,-d,-h);
glTexCoord2d(0,1); glVertex3d( w,-d,-h);
glEnd();
}

if(tex_ID[1] != -1) // nabindujeme texturu (to same jako glBindTexture(), ale ja textury ukladam trochu jinak, tak mam i jine fce.
{
Texture_Bind(GL_TEXTURE_2D,tex_ID[1]);
glBegin(GL_QUADS);
glTexCoord2d(1,0); glVertex3d( w,-d, h);
glTexCoord2d(0,0); glVertex3d( w, d, h); 
glTexCoord2d(0,1); glVertex3d( w, d,-h);
glTexCoord2d(1,1); glVertex3d( w,-d,-h);
glEnd();
}

if(tex_ID[3] != -1) // nabindujeme texturu (to same jako glBindTexture(), ale ja textury ukladam trochu jinak, tak mam i jine fce.
{
Texture_Bind(GL_TEXTURE_2D,tex_ID[3]);
glBegin(GL_QUADS);
glTexCoord2d(1,0); glVertex3d(-w, d, h);
glTexCoord2d(0,0); glVertex3d(-w,-d, h); 
glTexCoord2d(0,1); glVertex3d(-w,-d,-h);
glTexCoord2d(1,1); glVertex3d(-w, d,-h);
glEnd();
}

if(tex_ID[4] != -1) // nabindujeme texturu (to same jako glBindTexture(), ale ja textury ukladam trochu jinak, tak mam i jine fce.
{
Texture_Bind(GL_TEXTURE_2D,tex_ID[4]);
glBegin(GL_QUADS);
glTexCoord2d(1,0); glVertex3d(-w,-d, h);
glTexCoord2d(1,1); glVertex3d( w,-d, h); 
glTexCoord2d(0,1); glVertex3d( w, d, h);
glTexCoord2d(0,0); glVertex3d(-w, d, h);
glEnd();
}

if(tex_ID[5] != -1) // nabindujeme texturu (to same jako glBindTexture(), ale ja textury ukladam trochu jinak, tak mam i jine fce.
{
Texture_Bind(GL_TEXTURE_2D,tex_ID[5]);
glBegin(GL_QUADS);
glTexCoord2d(0,0); glVertex3d(-w,-d,-h);
glTexCoord2d(0,1); glVertex3d( w,-d,-h); 
glTexCoord2d(1,1); glVertex3d( w, d,-h);
glTexCoord2d(1,0); glVertex3d(-w, d,-h);
glEnd();
}

// opet vse zapneme
glEnable(GL_CULL_FACE);
glDepthMask(GL_TRUE); //Enable depth-buffer for writting
if(fog_used == TRUE) glEnable(GL_FOG);
glPopMatrix(); // vratime puvodni zobrazovaci matici

return FCE_OK;
};


A jak to vypadá
Teď už záleží jen na nás, jaké zvolíme textury. Textura v příkladu je použita ze hry Half-Life, bohužel nejsem moc dobrý grafik, tak si raději textury "půjčuji" :-) Na prvním obrázku je záběr SKYBOXu o rozměrech (2,2,2), ale porovnejte ho s obrázkem druhým, zde má SKYBOX rozměry (2,1,2). Tímto "zplácnutím" docílíte dojmu větší vzdálenosti mezi kamerou a pozadím. Na třetím obrázku jsou zvýrazněny hrany SKYBOXu.




Ale vše má své "mouchy"
Tento způsob se hodí spíše na nějakou krajinu, či město na pozadí. Pokud ale chcete vykreslit např. hvězdy, nedoporučuji používat SKYBOXy, protože se všechny pixely zvětší (pokud nepoužíváte textury 1024x1024) :-) a rozmažou (pokud máte zapnuto lineární interpolaci). A to nemluvím o velkých mlhovinách (například hra Home-World). Pokud chcete, aby body a mlhoviny měli stále stejnou ostrost (bez ohledu na rozlišení), musíte je prostě vykreslovat jako body nebo polygony. 

Hvězdné pole
U hvězdného pole je to velice jednoduché. Při startu programu si napočítáte pole bodů (můžete přidat i barvu) například o 2000 bodech. Výpočet bodu na kouli je velice jednoduchý. Stačí nám k tomu dva úhly (alpha, beta) a vzdálenost od středu. K napočítání všech bodů hvězdného pole přidáme ještě proměnnou num (počet bodů). 

typedef struct
{
  float  x,y,z;  -- pozice 
  float r,g,b;  -- barva
} T_STAR;

int C_STARFIELD::Init(int num,float radius,float r,float g,float b) //pocet hvezd, radius, barva
{
float alpha,beta;  //uhly
float q; // pomocna promenna
int i; //index v poli hvezd

if(num <= 0) return FCE_ERROR; // zadna hvezda, zadne pocitani

starfield = (T_STAR*)malloc(sizeof(T_STAR)*num);  // vytvorime si pole o velikosti num polozek

if(starfield == NULL) // pokud neni dostatek pameti, nic zkoncime
{
fprintf(stderr,"ERROR: Not enough memory for stars!\n");
return FCE_ERROR;
};

for(i = 0; i < num; i++)
{
//vytvorime nahodne uhly
 alpha = RAD((float)(rand() % 360));
  beta = RAD((float)(rand() % 360));

q = (float)cos(beta);  // viz obr.

vypocitame souradnice
starfield[i].x = (float)(radius * cos(alpha) * q); //
starfield[i].y = (float)(radius * sin(alpha) * q);
starfield[i].z = (float)(radius * sin(beta) * w);

//ulozime barvu 
starfield[i].r = r;
starfield[i].g = g;
starfield[i].b = b;
};

return FCE_OK;
};

Jak napočítáme onen bod na kouli?


1) napočítáme si body (x1,y1) na kružnici
  x1 = radius * cos(alpha);
  y1 = radius * sin(alpha);
2) spočítáme si Z souřadnici hledaného bodu a pomocnou proměnou q
  z = radius * sin(beta);
  q = cos(beta);
3) kdyby jsme teď zadali bod jako (x1,y1,z), ležel by na válci, ale ne na kružnici. Proto musíme "kružnici" na které leží body x1,y1 zmenšit, jako kdyby měla poloměr roven velikosti odvěsny v trojúhelníku vyznačeném zelenou, žlutou a kusem červené čáry. Když použijeme goniometrické fce. dostaneme vztah odvesna = radius * cos(beta); Z toho vyplývá, že když původní souřadnice pouze vynásobíme cos(beta) dostaneme to samé. Tímto jsme se dostali ke konečné podobě výpočtu
  x = radius * cos(alpha) * q;
  y = radius * sin(alpha) * q;
  z = radius * sin(beta);

Dále už jen vykreslíme body z tohoto pole jako (GL_POINTS), ale nesmíme před vykreslování zapomenout posunout střed této koule do pozice kamery.

int Draw(C_CAMERA * camera)
{
  int i;
// vypneme vse, co by nam mohlo vadit
  glDisable(GL_LIGHTING);
  glDisable(GL_TEXTURE_2D);
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_FOG); //pokud mlhu nepouzivame, muzeme toto vynechat

  glPushMatrix(); //ulozime zobrazovaci matici
  glTranslatef(camera->x,camera->y,camera->z); //posuneme stred koule do pozice kamery

  for( i =0; i < num; i++)
  {
    glBegin(GL_POINTS);
      glColor3f(starfield[i].r,starfield[i].g,starfield[i].b);  //nastavime barvu
      glVertex3f(starfield[i].x,starfield[i].y,starfield[i].z); //vykreslime bod
   glEnd();

  glPopMatrix(); // vratime zpet zobrazovaci matici

//vse zase hezky zapneme
  glEnable(GL_LIGHTING);
  glEnable(GL_TEXTURE_2D);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_FOG);  // pokud mlhu nepouzivame, muzeme toto vynechat
 
  return FCE_OK;
};

Mlhoviny
Stejným způsobem budeme pokračovat i u mlhovin, pouze si nahrajeme model nějaké mlhoviny a jeho střed opět posuneme do pozice kamery. Doporučuji vytvářet model jako polygony ležící na kouli, ne nějaké složité "prostorové" modely. K tomuto účelu nejlépe poslouží v 3D Studiu GeoSphere (skládá se z trojúhelníků ne ze čtyřúhelníků jako normální koule). Z této GeoSphery pouze "vyřežeme" / vymažeme trojúhelníky, které se do naší mlhoviny nehodí (EditMesh->Face). Potom naneseme texturu nebo každému vrcholu přiřadíme jinou barvu (VertexPaint). To už vyžaduje určitou zručnost, kterou já bohužel nemám. Model "vyexportujeme" do formátu, který umíme načíst, načteme a  vykreslíme. Opět vám doporučuji dát si pozor na NearClip a povypínat vše, co by mohlo způsobovat potíže jako osvětlování, detph-buffer, mlhu, případně textury, atd.

Toto byla koule, než jsem na ni aplikoval filtr Noise. Je vyřezaná z GeoSphere a barevné přelivy jsou udělány pomocí VertexPaint. Na druhém obrázku je použita mlhovina a hvězdné pole (jako pozadí).

A to je vše
Myslím si, že jsem popsal základní druhy efektů, které by se vám mohli hodit při vytváření nějaké té hry. Nezmínil jsem zde exploze, poněvadž je velice jednoduše můžeme vytvořit pomocí částicových systémů a animovaných textur. V podstatě by to bylo opakování již zmíněného. Také jsem se snažil do každého článku vložit potřebný zdrojový kód, který můžete s menšími změnami použít. Jelikož jsem používal obrázky z mého enginu, je velice složité vytvořit nějaké demo (musel by jsem prakticky zveřejnit celý zdrojový kód enginu), pokud ale chcete shlédnout všechny zde popsané efekty v praxi, stáněte si poslední demo z www.volny.cz/sdroids3d. Tímto se tedy s vámi loučím a doufám, že se opět setkáme v dalším seriálu Jak vyzrát na GUI, který právě připravuji.

 

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

Tématické zařazení:

 » Rubriky  » C/C++  

Poslat článek

Nyní máte možnost poslat odkaz článku svým přátelům:

Váš e-mail:

(Není povinný)

E-mail adresáta:

Odkaz článku:

Vzkaz:

Kontrola:

Do spodního pole opište z obrázku 5 znaků:

Kód pro ověření

 

 

 

 

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

 

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

Uživatelské jméno:

Heslo: