Tehát az effekt, amint a fenti ábra is mutatja, arról szól, hogy az eget próbáljuk utánozni. Ilyen látható Tim Clark Mars demójában. A magam programja megalkotásában pedig nagy segítség volt Mark Mackey (nem ismerem, de az volt a programjában, hogy felhasználható, csak említsem meg a nevét) progija. Egy térbeli végtelen döntött síkot kell ábrázolni, és textúrázni. A térbeliség csak annyit fog jelenteni, hogy a textúrán a lépésközt a Z koordináta szerint mindig módosítani kell kell. Mivel a Z csak egy irányban nől, egyszerű a dolog, miden Scanline-ra újra kell számolni a lépésközt. További könnyebséget jelent, hogy a textúrát nem kell forgatni, hanem vízszintesen rajzoljuk ki. Ezért a lépésköz csak x irányú.

Elöljáróban ennyit.

Tehát oldalról térben. A Z nyilvánvalóan lineárisan változik, ha az ábrát térben nézzük.

Azonban, ha a 2D-s állapotot vizsgáljuk, olyan arányossági tényezőre lesz szükségünk, ami a Z-vel fordítottan arányos, hiszen a lépésköz a textúrán fordítottan arányos a mélységgel. Tehát az igazi Z legyen egyenlő az Y koordinátával: Mivel a koordináta rendszerben Y lefele csökken, ki kell vonni a kezdőpontból.

Z=StartLine-Y

Az arányossági tényező, melynek segítségével a lépésköz kiszámítható:

MapZ=M*P*1/Z
MapZ=M*P*1/(StartLine-Y)

Ahol M egy tetszőleges nagyítás, P a perspektíva, StartLine-Y pedig azt jelenti, hogy hányadik scanline. Mert ugye ezt az értéket scanline-onként újra kell számolni.

Ennek megfelelően, a lépésköz a textúrán.

Step=A*MapZ

Ahol A egy tetszőleges nagyítás. Nem túl egzakt, de ezt úgy kell beállítani, hogy ‘szép legyen'. Ha a lépésköz megvan, a textúrán a kezdőpontot kell kiszámolni.

MapX=-Length/2*Step
MapY=B*MapZ

Magától értetődő, hiszen a sort annyival előbb kell majd kezdeni a textúrán, amennyivel több texturapont fér el a fél soron.

Az MapY pedig nyílván fordítottan arányos a valódi Z-vel. Ezért a már kiszámolt arányossági tényezővel is. Mivel a textúrán csak x irányban fogunk lépegetni, elég kiszámolni a kezdőpont címét, és ehhez a címhez adogatni a lépésközt.

A cím:
StartAddr=256*MapY+MapX

Persze csak 256X256 -os textúra esetén.

Így a következő pontot egyszerűen megkapjuk a lépésköz hozzáadásával. 

Hogy a dolog még bonyolultabb legyen, a gyorsaság kedvéért egyszerre két pixelt rakunk ki, és a szépség kedvéért megpróbáljuk elmosódottabbá tenni a képet (Dithering). 

Az egyszerre két pixel dolog igen egyszerű. Mivel eddig csak egy kezdőpontot számoltunk, számolunk még egyet. A lépésköz hozzáadásával meg is kapjuk. Ezután a lépésközt meg kell kétszerezni, mert különben a két pont mindig egymást fedné a textúrán.

Ezután nézzük a Dithering-et.

Lényege a kép finomítása, a határvonalak elmosása. Esetünkben 2X2-es dithering-et használunk. Annyit tesz, hogy megkülönböztetjük a páros, és a páratlan pontokat. Mivel úgyis két pontot számolunk egyszerre, megtehetjük, hogy az egyiket kinevezzük párosnak, a másikat páratlannak. Páros soroknál az egyiken csúsztatunk egy kicsit páratlanon pedig a másikon. Ez gyakorlatilag annyit jelent, hogy 0,5-öt hozzáadunk, tehát fél textúraponttal eltoljuk. Ez azért célravezető, mert így az egymás alatti sorokban nem a textúrán is egymás alatt elhelyezkedő pontok jelennek meg. Az egymást követő pixelek sem olyan unalmasan követik egymást. Egyszer erre csúszunk egy kicsit a textúrán, egyszer arra. Így nem olyan feltűnőek az esetleges töréspontok.

Lássuk a rutint:

Sky proc Arg X:DWORD,Y:DWORD,Z:DWORD Local MapX:DWORD,MapZ:DWORD Local Step:DWORD push es mov fs,TexSeg mov es,ScreenBufSeg mov di,Border mov ecx,StartLine

Eddig átvettük a szükséges dolgokat. A képernyőbuffer szegmensét, a textúra szegmensét, és 
az eltolást a képernyőn. Fontos, hogy az összes paraméter fel van szorozva 65536-al, tehát 216-al. Gyakorlatilag annyit jelent, hogy a felső 16 bit tartalmazza az egész részt, az alsó 16 bit pedig a törtrészt. Továbbiakban 16.16-al jelölve.

@@loop:         push cx         mov   ebx,ecx         xor   edx,edx         mov   eax,[Z]         shld  edx,eax,P         shl  eax,P         div   ebx         mov MapZ,eax    ;MapZ=P*Z/Line

Kiszámoltuk az arányossági tényezőt. Mivel minden érték 32 bites, a felszorzásál, balra tolásnál a magasabb helyiértékű bitek egyszerűen elvesznek. Ezért használjuk az shld utasítást. Hatására a megadott számú bit tolódik át az egyik operandusból a másikba. Azonban az eredmény csak az első opernadusban fog jelentkezni. Tehát esetünkben az edx-ben megjelenik az eax felső P bitje, de az eax nem változik. Ezért utána az eax-et is fel kell tolni P-vel, így tolódott fel az egész 32 bites érték P bittel. A 32 bites osztásnál pedig az edx.eax 64 bites értéket osztjuk egy 32 bitessel. Vigyázni kell, ha az eredmény nem fér bele eax-be (több mint 32 bit) ugyanazt a hibaüzenetet kapjuk, mint nullával osztásnál. Elég sok bonyodalmat tud okozni, mikor az ember keresi hol osztott nullával, mikor nem is az volt a probléma...

        mov   ebx,MapZ         shr   ebx,9         mov   Step,ebx

A lépésköz arányos az előbb kiszámított hányadossal. Esetünkben 9 bittel lejjebb tolódik, de kinek hogy tetszik, bármi más is lehetne. Az értéket persze továbbra is 16.16-ként kezeljük

        mov   eax,Llength/2         mul   dword ptr[Step]         neg   eax         add   eax,[X]         mov   MapX,eax    ;mapx=(Llength/2)*Step + X    in 16.16

A MapX kiszámolása a már ismertetett képlet alapján. A kód sokkal szebb lenne, ha 256 hoszú sorokat kezelne, mert akkor elég lene 7 bittel feltolni szorzás helyett,. de így meg szélesvásznon élvezhetjük.

        mov   eax,MapZ         shl   eax,Zscale         add   eax,[Y]          ;mapy=mapz*2^zscale + Y     in 16.16

MapY kiszámolása. Arányos MapZ-vel, ezért csak baraléptetjük Zscale-el. Ez is tetszés szerinti. Csak hozzá kell adni a paraméterként kapott Y-t.

        shr   eax,8            ;ah mapy egesz resze         mov   ebx,MapX         sar   ebx,16           ;bl Mapx egesz resze         mov   al,bl            ;ah=mapy  al=mapx                                ;ax=256*mapy+mapx
                                              <-kezdopont offsetje

Az ax-ben megkaptuk a kezdőpont eltolását a textúrán belül. A 16 bittel balra tolt MapY értéket 8 bittel viszatoltuk, így 8 bittel balra tolt lett. Tehát ah-ban elérhető az egész érték. A MapX-et 16 bittel toltuk vissza, ezzel az egészrész lekerült bx-be. A 256 alatti érték bl-ben érhető el. Tehá ah-ban van az y, al-ben az x érték. Ami egyenlő 256*y+x-el, ami pedig az offset.

        mov   bx,di        ;screen addr         mov   si,ax        ; si textura kezdocim egesz resze         mov   eax,MapX     ; ax tortresze                            ; si.ax-> start. pont  in 16.16         mov   di,si        ;si,di ket egeszresz         mov   dx,ax        ;di.bx = si.ax = map starting point

A két kezdőpont megadása. Mivel az értékek 16.16-os formában vannak, a tárolásukhoz két 16 bites regiszter szükséges. Az egyik fogja tartalmazni az egész, a másik a tört részt. Itt használjuk fel a címszámításnál dobott tört részét MapX-nek. Eax-be rakva ax fogja tartalmazni a tört részt.

        add dx,word ptr[Step]         adc di,word ptr[Step+2]

Eddig a két pont ugyanoda mutatott, most léptettük tovább az egyiket. Fontos, hogy a második összeadás adc-vel történjen. Így az első összeadásból kicsorduló bit meg fog jelenni a másodikban. Az eredmény ugyanaz, mintha egyetlen 32 bites összeadást végeztünk volna, csak technikailag lehetelen két külön 16 bites regisztert egyben kezelni. Fontos, hogy a step első két byte-ja tartalmazza a tört részt, a másidok 2 az egész részt. Ennek megfelelően kell az összeadást is elvégezni.

        and   cx,0001         jnz   @odd @even:         add   ax,8000h         adc   si,0         jmp   @ditherOK @odd:         add   dx,8000h         adc   di,0

Megtörtént a dithering. Páros soroknál az egyik, páratlanoknál a másik értékhez adunk 0,5-t. a 8000h ponotsan felet jelent, csak 16 bittel feltolva.

@ditherOK:         shl dword ptr[Step],1

Megszoroztuk 2-vel a lépésközt, mivel két pontunk van.

Az Inner Loop:

        offs=0 rept Llength/2    ; 2 pixels/l         add   ax,word ptr[Step]         adc   si,word ptr[Step+2]         mov   cl,fs:[si]         add   dx,word ptr[Step]         adc   di,word ptr[Step+2]         mov   ch,fs:[di]         mov   es:[bx+offs],cx         offs=offs+2 endm

A ciklust kiküszöböljük a rept makróval. Mindkét pontot ugyanavval a lépésközzel léptetjük, az egészértékeik segítségével, melyekkel címezni is tudunk, pedig megkapjuk a kirakandó pontot. Egyszerre két pontot rakunk ki, ezzel megnő a sebesség is.

        mov   di,bx         add   di,LSize            ; set up for next row

di a következő sor kezdőcímét tartalmazza

        pop   cx         dec   cx         cmp   cx,EndLine                ; Have we finished?         jge   @@loop @End:       pop es       ret sky endp
 

Ennyi.