/------+    --+ +--+
                          +-----+/|      | |  ++
                          |     | |    --+ |   |
                          |     |/       | |  ++
                          +-----+      --+ +--+

                          Programování 3D Enginů

                                  Část I


           Za  dobu  existence  Výhně  bylo v rubrice Programování mnoho
      návodů, jak kreslit polygony, jak je vyplňovat texturou, jak dělat
      env-mapping,  jak rotovat body kolem osy a dalších. To stačí tak k
      vytvoření   efektů   typu  rotující  brambora,  ale  žádný  trochu
      složitější  3D systém tím nevytvoříme. Co by měl umět takový lepší
      systém  umět?  Měl  by zvládat kreslení několika objektů a alespoň
      jedné  kamery  a  světla.  To všechno by se mohlo naprosto volně a
      nezávisle   na  sobě  pohybovat  prostorem.  Také  by  bylo  dobré
      umožnit  předdefinování  drah, po kterých se objekty pohybují, ale
      to lze přidat již relativně snadno.

           Předpokládám,  že  čtenář ví, jak v 3D prostoru zapsat pozici
      bodu  (pomocí 3 souřadnic,) co je to vektor, jak rotovat bod kolem
      os x,y a z (viz. článek v některém z prvních čísel Výhně) a byl by
      schopen napsat jednoduchý 3d systém, který by uměl alespoň rotovat
      množinu více bodů a nakreslit je. Také je téměř nutné vědět, co to
      je vektorový součin a skalární součin.


      Co obsahuje 3D scéna ?
      Jak jsou reprezentovány předměty ?

           3D scéna může obsahovat tělesa (objekty) a kameru. Světly ani
      ničím  jiným  se  zatím  nebudu  zabývat. Každý objekt se skládá z
      konvexních    n-úhelníkových    plošek,   většinou   ale   pouze z
      trojúhelníků,  protože  to je pro program mnohem jednodušší. Každý
      trojúhelník  má  samozřejmě  tři  vrcholy  - každý je bodem v naší
      soustavě souřadnic.

           Jednoduché  3D  enginy, často používané v demech hlavně kolem
      roku  1995, často mohly obsahovat jen jediný objekt - na obrazovce
      rotovalo  jedno těleso, po něm další a další, k tomu hrála hudba a
      už  byl  konec.  Různá dema navíc často obsahovala naprosto stejné
      objekty,  většinou  vypůjčené  z 3D Studia (většinou duck.3ds nebo
      chrmface.3ds)  Rotace  objektů  byla  spíše čistě náhodná, autorům
      prostě  stačilo,  že  se na obrazovce něco hýbe. Tělesa rotovala o
      nějaký úhel kolem osy x, o jiný kolem y a z a to bylo všechno.

           Když  však  chceme  ovládat  například  pohyb  tělesa jako je
      letadlo, pomocí rotací kolem hlavních os dlouho nevystačíme. Dejme
      tomu, že letadlo míří ve směru osy x, křída jsou rovnoběžná s osou
      y a osa z je na obě kolmá.
                                           .
                                              . x
                                              .
                                              /\
                                              ||
                              .........../----++-----\...................
                                        /-----++------\               y
                                              ||
                                             -++-
                                              .
                                              .
                                              .

           Nyní chceme, aby se letadlo otočilo doleva (z pohledu pilota)
      třeba  o  60  stupňů  - provedeme to jednoduše rotací kolem osy z.
      Dále  se má letadlo naklonit o 10 stupňů doprava (kolem své osy!).
      V  původní  pozici by to šlo jednoduše provést rotací kolem osy x,
      ale  co  teď?  Osa  letadla je nyní rovnoběžná s x a y a s x svírá
      úhel  60  stupňů - de to rovněž provést, avšak už o dost složitěji
      než  pouhou  rotací  kolem  y.  A  nakonec třetí úkol: chceme, aby
      letadlo  mířilo  vzhůru,  dejme  tomu  o 20 stupňů. Už se to velmi
      zamotává, rotace jsou stále obtížněji proveditelné. Je zde i další
      problém: pokud po každé rotaci letadla rotujeme všechny jeho body,
      ztratíme jeho původní data. Také se težko zjišťuje, v které poloze
      se  letadlo  nachází.  Když to chceme vyřešit tak, že původní body
      letadla  při  každém  jeho  vykreslování  znovu  rotujeme,  musíme
      pokaždé  zopakovat  všechny  předchozí  rotace  a  to  je většinou
      nezvládnutelné...

      Jak to tedy udělat ?


      Pro začátek je dobré si alespoň prohlédnout:

        Základy lineární algebry  

      I když většinu z toho nebudeme přímo potřebovat.

      Máme množinu bodů (a trojúhelníků,) definující 3D objekt. Ta se
      nebude vůbec měnit, bude vždy taková, jako na počátku. Dejme tomu
      že je to opět letadlo z minulého odstavce.

      Co potřebujeme znát, abychom mohli letadlo nakreslit?
    1) pozici
       2) směr, kterým letadlo míří
       3) směr, kterým míří křídla

      A teď stačí jen přenést (zobrazit, transformovat) každý bod letadla
      do prostoru, ve kterém ho chceme mít - v 3D grafice se mu říká
      worldspace (=prostor světa.) Podobně prostor, ve kterém je letadlo
      definováno a z kterého se do worldspace transformuje, se nazývá
      objectspace (=prostor objektu.)

      Jak takovou transformaci provést?
      Jak transformovat bod z objectspace do worldspace?

      Jednoduše - podívejme se znova, co všechno víme o poloze letadla:

       1) známe pozici, víme tedy, na jaký bod se bude transformovat
          bod [0,0,0] z objectspace
          -budu ji značit p a jeho složky p0, p1 a p2
       2) známe vektor, který odpovídá ose x
          -budu ho značit vx a jeho složky vx0, vx1 a vx2
       3) známe rovněž vektor, který odpovídá ose y
          -budu značit vy a jeho složky vy0, vy1 a vy2
       4) jistě se vyplatí znát y vektor osy z - dá se sice většinou
          vypočítat z vektorů os y a x skalárním součinem, ale jsou případy,
          kdy nechceme, aby byl na vektory os x a y kolmý.
          -budu značit vz a jeho složky vz0, vz1 a vz2

      Původní souřadnice bodu z objectspace budu značit x,y,z.
      Souřadnice po transformaci x', y', z'.
      Způsob, jak je vypočítat, je už asi nyní zřejmý:

        x'= x*vx0 + y*vx1 + z*vx2 + p0

        y'= x*vy0 + y*vy1 + z*vy2 + p1

        z'= x*vz0 + y*vz1 + z*vz3 + p2

      To velice připomíná lineární transformaci - zobrazení
      aritmetického lineárního prostoru Vn (náš 3D prostor
      je rovněž takový prostor) do Vn definované předpisem:

       y = C*x

       C je matice, x je vektor z prostoru Vn a y je vektor
       po transformaci.   Nyní je dobré si prostudovat nebo
       zopakovat matice -a ti, kdo to ještě nevědí, to snad
       pochopí:

         Shrnutí matic a maticové algebry  

       Matice C by tedy vypadala asi takto:

        +                      +
        | vx0   vx1   vx2   p0 |
        | vy0   vy1   vy2   p1 |
        | vz0   vz1   vz2   p2 |
        +                      +

       a transformovala by (x,y,z) na (x',y',z')
       Kdo si prostudoval matice důkladně, přišelna jeden zádrhel:
       matice transformace musí být čtvercová, my naše matice má
       rozměry 3x4. Co s tím? Přidáme jeden řádek:

        +         +
        | 0 0 0 1 |
        +         +

       a dostáváme:

        x'= x*vx0 + y*vx1 + z*vx2 + w*p0

        y'= x*vy0 + y*vy1 + z*vy2 + w*p1

        z'= x*vz0 + y*vz1 + z*vz3 + w*p2

        w'= x*0   + y*0   + z*0   + w*1

      Kde vzít souřadnici w ?

           Používáme  sice  třírozměrný  prostor  a ne čtyřrozměrný, ale
      když jako w dosadíme číslo 1, výsledné souřadnice jsou stejné jako
      předtím  a  w  vyjde  1.  (Kvůli tomu je v přidaném řádku na konci
      jednička.)
           Když dosadíme za w číslo 0, výsledky se liší a w vyjde 0. Při
      bližším   zkoumání  zjistíme,  že  pokud  se  w=0,  transformujeme
      vektory, a pokud se w=1, transformujeme body.

         Bod má tedy tvar: (x,y,z,1)
                 A vektor: (x,y,z,0)

           Pokud  sečteme 2 vektory, vyjde zase vektor. Když sčítáme bod
      a  vektor,  vujde bod (posunutý o vektor.) Když odčítáme jeden bod
      od druhého, vyjde vektor. Takže všechno jak má být. :)
           Zbývá ještě jedna možnost, když sčítáme několik bodů, vyjde w
      vyšší  než  1,  a  tudíž výsledek není ani vektor, ani bod. To nám
      rovněž vyhovuje.


      Jak ale matice použít?

           Na  začátku  se  matice nastaví tak, aby odpovídala počáteční
      poloze  a  směru  objektu. Často se nastaví na jednotkovou matici,
      takže  platí  x = x' atd. (Jednotková matice má samé nulové prvky,
      jenom v hlavní diagonále má jedničky.

           Veškeré  další změny pozice či směru objektu se provádejí jen
      a pouze úpravou jeho matice!

           Dejme  tomu, že chceme objekt otočit - jeho matici vynásobíme
      maticí pro otočení a je to. když chceme objekt zvětšit, vynásobíme
      jeho  matici  maticí  pro  zvětšení.  Když chceme objekt posunout,
      vynásobíme  ho  maticí  pro  posunutí.  Všechno  je opravdu velice
      jednoduché...

           Znamená  to  také, že pokud chceme mít najednou více objektů,
      musíme  u každého uchovávat jeho aktuální transformační matici. Ta
      zachycuje všechny transformace, které s objektem provádíme, jako:

        posunutí (translation)
        rotace (rotation)
        změna velikosti (scaling)
        a jiné...

           Matice  objektu  uchovává  úplně  všechny jeho transformace v
      jakékoliv kombinaci. A to všechno zabírá jen pár desítek bajtů...


      Jaké matice ale použít pro které transformace?

           Jde je snadno odvodit, proto nebudu uvádět, proč která matice
      vypadá  tak,  jak  vypadá...  Jednoduše  uvedenou maticí vynásobte
      matici objektu. Ale ne v opačném pořadí!


      Posun (Translation)


      (Podle vlastních os objektu! Takže když posunu letadlo po ose
       x, vždycky se posune opravdu letadlo dopředu, ať je v jakékoliv
       poloze!)

        +                 +       tx...posunutí po ose x
        | 1   0   0    tx |       ty...posunutí po ose y
        | 0   1   0    ty |       tz...posunutí po ose z
        | 0   0   1    tz |
        | 0   0   0    1  |
        +                 +


      Zvětšování (Scaling)

        +                +       sx...zvětšení na ose x
        | sx  0   0    0 |       sy...zvětšení na ose y
        | 0   sy  0    0 |       sz...zvětšení na ose z
        | 0   0   sz   0 |       (když nechcete zvětšovan
        | 0   0   0    1 |        na všech osách, jednoduše
        +                +        dosaďte jedničku :)

      Rotace kolem osy X

        +                        +
        | 1      0       0     0 |      u...úhel rotace
        | 0   cos(u)  -sin(u)  0 |      (pokud by objekt rotoval
        | 0   sin(u)   cos(u)  0 |       opačném směru, prohoďte
        | 0      0       0     1 |       znaménka u funkcí sin)
        +                        +


      Rotace kolem osy Y

        +                     +
        | cos(u) 0  sin(u)  0 |      u...úhel rotace
        |   0    1    0     0 |      (pokud by objekt rotoval
        |-sin(u) 0  cos(u)  0 |       opačném směru, prohoďte
        |   0    0    0     1 |       znaménka u funkcí sin)
        +                     +

      Rotace kolem osy Z

        +                      +
        | cos(u) -sin(u)  0  0 |      u...úhel rotace
        | sin(u)  cos(u)  0  0 |      (pokud by objekt rotoval
        |   0       0     1  0 |       opačném směru, prohoďte
        |   0       0     0  1 |       znaménka u funkcí sin)
        +                      +

           Pokud  byste  rádi  viděli alespoň nějaký zdroják pro práci s
      maticemi, tak tady je! Je to část starší verze mého 3D enginu a za
      případné  chyby se zase omlouvám. A také ze místy nekvalitní kód -
      psal jsem to, když jsem ještě pořádně neuměl programovat v C...
      A používal jsem to až donedávna :-)

           A  to  je  prozatím vše. Ještě důležitá rada: prvky matice je
      téměř nutné uchovávat jako proměnné s pohyblivou desetinou čárkou.
      Jinak  již po pár desítkách transformací začnou ztrácet přesnost a
      po chvíli již jsou nepoužitelné. Naproti tomu třeba proměnné typu
      float vydrží mnohem víc, a ze ztráty přesnosti není třeba mít strach,
      neboť k znatelné ztrátě dojde až za velice dlouhou dobu, vydrží
      možná i miliony transformací...

           A  ještě upozornění: spousta 3D enginů a různých jiných textů
      vysvětlujících  použití  matic v 3D grafice uvádějí matice v jiném
      tvaru   -   jsou   prohozené  osy,  jinak  jsou  shodné.  Matice s
      prohozenými osami používá též Direct3D.


      A co zbývá pro příště ? (a určitě i přespříště :-)


      Zatím jsem se vůbec nezabýval tím, jak přidat kameru, která
      se může volně pohybovat, ani hierarchiemi objektů.
      Také bych se rád zmínil o tom, jak používat v 3D enginu
      soubory 3DS vytvořené 3D Studiem.
      A rovněž příště nebude chybět funkční zdroják jednoduchého
      3D enginu včetně kamery a jejího ovládání klávesnicí!
      Bude psaný v C a kompilovat pujde pomoci DJGPP a WATCOM C.
      A pokud máte jakékoliv dotazy, neváhejte a zeptejte se.
      Rovněž mě informujte o případných chybách!
      Uvítám také jakékoliv nápady na pokračování tohoto seriálu.


                               Shakul / MovSD (a.k.a. Lukáš Pokorný)

      email:
        shakul@iname.com
        pokornylukas@bbs.infima.cz

      www:
        http://shakul.home.ml.org

      mail:
        Lukáš Pokorný
        Pomněnková 545
        25243 Průhonice


            výheň