OpenGL (9.) - Generování textur a vylepšení FarClip Distance

Tak jsem se dal do psaní dalšího článku o OpenGL. Dnes jsem pro vás připravil automatické generování textur, výpočet fps a vylepšený Far-clip distance (omezeni viditelné vzdálenosti).

Generování textur

Na serveru FlipCode.com jsem narazil na velmi zajímavý článek nazvaný Terrain Texture Generation. Technika je zde pojmenována jako "procedural texture generation" a není vůbec složitá. Máme bitmapu s odstínem šedé pro generování výšky trojůhelníků (černá barva=nížiny, bílá barva=hory). Dále máme několik (v mém případě 3) bitmap, ve kterých je vlastně charakteristika každého prostředí: (nížina: žlutá bitmapa=písky, hory: vrásčitá bílo-šedá bitmapa=skály, střed: nazelenalá barva=vegetace. Teď, když máme nachystány všechny bitmapy, si můžeme spočítat procentuální složku bitmapy pro každý bod. K tomu nám poslouží následující funkce:

function texfactor(h1:TGLFloat; h2:integer): TGLFloat;
    var percent: TGLFloat;
  begin
    percent := (128 - ABS(h2 - h1)) / 128;
    if percent < 0 then percent := 0
      else if percent>1 then percent := 0;
    result := percent;
  end;
Popis obrázku

Jako první parametr funkce zadáme hlavní výškový bod prostředí, pro které počítáme složku (nížiny=0, střed=128, hory=256), a do druhého parametru dosadíme výšku mapy. Funkce nám "vyhodí" procentní hodnotu každé složky pro dosazený bod. Budeme muset získat všechny bitmapy a postupně si je uložíme do polí buffer1-buffer3:

  LoadTexture('snow.jpg');        //Funkce nacte texturu snehu
  buffer1:=buffer; 
  LoadTexture('grass.jpg'); 	  //Funkce nacte texturu travy
  buffer2:=buffer;
  LoadTexture('sand.jpg');        //Funkce nacte texturu pisku
  buffer3:=buffer;

Teď z těchto tří bitmap vypočteme jednu výslednou bitmapu, která obsahuje složky nížin, vegetace a hor podle výškového uspořádání:

  for j:=1 to 256 do
    for i:=1 to 256 do begin
      hmap_height := Map[j,i].red;

      tex_fact[1] := texfactor(256, hmap_height);
      tex_fact[2] := texfactor(128, hmap_height);
      tex_fact[3] := texfactor(0, hmap_height);

      old_red[1] := buffer1[i,j].red;
      old_green[1] := buffer1[i,j].green;
      old_blue[1] := buffer1[i,j].blue;

      old_red[2] := buffer2[i,j].red;
      old_green[2] := buffer2[i,j].green;
      old_blue[2] := buffer2[i,j].blue;

      old_red[3] := buffer3[i,j].red;
      old_green[3] := buffer3[i,j].green;
      old_blue[3] := buffer3[i,j].blue;



      new_red := round((tex_fact[1]*old_red[1]) + 
	             (tex_fact[2]*old_red[2]) + (tex_fact[3]*old_red[3]));
      new_green := round((tex_fact[1]*old_green[1]) + 
	             (tex_fact[2]*old_green[2]) + (tex_fact[3]*old_green[3]));
      new_blue := round((tex_fact[1]*old_blue[1]) + 
	             (tex_fact[2]*old_blue[2]) + (tex_fact[3]*old_blue[3]));

     buffer[i,j].red := new_red;
     buffer[i,j].blue := new_blue;
     buffer[i,j].green := new_green;
   end;

A takto vygenerovanou texturu nastavíme jako texturu 1, která je nastavená jako textura pro krajinu:

  glGenTextures(1, @textura[1]);
  glBindTexture(GL_TEXTURE_2D, textura[1]);
  glTexParameterf(GL_Texture_2D,GL_Texture_Wrap_S,GL_REPEAT);
  glTexParameterf(GL_Texture_2D,GL_Texture_Wrap_T,GL_REPEAT);
  glTexParameterf(GL_Texture_2D,GL_Texture_Mag_Filter,GL_Linear);
  glTexParameterf(GL_Texture_2D,GL_Texture_Min_Filter,GL_Linear);
  glTexImage2d(GL_Texture_2D,0,3,Velikost+1,Velikost+1,
                  0,GL_RGB,GL_Unsigned_byte,@buffer);

Teď ukázka jak to funguje ve skutečnosti. Zdroják dnešního snažení si můžete stáhnout dole.

Počáteční bitmapa odstínů šedéVygenerovaná texturaTakhle to vypadá nasazené do krajiny

Nejlepší na této metodě je, že k tvorbě jednoho levelu potřebujeme pouze jeden obrázek s odstíny šedé, který určuje výšku každého bodu a tím zároveń i texturu, kterou celý "svět" potáhneme.

Vylepšení Far-Clip distance

V proceduře, která zjišŤuje kolize jsem si zároveň vypočítal nejmenší vzdálenost každého trojúhelníku ve scéně ke kameře a tuto hodnotu jsem uchoval pro pozdější rozhodování.

if i=0 then begin      //Zjisti min. vzdalenost trojuhelnika od kamery
  if (Akamera<Bkamera) AND (Akamera<Ckamera) then Min:=Akamera;
  if (Bkamera<Akamera) AND (Bkamera<Ckamera) then Min:=Bkamera;
  if (Ckamera<Akamera) AND (Ckamera<Bkamera) then Min:=Ckamera;

  
   //Hodnoty o vzdalenosti trojuhelnika od kamery dosadi do pole
   //pro mozne budouci serazeni od nejvzdalenejsiho
  vzdalenost[count].Id:=count;
  vzdalenost[count].Min:=Min;
end;

Taky jsem tuto proceduru vylepšil v tom, že se detekce kolizí počítá jen pro trojúhelníky, které jsou vzdáleny od kamery méně než 20 jednotek. Tímto se zvýšila fps alespoň o 30fps.

  if vzdalenost[count].Min<30 then begin
    ...
  end;

A konečně vykreslíme jen ty trojúhelníky, které mají od kamery menší vzdálenost než je hodnota Far-Clip. Tyto příkazy jsou mnohem efektivnější, nežli ořezávání scény pomocí řezů ClipPlane.

	   //Vykresleni sceny po trojuhelnicich
  for i:=1 to MaxTriangles do  //Vykresli trojuhelniky podle poradoveho cisla
    if vzdalenost[i].min<FarClip then begin
      glBindTexture(GL_TEXTURE_2D, textura[1]);	     //nacte texturu
      glTexCoordPointer(2, GL_FLOAT, 0, @CoordPointer[i]);  //nacte tex. koord.
      glVertexPointer(3, GL_FLOAT, 0, @VertexPointer[i]);   //nacte pole bodu
      glDrawElements(GL_TRIANGLES, 4, GL_UNSIGNED_INT, @Indices[i]);
    end;

Tenhle příklad je vhodný jen pro omezení viditelné vzdálenosti "do dálky", ale všechny trojúhelníky vzdálené méně než 20 jednotek se vykreslují i přesto, že jsou nakonec mimo obrazovku (za kamerou, vedle kamery). Proto je vhodné (a já to také někdy popíšu) také determinovat zobrazování i těch trojúhelníků které budou ležet mimo viditelnou oblast. To lze (snad) udělat snadno pomocí zjištění toho nejmenšího úhlu, který svírá vektor jednoho ze tří bodů trojúhelníka se směrovým vektorem kamery. Myslím, že to tak půjde...

Výpočet fps

Pro výpočet obnovovací frekvence nám dobře poslouží následující procedura, kterou jsem objevil kdesi na netu:

procedure TForm1.CalcFPS(CStep: integer);
var
  i: TLargeInteger;
  FrameTime: double;
begin
  if CStep = 1 then begin  //Calculate start Frequency and start Counter
    QueryPerformanceCounter(i);
    StartCount := i;
    QueryPerformanceFrequency(i);
    StartFreq := i;
    StartCount := StartCount / StartFreq;
  end;

  
  if CStep = 2 then begin	 //Calculate Counter before draw scene
    QueryPerformanceCounter(i);
    Count1 := i / StartFreq;
  end;

  
  if CStep = 3 then begin	 //Calculate Counter after draw scene
    QueryPerformanceCounter(i);
    Count2 := i / StartFreq;
    FrameTime := (Count2 / StartCount) - (Count1 / StartCount);   
    FPS :=  1 / FrameTime;                                      //calculate FPS
    FPS := FPS / StartCount;
    Label1.Caption:=inttostr(round(FPS))+'fps / ';
  end; 
end;

Musíme také mít nadefinovány tyto nové proměnné:

  StartFreq: TLargeInteger;
  StartCount,Count1, Count2, FPS: double;
  FPSi: integer;

Při inicializaci zavoláme proceduru CalcFPS s parametrem 1...

CalcFPS(1);

A poté jen vložíme do místa, kudy program prochází při každém počítání scény, já jsem jej umístil do procedury TForm1.ZobrazitScenu:

  if FPSi = 2 then begin	  //Vypocet FPS
    CalcFPS(2);
    FPSi := 3;
  end else begin
    CalcFPS(3);
    FPSi := 2;
  end;

Už v samotné proceduře je přidáno Label1.caption:=inttostr(round(FPS))+'fps';, takže nemusíme nikterak čekat na výsledek a ten se nám zobrazí v Label1.

Celý zdroják včetně obrázků si můžete stáhnout Tady (229 kB).

Vyšlo 20.01.2002, v blogu: 0 1 2 3 4 5 6 7 8

Děkuji, že jste se rozhodl(a) přečíst tento článek. Budu rád i za komentář. Pokud Vás tento článek zaujal a rádi byste jej doporučili ostatním, podpořte mně prosím tím, že věnujete minutku svého času a uděláte mi reklamu na linkuj.cz, vybrali.sme.sk či jagg.cz. Přeji příjemné čtení

Poslední články

Diskuse k blogu

Zatím nikdo nevložil komentář. Chcete být první? Přidání příspěvku
©PC-guru.cz 2000-2008 | Optimalizováno pro 1024*768