Jak vyzrát na efekty 3.díl - stopy - 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 3.díl - stopy

effekt

12. dubna 2002, 00.00 | Dnes se budeme zabývat vykreslením čar v prostoru(např. laserové paprsky,atd.), stopami za motory, střelami, atd.

A máme tu další díl seriálu Jak vyzrát na kolize. Tentokrát se budeme zabývat vykreslováním "čar". Nebudou to čáry jako takové, spíše čtyřúhelníky, které zadáme dvěma body a tloušťkou (jako u čáry). Pomocí této techniky si ušetříme spoustu času, poněvadž vykreslujeme jen jeden čtyřúhelník místo několika čtyřúhelníků a můžeme tím dosáhnout ještě lepšího efektu. O čem to vlastně mluvím? Představme si např. nějakou akční hru. Většina asi zná hru Half-Life, tak si to popíšeme na ní. Pokud se nepletu, po několika dnech urputného boje s emzáky dostanete včeličkomet (každý to nazývá jinak, je to taková ta ošklivá zbraň, jediná, která má nekonečno nábojů). Když z ní vystřelíte, spatříte za výstřely stopu, která je z jakéhokoliv úhlu pohled stejně tlustá. Tohoto efektu je možné docílit dvěma způsoby :
a) budeme za střelou vykreslovat válec, jehož jedna podstava leží v hlavni a druhá ve střele (to ale zabere spoustu času)
b) vykreslíme za střelou čtyřúhelník tak, aby jeho normála byla stále orientována ke kameře (viz obr.-z pohledu kamery)


Jak ho vykreslit?
Jak jsme si již řekli, chceme vykreslit čtyřúhelník který bude mít libovolnou tloušťku (width), jeho osa bude procházet body A a B a normála bude stále orientována ke kameře. Nejprve si určíme vektor v (z A do B), potom vektor u (od kamery do A). Upozorňuji, že musíme počítat s jednotkovými vektory!!! Tak, teď máme dva vektory, ještě zbývá vektor, který je kolmý na vektor u (u je osa, takže kolmý vektor na ni bude normála). Označme si ho tedy b. Pomocí skalárního součinu můžeme "promítnout" vektor u do v a dostaneme vektor x. (x = v * (u * v)). Jak je patrné z obrázku, vektor u je součet vektorů b a x, x a u známe, tak můžeme spočítat vektor b (u = b + x ... b = u - x). Po dlouhých útrapách jsme dostali normálový vektor požadované plochy. Ještě ale zbývá nějaký vektor, který nám řekne kde je v té ploše "nahoře". Nazvěme tedy tento vektor c a spočítejme ho tak, že vektorovým součinem vynásobíme vektory v a b. Opět zdůrazňuji, že musíme počítat s vektory jednotkovými! A je hotovo! Ve finále určíme souřadnice bodů P1,P2,P3,P4.
P1x = Ax + cx * width (tloušťka)
P1y = Ay + cy * width (tloušťka)
P1z = Az + cz * width (tloušťka)
...


A co dál
Mnozí z vás si asi řeknou :" A co s tím? Vždyť čáru můžeme vykreslit i normálně." To je sice pravda, ale čára, kterou budeme kreslit normálním způsobem (v OpenGL pomocí glBegin(GL_LINES)...glEnd() ) se nebude zmenšovat v závislosti na vzdálenosti od kamery, ale bude stále stejně tlustá. Domnívám se, že to nebude tvořit příliš dobrý dojem, když budete 10cm od výstřelu a stopa za projektilem bude stejně tlustá, jako když budete od výstřelu vzdáleni 300km. Taky pomocí normálního kreslení čáry nedocílíte efektu zužování čáry, který budeme potřebovat při kreslení stop za motory. A to jsem ještě nezmínil  problém s nanášením textury na "normálně" vykreslenou čáru.

Jak bude vypadat vykreslovací funkce?
Vykreslovací funkce by měla mít asi tyto parametry:
a) výchozí bod (A nebo sx,sy,sz)
b) koncový bod (B nebo ex,ey,ez)
c) tloušťku na začátku a na konci (width_start a width_end)
d) kameru, pro kterou čáru kreslíme (použijeme třídu C_CAMERA, která obsahuje x,y,z)
e) barvu na začátku a na konci (použijeme typ T_COLOR_RGBA jde o pole 4 floatů (r,g,b,alpha) color_start a color_end)

U bodu P1 nastavíme texturový koordinát automaticky na (1.0f,0.0f), u P2 na (1.0f,1.0f), P3 na (0.0f,0.0.f) a u P4 na (0.0f,1.0f).

