Uvedený jazzový štandard som sa učil hrať na gitare v rámci výuky hry na gitare ako samouk. Chcel som zistiť a vedieť aké to je naučiť sa celý jazzový štandard. Tento jazzový štandard je z knihy Ballads for Jazz Guitar od Antonia Ongarella.
Hudobná ukážka v mojom podaní: (Harold Arlen - Over the Rainbow)
Uvedený jazzový štandard som sa učil hrať na gitare v rámci výuky hry na gitare v Prahe v roku 2015. Poskytuje vhodné jazzové a bluesové akordy, ktoré sú veľmi dobre široko uplatniteľné.
Hudobná ukážka v mojom podaní: (Antonio Carlos Jobim - The Girl from Ipanema)
Uvedený jazzový štandard som sa učil hrať na gitare v rámci výuky hry na gitare v Prahe v roku 2015. Poskytuje vhodné jazzové a bluesové akordy, ktoré sú veľmi dobre široko uplatniteľné.
Hudobná ukážka v mojom podaní: (Eden Ahbez - Nature Boy)
Diferenciálny počet a jeho proces derivácie ma fascinoval už od strednej školy. Princíp som poznal už na základnej škole, avšak aj na strednej škole a neskôr mi podstata ostala viac-menej utajená. Pritom použitie bolo pomerne jednoduché. Určitú sadu vzorcov sme aplikovali na nejaké funkcie, v ktorých sme očakávali určité výstupy. Avšak čo sa dialo na pozadí tohto procesu bolo určitým spôsobom záhadné a jedinečné.
Isaac Newton ako "objaviteľ" kalkulu (diferenciálneho a integrálneho počtu), ak sa to tak dá nazvať, trpel chorobným strachom z kritiky. Takýto strach často sprevádzajú úzkosti a pocity menejcennosti. Aj preto väčšinu svojich prác nepublikoval, vrátane kalkulu. Až astronóm Edmund Halley ho presvedčil, aby pripravil k vydaniu niektoré časti o zákonoch pohybu a gravitácie. Jeho dielo Matematické základy prírodovedy navždy zmenili pohľad na prírodné vedy. Gottfried Wilhelm Leibniz objavil a vydal kalkulus o niečo málo neskôr ako Newton, čo mu mnohí vytýkali, že to od neho prebral. Newton mu niektoré zápisky poslal dobrovoľne, na jeho požiadanie. Newtonové pojatie kalkulu sa odlišovalo od Leibnizovho hlavne zápisom. Newtonová verzia nebola dobre zrozumiteľná, Leibniz používal geometrický princíp, jeho metóda bola prirodzenejšia. Takže Newtonová symbolika sa používa dodnes skôr vo fyzike a Leibnizova v matematike (aj keď sa prelínajú samozrejme). Dnes považujeme obidvoch za otcov kalkulu. Ale treba podotknúť, že samotné zárodky kalkulu, jeho metódy, a vôbec myšlienka ako taká už bola známa niektorím vedcom už dávnejšie. Bolo to niečo podobné ako s Einsteinovou teóriou relativity. Tieto teórie nevznikali zo dňa na deň, boli výsledkom mnohých vedcov po mnohé stáročia.
Diferenciálnym počtom už ako z názvu vyplýva je možné počítať nejaké diferencie, zmeny. Teda diferenciálnym počtom je možné analyzovať zmeny objektu, jeho pohyb. Ale medzi pohybom a zmenou musí existovať vzťah. A tento vzťah hľadáme práve diferenciálnym počtom. Čiže snažíme sa nájsť mieru zmeny nejakej meniacej sa veličiny, objektu. A práve na to slúži derivovanie. Smer pohybu daného objektu musí byť popísaný vzorcom. Derivovaním tohto vzorca sa vytvorí nový vzorec, ktorý už udáva hľadanú mieru zmeny.
Typickým príkladom je klasický príklad z fyziky, kde máme auto idúce po ceste. Premenná s bude označovať dráhu auta, ktorá sa mení v závislosti na čase t podľa vzorca:
Podľa diferenciálneho počtu je rýchlosť auta v, čiže miera zmeny polohy, v ľubovoľnom čase t daná vzorcom:
Výraz 10t+3 vznikol derivovaním výrazu 5t^2+3t. Rýchlosť auta nie je konštantná a mení sa s časom, tak ako aj dráha. Ak aplikujeme proces derivovania ešte raz získame zrýchlenie a = 10, ktoré je konštantné.
Základom všetkého je funkcia. Bez funkcie by neexistovala derivácia. Funkcia je predpis podľa ktorého je ku každému číslu z danej množiny priradené iné číslo, tzv. funkčná hodnota. Takáto funkcia je zobraziteľná v euklidovskom priestore, čo je pri derivácii výhodné z hľadiska pochopenia. Derivácie ako také je možné použiť aj v iných odvetviach vedy, často vo fyzike, geometrii a technických disciplínach. Derivácia je dobrá v tom (okrem iného), že nám dokáže povedať niečo o priebehu pôvodnej funkcie. Takže napr. máme funkciu (na obrázku nižšie znázornená červenou farbou):
Táto môže predstavovať čokoľvek, od rastu cien HDP až po priebeh teploty. Máme potom druhú funkciu, jej deriváciu v tvare (na obrázku znázornená modrou farbou):
Pôvodná (nederivovaná) funkcia f je rastúca, keï jej derivácia nadobúda kladné hodnoty a klesajúca, keď derivácia nadobúda záporné hodnoty. Bod, v ktorom má funkcia fmaximum je v bode, kde je hodnota derivácie nulová. Teda z derivácie je možné vyčítať niektoré informácie o priebehu pôvodnej funkcie.
Takúto deriváciu je možné naprogramovať. Ale ešte predtým ju treba zadefinovať. Teda, nech funkcia f(x) je definovaná v okolí bodu a. Potom môžme napísať niečo také:
Túto limitu nazveme deriváciou funkcie f(x) v bode a, čo sa označuje f'(a).
Príklad: Máme vypočítať deriváciu funkcie v bode a.
Výsledkom tejto derivácie bude hodnota, číslo, v bode a. Toto číslo dostaneme tak, že spočítame limitu pre x idúce do a. Prečo počítame toto a prečo takto sa dozvieme neskôr. Zatiaľ vypočítame príklad podľa vzťahov. Takže dostaneme niečo také:
Čiže, ak zderivujeme funkciu f(x) = x^3 dostaneme f'(x) = 3x^2. V našom prípade v bode a a dosadením ľubovoľného čísla v tomto bode dostaneme hodnotu zderivovanej funkcie f(x).
Program vyzerá nasledovne. Bol použitý programovací jazyk C s hlavičkovými súbormi:
#include <stdio .h="">
#include<stdlib .h="">
#include <math .h="">
</math></stdlib></stdio></pre>
Zadefinujeme si funkciu <code><font size="3">f</font></code> s jedným vstupným parametrom typu <code><font size="3">float</font></code>, ktorý vráti hodnotu funkcie typu <code><font size="3">float</font></code> v tomto bode:
<pre>float f(float x)
{
return x*x*x;
}
A teraz si definujeme funkciu, ktorá bude počítať, aproximovať, funkciu v bode a:
float PrvaDerivacia(float bod)
{
float xt = bod;
float h = 0.01 * (1 + abs(xt));
bod = xt + h;
float fp = f(bod);
bod = xt - h;
float fm = f(bod);
bod = xt;
return (fp - fm)/2/h;
}
A ešte hlavnú funkciu main, ktorá len zavolá funkciu prvaDerivacia a vypíše výsledok na obrazovku:
Ak program preložíme a spustíme, vypíše hodnotu 12.000895, čo je približne 12. A naozaj. Funkcia x^3 v bode 2 je 8 a jej derivácia 3x^2 v bode 2 je 12. Teda naozaj nami naprogramovaná funkcia derivuje.
Newton a Leibniz mali za úlohu vyriešiť ako stanoviť "rýchlosť" zmeny funkcie, teda pomer zmeny y = f(x) k zmene x. V grafickom znázornení, na obrázku nižšie, to znamená, nájsť v danom bode x sklon krivky, ktorý je daný veľkosťou uhlu medzi dotyčnicou ku krivke v danom bode (P, Q) a osou x. V číselnom vyjadrení sa veľkosť uhlu vyjadruje ako smernica dotyčnice k danej krivke alebo aj tangens uhla. Táto veľkosť smernice nie je konštantná, závisí od charakteru krivky. Smernica dotyčnice ku krivke závisí od hodnoty x a táto hodnota závisí od y. Čiže hodnoty smerníc definujú našu krivku.
Budeme uvažovať kvôli jednoduchosti funkciu y = x^2 znázornenú na obrázku vyššie. Na konci uvažovania získame všeobecný predpis pre funkciu y = x.
Ako je možné vidieť na obrázku, s rastúcou hodnotou x rastie nie len hodnota funkcie y, ale aj smernica, teda uhol. Nebudeme počítať priamo uhol, ale len pomer sklonu medzi bodmi P a Q, teda smernicu úsečky spájajúcu bod P a Q. Výsledkom bude len pomerné číslo. Takže pri danej hodnote x je výška krivky daná výrazom x^2. Ako spočítame smernicu pre danú hodnotu x ? Čiže ako spočítame v trojuholníku na obrázku uhol medzi osou x a úsečkou PQ ? Myšlienka bola nasledujúca. Pozrieme sa na bod Q, ktorého súradnica je x + h. Výška bodu P je x^2 a výška bodu Q je (x + h)^2. Od bodu P do bodu Q sa krivka ohýba smerom hore. Nemôžeme počítať smernicu v takomto rozpoložení, pretože body P a Q sú od seba prive¾mi vzdialené. Sú od seba vzdialené natoľko, že úsečka PQ nesplýva s krivkou (je medzi nimi medzera). Musíme teda úsečku PQ zmenšiť na menšie úsečky, aby tzv. (lepšie) aproximovala danú krivku. Vtip spočíva v tom, že pri veľmi malých hodnotách hodnoty h bude platiť, že v bode P sa smernica bude blížiť smernici tejto úsečky. Čiže stačí nám spočítať smernicu v bode P, teda prírastok výšky, toho trojuholníka, vydelíme prírastkom vo vodorovnom smere. Prírastok výšky je daný výrazom:
Tento prírastok označíme ako dy (dy = funkčná hodnota v bode Q - funkčná hodnota v bode P). Takto ho označil aj Leibniz, ovšem Newton bol viac fyzik než matematik a označil ho ako dr, podľa vzorca r = t^2. To preto, pretože Newton používal fyzikálny model podľa ktorého označil r ako vzdialenosť polomeru a t ako čas. Leibnizov model sa používa častejšie, najmä v matematike. Newtonové označenie sa používa z veľkej časti vo fyzike.
Prírastok vo vodorovnom smere je h, pretože rozdiel x + h - x = h. Tento prírastok si označíme ako dx.
Takže smernica úsečky spájajúca bod P a Q je daná pomerom dy ku dx, z čoho dostaneme:
Čitateľ upravíme na tvar:
Po úprave dostaneme:
Tento výraz udáva smernicu úsečky PQ. Ale nespočítali sme smernicu krivky v bode P! Teraz sme v podstate len zderivovali funkciu y = x^2, čoho výsledkom je funkcia y = 2x. Ovšem nie 2x, ale 2x + h. Newton a Leibniz považovali túto hodnotu h = 0, čo nebolo celkom správne. Ak sa pozrieme na obrázok tak so zmenšujúcou sa hodnotou h sa bod Q približuje k bodu P. Pre každý prírastok h odpovedá výraz 2x + h, tzn. že pre akýkoľvek najmenší prírastok h platí smernica úsečky PQ, ktorú sme spočítali. Takže, ak zvolíme za x = 5 a budeme postupne zmenšovať krok h z 0.1 na 0.01 až 0.000001 atï, tak dostaneme postupne hodnoty, 10.1, 10.01, až 10.000001 atï. A tu je jasne vidieť, že so zmenšujúcim sa krokom h sa smernica blíži k celej hodnote čísla, v našom prípade hodnote 10. Teda počítame vlastne limitu od x idúcu k 0. V reálnej praxi by to znamenalo dosadiť h = 0. Newton a Leibniz uvažovali priamo h = 0, čo z matematického hľadiska nebolo správne, pretože ak dosadíme do poslednej rovnice h = 0 tak výraz na ľavej strane nebude definovaný, pretože podiel 0/0 v matematike nie je definovaný. Preto bolo potrebné uvažovať limitné prípady. To znamená, že z geometrického h¾adiska sa so zmenšujúcim sa h bod Q približuje bodu P a rozdiel medzi smernicou úsečky PQ a smernicou krivky v bode P postupne mizne, čo znamená, že limitná hodnota smernice úsečky PQ bude presnou hodnotou smernice krivky v bode P! Čiže smernica krivky pre hodnotu x je daná výrazom 2x, čo je limitná hodnota výrazu 2x + h pre h blížiace sa k nule.
Tento nedostatok neskôr odstránil Karl Weierstrass zavedením pojmu limity.
Použitá inšpirácia:
• Jazyk matematiky | K. Devlin | Dokořán | Praha 2002
• Prehľad matematiky | P. Horák, L. Niepel | SPN | Bratislava 1982
• Introduction to Differential Calculus | Ch. Thomas | Lecture Notes | Sydney 1997
V minulej časti bol popísaný postup ako vykresliť dvojrozmerné objekty na obrazovku počítača (na tzv. plátno). Takýto postup je priamočiary, to znamená, akýmkoľvek spôsobom zadáme súradnice objektu, na plátno vykresľujeme v tých istých súradniciach, prípadne obohatené o transformačné vzťahy, najčastejšie posunu objektu, otočenie alebo zmenu mierky objektu.
Situácia sa zmení pridaním ďalšieho rozmeru obrazu - rozmeru håbky. V tomto okamihu už dochádza k simulácii ľudského videnia, avšak stále len k simulácii, napodobovaniu, pretože zostrojiť úplne dokonalý obraz ľudského videnia reálneho sveta nie je doposiaľ možné. Z fyzikálneho hľadiska si môžme predstaviť okolitý svet ako spojitý, analógový a svet elektroniky ako nespojitý, digitálny. Najlepšie to reprezentuje videokamera alebo fotoaparát, ktorým snímame jednotlivé obrázky. Procesom snímania okolitého sveta prostredníctvom objektívu je vždy nejaký prevod obrazu z tohto objektívu (analógový signál) na obrazový formát (digitálny signál). Tento prevod je daný nejakou transformáciou. Doposiaľ žiadny objektív nedokáže zachytiť obraz bezchybne, takže okrem samotnej transformácie obrazu dochádza aj k transformačným, korekčným chybám objektívov. Tieto korekcie, transformácie sú vo všeobecnosti vzťahy, ktoré vyjadrujú spojitosť, väzbu, medzi jednotlivými prvkami. Premietanie v našom prípade je transformácia, ktorá charakterizuje prevod trojrozmerných súradníc objektu do dvojrozmerných súradníc, pričom v takejto reprezentácii, ako už bolo spomínané, dochádza vždy k strate priestorovej informácie. Deskriptívna geometria je jedna z matematických disciplín, ktorá sa zaoberá rôznymi prepismi geometrických scén.
Proces ľudského videnia je založený v konečnom dôsledku na vyhodnotení signálov v mozgu. Preto, čím vernejšie vykreslíme 3D scénu na monitore počítača, tým prirodzenejšie bude schopný mozog spracovať jednotlivé signály. Pri spracovaní videnia v určitej fáze prebieha v mozgu k tzv. stranovému kríženiu obrazu (okrem toho aj k zmenšeniu a dvojitému stranovému otočeniu obrazu) z ľavého a pravého oka. Týmto procesom vnímame okolitý svet priestorovo, vzniká dojem håbky. Takýto priestor nie je možné vytvoriť v mozgu z roviny akéhoko¾vek monitora, takže aj keï prakticky neustále dochádza k stranovému kríženiu obrazu, výsledný obraz bude priestorový len do kvality poskytnutej 3D transformácie.
Poznámka: V súvislosti s procesom videnia mozgom, pozná neurológia určitý stav, kedy vidíme obraz, nejaké predmety, ale nedokážeme ich identifikovať. Teda, na sietnici oka sa premieta obraz z okolitého prostredia, ale napriek tomu náš mozog nedokáže jednotlivé signály spracovať a v dôsledku toho, vlastne nevidíme nič, sme slepí. Takýto stav sprevádzajú pomerne ťažké neurologické ochorenia, ale aj keï nie presne v tomto význame ich občas môže zažiť aj zdravý človek, napr. nieko¾ko sekúnd po prebudení zo spánku, alebo tesne pri zaspávaní a v neposlednom rade aj pri požití omamných a psychotropných látok.
Premietanie
Premietanie je transformácia, ktorá charakterizuje prevod trojrozmerných súradníc objektu do dvojrozmerných súradníc. Obraz, ktorý vznikne premietaním sa nazýva priemet. Takyto priemet sa premieta na priemetni, co je možné si predstaviť ako monitor počítača, na ktorom vidíme už výsledný obraz scény. Tento obraz na priemetni vznikne premietajúcimi lúčmi, ktoré dopadnú na túto priemetòu. Premietací lúč si je možné predstaviť matematicky ako polpriamku, ktorá vychádza v určitom konkrétnom bode a ktorej smer určuje konkrétna premietacia metóda. Smer premietacich lucov moze byt vo vseobecnosti v zasade len bud rovnobezny, tzv. rovnobezne premietanie (paralelne), kde jednotlive polpriamky su navzajom rovnobezne, alebo roznobezny, tzv. stredove premietanie (perspektivne), kde vychadzaju z jedneho bodu. Cieľom je nájdenie transformačných matíc, ktoré definujú transformáciu súradníc premietaných bodov.
V nasledujúcich častiach budú popísané najpoužívanejšie typy premietania, rovnobežné premietanie, ktorej zástupcom je axonometrické premietanie a stredové premietanie, alebo tiež perspektívne. Ostatné typy sú podobné týmto typom a líšia sa len zmenou niektorých parametrov.
Rovnobežné premietanie
Do rovnobežného premietania patrí axonometria a jej špeciálne prípady ako dimetria, trimetria, izometria. Špeciálny prípad rovnobežného premietania je kolmé premietanie a jej charakteristickým prípadom je Mongeové premietanie, kedy premietacie lúče sú kolmé na priemetòu, čím vytvárajú priemet objektu v troch smeroch, spredu (nárys - Front), zboku (bokorys - Side), zhora (pôdorys - Top).
V súradnicovej sústave P, x_1, x_2 je axonometria reprezentovaná súradnicovou sústavou O_a, E_{a}^{x}, E_{a}^{y}, E_{a}^{z}, uhlami \xi, \eta a tzv. axonometrickým trojuholníkom, ktorý tvoria jednotkové vektory e^x, e^y, e^z.
Nech bod A \in E^3 má súradnice A(x^A, y^A, z^A) a jeho pôdorys A_1(x^A, y^A, 0). V axonometrii \phi: E^3 \rightarrow \varepsilon \times \varepsilon[/latex] je obrazom bodov A, A_1 usporiadaná dvojica bodov (A_a, A_{1a}) pre ktorú platí [latex]A_a A_{1a} \parallel z_a. Vzh¾adom na axonometrickú súradnicovú sústavu O_a, E_{a}^{x}, E_{a}^{y}, E_{a}^{z} majú body A_a A_{1a} súradnice A_a(x^A e^x, y^A e^y, z^A e^z), A_{1a}(x^A e^x, y^A e^y, 0).
Výpočet konkrétneho bodu A_a(x_1^A, x_2^A) vzhľadom na súradnicovú sústavu Px_1x_2 bude záležitosťou použitia goniometrických funkcií. Teda, podľa daného obrázku platí:
Jednotlivé uhly vyjadrujú určité typy axonometrie, napr. dimetria, izometria, vojenská axonometria a podobne. Tento zápis je možné napísať aj pomocou maticového zápisu vo forme transformačného vzťahu v homogénnych súradniciach:
Samotný programový kód v podstate na každý bod obrazu aplikuje uvedené transformačné vzťahy pre určitý typ axonometrie.
namespace _3D_Renderer
{
class Projection
{
int[,] transformation = new int[8, 2];
public void Axonometric(int x, int y, int z, int n)
{
transformation[n, 0] = (int)(-x * Math.Cos(-0.7) + y * Math.Cos(0.8) + 800 / 2);
transformation[n, 1] = (int)(-x * Math.Sin(-0.3) - y * Math.Sin(-0.2) + z + 600 / 2);
}
public int[,] Transformation
{
get
{
return transformation;
}
}
}
class Program
{
static void Main(string[] args)
{
int width = 800;
int height = 600;
Canvas canvas = new Canvas(width, height);
Projection projection = new Projection();
Color background = new Color(0, 0, 0);
Color white = new Color(255, 255, 255);
canvas.clearColor(background);
int[,] mVertices = { { 0, 0, 0 }, { 100, 0, 0 }, { 100, 0, 100 }, { 0, 0, 100 }, { 0, 100, 0 }, { 100, 100, 0 }, { 100, 100, 100 }, { 0, 100, 100 } };
int[,] mIndices = {
{ 0, 1, 0, 1, 1, 0, 0, 0 },
{ 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 0, 1, 0 },
{ 1, 0, 1, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 1, 0, 1 },
{ 0, 1, 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 0, 0, 1, 0, 1 },
{ 0, 0, 0, 1, 1, 0, 0, 0 },
};
for (int n = 0; n < 8; n++)
{
projection.Axonometric(mVertices[n, 0], mVertices[n, 1], mVertices[n, 2], n);
}
for (int i = 0; i < 8; i++)
{
for (int j = i; j < 8; j++)
{
if (mIndices[i, j] == 1)
{
canvas.drawLine(white, projection.Transformation[i, 0], projection.Transformation[i, 1], projection.Transformation[j, 0], projection.Transformation[j, 1]);
}
}
}
canvas.render();
System.Diagnostics.Process.Start("Frame.exe", "frame.ppm");
}
}
}
V našom prípade zobrazíme všeobecný typ axonometrie, kedy sú všetky tri uhly navzájom rôzne (trimeria).
Stredové premietanie
Stredové premietanie je charakteristické premietajúcimi lúčmi vychádzajúcich z jedného bodu. Týmto spôsobom tento model premietania odpovedá najviac ľudskému videniu, pretože zobrazuje objekty tak, že sa nám na ploche javia s určitou håbkou, perspektívou, a aj z toho dôvodu sa označuje ako perspektívne premietanie.
Pri hľadaní perspektívneho zobrazenia sú určujúce jednotlivé priesečníky priemetne \varepsilon so stenou \nu = xz prakticky vytvárajúci pravouhlý trojhran, a tiež voľba stredu lineárnej perspektívy S(s, -d, v). Podľa obrázku (niektoré nasledujúce symboly nie sú na obrázku označené) reprezentuje v, výšku začiatočného bodu S, úsečky SR (teda táto výška rovnobežná s osou z=x_2) a z hľadiska manipulácie perspektívneho obrazu bude možné týmto parametrom meniť výšku celkového pohľadu výsledného obrazu, teda uhol otočenia okolo osy x. Jedná sa o vzdialenosť od tzv. základnej roviny. V prípade v=0 sa budeme nachádzať priamo v horizonte, na obrázku s označením h. Vzdialenosť s je daná začiatočným bodom S, úsečky SR a osy y, resp. pomyselnej roviny yz a bodu S. Týmto parametrom bude možné meniť šírku celkového pohľadu, teda uhol natočenia okolo osy z. A napokon parameter d je tzv. dištancia a je to veľkosť úsečky SH, teda vzdialenosť bodu S od bodu horizontu H. Týmto parametrom bude možné nastaviť celkovú håbku perspektívy.
Z uvedeného popisu perspektívnej scény potrebujeme vyjadriť parametrické rovnice priamky SR. Tie vyjadríme pre každú zložku nasledujúcim spôsobom:
Parameter d (dištancia) je v tomto prípade záporný, pretože os y nadobúda vo vyznačenom smere kladné hodnoty, a zároveň preto, že na stanovenie priesečníka R_s by mal začiatočný bod S ležať za priemetňou \varepsilon.
Bod R_s je priesečník úsečky SR a priemetne \varepsilon, ktorá má rovnicu y=0. Hodnotu parametra t vypočítame nasledovne:
odtiaľ
Po dosadení a úprave získame súradnice bodu, priesečníka R_s v sústave E^3:
V súradnicovej sústave E^2 platia vzťahy pre x_1 a x_2. Tieto vzťahy budú obsahom nasledovného kódu, ktorý jednotlivé body obrazu z priestoru konvertuje uvedenými transformačnými vzťahmi do roviny.
namespace _3D_Renderer
{
class Projection
{
int[,] transformation = new int[8, 2];
public void Perspective(int x, int y, int z, int n)
{
int d = 150;
int s = 200;
int v = -75;
transformation[n, 0] = (int)((d * x + s * y) / (d + y) + 800 / 2);
transformation[n, 1] = (int)((v * y + d * z) / (d + y) + 600 / 2);
}
public int[,] Transformation
{
get
{
return transformation;
}
}
}
class Program
{
static void Main(string[] args)
{
int width = 800;
int height = 600;
Canvas canvas = new Canvas(width, height);
Projection projection = new Projection();
Color background = new Color(0, 0, 0);
Color white = new Color(255, 255, 255);
canvas.clearColor(background);
int[,] mVertices = { { 0, 0, 0 }, { 100, 0, 0 }, { 100, 0, 100 }, { 0, 0, 100 }, { 0, 100, 0 }, { 100, 100, 0 }, { 100, 100, 100 }, { 0, 100, 100 } };
int[,] mIndices = {
{ 0, 1, 0, 1, 1, 0, 0, 0 },
{ 1, 0, 1, 0, 0, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 0, 1, 0 },
{ 1, 0, 1, 0, 0, 0, 0, 1 },
{ 1, 0, 0, 0, 0, 1, 0, 1 },
{ 0, 1, 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 0, 0, 1, 0, 1 },
{ 0, 0, 0, 1, 1, 0, 0, 0 },
};
for (int n = 0; n < 8; n++)
{
projection.Perspective(mVertices[n, 0], mVertices[n, 1], mVertices[n, 2], n);
}
for (int i = 0; i < 8; i++)
{
for (int j = i; j < 8; j++)
{
if (mIndices[i, j] == 1)
{
canvas.drawLine(white, projection.Transformation[i, 0], projection.Transformation[i, 1], projection.Transformation[j, 0], projection.Transformation[j, 1]);
}
}
}
canvas.render();
System.Diagnostics.Process.Start("Frame.exe", "frame.ppm");
}
}
}
Teda, z hľadiska kódu sa jedná o analogický postup ako pri axonometrii. Jednotlivé vstupné parametre metódy Perspective(), predstavujú už spomínané perspektívne parametre, pričom tieto parametre sa uplatòujú po korekcii šírky a výšky obrazu, takže z toho dôvodu sa obraz nachádza v štvrtom kvadrante celkového obrazu. Ak chceme zobraziť pohľad smerujúci zhora na daný objekt, tak parameter v by mal mať zápornú hodnotu, pretože začiatok súradnicovej sústavy (0,0) je v ľavom hornom rohu obrazu a keï chceme posunúť bod S smerom kolmo nahor, tak potrebujeme odpočítať určitú hodnotu od obrazovej osy y.
Výsledný obrázok perspektívneho zobrazenia kocky pomocou jedného úbežníka je na nasledujúcom obrázku:
Renderovanie
Uvedené spôsoby poukázali na niektoré najčastejšie typy premietaní. Vykresľovanie doposiaľ prebiehalo spôsobom známym z OpenGL/DirectX, kedy len pomocou bodu a úsečky vykresľujeme komplexné objekty. Grafické priemyselné štandardy pracujú v režime vykresľovania scény v reálnom čase, to znamená, že akékoľvek grafické objekty sú na priemetni prekresľované v pravidelných časových intervaloch a navyše takýto spôsob vykresľovania úzko súvisí s hardwarovými vymoženosťami grafickej karty. V celom seriály ako aj u uvedenom prípade bol použitý spôsob vykresľovania objektov z grafických priemyselných štandardov, avšak vykresľovanie scény bolo realizované jednorazovym prepisom prostredníctvom zaznamenanych bodov z platna do obrazového suboru typu PPM. Prakticky to znamenalo to, že sme pouzili vektorovy sposob zapisu objektov, a tento zapis sme priamo aplikovali na priemetnu v povodnom stave, ktory sme uz len jednoducho ulozili do uz spominaneho obrazového suboru. Každe vykreslovanie grafických objektov urcitym systemom sa nazyva renderovanie, avšak v povodnom vyzname tohto oznacenia sa jedna o proces, ktory vedie k fotorealistickému zobrazovaniu geometrických objektov.
V nasledujucich castiach serialu pojde o tzv. renderovanie v klasickom vyzname, to znamena, proces tvorby obrazu z 2D/3D modelov pouzitim textur, osvetlovacich modelov a algoritmami tienovania. Jedna sa o systém znamy z integrovanych renderovacich nastrojov od spolocnosti Autodesk, ktora vydava svoje jednotlive produkty ako napr. 3D Studio MAX, Maya, alebo niektore verzie AutoCADu.
Možností ako reprezentovať geometrické objekty je niekoľko. To, ktorý spôsob použijeme závisí na tom, čo chceme zobrazovať a ako chceme s danými objektmi manipulovať. Niekedy nám stačí zobraziť len pár hlavných bodov objektu, inokedy potrebujeme vedieť detailne zobraziť celú polygónovú sieť objektu s riešením viditeľnosti hrán, textúrou povrchu ako aj osvetlením.
V zásade je jedno, či sa bude jednať o reprezentáciu 2D alebo 3D objektov. Odlišnost v zápise je len v počte súradníc, aj keï reprezentácia v 3D je z hľadiska ïalšieho spracovania zložitejšia. Samotná reprezentácia v podstate vychádza z možností zobrazovania grafickej karty počítača. Pomocou grafickej karty sme schopný vykresľovať len body a úsečky, pričom grafickým procesorom je podporované vykresľovanie trojuholníkov. Práve tento trojuholník sa javí z hľadiska reprezentácie ako efektívny, pretože existujú algoritmy, ktoré popisujú jeho vyplňovanie, je možné tiež veľmi efektívne optimalizovať výpočty priesečníkov lúčov s trojuholníkmi. Túto skutočnosť používajú rôzne vykresľovacie (renderovacie) techniky.
Takúto sieť trojuholníkov je možné vidieť napr. na jednoduchom teréne. Intuitívne by nás napadlo vyskladať takúto sieť tak, že zadáme súradnice každého bodu a nejakým spôsobom pospájame jednotlivé body hranami. Ako bolo spomenuté, ideálne je pospájať tieto body ako vrcholy trojuholníka, kde každý trojuholník bude obsahovať teda tri vrcholy (vertexy) a tri hrany (edges).
Hranová reprezentácia
Jeden zo spôsobov akým je možné reprezentovať objekty je tzv. hranová reprezentácia, alebo tiež drátová (wire-frame). Tento typ budeme používať v našom programe, pretože je jednoduchý a názorný. Ide o spôsob, kde zapisujeme do zoznamu súradnice vrcholov a jednotlivé hrany, osobitne. Takto získame zoznam, ktorý bude popisovať geometriu objektu v podobe vrcholov. Lenže, ak si predstavíme 4 vrcholy, tak spôsobov akým môžeme pospájať jednotlivé vrcholy je niekoľko, napr. spojenie môže predstavovať štvorec (obdlžník), ale tiež aj tvar písmena X alebo spojenie, kde z jedného vrcholu pôjde hrana do všetkých ostatných. Práve na toto odlíšenie potrebujeme ešte ïalší zoznam, ktorý bude popisovať topológiu objektu v podobe hrán, teda ktoré vrcholy budú tvoriť jednotlivé hrany.
Tieto zoznamy sú z matematického hľadiska v podstate množiny obsahujúce informácie o danom objekte. Spolu tieto množiny môžu tvoriť graf, ako matematickú štrukúru, ktorá pozostáva z týchto dvoch množín. Graf je usporiadaná dvojica (V, H), kde V je množina vrcholov a H je množnina hrán, ktorá je navyše ako dvojprvková podmnožina tejto množiny. Dvojprvková preto, pretože obsahuje dvojice vrcholov medzi ktorými sa nachádza hrana.
Napísať a nakresliť takýto graf je možné vo forme diagramu. Máme napr. jeden diagram grafu G_1 = (V_1, H_1), kde V_1 = \left \{ 1,2,3,4,5 \right \} a H_1 = \left \{ \left \{ 1,2 \right \}\left \{ 2,3 \right \}\left \{ 3,4 \right \}\left \{ 4,5 \right \}\left \{ 5,1 \right \} \right \} a diagram grafu G_2 = (V_2, H_2), kde V_2 = \left \{ a,b,c,d,e \right \} a [latex]H_2 = \left \{ \left \{ a,b \right \}\left \{ b,c \right \}\left \{ c,d \right \}\left \{ d,e \right \}\left \{ e,a \right \} \right \}.
Na obrázku s diagramami jasne vidieť rozdiel v spôsobe reprezentácie vrcholov a hrán. Takto je možné zapisovať rôzne diagramy a navyše nad takto zapísanými diagramami počítať ďalšie charakteristiky. Navyše jednotlivé hrany znázornené na diagrame môžu byť neorientované, teda bez označenia smeru od jednotlivých vrcholov, alebo orientované, s označením smeru, čím dané možnosti výpočtu rozširuje. Ono, teória okolo grafov je zložitá približne do miery kombinatoriky ako takej, ale na popis objektov stačí základná definícia.
Diagramy sú názorné, celkom dobre je z nich čítať a vyhodnocovať rôzne charakteristiky, avšak na to, aby sme tieto charakteristiky podrobili počítačovému spracovaniu je potrebné zaviesť maticový zápis, pretože programovacie jazyky obsahujú dátovú štruktúru po¾a, ktorá umožňuje takýto maticový zápis realizovať. V zásade potrebujeme vyjadriť diagramový popis na číslicový, kde v podstate diagram zapíšeme vo forme príznakov v podobe číslic.
Keďže diagram popisujú dve množiny, tak budeme potrebovať dve matice. V hranicovej reprezentácii, ako bolo spomenuté, budeme mať jeden zoznam pre maticu súradníc vrcholov a druhý zoznam pre identifikáciu hrán medzi jednotlivými vrcholmi. Z hľadiska grafov budeme diagramy v maticovom vyjadrení reprezentovať pomocou matice incidencie a matice susednosti. Obidve tieto matice popisujú spôsob reprezentácie hrán medzi vrcholmi. Teda súradnice vrcholov tieto dve matice neobsahujú. V podstate je jedno, ktorou maticou budeme popisovať jednotlivé hrany medzi vrcholmi. V obidvoch prípadoch dostaneme tú istú reprezentáciu hrán.
Matica incidencie grafu G bude predstavovať maticu A s prvkami a_{i,j} typu (n, m), ak pre jej prvky bude platiť:
V tejto matici bude počet riadkov predstavovať počet vrcholov a počet stĺpcov bude predstavovať počet hrán. Incidencia v tomto zmysle znamená, príznak, kde vrchol incidujúci s hranou znamená, že vrchol a hrana sú vo vzájomnej zhode, spolu tvoria súčasť, alebo tiež opačne, kde hrana inciduje s vrcholom, teda hrana tvorí súčasť s vrcholom. Potom môže napr. hrana incidovať s dvoma vrcholmi, ktoré budú navzájom susedné. Takže majme nasledujúci diagram:
Je vidieť, že vrchol v_1 inciduje s hranou h_1, zároveò s hranou h_3 a h_5. Na týchto miestach budú jednotky, k ostatným hranám sa z vrcholu v_1 dostať nevieme, teda neincidujú s nimi, preto budú obsahovať nuly. A postupne vrchol v_2 inciduje z hranou h_1, h_2 a h_4. Analogicky ostatné vrcholy. Takto získame maticu incidencie:
V hranovej reprezentácii potrebujeme vedieť, kde je hrana a to z tejto matice získame tak, že si všímame jednotky v jednotlivých stĺpcoch matice. Takto jednoducho vytvoríme program, ktorý bude vedieť podľa tejto matice bezpečne určiť, kde je hrana. Ono, výhoda oproti matici susednosti je taká, že v tejto matici môžme priamo určiť o ktorú hranu v poradí sa jedná, čiže môžme priamo identifikovať hranu v poradí.
Matica susednosti grafu G bude predstavovať maticu B s prvkami _{i,j} typu (n, n), ak pre jej prvky bude platiť:
V tomto prípade, ak medzi dvoma rôznymi vrcholmi bude hrana, tak v matici bude reprezentovať jednotku, v opačnom prípade nulu. Ak si vezmeme predchádzajúci diagram tak matica susednosti bude vyzerať nasledovne:
Pôjde o symetrickú maticu, pretože nezáleží na poradí vrcholov tvoriacich hranu. Ak by išlo o orientované grafy, tak by to bolo v niektorých prípadoch jednosmerné, v tomto prípade je to všade obojsmerné.
Teraz potrebujeme v našom programe nakresliť úsečku. Náš program dokáže kresliť len bod a úsečku. Grafické priemyselné štandardy ako OpenGL a DirectX na vykreslenie primitív (útvarov) nepoužívajú viac elementov ako bod a úsečku. Funguje to tak, že sa definuje počet vertexov (bodov) a spôsob akým sa majú dané body navzájom pospájať. Ako bolo uvedené vyššie nie je jedno aký spôsob sa použije. V našom prípade to bude podobné. Na vykreslenie úsečky môžme použiť priamo metódu drawLine(), ktorá vykreslí úsečku so zadanými súradnicami.
namespace _2D_Renderer
{
class Program
{
static void Main(string[] args)
{
int width = 800;
int height = 600;
Canvas canvas = new Canvas(width, height);
Color background = new Color(0, 0, 0);
Color white = new Color(255, 255, 255);
canvas.clearColor(background);
canvas.drawLine(white, 300, 200, 400, 300);
canvas.render();
System.Diagnostics.Process.Start("Frame.exe", "frame.ppm");
}
}
}
Vizuálne to bude vyzerať nasledovne:
Čo sa týka zdrojového kódu úsečky, metódy drawLine(), bol použitý Bresenhamov algoritmus úsečky, ktorých je na internete kvantum. Pre konkrétnosť uvedieme zdrojový kód daného algoritmu:
public void drawLine(Color color, int x1, int y1, int x2, int y2)
{
if ((x1 - x2) > 0)
{
drawLine(color, x2, y2, x1, y1);
return;
}
if (Math.Abs(y2 - y1) > Math.Abs(x2 - x1))
{
bresteepline(color, y1, x1, y2, x2);
return;
}
int x = x1, y = y1, sum = x2 - x1, Dx = 2 * (x2 - x1), Dy = Math.Abs(2 * (y2 - y1));
int deltaDy = ((y2 - y1) > 0) ? 1 : -1;
for (int i = 0; i <= x2-x1; i++)
{
setPixel(x, y, color);
x++;
sum -= Dy;
if (sum < 0)
{
y = y + deltaDy; sum += Dx;
}
}
}
private void bresteepline(Color color, int x3, int y3, int x4, int y4)
{
if ((x3 - x4) > 0)
{
bresteepline(color, x4, y4, x3, y3);
return;
}
int x = x3, y = y3, sum = x4 - x3, Dx = 2 * (x4 - x3), Dy = Math.Abs(2 * (y4 - y3));
int deltaDy = ((y4 - y3) > 0) ? 1 : -1;
for (int i = 0; i <= x4-x3; i++)
{
setPixel(y, x, color);
x++;
sum -= Dy;
if (sum < 0)
{
y = y + deltaDy; sum += Dx;
}
}
}
Ak by sme chceli vykresliť napr. štvorec, mohli by sme 4x zavolať metódu drawLine() s relevantnými súradnicami vrcholov. Ale ak by sme chceli ponechať súradnice vrcholov a nakresliť jednotlivé úsečky z iných vrcholov museli by sme pomerne pracne prepisovať jednotlivé súradnice vrcholov. Práve pre tento účel bola definovaná matica susednosti a incidencie. Nasledujúci zdrojový kód popisuje ich použitie.
namespace _2D_Renderer
{
class Program
{
static void Main(string[] args)
{
int width = 800;
int height = 600;
Canvas canvas = new Canvas(width, height);
Color background = new Color(0, 0, 0);
Color white = new Color(255, 255, 255);
canvas.clearColor(background);
int[,] mVertices = { { 300, 200 }, { 400, 300 } };
int[,] mIndices = {
{ 0, 1 },
{ 1, 0 }
};
int x1, y1, x2, y2;
for (int i = 0; i < 2; i++)
{
for (int j = i; j < 2; j++)
{
if (mIndices[i, j] == 1)
{
x1 = mVertices[i, 0];
y1 = mVertices[i, 1];
x2 = mVertices[j, 0];
y2 = mVertices[j, 1];
canvas.drawLine(white, x1, y1, x2, y2);
}
}
}
canvas.render();
System.Diagnostics.Process.Start("Frame.exe", "frame.ppm");
}
}
}
Vykreslenie tejto úsečky má rovnaké súradnice ako v predošlom prípade len s tým rozdielom, že sme použili maticu susednosti. Pôvodne sme definovali úsečku so súradnicami, teraz definujeme súradnice v matici vrcholov (mVertices) a kreslíme danú úsečku podľa akejsi šablóny, matice susednosti (mIndices), ktorá určuje, ktoré hrany majú byť vykreslené od jednotlivých vrcholov. V tomto prípade podľa matice susednosti by mal program kresliť úsečku 2x, pretože matica je symetrická, teda od jedného vrcholu k druhému a naopak. Aby zápis korešpondoval s definíciou matice susednosti budeme zapisovať celú maticu susednosti, avšak program reálne kreslí len prvky nad diagonálou tejto matice. Samozrejme, mohli by sme použiť analogicky aj maticu incidencie.
Poznámka: Maticu susednosti v programe označujeme ako mIndices, teda ako maticu indexov. Použil som toto pomenovanie známe z knižnice DirectX, kde sa takto označuje matica, ktorá nejakým spôsobom definuje vzťahy medzi vertexami. Použitie pomenovania napr. mAdjacent alebo mAdj by mohlo byť zavádzajúce vzhľadom k tomu, že existuje tzv.adjungovaná matica, čo je transponovaná matica, kde sú navzájom vymenené riadky za stĺpce.
Ono, napr. v OpenGL, ale aj DirectX sa súradnice vrcholov zapisujú do osobitnej metódy, kde sa definuje len jeden vertex (bod), pričom je tiež možné definovať pole vertexov, podobne ako v našom prípade. Priemyselné štandardy už niečo také ako vykresľovanie podľa matice susednosti v takom rozpoložení ako ukazuje tento kód primárne nepoznajú, ale určite by to bolo možné aplikovať na nejaké čiastkové vykresľovanie. Principiálne to tam existuje vo forme dodefinovania vertexu, alebo pri samotnom vykreslení sa určí o aký typ vykreslenia ide, napr. o zoznam (zreťazených) úsečiek, zoznam (zreťazených) trojuholníkov, alebo veárovitý spôsob, kde jeden vrchol prináleží viacerým vrcholom spojených hranou.
Teraz podobným spôsobom môžme vykresliť štvorec, ale aj akékoľvek iné útvary. Ako sme už spomínali, stačí definovať vrcholy a spôsob, akým jednotlivé vrcholy budú pospájané je už na matici susednosti. Nasledujúci zdrojový kód teda ilustruje vykreslenie štvorca:
namespace _2D_Renderer
{
class Program
{
static void Main(string[] args)
{
int width = 800;
int height = 600;
Canvas canvas = new Canvas(width, height);
Color background = new Color(0, 0, 0);
Color white = new Color(255, 255, 255);
canvas.clearColor(background);
int[,] mVertices = { { 300, 200 }, { 400, 200 }, { 300, 300 }, { 400, 300 } };
int[,] mIndices = {
{ 0, 1, 1, 0 },
{ 1, 0, 0, 1 },
{ 1, 0, 0, 1 },
{ 0, 1, 1, 0 }
};
int x1, y1, x2, y2;
for (int i = 0; i < 4; i++)
{
for (int j = i; j < 4; j++)
{
if (mIndices[i, j] == 1)
{
x1 = mVertices[i, 0];
y1 = mVertices[i, 1];
x2 = mVertices[j, 0];
y2 = mVertices[j, 1];
canvas.drawLine(white, x1, y1, x2, y2);
}
}
}
canvas.render();
System.Diagnostics.Process.Start("Frame.exe", "frame.ppm");
}
}
}
Vizuálne to bude vyzerať nasledovne:
Ak napr. chceme spojiť diagonálu, môžme tak urobiť spojením vrcholu v_2 z v_3, čo v matici susednosti reprezentuje jednotka na pozícii [1, 2] a [2, 1].
Podobne môžme kresliť rôzne útvary pozostávajúce z vrcholov a úsečiek. Pri kreslení kriviek sa používajú interpolačné a aproximačné vzťahy. Krivky a plochy budú obsahom ďalších článkov.
Priestorové objekty zatiaľ kresliť nemôžme. Mohli by sme imitovať hĺbku obrazu tak, že by sme vytvorili pomocou dvojrozmerných súradníc tretiu súradnicu hĺbky, ale nešlo by skutočne o rozšírenie rozmeru, pretože z hľadiska programu by sme mali k dispozícii len rovinné súradnice. Vždy sa jedná o rovinu, plochu ako obraz (2D), teda reálne nie je možné nakresliť akýkoľvek priestorový objekt tak, aby sme skutočne vnímali hĺbku. Stále pôjde len o napodobenie skutočného obrazu sveta. Teda súradnica hĺbky obrazu bude vždy zobraziteľná (premietaná) na rovnakej ploche obrazu ako ostatné súradnice, avšak jej súradnice vzhľadom na plochu sa budú prepočítavať rôznymi vzťahmi v závislosti na požadovanom zobrazení, napr. axonometrické zobrazenie, perspektívne a iné. Navyše sa môžu už prepočítané súradnice znovu prepočítavať vzhľadom na pozíciu a pohľade pozorovateľa, pri kamere alebo fotoaparáte v zmysle pozície a natočenia objektívu.
Teda, nato, aby sme mohli kresliť, premietať, priestorové objekty na rovinu potrebujeme najskôr rôzne transformačné vzťahy pre vytvorenie priestoru. K tomuto účelu bude slúžiť nasledujúci článok.
Hraničná reprezentácia
Niekedy nie je potrebné zaznamenávať informácie o všetkých hranách objektu, ale len o tých, ktoré sú z hľadiska viditeľnosti prístupné nášmu pohľadu. Ostatné hrany sa ignorujú. To je prípad hraničnej reprezentácie (boundary representation, B-rep). Mohlo by sa zdať, že tento typ rieši určitú problematiku viditeľnosti hrán, avšak je to len zdanie, pretože zadávame len tie hrany, ktoré z hľadiska pohľadu majú byť viditeľné. Teda, popisujeme len plášť objektu (aj keď samotný plášť netvorí plocha, ktorá je nejakým spôsobom definovaná), jeho obrysy. Pri jednoduchých objektoch je táto metóda ešte zvládnuteľná z hľadiska manuálnej selekcie hrán, ale pri zložitejších objektoch to býva časovo náročné.
Jednoduchá a štruktúrovaná plôšková reprezentácia
Plôšková reprezentácia obsahuje informácie o plochách objektu, z ktorého pozostáva. Je to už reálna plocha narozdiel od predchádzajúcej reprezentácie, takže štruktúra obsahuje okrem vrcholov a hrán aj informáciu o plochách. To znamená napr. to, že je možné daný objekt osvetľovať, keďže už obsahuje plochy, a tiež je možné realizovať viditeľnosť jednotlivých častí objektu, aj keď v princípe je možné riešiť viditeľnosť pri akomkoľvek type reprezentácie, ale nie vždy to bude efektívne. V každom prípade budeme môcť zobrazovať objekty na pomerne slušnej optickej úrovni. Lenže z hľadiska vnútornej reprezentácie plôch objektu nám môžu chýbať údaje o type hrán alebo susednosti jednotlivých plôch s inými plochami. Práve tento nedostatok rieši štruktúrovaná reprezentácia, ktorá je známa pod názvom okrídlená hrana (winged-edge), okrídlená preto, pretože jedna hrana spolu so susednými hranami pripomína krídla v tom zmysle, že ku každej jednej hrane ústia v jej vrcholoch práve dve ďalšie hrany, ktorá takto spoločne na čele s danou hranou vizuálne tvoria krídla.
Bodová reprezentácia
Objekt môžme popísať aj hranicami bodov, z ktorých pozostáva jeho povrch. Nepotrebujeme vedieť informácie o hranách, ktoré by tak spolu tvorili daný objekt, stačí nám vedieť informácie každého jedného bodu, z ktorého pozostáva objekt. Je to jednoduchšie v tom, že získať informáciu o bode a z neho vytvoriť objekt je menej náročné než získať informácie o hranách, ktoré sa spolupodieľajú na objekte. Jednotlivé body objektu nesú informácie o svojej polohe, normálovom vektore, farbe a ostatných vlastnostiach materiálu alebo svetla. Obyčajne sa tieto body získavajú cestou digitálneho snímania reálnych objektov. Je zrejmé, že táto metóda bude najpresnejšia, ale zároveň najnáročnejšia na pamäťové nároky. V tomto zmysle je to podobné ako pri metóde sledovania lúča, kedy v podstate počítame priesečníky objektov a zobrazujeme jednotlivé body povrchu objektov. Avšak pri týchto metódach existujú rôzne metódy optimalizácie zobrazenia.
Pre ostatné typy reprezentácie objektov ako napr. konštruktívna geometria telies, modelovanie pomocou deformácii, objemová reprezentácia, fraktálová geometria, modelovanie krajín alebo časticové systémy budú čiastočne popísané na inom mieste.
Základom každej počítačovej hry typu stratégia, ale aj mnohých iných hier, kde existuje najaká hracia plocha, je terén v širšom alebo užšom chápaní. Niekedy to je možné označiť aj ako hracia plocha. Každá hra obsahuje nejakú hraciu plochu. Ani neviem prečo, ale osobne ma nejakým spôsobom zaujali práve také hry, ktoré obsahovali pevninový terén v rámci geografického reliéfu. Nie, že by to bola jediná vec, ktorá ma na hre zaujala, ale v rámci budovania pevninových reliéfov som nadobudol pocit, že môžem hru lepšie ovládať, získať nad ňou lepšiu kontrolu. Jednou z takýchto hier bola počítačová hra Transport Tycoon z roku 1994.
Pamätám sa ako som túto hru spustil prvýkrát v lete roku 1997 na počítači 386 DX-4 40 MHz a 4 MB RAM. Okamžite ma táto hra zaujala. Hral som ju po nociach, deň, čo deň. Nechal som počítač "zarábať peniaze", pretože keď ste hru nechali spustenú, tak jednotlivé dopravné prostriedky zarábali peniaze. Stále som sa tešil, až ráno po prebudení rozsvietim monitor počítača a uvidím tú sumu zarovených peňazí. Nešlo mi priamo o peniaze, ale o spájanie jednotlivých dopravných prostriedkov spôsobom "vyklikávania" jednotlivých "štvorčekov" na mape.
Ono, veľa hier predtým aj potom boli koncipované tak, že vám túto prácu akoby uľahčovali. Mohli ste upravovať terén "brush, štetcom", kedy ste upravovali nie 1 tile, štvorec na mape, ale skupinu napr. 5x5 tilov, štvorčekov na mape. Nebolo to zlé, ale problém bol v tom, že už to stratilo akúsi "poctivosť". To je to správne slovo. Tie počítačové hry po roku 1997 s nástupom DirectX už akoby stratili "poctivosť". Píšem to v úvodzovkách pretože, tie modernejšie hry boli určite poctivé, čo sa týka grafického spracovania a niektoré aj herného spracovania, ale nedosahovali akúsi hernú poctivosť. Neboli to úplne všetky, samozrejme, napr. hra Settlers, Age of Empires, SimCity, Civilization, ale aj Theme Hospital, The Sims a podobne, mali svoje herné vymoženosti, ale hra Transport Tycoon bola špeciálna.
Teda, zaujímavým prvkom hry bola teda možnosť realizovať výstavbu terénu. Takže obsahovala možnosť editácie terénu, mapy. Nie je to nič výnimočné, ale na vtedajšie hry nebolo tak veľa hier, ktoré by ponúkali možnosť upravovať terén počas hrania samotnej hry. Na druhej strane, to, že mnohé hry ponúkali možnosť upravovať terén počas hrania hry, nebolo natoľko zaujímavé, pretože úprava terénu vo väčšom merítku než 1x1 už nemala taký zmysel. Pretože jednotlivé prevýšenia medzi dvoma úrovňami terénu neboli natoľko nežiadúce, aby ich hráč musel redizajnovať, meniť a to z toho dôvodu, že nejaké herné objekty bolo možné aplikovať aj na terén, ktorý nemal veľké prevýšenie.
A tak sa stalo to, že hráč paradoxne nepotreboval realizovať nejaké terénne úpravy, resp. minimálne. Lenže v hre Transport Tycoon potrebujete realizovať priame úpravy terénu, pretože terén je realizovaný akoby "jednotkovo", tiež atomicky. Na určitý typ prevýšenia nie je možné postaviť nejaký herný objekt, budovu, to znamená, že je nutné revidovať takýto terén.
Jednotlivé "štvorčeky, tile" sú dimenzované špecificky. Ako vidieť na obázku, každý typ štvorčeka má svoj význam, ale určitý typ štvorčeka nie je možné postaviť nejaký herný objekt. Lenže, ak by ste mali terén, kde by bola skupina štvorčekov rovnakého charakteru, tak nemá zmysel upravovať dané plôšky. Len tie, ktoré by mali veľmi veľké terénne prevýšenie.
Otázka je, ako naprogramovať takýto typ terénu. Ono, mohlo by sa zdať, že samotný autor hry Transport Tycoon realizoval hru v 3D prevedení. Avšak nie je to pravda. Samotná hra bola realizovaná v 2D prevedení s tým, že jednotlivé štvorčeky boli koncipované ako 3D a teda navonok to vyzeralo ako 3D izometrická hra. Áno, vyzerá to ako šialenstvo, generovať toľko 2D štvorčekov, ale v skutočnosti to takto bolo realizované. Programovať to takýmto spôsobom v dnešnej modernej dobe je tzv. "obsolete", zastarané. Aj keď pri dnešných "javascriptových" jazykoch by to nemuselo byť zlé riešenie, v každom prípade nie je až také veľmi náročné to realizovať v 3D prevedení.
Od čias, keď som objavil hru Transport Tycoon som chcel naprogramovať vlastnú hru, typologicky podobnú tejto hre. Skúšal som viackrát naprogramovať základný terén v C/C++ OpenGL, neskôr v C/C++ DirectX, ale zakaždým som narazil na rovnaký problém. Nevedel som realizovať prepočet trojuholníkov tak, aby jednotlivé trojuholníky boli koncipované "dobre, správne". Ono, bolo by možno zadefinovať exaktne, matematicky, čo je to "správne", ale vysvetlím to na jednoduchom príklade.
Čierna sieť reprezentuje klasickú 3D polygonálnu sieť, kde je možné vidieť, že jednotlivé trojuholníky sú dimenzované všetky v jednom smere. Mohli by byť aj v opačnom smere, ale tiež všetky jednostranne. Ak by ste na takomto teréne klikli myšou na vertex a vyvýšili by ste jeho polohu o nejakú vzdialenosť (v zvislom smere, kolmom na všeobecnú rovinu, nie na danú aktuálnu plôšku), tak jednotlivé okolité plôšky by sa modifikovali určitým spôsobom, ako je zobrazené na obrázku. Teda, neboli by realizované akýmsi "uzavretým" spôsobom, teda takým ako je možné vidieť na priložených obrázkoch priamo z hry Transport Tycoon. Takže je nutné vykonať zmenu v polohe uhlopriečky daných štvorcov.
Zelenou farbou je vyznačená zmena v polohovaní uhlopriečok tak, že každá uhlopriečka je kolmá na daný kliknutý vertex. Podobný efekt je možné realizovať tak, že uhlopriečka bude dimenzovaná v 45 stupňovom smere oproti vertexu, na ktorý sme klikli myšou. Transport Tycoon používa spôsob vykreslenia, ktorá je indikovaná zelenou farbou, ale napr. hra SimCity používa spôsob vykreslenia modrou farbou.
Ako to naprogramovať ?
Osobne som dlho nad tým premýšľal od roku 1997, aj keď nie každý deň, napadli ma rôzne riešenia, ale vo výsledku som nevedel ako to realizovať v prostrední DirectX/OpenGL, teda takom grafickom prostredí, kde musíte uvažovať nad 1 štvorcom tak, že tento štvorec (Face) je dimenzovaný ako 2 trojuholníky. Toto je prapodstata podstaty moderných grafických vykresľovacích štandardov. Zo začiatku som si myslel, že riešenie je úplne jednoduché v tom duchu a zmysle, že nemusíme uvažovať nad uhlopriečkami. Že teda, grafická reprezentácia 1 face bude korelovať "tak ako chceme" a zobrazí sa terén v "správnom rozpoložení".
Prečo som si to dlho myslel ?
Pretože DirectX/OpenGL poskytoval na vykreslenie grafického objektu v 3D scéne možnosť tvorby pomocou vertex a vertex buffera. Primárne poskytoval takýto prístup, ale sekundárne poskytoval aj prístup pomocou tzv. indices, alebo tiež index buffer, teda "tie uhlopriečky". Na to, aby ste vykreslili nejaký model v 3D scéne, nemusíte tento model realizovať na báze "indices". Grafický systém dokáže "pospájať" tieto vertexy, vrcholy nejakým určeným spôsobom, ktorý obvykle používa asi 7 typov spojenia (TriangleList, TriangleStrip, LineStrip, a podobne). Každý z týchto spojení je nejakým spôsobom špecifický, ale určite nerealizuje to, že to akoby "vykreslí správne". Je otázka, čo je správne a nesprávne, ale v každom prípade, dané pospájanie nebude korelovať s "tycoon terrain" spojením. Preto som si myslel, že stačí len definovať vertexy a ono sa to nejako samo od seba pospája správne. Skúšal som naprogramovať aj toto, teda len vyvýšenie vertexu, ale vzhľadom na vtedajšie nižšie programátorské znalosti sa mi to nepodarilo naprogramovať. Nie, že by to bolo ťažké, ale nepristupoval som k tomu správne. Predpokladal som, že grafický systém DirectX/OpenGL vykonáva určité grafické operácie automaticky. Konkrétne mám na mysli, update posunutého vertexu. Myslel som si, že ak zmením pozíciu vertexu, tak grafický systém danú zmenu vykoná bez toho, aby som to musel ešte dodatočne explicitne dodefinovať. Lenže štandardne sa takéto operácie nevykonávajú implicitne.
Nejaký čas som to nechal tak, až napokon sa objavil grafický editor Unity3D. V tomto to bolo možné naprogramovať jednoduchšie, resp. mali ste bližší kontakt s Editorom, takže programovanie nebolo také ťažkopádne. Takže som si mohol vyskúšať v reálnom čase aj rôzne chybové stavy v dohľadnom čase.
Stále som nevedel prísť na to, ako dispozične otočiť uhlopriečku tak, aby to korešpondovalo s okolitým prostredním. Napadlo ma, rozdeliť mapu na štvorce s diagonálnymi uhlopriečkami. Vyzeralo to ako správne riešenie, avšak nefungovalo to v každom smere, samozrejme. Vyskúšal som rozdeliť mapu na iné diagonálne rozpoloženie, pootočené o 45 stupňov, ale ani toto nefungovalo správne. Vždy to bolo o tom, že ste museli "ešte doupraviť" určité uhlopriečky, aby to korešpondovalo. Takže vás napadlo, že teda musíte naprogramovať algoritmus taký, ktorý by "to kontroloval", či je to správne. A napísať ten kontrolný mechanizmus bolo relatívne komplikované.
Autori to riešia tak, že kontrolujú dané "správne" pootočenie uhlopriečky. Rozumiem prečo to vykonávajú, ale časom som zistil, že takáto kontrola nie je nutná.
Ako ma napadlo vykonať to, že z každého vertexu budeme smerovať práve jednu hranu do každého protiľahlého vertexu ?
Povedal som si, že vždy, keď sa zdvihne terén pomocou vertexu, tak vždy musí byť zabezpečený na vôkol tohto vertexu určité rozpoloženie také, ktoré bude v okolí vertexu sústredené okolo daného vertexu centrovaným spôsobom, alebo tiež veárovitým spôsobom. To, aký dôsledok bude po stranách nebude podstatné, ale dôležité bude, že v danom okolí to VŽDY bude musieť byť v takejto konštelácii. Povedal som si, že OK, ale určite to nebude korešpodovať s okolitými vertexmi a teda som na začiatku túto vísiu zavrhol. Až potom som si povedal, pri obedovaní obedu, kde som videl obrus so štvorčekovým vzorom, že predsa by to mohlo byť správne riešenie. Predstavil som si vo vzore na obruse ako som zdvihol 1 vertex a dispozične upravil uhlopriečky do hviezdy. A potom som si predstavil, čo sa stane, ak zdvihnem druhý vertex vedľa tohto a vykonám rovnakú dispozičnú zmenu, že teda z daného vertexu vyšlem 4 hrany do protiľahlých vertexov. Tam som si všimol prvýkrát, že na 2 face, spoločných pre tento vzniknutý terén, sa stane to, že zmenou uhlopriečky sa nič nestane !
Toto bol môj objav, heuréka !
Zaujímavé je to, že to bude fungovať v každom prípade. Že tým, že sa síce zmení uhlopriečka aj tam, kde "by sa nemala", sa nič nestane, pretože daná plôška bude mať stále platný vykresliteľný face.
Takže, keď som to realizoval v Unity3D, bez tejto úpravy, tak ako je možné vidieť, daný terén je v takom rozpoložení, že krajné uhlopriečky sú nesprávne situované. Programový kód je nasledovný:
mesh = GetComponent<MeshFilter>().mesh;
vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 0, 2), new Vector3(1, 0, 0),
new Vector3(1, 0.5f, 1), new Vector3(1, 0, 2), new Vector3(2, 0, 0), new Vector3(2, 0.5f, 1),
new Vector3(2, 0, 2), new Vector3(3, 0, 0), new Vector3(3, 0, 1), new Vector3(3, 0, 2) };
triangles = new int[] { 0,1,4,3,0,4,1,2,5,4,1,5,3,4,7,6,3,7,4,5,8,7,4,8,6,7,10,9,6,10,7,8,11,10,7,11 };
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles;
Ak som následne vykonal zmenu uhlopriečok, stalo sa to, že daný terén sa vykreslil správne.
mesh = GetComponent<MeshFilter>().mesh;
vertices = new Vector3[] { new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 0, 2), new Vector3(1, 0, 0),
new Vector3(1, 0.5f, 1), new Vector3(1, 0, 2), new Vector3(2, 0, 0), new Vector3(2, 0.5f, 1),
new Vector3(2, 0, 2), new Vector3(3, 0, 0), new Vector3(3, 0, 1), new Vector3(3, 0, 2) };
{
int v = 4;
int v0 = v; //ten vertex, na ktory sme klikli mysou
int v1 = v + 1;
int v2 = v - 1;
int v3 = v + 3;
int v4 = v - 3;
int v5 = v + 4;
int v6 = v + 2;
int v7 = v - 4;
int v8 = v - 2;
triangles01 = new int[] { v0, v1, v5, v0, v5, v3, v0, v3, v6, v0, v6, v2, v0, v2, v7, v0, v7, v4, v0, v4, v8, v0, v8, v1 };
}
{
int v = 7;
int v0 = v; //ten vertex, na ktory sme klikli mysou
int v1 = v + 1;
int v2 = v - 1;
int v3 = v + 3;
int v4 = v - 3;
int v5 = v + 4;
int v6 = v + 2;
int v7 = v - 4;
int v8 = v - 2;
triangles02 = new int[] { v0, v1, v5, v0, v5, v3, v0, v3, v6, v0, v6, v2, v0, v2, v7, v0, v7, v4, v0, v4, v8, v0, v8, v1 };
}
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles01.Concat(triangles02).ToArray();
Virtuálne som predpokladal akože "kliknutie" na daný vertex, teda vertex číslo 4 a 7. Kvázi algoritmus nie je nijako špeciálny. Dané prírastky pre vertexy znamenajú to, že indikujú danú uhlopriečku. Index v0 je vždy ten, na ktorý sme klikli myšou, teda prvý index. V kóde je to napísané 2x, ale v skutočnosti je to pre každé kliknutie rovnaké, len s iným indexom vertexu.
Podstata je v tom, že generujeme index buffer od vertexu v0, na ktorý sme klikli myšou. Generovanie indices je realizované vždy v smere hodinových ručičiek (v protismere by sa daný Face vykreslil z opačnej strany). Pre každý 1 face existuje 6 možností popísania trojuholníka. V našom prípade potrebujeme pre kliknutý vertex v0 hranu idúcu do protiľahlého vertexu, takže toto vykonáme tak, že z vertexu v0 budeme potrebovať vertex o 1 vyššie. Idea je taká, že ak zväčšíme index o +1, tak to bude reprezentovať vertikálny smer (4+1=5, teda vertex v1, alebo 4-1=3, teda vertex v2). Ak pripočítame k vertex indexu +3, -3, tak to bude analogicky reprezentovať horizontálny smer, teda 1 a 7. A potom tiež +2 a -2 v uhlopriečkach. Jedná sa o to, že index buffer je pole celých čísel, ktoré definujú vykreslenie trojuholníka v každom Face, štvorci.
Donedávna som nevedel vyriešiť to, že medzi plôškami na obrázku s indexami vertexov 3, 6, 4, 7, 5, 8 sa dané hrany objavia 2x, teda ešte z predchádzajúcej úpravy vertexov. Daný grafický systém to beztak zobrazí správne, pretože na vykreslenie 1 face postačuje 1 platná uhlopriečka a každá ďalšia navyše sa síce vykreslí, ale na vykreslenie samotného Face to nemá nejaký negatívny vplyv.
Podarilo sa mi vyriešiť aj tento problém nadbytočnej hrany. Nemám to síce už naimplementované, pretože by to bolo analogické a vzhľadom na danú programovú korektnosť by to už bolo funkčné. Jedná sa o to, že danú hranu, alebo hrany by bolo možné zmazať tak, že z daného indices zoznamu by sa vyhľadala vždy najskôr tá hrana, ktorá by mala byť pridaná. Takže, algoritmus by fungoval tak, že najskôr by vymyzal hrany z daných 4 Face, okolo toho kliknutého v0 vertexu a potom by realizoval to, čo je popísané vyššie, že by teda vygeneroval 4 hrany do protiľahlých vertexov.
Ako zmazať nadbytočné hrany z indices ?
Ako je možné vidieť v programe, indices, alebo tiež triangle list spájam s ďalším zoznamom indices. Napriek tomu, že vykonávam túto operáciu, nemá to vplyv na samotné vykreslenie trojuholníkov. Z toho vyplýva, že nezáleží na poradí jednotlivých vertexov v rámci triangle zoznamu. Je nutné zachovať poradie generovania trojuholníkov v rámci súslednosti v smere hodinových ručičiek, ale potom už nezáleží na tom, či daný zoznam triangle bude alokovaný na začiatku celkového triangle zoznamu, alebo na jeho konci (alebo dokonca aj kdesi uprostred zoznamu, resp. v ľubovoľnom prvku v zozname). Keďže je toto takto definované, tak to znamená, že určite bude možné nájsť 1 triádu napr. (4, 5, 8) v celkovom zozname indices, triangle list. Myslel som si, že bude nemožné to násjť v zozname, pretože pre index 4 existuje minimálne 8 triád s číslom 4, ak zabezpečíme, že dané triády budeme generovať rovnakým spôsobom. Jedná sa o to, že dané triády bude vždy generované rovnakým spôsobom, podľa akejsi vnútornej dohody. Ak to takto bude, tak potom vždy nájdeme dané triády. Nikdy sa nestane, že ak bude nejaká triáda obsahovať číslo s indexom napr. 4, že to bude taká triáda, ktorú tam nenájdeme v zozname triangle list. Ak toto bude zachované, tak potom sa stane to, že po kliknutí na vertex v0, nájdeme 8 triád z jednej strany a 8 triád z druhej strany, pretože trojuholník može byť situovaný s uhlopriečkou v dvojakom rozpoložení. Tieto triády zmažeme a vygenerujeme 8 triád podľa "správneho" uváženia, teda, že z 1 vertexu bude vychádzať 1 hrana do vertexu protiľahlého vrchola.
Tento problém je možné naprogramovať aj v DirectX, čo som teda podľa tutoriálu naprogramoval s rovnakým výsledkom.
Programový kód pre DirectX je komplikovanejší v rámci toho, že musíte napísať obslúžne programové kódy pred samotným generovaním vertexov a indexov, teda vykreslením grafiky na frame buffer. Neuvediem úplne celý programový kód, pretože obslúžny programový kód je defacto takmer nemenný a striktne daný. Takže nasleduje len programový kód pre samotné vykreslenie vertexov a indexov.
Bol to upravený kód pre vykreslenie Kocky (Box). Ale ako je možné vidieť, programový kód pre vykreslenie samotného terénu je veľmi podobný ako pre Unity.
Ako tento problém riešia iní autori ?
Predtým, než som toto vymyslel som nemal žiadne indície od niekoho. Síce dané algoritmy existovali a aj som si niektoré programové kódy pozrel, ale nerozumel som časti pre "kontrolu uhlopriečok". Z toho dôvodu som z daných programových kódov nemal prakticky žiadnu výpovednú hodnotu. Algoritmus tohto problému som nevedel a ani neviem nájsť, ale koreluje s už spomínaným algoritmom prezentovaným mnou na tomto blogu v rámci tzv. Diamond-Square algoritmom. Avšak nie je to tento algoritmus. Vyzerá to síce podobne, je tam "nejaká hviezda a štvorec", ale tento algoritmus nemá za cieľ generovať terén takýmto spôsobom, ale len v rámci daného Face rozložiť daný Face na typologicky Diamond-Square spôsob. Nič viac, nič menej. Predtým, než som vymyslel tento algoritmus, som nepoznal tento algoritmus. A aj keby som ho poznal, tak idea hviezdy v rámci Face by mi nepomohla, pretože jednak som to už dávnejšie vedel a jednak som to aj zavrhol ako "nesprávne riešenie".
Iní autori to riešia tak, že generujú tzv. height mapu, s určitými číslami a nad touto mapou aplikujú algoritmus, ktorý "pospája" dané uhlopriečky požadovaným spôsobom v tycoon štýle. Nie je na tom nič zlé, len je tam ten problém, že autori tohto algoritmu musia KONTROLOVAŤ vertexy a uhlopriečky, "či sú v správnom rozpoložení". Oni to napokon tiež majú realizované tak, že z daného vertexu bude vychádzať práve 1 hrana. Jedná sa o to, že kontrolujú úplne všetky vertexy a ešte nad to kontrolujú tie, ktoré sú náležite hodné.
Samozrejme môj algoritmus predpokladá, že daný terén už je daný a je na začiatku v správnom uhlopriečkovom rozpoložení. Ale to je jediná vstupná požiadavka. Potom sa vykonáva zmena vždy len po kliknutí na daný 1 vertex, bez akejsi ďalšej kontroly.
using UnityEngine;
public class terrainManager : MonoBehaviour {
public static terrainManager instance;
public int terrainWidth, elementWidth;
public terrainElement terrainPrefab;
[HideInInspector]
public Vector3[] coords;
[HideInInspector]
public terrainElement[] terrainElements;
// void OnDrawGizmosSelected()
// {
// foreach(Vector3 vec3 in coords)
// {
// Gizmos.color = Color.red;
// Gizmos.DrawSphere(vec3, .1f);
// }
// }
void Start()
{
instance = this;
CreateCoords();
CreateTerrainElements();
}
private void CreateCoords()
{
coords = new Vector3[(terrainWidth + 1) * (terrainWidth + 1)];
/*
every side needs to be 1 unity longer
*/
for (int i = 0, z = 0; z <= terrainWidth; z++)
{
//outer loop, z-axis
for (int x = 0; x <= terrainWidth; x++, i++)
{
/*
inner loop, x-axis
setting height value based on perlin noise
needs to be optimized low, mid and high frequency noise
*/
float y = Mathf.PerlinNoise((float)x / 20, (float)z / 20) *10;
y = Mathf.Floor(y) / 2;
coords[i] = new Vector3(x, y, z);
}
}
}
private void CreateTerrainElements()
{
int tilesPerSide = terrainWidth / elementWidth;
terrainElements = new terrainElement[tilesPerSide*tilesPerSide];
for (int i = 0, z = 0; z < tilesPerSide; z++)
{
//outer loop, z-axis
for (int x = 0; x < tilesPerSide; x++, i++)
{
terrainElement elementInstance = Instantiate(terrainPrefab, this.transform);
elementInstance.Initialize(x,z);
terrainElements[i] = elementInstance;
}
}
}
}
TerrainElement.cs
using UnityEngine;
public class terrainManager : MonoBehaviour {
public static terrainManager instance;
public int terrainWidth, elementWidth;
public terrainElement terrainPrefab;
[HideInInspector]
public Vector3[] coords;
[HideInInspector]
public terrainElement[] terrainElements;
// void OnDrawGizmosSelected()
// {
// foreach(Vector3 vec3 in coords)
// {
// Gizmos.color = Color.red;
// Gizmos.DrawSphere(vec3, .1f);
// }
// }
void Start()
{
instance = this;
CreateCoords();
CreateTerrainElements();
}
private void CreateCoords()
{
coords = new Vector3[(terrainWidth + 1) * (terrainWidth + 1)];
/*
every side needs to be 1 unity longer
*/
for (int i = 0, z = 0; z <= terrainWidth; z++)
{
//outer loop, z-axis
for (int x = 0; x <= terrainWidth; x++, i++)
{
/*
inner loop, x-axis
setting height value based on perlin noise
needs to be optimized low, mid and high frequency noise
*/
float y = Mathf.PerlinNoise((float)x / 20, (float)z / 20) *10;
y = Mathf.Floor(y) / 2;
coords[i] = new Vector3(x, y, z);
}
}
}
private void CreateTerrainElements()
{
int tilesPerSide = terrainWidth / elementWidth;
terrainElements = new terrainElement[tilesPerSide*tilesPerSide];
for (int i = 0, z = 0; z < tilesPerSide; z++)
{
//outer loop, z-axis
for (int x = 0; x < tilesPerSide; x++, i++)
{
terrainElement elementInstance = Instantiate(terrainPrefab, this.transform);
elementInstance.Initialize(x,z);
terrainElements[i] = elementInstance;
}
}
}
}
Následne vykreslenie danej scény pre tento programový kód v Unity bude vyzerať nasledovne.
Ako je možné vidieť výsledok je rovnaký. Takže takýmto spôsobom je možné realizovať vykreslenie terénu v štýle hry Transport Tycoon. Osobne som sa o to pokúšal veľmi dlhý čas, niečo málo cez 25 rokov a nikdy sa mi to nepodarilo v adekvátnom grafickom rozpoložení. Nikdy by som ani nebol povedal, že na to prídem v podstate úplne sám a defacto úplnou náhodou v tom zmysle, že revidujem to, čo ma kedysi už napadlo.
Až na tomto algoritme som si uvedomil, aké je dôležité veriť v svoju intuíciu a v sile myšlienky. Znie to ako klišé, tak lacno a jednoducho, ale skutočne až v tomto okamihu som si uvedomil, že veci nie sú až také zložité ako vyzerajú a že aj tie najťažšie problémy sú riešiteľné pomerne jednoduchým spôsobom :)
V nasledujúcom videu to popisujem podrobnejšie, alebo minimálne vizuálne prívetivejšie: