|
3D - Engine 07 - S-buffer
Zdravím všechny programátory a programátorky, co se pořád chodí dívat, jestli něco nepřibylo
a jejich snahu chválím. Omlouvám se za svoji lenost a budu se to snažit dohnat. Tak jsem
slíbil, že se podíváme na S-Buffery a je to tady. Tak napřed :
Co to je S-Buffer a jak to funguje ?
S-Buffer je jak název napovídá buffer segmentů (spanů). To vám asi moc neříká, co ? Z-Buffer
funguje jako buffer všech souřadnic Z na celém obraze. Ale přitom na povrchu polygonů je Z
rozložená lineárně, tak proč ji zbytečně počítat pro celou obrazovku ? Dělá se to tak,
že při rasterizaci se polygon nekreslí, ale jeho jednotlivé segmenty se posílají do bufferu
a tam se protínají s ostatními. Segmenty jsou na řádce obvykle seřazené (rychleji se v nich
hledá), obsahují svoji pozici (první a poslední x) a souřadnice textury. Nakonec se zavolá
nějaká funkce, která vykreslí celý obsah bufferu do paměti.
S-Buffer versus Z-Buffer
S-Buffer
- Méně závislý na rozlišení (velikost = výška, segmentů na řádce s rozlišením nepřibývá)
- Nulové překreslování (ušetří čas v texturovací smyčce)
- Dobré prolínání objektů
- Závisí na složitosti scény
- Zabere relativně málo paměti
Z-Buffer
- Závislý na rozlišení (velikost = výška × šířka)
- Neomezené překreslování
- Dobré prolínání objektů
- Nezávisí na složitosti scény
- Zabere hodně paměti
Tak, to by bylo zhruba srovnání S-Bufferu se Z-Bufferem. Jen k té závislosti na složitosti
scény - S-Buffer je dobrý na kreslení terénu, budov a low-poly modelů. Jak počet polygonů
na obrazovce příliš stoupne, přidávání segmentů se zpomaluje a potom je pomalejší i kreslení
(kreslí se víc kousků a musí se víckrát perspektivně korigovat). Ale to se projeví
až u opravdu složitých (cca 3000 - 5000 poly) modelů.
Varianty S-Bufferu
S-Buffer má jednu variantu, a tou je takzvaný C-Buffer (coverage). Ten trochu řeší problém
se složitostí scény. Funguje to tak, že se segmenty napřed dají do bufferu, protnou se a to,
co z nich zbude se okamžitě kreslí. Vyžaduje to ale polygony setříděné odpředu dozadu.
Potom se vlastně v Bufferu neuchovává nic kromě x-pozic segmentů a segmenty se tudíž mohou
spojovat, a tak jich nevznikne tolik a vyhledávání je pořád rychlé. Možná že se dokopu
k tomu, udělat example, abyste to mohli srovnat s klasickým S-Bufferem.
Realizace
Pro S-Buffer je nejlepší asi dvousměrný spojový seznam. (můžete zkusit klasické pole s
binárním hledáním, ale tohle bude asi rychlejší) Spojový seznam je struktura, kde každá
buňka má pointer na předchozí a další buňku :
struct CELL {
struct CELL *prev, *next;
};
|
Takhle nějak by to vypadalo v c-čku. První buňka má ukazatel prev = NULL a poslední zase
next = NULL. Nevýhodou tohoto seznamu je nemožnost indexace, k buňce si musíte "doskákat".
Výhodou je, že pro přesunutí / přidání buňky nemusíte posouvat celé pole. K seznamu ještě
potřebujete něco, co bude velice rychle přidělovat paměť na nové segmenty. (kdybyste
alokovali paměť po kouscích při kreslení, nebylo by to moc rychlé) Něco takového se obvykle
dělá pomocí manageru, který na začátku naalokuje např. 1000 segmentů a když si o ně
rasterizer řekne, nějaké mu přidělí. Když vyčerpá všech 1000, naalokuje dalších 1000 a tak
pořád. Jediný problém je, že by se neměly měnit pointery na jednotlivé prvky, ale to lehce
vyřeší dvojrozměrné pole. (realloc() občas změní adresu celého pole).
Potom je potřeba nějaká struktura pro obrazovou řádku, jejichž pole vlastně tvoří S-Buffer.
struct {
Segment *seg;
int n_seg_num;
int n_seg_used;
} Scanline;
|
seg je pointer na první segment, n_seg_num je počet segmentů ve scanline a n_seg_used je počet
použitých segmentů (do scanline se segmenty jen přidávají, takže nelze spoléhat na posledí
segment kde next = NULL). Ze scanline se tedy udělá seznam a každé se pro začátek přiřadí
50 segmentů. n_seg_used se všude nastaví na 0 a může začít rasterizace. Pro ni potřebujeme
nějakou funkci, která segmenty protne. Jak na to ? Dva segmenty mohou být jen v několika
vzájemných polohách :
Proč se rozlišuje přidaný a stávající segment ? To proto abyste se lépe orientovali v example,
protože tam jsou jednotlivé pozice označeny právě takhle. Když už pomocí různých podmínek
zjistíte, kam (pod jakou situaci) segment patří, je jednoduché ho rozdělit. Stačí k tomu
znalost rovnice přímky, pomocí které spočítáte průsečíky obou segmentů a podle toho se
situace řeší.
Example
Example je v podstatě na stejné úrovni jako BSP-Engine z minulého dílu, jen používá
S-Buffer. Je to proto, abyste mohli zhruba porovnat rychlost této techniky. Example však
nedemonstruje sílu S-Bufferu naplno, protože se v podstatě nemá co překreslovat. V místnosti
jsou jen ty pilíře, které velké překreslení nezpůsobí. Proto je tam ještě jeden svet.3ds
a svet.bsp, kde je zase stejný svět, ale naplno v něm dolehne nedokonalost samotného BSP.
Je to taková chodba s trojúhelníkovým profilem, neměl jsem na to moc času. Když se podíváte
z kraje, BSP-Stromy budou několikrát překreslovat obrazovku - napřed se nakreslí chodba
vzadu, pak ta před ní a nakonec ta ve které stojíte. S-Buffer ale bude kreslit jen to, co je
vidět. Kód nebudu nějak dopodrobna vysvětlovat, protože vlastně není co. Tak popořadě :
Directx.cpp
|
Rozhraní pro directy. Tady se nezměnilo vůbec nic.
(můžete to samozřejmě přepsat, pokud děláte pod dosem / linuxem)
|
Font_Eng.cpp
|
Tohle se taky nezměnilo. Je tady funkce pro nahrávání truecolor bitmap (chystám ukázky
jak nahrávat i jiné formáty - .jpeg .pcx .tga .raw možná .gif a .png) a funkce pro tisk
normálního stringu do videopaměti. (font je v bitmapě)
|
Load_3ds.cpp
|
Modul pro nahrávání *.3ds scén. Možná přibude modul pro nahrávání Quake ]|[ levelů, protože
hodně z vás (podle mailů) nemá 3d - studio / 3d - max. Možná že dám něco do downloadů...
|
Main.cpp
|
Funkce WinMain() skrývá inicializační funkce, loadování .. a ve funkci Wokno se kreslí
obraz a hýbe kamerou. Tady je to nejdůležitější pro S-Buffer :
InitScreen();
for (i = 0; i < svet->n_object_num; i ++) {
DrawObject(&svet->object[i].mobject, &cmatrix,
&svet->object[i], svet->material);
}
ReleaseScreen(buffer);
|
InitScreen() smaže S-Buffer (označí ve všech scanline počet nepoužitých segmentů na 0 - to
samé, co dělal Clean_ZBuffer();) Potom je tam smyčka co kreslí jednotlivé objekty,
ta se taky nezměnila, jen potom funkce DrawObject() vypadá jinak. No a nakonec se volá
funkce ReleaseScreen(), která vykreslí obsah S-Bufferu do nějaké videopaměti.
|
Matice.cpp
|
Standardní operace s maticemi 4. řádu ...
(co dodat ? - uspořádání matice je shodné s maticemi v OpenGL)
|
Pyramid.cpp
|
Klipovací pyramida, snad beze změny ...
|
Raster.cpp
|
Pozor, tak tady je toho dost. V Init_Engine() se vytvoří S-Buffer :
InitSManager();
if (!(s_buffer = (Scanline*)malloc(Height * sizeof (Scanline))))
return 0;
for (i = 0; i < Height; i ++) {
if (!InitScanline(st_segs, &s_buffer[i]))
return 0;
s_buffer[i].n_seg_used = 0;
}
|
Na začátku se naalokuje pole Scanline o velikosti shodné s výškou obrazu. Potom se každé
scanline přiřadí st_segs (= 20) segmentů a počet použitých se nastaví na 0. (Takže volání
InitScreen() je při prvním snímku zbytečné. Jo, ještě první řádek - zavolá se náš Memory
Manager, aby si ukousl něco paměti pro přidělování segmentů). v Uninit_Engine() se zase
tohle všechno uvolní. Potom jsou tam navíc funkce InitScreen() a ReleaseScreen(), které
smažou / vykreslí S-Buffer (jejich obsah je velice prostý, snad to všichni pochopíte.)
Jen snad - v ReleaseScreen (jak vás možná napadlo) by se obrazovka mohla zrovna mazat, takže
by úplně odpadlo volání InitScreen(), ale jsou tu případy, kdy obsah bufferu budeme ještě
potřebovat (kreslení sprite, efektů ...) Dál, ve funkci Draw_Polygon() není jako parametr
videopaměť, protože se polygon nekreslí do video, ale do S-Bufferu. Jinak rasterizace
probíhá normálně, jen je tam jeden segment, kterému se na začátku nastaví parametry jako
textura, její rozložení po obrazovce ... (parametry stejné po celém polygonu) a při kreslení
každé řádky se nastaví ještě specifické parametry (pozice, pozice textury) a pošle se do
S-Bufferu. V DrawObject() je změna taky jen to, že nepřenáší pointer videopaměti.
|
Seg_Man.cpp
|
To je v podstatě srdce S-Bufferu. Na začátku je několik konstant :
startup_seg = 1024; - počet segmentů na začátku
seg_inc = 256; - počet segmentů, které se budou alokovat, když dojde těch 1024.
Nemusí to být mocniny dvou, jen mi došla fantazie. S tím seg_inc je dobré si pohrát,
aby když počet segmentů o pár přeroste doustupné se zbytečně nealokovalo moc paměti.
Nakonec ještě přidám pár grafů, jak to v praxi vypadá. Potom je tam pole stack, které
funguje jako zásobník, ze kterého se přiděluje paměť. Funguje to tak, že první dimenze
se mění. Druhá dimenze je v prvním případě pole o velikosti 1024 prvků (startup seg)
a dál už jen 256 (seg_inc). Proč je to takhle složitě ? Kdyby se volalo realloc() jen
pro jednorozměrné pole, mohla by se změnit (a taky že se mění) jeho adresa. Takhle se mění
jen adresa první dimenze pole, na čemž nezáleží a adresa jednotlivých segmentů zůstává
pořád stejná (Používá se spojový seznam -> uchovávají se jen pointery na jeho prvky, tudíž
jejich adresa se nesmí změnit.) Potom následuje pár proměnných, které informují o tom,
jak velké už je primární pole, kolik z něj je už přiděleno a zda se poslední přidělování
paměti povedlo (mem_stat). Potom následují funkce na uvolnění paměti - UninitSManager(),
uvolnění všech naalokovaných segmentů (ale ne z paměti - jen pro opětovné přidělení)
Reset_SManager(), pro zjištění kolik paměti je naalokováno (GetMem_Usage()) a jestli někde
není chyba (GetMem_Status()). Funkce Grow_Reservoir() zvětší zásobník o seg_inc jednotek.
Následující funkce jsou pro přidělování segmentů :
int InitScanline(int seg_n, Scanline *sl)
|
- přidá seg_n segmentů na konec scanline sl
|
inline Segment *Get_Segment(Scanline *sl, Segment *seg)
|
- vrátí adresu prvního (nepoužitého) segmentu ve scanline sl a zkopíruje do něj
obsah segmentu seg (kromě návaznosti v seznamu)
|
inline Segment *GetSegmentBehind(Scanline *sl, Segment *cur, Segment *seg)
|
- vrátí adresu segmentu, který ubere z konce scanline a umístí před segment cur.
zase zkopíruje obsah seg
|
inline Segment *GetSegmentNext(Scanline *sl, Segment *cur, Segment *seg)
|
- stejné jako GetSegmentBehind(), ale segment bude ležet až za cur
|
inline void RemoveSegment(Scanline *sl, Segment *seg)
|
- odstraní segment ze scanline (přidá ho na konec a sníží počet využitých o 1)
|
Každá funkce, pokud ve scanline nemá dostatek segmentů, volá Grow_Reservoir();
Potom následuje funkce, kterou volá rasterizer, když chce přidat segment :
void _fastcall AddSegment(Segment *seg, Scanline *sc)
{
Segment *tmp, *cur;
int i = 0, x, s;
cur = sc->segm;
while (i < sc->n_seg_used) {
if (seg->x2 <= cur->x1) { GetSegmentBehind(sc, cur, seg);
return ;
} else if (seg->x1 >= cur->x2) { i ++;
cur = cur->next;
continue ;
} else if (seg->x2 > cur->x1 && seg->x1 < cur->x1 && seg->x2 < cur->x2) {
x = SEG_SPLIT(seg, cur);
if (x > cur->x1 && x < seg->x2) {
if (SEG_Z(cur, -0x1000) < SEG_Z(seg, -0x1000)) {
cur->x1 = x;
seg->x2 = x;
GetSegmentBehind(sc, cur, seg);
return ;
} else {
s = cur->x1;
cur->x1 = seg->x2;
if (!(tmp = GetSegmentBehind(sc, cur, seg)))
return ;
tmp->x1 = x;
if (!(tmp = GetSegmentBehind(sc, tmp, cur)))
return ;
tmp->x1 = s;
tmp->x2 = x;
if (!(tmp = GetSegmentBehind(sc, tmp, seg)))
return ;
tmp->x2 = s;
return ;
}
} else {
x = cur->x1 + (int )((float )(seg->x2 - cur->x1) / 2 + 0.5);
if (SEG_Z(cur, x) < SEG_Z(seg, x)) {
cur->x1 = seg->x2;
GetSegmentBehind(sc, cur, seg);
return ;
} else {
seg->x2 = cur->x1;
GetSegmentBehind(sc, cur, seg);
return ;
}
}
} else if (seg->x1 > cur->x1 && seg->x1 < cur->x2 && seg->x2 > cur->x2) {
x = SEG_SPLIT(seg, cur);
if (x > seg->x1 && x < cur->x2) {
if (SEG_Z(cur, -0x1000) < SEG_Z(seg, -0x1000)) {
s = cur->x2;
cur->x2 = seg->x1;
if (!(tmp = GetSegmentNext(sc, cur, seg)))
return ;
tmp->x2 = x;
if (!(tmp = GetSegmentNext(sc, tmp, cur)))
return ;
tmp->x1 = x;
tmp->x2 = s;
seg->x1 = s;
cur = tmp;
i += 2;
continue ;
} else {
cur->x2 = x;
seg->x1 = x;
cur = cur->next;
i ++;
continue ;
}
} else {
x = seg->x1 + (int )((float )(cur->x2 - seg->x1) / 2 + 0.5);
if (SEG_Z(cur, x) < SEG_Z(seg, x)) {
cur->x2 = seg->x1;
i ++;
cur = cur->next;
continue ;
} else {
seg->x1 = cur->x2;
i ++;
cur = cur->next;
continue ;
}
}
} else if (seg->x1 > cur->x1 && seg->x2 < cur->x2) { x = SEG_SPLIT(seg, cur);
if (x > seg->x1 && x < seg->x2) {
if (SEG_Z(cur, -0x1000) < SEG_Z(seg, -0x1000)) {
seg->x2 = x;
if (!(tmp = GetSegmentNext(sc, cur, seg)))
return ;
if (!(tmp = GetSegmentNext(sc, cur->next, cur)))
return ;
tmp->x1 = x;
cur->x2 = seg->x1;
return ;
} else {
seg->x1 = x;
if (!(tmp = GetSegmentNext(sc, cur, seg)))
return ;
if (!(tmp = GetSegmentNext(sc, cur->next, cur)))
return ;
tmp->x1 = seg->x2;
cur->x2 = x;
return ;
}
} else {
x = seg->x1 + (int )((float )(seg->x2 - seg->x1) / 2 + 0.5);
if (SEG_Z(cur, x) < SEG_Z(seg, x)) {
if (!(tmp = GetSegmentNext(sc, cur, seg)))
return ;
if (!(tmp = GetSegmentNext(sc, cur->next, cur)))
return ;
tmp->x1 = seg->x2;
cur->x2 = seg->x1;
return ;
}
return ;
}
} else if (seg->x1 < cur->x1 && seg->x2 > cur->x2) { x = SEG_SPLIT(seg, cur);
if (x > cur->x1 && x < cur->x2) {
if (SEG_Z(cur, -0x1000) < SEG_Z(seg, -0x1000)) {
s = cur->x2;
cur->x1 = x;
if (!(tmp = GetSegmentBehind(sc, cur, seg)))
return ;
tmp->x2 = x;
seg->x1 = s;
cur = cur->next;
i += 2;
continue ;
} else {
s = cur->x1;
cur->x2 = x;
if (!(tmp = GetSegmentBehind(sc, cur, seg)))
return ;
tmp->x2 = s;
seg->x1 = x;
cur = cur->next;
i += 2;
continue ;
}
} else {
x = cur->x1 + (int )((float )(cur->x2 - cur->x1) / 2 + 0.5);
if (SEG_Z(cur, x) < SEG_Z(seg, x)) {
tmp = cur;
cur = cur->next;
RemoveSegment(sc, tmp);
continue ;
} else {
x = cur->x1;
s = cur->x2;
if (!(tmp = GetSegmentBehind(sc, cur, seg)))
return ;
tmp->x2 = x;
seg->x1 = s;
cur = cur->next;
i += 2;
continue ;
}
}
} else if (seg->x1 == cur->x1 && seg->x2 < cur->x2) { x = SEG_SPLIT(seg, cur);
if (x > seg->x1 && x < seg->x2) {
if (SEG_Z(cur, -0x1000) < SEG_Z(seg, -0x1000)) {
seg->x2 = x;
cur->x1 = x;
GetSegmentBehind(sc, cur, seg);
return ;
} else {
s = cur->x2;
cur->x2 = x;
if (!(tmp = GetSegmentNext(sc, cur, seg)))
return ;
tmp->x1 = x;
if (!(tmp = GetSegmentNext(sc, cur->next, cur)))
return ;
tmp->x1 = seg->x2;
tmp->x2 = s;
return ;
}
} else {
x = seg->x1 + (int )((float )(seg->x2 - seg->x1) / 2 + 0.5);
if (SEG_Z(cur, x) < SEG_Z(seg, x)) {
cur->x1 = seg->x2;
GetSegmentBehind(sc, cur, seg);
return ;
}
return ;
}
} else if (seg->x1 == cur->x1 && seg->x2 > cur->x2) { x = SEG_SPLIT(seg, cur);
if (x > cur->x1 && x < cur->x2) {
if (SEG_Z(cur, -0x1000) < SEG_Z(seg, -0x1000)) {
s = cur->x2;
cur->x1 = x;
if (!(tmp = GetSegmentBehind(sc, cur, seg)))
return ;
tmp->x2 = x;
seg->x1 = s;
cur = cur->next;
i += 2;
continue ;
} else {
cur->x2 = x;
seg->x1 = x;
i ++;
cur = cur->next;
continue ;
}
} else {
x = cur->x1 + (int )((float )(cur->x2 - cur->x1) / 2 + 0.5);
if (SEG_Z(cur, x) < SEG_Z(seg, x)) {
tmp = cur;
cur = cur->next;
RemoveSegment(sc, tmp);
continue ;
} else {
seg->x1 = cur->x2;
i ++;
cur = cur->next;
continue ;
}
}
} else if (seg->x1 > cur->x1 && seg->x2 == cur->x2) { x = SEG_SPLIT(seg, cur);
if (x > seg->x1 && x < seg->x2) {
if (SEG_Z(cur, -0x1000) < SEG_Z(seg, -0x1000)) {
s = cur->x2;
cur->x2 = seg->x1;
seg->x2 = x;
if (!(tmp = GetSegmentNext(sc, cur, seg)))
return ;
if (!(tmp = GetSegmentNext(sc, cur->next, cur)))
return ;
tmp->x1 = x;
tmp->x2 = s;
return ;
} else {
cur->x2 = x;
seg->x1 = x;
GetSegmentNext(sc, cur, seg);
return ;
}
} else {
x = seg->x1 + (int )((float )(seg->x2 - seg->x1) / 2 + 0.5);
if (SEG_Z(cur, x) < SEG_Z(seg, x)) {
cur->x2 = seg->x1;
GetSegmentNext(sc, cur, seg);
return ;
}
return ;
}
} else if (seg->x1 < cur->x1 && seg->x2 == cur->x2) { x = SEG_SPLIT(cur, seg);
if (x > cur->x1 && x < cur->x2) {
if (SEG_Z(cur, -0x1000) < SEG_Z(seg, -0x1000)) {
seg->x2 = x;
cur->x1 = x;
GetSegmentBehind(sc, cur, seg);
return ;
} else {
cur->x2 = x;
if (!(tmp = GetSegmentNext(sc, cur, seg)))
return ;
tmp->x1 = x;
seg->x2 = cur->x1;
GetSegmentBehind(sc, cur, seg);
return ;
}
} else {
x = cur->x1 + (int )((float )(cur->x2 - cur->x1) / 2 + 0.5);
if (SEG_Z(cur, x) < SEG_Z(seg, x)) {
seg->prev = cur->prev;
seg->next = cur->next;
memcpy(cur, seg, sizeof (Segment));
return ;
} else {
seg->x2 = cur->x1;
GetSegmentBehind(sc, cur, seg);
return ;
}
}
} else if (seg->x1 == cur->x1 && seg->x2 == cur->x2) { x = SEG_SPLIT(seg, cur);
if (x > seg->x1 && x < seg->x2) {
if (SEG_Z(cur, -0x1000) < SEG_Z(seg, -0x1000)) {
seg->x2 = x;
cur->x1 = x;
GetSegmentBehind(sc, cur, seg);
return ;
} else {
seg->x1 = x;
cur->x2 = x;
GetSegmentNext(sc, cur, seg);
return ;
}
} else {
x = seg->x1 + (int )((float )(seg->x2 - seg->x1) / 2 + 0.5);
if (SEG_Z(cur, x) < SEG_Z(seg, x)) {
seg->prev = cur->prev;
seg->next = cur->next;
memcpy(cur, seg, sizeof (Segment));
return ;
}
return ;
}
}
cur = cur->next;
i ++;
}
if (!Get_Segment(sc, seg))
return ;
}
|
Tady se segment protíná s ostatními. Makro SEG_Z(seg, x) zjistí z-souřadnici segmentu seg
na nějaké x souřadnici. A SEG_SPLIT(a, b) zjistí pozici průsečíku dvou segmentů a a b.
Můžete si zkontrolovat podmínky, jak sedí na ilustrační obrázek na začátku.
int _fastcall Segment_Visibility(Segment *seg, Scanline *sc)
|
Funkce, která vrátí true, když je segment seg (není součástí scanline)
ve scanline sc viditelný. Kdyby tam už byl, asi by to vrátilo vždycky nulu.
|
|
Tak, a to je konec, tády dády dá ...
Tady si můžete stáhnout examplesák + zdroják :
S-Buffer Engine
No, a o čem to bude příště ? Když se podíváte na obrázek, vidíte že ty díry ve stropě jsou
trochu "rozsypané" - textura je moc velká na obraz, takže se přeskakují některé její pixely.
důsledkem čehož je blikání těch mříží. To odstraníme právě příště metodou, která se nazývá
Mip-Mapping (ale o tom opravdu až příště)
Jo, tady jsou ještě ty grafy (je to počet segmentů o různé délce v každé scéně) :
1 ( 100) : **************************************
2 ( 127) : ************************************************
3 ( 130) : *************************************************
4 ( 130) : *************************************************
5 ( 100) : **************************************
6 ( 103) : ***************************************
7 ( 107) : ****************************************
8 ( 99) : *************************************
9 ( 98) : *************************************
10 ( 83) : *******************************
11 ( 82) : *******************************
12 ( 68) : **************************
13 ( 134) : **************************************************
14 ( 35) : **************
15 ( 37) : **************
16 ( 38) : ***************
17 ( 29) : ***********
18 ( 34) : *************
19 ( 34) : *************
20 ( 30) : ************
21 ( 31) : ************
22 ( 26) : **********
23 ( 25) : **********
24 ( 27) : ***********
25 ( 22) : *********
26 ( 19) : ********
27 ( 18) : *******
28 ( 13) : *****
29 ( 13) : *****
30 ( 11) : *****
31 ( 5) : **
32 ( 3) : **
33 ( 4) : **
34 ( 5) : **
35 ( 7) : ***
36 ( 3) : **
37 ( 3) : **
38 ( 5) : **
39 ( 6) : ***
40 ( 4) : **
41 ( 5) : **
42 ( 5) : **
43 ( 3) : **
44 ( 4) : **
45 ( 9) : ****
46 ( 8) : ***
47 ( 10) : ****
48 ( 9) : ****
49 ( 11) : *****
50 ( 8) : ***
51 ( 6) : ***
52 ( 4) : **
53 ( 2) : *
54 ( 131) : *************************************************
55 ( 2) : *
56 ( 3) : **
57 ( 2) : *
58 ( 2) : *
59 ( 3) : **
60 ( 76) : *****************************
61 ( 2) : *
63 ( 1) : *
64 ( 3) : **
65 ( 2) : *
66 ( 2) : *
67 ( 6) : ***
68 ( 4) : **
71 ( 1) : *
72 ( 1) : *
73 ( 1) : *
74 ( 4) : **
76 ( 1) : *
77 ( 1) : *
78 ( 2) : *
79 ( 2) : *
80 ( 2) : *
81 ( 2) : *
82 ( 2) : *
83 ( 1) : *
84 ( 3) : **
85 ( 2) : *
86 ( 1) : *
87 ( 3) : **
88 ( 1) : *
89 ( 3) : **
90 ( 3) : **
91 ( 2) : *
92 ( 1) : *
93 ( 2) : *
94 ( 7) : ***
95 ( 2) : *
96 ( 1) : *
97 ( 5) : **
98 ( 3) : **
99 ( 1) : *
100 ( 3) : **
101 ( 4) : **
102 ( 2) : *
104 ( 6) : ***
105 ( 4) : **
106 ( 2) : *
107 ( 4) : **
108 ( 2) : *
109 ( 3) : **
110 ( 4) : **
111 ( 8) : ***
112 ( 7) : ***
113 ( 2) : *
114 ( 4) : **
115 ( 5) : **
116 ( 10) : ****
117 ( 59) : ***********************
118 ( 2) : *
196 ( 3) : **
197 ( 2) : *
198 ( 3) : **
199 ( 3) : **
200 ( 2) : *
201 ( 3) : **
202 ( 3) : **
203 ( 2) : *
204 ( 3) : **
205 ( 8) : ***
Takhle to vypadá v jednoduché scéně - převažují segmenty s malou délkou
1 ( 270) : *********************************************
2 ( 302) : **************************************************
3 ( 285) : ************************************************
4 ( 262) : ********************************************
5 ( 266) : *********************************************
6 ( 265) : ********************************************
7 ( 223) : *************************************
8 ( 216) : ************************************
9 ( 166) : ****************************
10 ( 171) : *****************************
11 ( 133) : ***********************
12 ( 128) : **********************
13 ( 124) : *********************
14 ( 117) : ********************
15 ( 95) : ****************
16 ( 88) : ***************
17 ( 92) : ****************
18 ( 61) : ***********
19 ( 64) : ***********
20 ( 61) : ***********
21 ( 63) : ***********
22 ( 53) : *********
23 ( 65) : ***********
24 ( 48) : ********
25 ( 52) : *********
26 ( 41) : *******
27 ( 51) : *********
28 ( 35) : ******
29 ( 38) : *******
30 ( 41) : *******
31 ( 35) : ******
32 ( 21) : ****
33 ( 23) : ****
34 ( 26) : *****
35 ( 20) : ****
36 ( 23) : ****
37 ( 22) : ****
38 ( 22) : ****
39 ( 10) : **
40 ( 10) : **
41 ( 13) : ***
42 ( 10) : **
43 ( 6) : *
44 ( 14) : ***
45 ( 7) : **
46 ( 9) : **
47 ( 11) : **
48 ( 10) : **
49 ( 8) : **
50 ( 10) : **
51 ( 9) : **
52 ( 8) : **
53 ( 7) : **
54 ( 6) : *
55 ( 6) : *
56 ( 3) : *
57 ( 3) : *
58 ( 4) : *
59 ( 3) : *
60 ( 3) : *
61 ( 5) : *
62 ( 2) : *
63 ( 3) : *
64 ( 4) : *
65 ( 3) : *
66 ( 2) : *
67 ( 5) : *
68 ( 2) : *
69 ( 3) : *
70 ( 5) : *
72 ( 4) : *
73 ( 3) : *
74 ( 2) : *
75 ( 3) : *
76 ( 4) : *
77 ( 1) : *
78 ( 5) : *
79 ( 3) : *
80 ( 2) : *
81 ( 3) : *
82 ( 3) : *
83 ( 3) : *
84 ( 3) : *
85 ( 2) : *
86 ( 3) : *
87 ( 4) : *
88 ( 1) : *
89 ( 3) : *
90 ( 4) : *
91 ( 1) : *
92 ( 2) : *
93 ( 4) : *
94 ( 2) : *
95 ( 2) : *
96 ( 4) : *
98 ( 4) : *
99 ( 4) : *
100 ( 2) : *
101 ( 2) : *
102 ( 3) : *
103 ( 2) : *
104 ( 3) : *
105 ( 2) : *
106 ( 2) : *
A tohle byl obrázek terénu. Delší segmenty se téměř nevyskytují.
(není moc vidět že to je terén - není tam stínování...)
... tak zase příště !
-tHE SWINe-
Valid HTML 4.01
|