int Draw_Line(float sx,float sy,float sz,float ex,float ey,float ez,const C_CAMERA& camera,float width_start,float width_end,const T_COLOR_RGBA& color_start,const T_COLOR_RGBA& color_end)
{
C_VECTOR v,u,a,b,c;  - pomocne vektory

v.Set(ex - sx,ey - sy,ez - sz);
v.Normalize();
u.Set(sx - camera.x,sy - camera.y,sz - camera.z);
u.Normalize();

b = u - (v * (v.vx * u.vx + v.vy * u.vy + v.vz * u.vz));  -- spocitame vektor b

c = v * b;
c.Normalize();

a = c * (width_end / 2.0f); -- "smer nahoru" u bodu B (rovnou ho vynasobime tloustkou cary, pak uz jen pricteme k bodu B) -- musime pocitat s jednotkovymi vektory!!!
c = c * (width_start / 2.0f); -- "smer nahoru" u bodu A  ---||---

glDisable(GL_LIGHTING);  -- zakazeme osvetlovani

if(color_start[3] * color_end[3] != 1.0f)
{
Enable_Blending();  -- pokud je Alpha mensi nez 1.0f zapneme alpha-blending=pruhlednost
}

glBegin(GL_QUADS); -- zacneme vykreslovat ctyruhelniky
glColor4fv(color_end);  --  nastavime barvu konce
glTexCoord2f(0.0f,0.0f); glVertex3f(ex + a.vx,ey + a.vy,ez + a.vz);

glColor4fv(color_start);  -- nastavime barvu zacatku
glTexCoord2f(1.0f,0.0f); glVertex3f(sx + c.vx,sy + c.vy,sz + c.vz);
glTexCoord2f(1.0f,1.0f); glVertex3f(sx - c.vx,sy - c.vy,sz - c.vz);

glColor4fv(color_end); -- nastavime barvu konce
glTexCoord2f(0.0f,1.0f); glVertex3f(ex - a.vx,ey - a.vy,ez - a.vz);
glEnd();

Disable_Blending(); -- zakazeme pruhlednost (alpha-blending)

return FCE_OK;
};

Kde jsou ty stopy?
Teď sice dokážeme vykreslovat čáry (plochy, prostě říkejme tomu čáry) :-) Ale pokud budeme chtít vykreslit stopu za motorem nebo střelou, budeme muset tuto funkci trochu upravit. Za prvé si musíme do nějakého pole uložit značky pro stopy (tzn. např. každých 100ms necháme v prostoru značku, která bude mít souřadnice motoru). řekněme tedy, že chceme nakreslit stopu za motorem, která bude mít 20 značek (čím více, tím je stopa hezčí, ale program je pomalejší), jedna značka bude existovat 1000ms(zivot) = 1s. Vytvoříme tedy pole o 20 prvcích (pocet_znacek) (nejlépe x,y,z) a spočítáme si interval po kterém budeme muset vytvořit novou značku, protože jedna již nebude existovat (interval_nova = zivot / pocet_znacek). Z toho vyplývá, že po čase interval_nova zrušíme poslední značku v poli (pole[pocet_znacek - 1]) a ostatní položky tohoto pole posuneme o jednu dále (pole[i + 1] = pole[i]). A do pole[0] (první značka) uložíme x,y,z motoru (nebo projektilu,...). Při vykreslování budeme vykreslovat čáry mezi pole[i] a pole[i+1]. Také doporučuji vykreslit čáru mezi motorem a pole[0]. :-) Každá značka může mít jinou barvu, tloušťku, to už záleží na vás. Také můžete říci, že má stopa mít na začátku barvu takovou a na konci barvu makovou a pomocí lineární interpolace dopočítat barvu ostatních značek (systém výpočtu je stejný jako u částic, proto ho zde nebudu znovu popisovat, pokud si s tím nevíte rady, nahlédněte do článku Jak vyzrát na efekty 2.díl). Tímto způsobem můžeme měnit nejen barvu, ale i tloušťku, atd.

Je tu ale jeden malý problémek: čáry mezi jednotlivými značkami nemůžeme vykreslovat takto:
Draw_Line(pole[0],pole[1],...);
Draw_Line(pole[1],pole[2],...);
Protože tento způsob funguje pouze v případě, že značky pole[0],pole[1],pole[2],... leží v jedné přímce. Pokud tomu tak není, liší se vektor z bodu A do B, tím i vektor, který udává, kde je v ploše "nahoře" (viz. výše). Tím pádem neleží jednotlivé body plochy (vykreslované čáry) v jedné rovinně a dojde ke kostrbatému vzhledu (viz obr.1).

My se budeme snažit přiblížit obr.2. Pro tento účel musíme trochu opravit předešlou vykreslovací fci. Jednou z hlavních změn bude to, že ji nebudeme zadávat dva body, ale jen jeden bod. čára se vykreslí až po zadání druhého bodu. Tzn. k vykreslení dvou spojitých čar budeme potřebovat 3 body. 
int Draw_Continuous_Line(float x,float y,float z,const C_CAMERA& camera,float width,float cr,float cg,float cb,float alpha,int flag);

x,y,z jsou souřadnice značky, C_CAMERA je x,y,z kamery, width je tloušťka značky, cb,cg,cr je barva značky, alpha je průhlednost.
flag je parametr:
  RENDERER_SHAPE_BEGIN ... začátek nové čáry (nastaví nové výchozí x,y,z)
  RENDERER_SHAPE_SEGMENT
...
segment čáry (z předešlého x,y,z vypočítá potřebné vektory a vektor "nahoru" si uloží)

int C_RENDERER::Draw_Continuous_Line(float x,float y,float z,const C_CAMERA& camera,float width,float cr,float cg,float cb,float alpha,int flag)
{
static float sx,sy,sz,width_start;  -- parametry predchozi znacky
static C_VECTOR c;
static T_COLOR_RGBA color_start; -- barva predchozi znacky
static int last_flag = 3; -- minuly parametr (nic)

C_VECTOR v,u,b,a; -- potrebne vektory

if(flag == RENDERER_SHAPE_BEGIN)  -- kdyz zadavame zacatek cary
{
sx = x;  -- ulozime potrebne informace
sy = y;
sz = z;
width_start = width;
color_start[0] = cr; color_start[1] = cg; color_start[2] = cb; color_start[3] = alpha;
last_flag = RENDERER_SHAPE_BEGIN;  -- rekneme ze jsme naposledy zavolali fci, s parametrem "zacni caru"

return FCE_OK; -- opustime fci.
};

if((flag == RENDERER_SHAPE_SEGMENT) && (last_flag == RENDERER_SHAPE_BEGIN))  -- pokud je parametr "segment cary" a predesly byl "nova cara"
{
v.Set(x - sx,y - sy,z - sz);  -- vypocitame vektor od A do B
v.Normalize();
u.Set(sx - camera.x,sy - camera.y,sz - camera.z); -- vektor od A do camera
u.Normalize();

b = u - (v * (v.vx * u.vx + v.vy * u.vy + v.vz * u.vz)); -- normala plosky (cary)

c = v * b;  -- vektor, ktery urcuje kde je "nahore"
c.Normalize();

c = c * (width_start / 2.0f);

last_flag = RENDERER_SHAPE_SEGMENT; -- posledni parametr byl "segment cary"
};

if(flag == RENDERER_SHAPE_SEGMENT) -- kdyz se parametr segment "cary"
{

v.Set(x - sx,y - sy,z - sz);  -- urcime potrebne vektory (viz. vise)
v.Normalize();
u.Set(sx - camera.x,sy - camera.y,sz - camera.z);
u.Normalize();

b = u - (v * v.Dot(u)); -- zjednoduseny zapis

a = v * b;
a.Normalize();

a = a * (width / 2.0f); -- koncove "nahore" pro zacatek mame vektor c, pro konec mame vektor a

glDisable(GL_LIGHTING); -- vypneme osvetlovani

if(color_start[3] * alpha != 1.0f)
{
Enable_Blending(); -- zapneme pruhlednost, pokud ji chceme
}

glBegin(GL_QUADS);
glColor4f(cr,cg,cb,alpha);
glTexCoord2f(0.0f,0.0f); glVertex3f(x + a.vx,y + a.vy,z + a.vz);  -- P3

glColor4fv(color_start);
glTexCoord2f(1.0f,0.0f); glVertex3f(sx + c.vx,sy + c.vy,sz + c.vz); -- P1
glTexCoord2f(1.0f,1.0f); glVertex3f(sx - c.vx,sy - c.vy,sz - c.vz);  -- P2

glColor4f(cr,cg,cb,alpha);
glTexCoord2f(0.0f,1.0f); glVertex3f(x - a.vx,y - a.vy,z - a.vz); -- P4
glEnd();

Disable_Blending(); -- vypneme pruhlednost

// seve parameters for next segment -- ulozime vsechny potrebne parametry
sx = x;
sy = y;
sz = z;

color_start[0] = cr; color_start[1] = cg; color_start[2] = cb; color_start[3] = alpha;

c.Set(a.vx,a.vy,a.vz);

last_flag = RENDERER_SHAPE_SEGMENT;

};

return FCE_OK;
};

A je hotovo!
Nadefinovali jsme si tedy vše potřebné funkce a teď se můžeme podívat, čeho se dá s nimi dosáhnout:

1)Výchozí barva je modrá, koncová černá, používáme alpha-blending, počáteční šířka je 0.5f, koncová 0.0f

2)Tady je situace podobná, jenom jsme použili noise pro barvu (viz částicové systémy - Jak vyzrát na efekty 2.díl), počáteční šířka je 0.5f, koncová 0.0f, životnost 10s, 50 značek

3)Tyto dva obrázky jsou zde pro porovnání rozdílu stopa s texturou a bez (nastavení je stejné jako u prvního obrázku, textura je u druhého obr. tato  )

4) situace jako u obr. 2, liší se pouze koncovou šířkou, zde je 2.0f (počáteční je 0.5f)

5) toto jsou ukázky předešlých nastavení, místo přechodu modrá-černá jsem použil přechod žlutá-zelená




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: