perjantai 25. tammikuuta 2008

Pikselin piirtoa

Kun nyt olemme alkaneet puhua 3d:stä niin meidän pitää puhua myös pikseleistä, koska ilman niitä emme saa aikaan kunnon 3d:tä. Toki jonkinlaista saadaan rendattua ilman pikseleitäkin, mutta ongelmia tulee. Varsinkin teksturoidessa ja Z-bufferin kanssa. Teksturointi vielä joten kuten onnistuu, mutta Z-Buffer on mahdoton ilman pikseleitä ja vaihtoehdoksi jää Z-Sort, joka ei toimi kunnolla. Tästä syystä Sandyn ja Papervisionin polyt napsahtelee eikä piirry aina oikein.

Ongelma kuitenkin on, että flash on hidas tuhertamaan pikseleitä. Nyt kuitenkin meillä on AS3 ja sen kehuttu huima nopeus käytössämme, joten testataan millä vahudilla pikseliä ruutuun saadaan ja millä tavalla saadaan niitä sinne nopeitein.

Tässä siis testiä parista eri tavasta piirtää pikseleitä. Testit piirtää kuvan mukaisen kuvan 10 kertaa ja laskee keskiarvon kokonaispiirtoajasta. Testikoneena Intel dual core 1.86GHz, 2GB ja GeForce 7600 GT.



Testi 1: Pikselin piirtoa joka pikselille setPixel() methodilla.
pixelBench1.swf
pixelBench1.fla

Keskimääräinen piirtoaika: 24.4ms
FPS: 40.98

Testi 2: Pikselit kirjoitetaan ensin byteArray objektiin josta se piirretään kerralla bitmapData objektiin ja näytetään ruudulla.
pixelBench2.swf
pixelBench2.fla

Keskimääräinen piirtoaika: 22.6ms
FPS: 44.25
Parannusta testiin 1: 7.38%
Tämä testi koossa 640*480 vei aikaa 92ms eli fps oli 10.87.
Jos poistamme jakolaskut for-looppien sisältä saamme nipistettyä piirtoajan 68.6ms fps 14.58.

Testi 3: Sama kuin testi 2, mutta tällä kertaa emme luo bytearrayta joka kerta uudestaan vaan käytämme samaa arrayta. Tämä ei yllättäen nopeuttanut mitään. Tosin matikkaa tarvittiin vähän lisää, josta johtunee tuo hidastuminen. Yleensä kuitenkin muistin varaaminen dynaamisesti on hidasta ja jota pitäisi välttää, mutta tässä kohtaa ei niin näytä olevan. Kenties flash sisäisesti käsittelee bytearrayta niin ettei tästä ole hyötyä.
pixelBench3.swf
pixelBench3.fla

Keskimääräinen piirtoaika: 28.6ms
FPS: 34.97

Huomaamme että versio 2 on nopein, mutta sekin on kuitenkin todella hidas ja aivan liian hidas jotta voisimme piirtää sillä oikeaa 3d grafiikka reaaliaikaisesti. Eli joudumme edelleen AS3 ajallakin välttelemään pikselin piirtoa ainakin minun koneellani :(.

perjantai 18. tammikuuta 2008

3D-grafiikkaohjelmointia Flashilla.

Tämä on varsinainen mammuttiaihe, joten tulemme puhumaan tästä osissa aika ajoin tällä blogilla.

Oletan että kaikki lukijat tietävät mitä on 3d, joten ei juuri yritä selittää mikä on kolmiuloitteinen avaruus tai -malli. Myös perustieto 3d grafiikasta on erittäin suositeltavaa.

3D

Tietokoneen monitori on 2d-taso eli siihen ei voi piirtää 3d-pistettä. Tästä syystä 3d-piste on ensin projisoitava 2d-tasolle, jotta voit piirtää sen ruudulle. Piste pitää kuitenkin ensin transformoida eli siirtää oikeaan paikkaan ennen projisointia. Tämä projisointi tehdään 3d-mallin jokaiselle geometria verteksille, jonka jälkeen saisimme aikaan mallin josta osaamme piirtää verteksit.

Jos yhdistät pisteet viivalla oikeassa järjestyksessä saat aikaan wireframe mallin eli rautalankamallin.

Jos täytät viivojen rajaamat alueet saat solidface mallin. Tällöin mallisi olisi kuitenkin tasaisen yksi värinen.

Jos lisäät ohjelmaasi varjostuslaskentaa saat aikaan varjostetun 3d-mallin. Varjostustapoja onkin sitten monia, mutta alkuun kannattaa opetella joku yksinkertainen kuten esim. Z-Flat.

Varjostuksesta on luonnollista siirtyä tekstuureihin, joka on Flashilla hankala toteuttaa. Se on kuitenkin mahdollista rotailemalla ja skewaamalla kuvaa sopivasti. Tästä tulee kuitenkin ongelmia isojen polygonien kanssa johon palaamme myöhemmin. Sanotaan kuitenkin, että ongelmana on ettei tekstuureja ole perspektiivikorjattu. Ongelmaa voidaan vähentää tesseloimalla polygonia, mutta tämä on kuitenkin vain purkkaviritelmä, joka ei oikeasti kunnolla toimi, mutta korjaa kyllä ongelmaa jonkin verran. Palataan tähän aiheeseen kun oikeasti asiaa käsittelemme.

Kun olemme käsitelleet yllä olevat asiat alkaa sinulla ollakin jo melko hyvä pohja 3d-ohjelmoinnissa. Paljon kuitenkin jää asioita opittaviksi tämänkin jälkeen, mutta aloitetaan ensin aivan alusta.


Pisteen projisointi

Pisteen projisointi 2d-tasolle on helppoa. Se vaatii vain kaksi yksinkertaista laskukaavaa.
sx = x*256/z
sy = y*256/z

Näin siis projisoimme 3d pisteen 2d pisteeksi. Luku 256 on katselupisteen etäisyys projisoitavasta pinnasta.

Nyt periaatteessa osaisit tehdä 3d starfieldin. :)






Starfield.fla

Starfield on varmasti yksinkertaisin 3d-ohjelma mitä on, mutta siinä tulee projisio hyvin tutuksi. Jatketaan tästä seuraavalla kerralla.

keskiviikko 16. tammikuuta 2008

Binääritiedostojen lukeminen Flashilla.

Jatketaan binääritiedostoilla. Virheellisesti viime postauksessa väitin ettei binääritiedostoja voi lukea Flashilla. Kyllähän niitä voikin ja se helpottaa taas kummasti elämää.

Binääritiedoston lukeminen on jonkin verran vaikeampaa, kuin normaalin ascii muotoisen tiedoston lukeminen, joita ovat mm. tekstitiedostot ja xml-tiedostot. Tekstitiedostoissa ei sinänsä ole mitään vikaa, mutta ne alkavat viedä melkoisesti tilaa, jos niihin tallennetaan paljon dataa. Binääritiedostot on taas mahdollista saada paljon pienempään tilaan ja sitä pidän erityisen tärkeänä varsinkin internetissä.

Binääritiedostoa ei kuitenkaan voi lukea, jos et tiedä tiedostoformaattia. Tässä tapauksessa teemme formaatin itse joten tietysti myös tiedämme millainen se on. En käy tässä varsinaisesti läpi miten hoidamme homman UV rendaajassa vaan käydään läpi ihan perustasolla miten nämä binääritiedostot toimivat Flashissa. Palaamme UV rendaajaan vielä myöhemmin tässä blogissa.

Tein scriptin 3dsmaxissa, joka kirjoittaa yksinkertaisen binääritiedoston.
00h 4 byte integer
04h 4 byte integer
08h 4 byte float
0Ch 4 byte float
Tällaisen binääritiedoston lukeminen Flashilla ei ole erityisen vaikeaa.


// ActionScript 3.0

import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.utils.ByteArray;

var cURLLoader:URLLoader = new URLLoader(null);
var cURLRequest:URLRequest = new URLRequest(null);

cURLLoader.addEventListener(Event.COMPLETE, cURLLoader_onComplete);

cURLLoader.dataFormat = URLLoaderDataFormat.BINARY;
cURLRequest.url = "test.bin";

cURLLoader.load(cURLRequest);

function cURLLoader_onComplete(event:Event):void
{
var byteArray:ByteArray = cURLLoader.data;
byteArray.endian = Endian.LITTLE_ENDIAN;
byteArray.position = 0;
trace (byteArray.readInt());
trace (byteArray.readInt());
trace (byteArray.readFloat());
trace (byteArray.readFloat());
}

Ellet kuitenkaan ole tutustunut juurikaan binääritiedostojen lukemiseen flashilla niin tuo voi näyttää vähän mystiseltä. Eli käydään koodi läpi.

Importoidaan tarvittavat kirjastot.
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.utils.ByteArray;

Luodaan URLLoader -ja URLRequest oliot, jota käytämme tiedostomme lataamiseen. URLLoader osaa ladata tiedostot binäärimuotoisena. URLRequest on URLLoaderin vaatima luokka, jolla määritellään URL osoite. Alustamme kummankin luokan muodostimen parameterin arvolla null.
var cURLLoader:URLLoader = new URLLoader(null);
var cURLRequest:URLRequest = new URLRequest(null);

Lisätään cURLLoader oliolle tapahtumakuuntelija tapahtumaan COMPLETE, joka lähetetään kun tiedosto on ladattu.
cURLLoader.addEventListener(Event.COMPLETE, cURLLoader_onComplete);

Asetetaan cURLLoader olion data tyypiksi tai formaatiksi binääri ja cURLRequest oliolle kerrotaan URL, josta ladattava tiedosto löytyy.
cURLLoader.dataFormat = URLLoaderDataFormat.BINARY;
cURLRequest.url = "test.bin";

Ladataan tiedosto cURLLoader oliolla.
cURLLoader.load(cURLRequest);

Function cURLLoader_onCompelete määrittely. Tätä funktiota kutsutaan kun tiedosto on ladattu muistiin.
function cURLLoader_onComplete(event:Event):void

Pyydetään cURLLoader luokalta sen ByteArray olio, joka sisältää tiedoston datan binäärimuodossa. Asetetaan endian vastaamaan tiedoston endiania, joka tässä tapauksessa on little endian. Lopuksi varmistetaan, että ollaan datan alussa.
var byteArray:ByteArray = cURLLoader.data;
byteArray.endian = Endian.LITTLE_ENDIAN;
byteArray.position = 0;

Sitten luetaan tiedostoformaatin mukaisesti ensin kaksi 4 tavuista inttiä ja sitten kaksi 4 tavuista floattia.
trace (byteArray.readInt());
trace (byteArray.readInt());
trace (byteArray.readFloat());
trace (byteArray.readFloat());

Siinäpä se olikin.

tiistai 8. tammikuuta 2008

UV datan pakkaaminen kuvaan

Haluaisitko flash ohjelman jossa 3d-animaation pintatekstuureja voisi muuttaa. Voisit esim. tehdä renderoidun kuvan huoneesta ja muuttaa huoneen seinien tapetit ja vaihtaa lattian. Mikä olisi erityisen hienoa voisit laittaa seinälle minkä tahansa tapettikuva tai vaikka oman naamasi jos siltä jostain syystä tuntuu :).

Koska flashin tehot eivät riitä "kunnolliseen" oikeaan 3d-renderointiin (joka olisi tietysti ideaalista) olisi erityisen mieluisaa saada edes 3d-mallin tai scenen UV-koordinaatit flashiin per pikseli. Tämä mahdollistaisi 3d-mallien tekstuurien vaihtamisen ilman, että 3d-mallia tarvitsee oikeasti renderoida ja vieläpä perspektiivi korjattuna. Lisäksi tällä tavalla 3d-mallin polygonimäärällä ei ole merkitystä, joten 3d-malli voi sisältää miljoonia polygoneja, jos niin tahdot.

Niinpä tein 3D Studio Maxilla scriptin, joka tallensi renderoidun kuvan jokaisen pikselin UV-koordinaatit ja kirjoitti ne tekstitiedostoon. Ongelmaksi tuli hyvin nopeasti tekstitiedoston kasvava koko, joten aloin kirjoittaa data binäärimuotoisena. Tämä korjasi koko ongelmaa jossain määrin, mutta yllätyin melkoisesti huomatessani ettei flashilla voinut lukea binäärimuotoisia tiedostoja. Niinpä päätin pakata UV-datan kuvaan, joita flash lukee kiltisti eikä minun edes tarvitse kirjoittaa omaa tiedostoformaattia.

Itse UV-koordinaattien pakkaaminen kuvaan ei ole kovin hankalaa. Periaatteessa normaaliin PNG kuvaan voi tallentaa mitä tahansa 32 bittistä tietoa, koska jokainen neljä kanavaa (ARGB) on 8 bittisiä. ARGB kuvat kuitenkin alkavat viedä ikävästi tilaa ja kestävät ladata palvelimelta, joten päätin tyytyä 24 bittiseen tarkkuuteen eli R, G ja B kanaviin.

Ajatellaan UV-koordinaatistoa 24bit 2d-matriisina jolloin sen koko on 256^3 = 16777216. Koska haluamme matriisista yhtä leveän kuin korkeankin saamme leveyden laskettua seuraavasti Math.sqrt(16777216) = 4096. Tämän perusteella voimme sanoa, että maksimi tektuuriresoluutio on 4096*4096 pikseliä. Toki sen kokoinen tekstuuri veisi melkoisesti tilaa, mutta se on tällä systeemillä maksimi koko.
Meillä on siis 4096*4096 kokoinen matriisi eli 4096 numeroa U koordinaatille ja 4096 numeroa V koordinaatille. UV:t on kuitenkin usein yksikkövektoreita joten ne pitää skaalata meidän rangellemme U*4096 ja V*4096. Lopuksi vielä pyöristämme U:n ja V:n kokonaisluvuksi. Näin meillä on kaksi kokonaislukua jotka voimme pakata yhdeksi luvuksi näin V*4096+U. Nyt meillä on yksi luku jonka voimme jo kirjoittaa kuvaan. Kuvaformaattien lukijat tietysti luulevat, että numero tarkoittaa RGB arvoa ja piirtää siksi melko erikoisia kuvia.














UV koordinaatit pakattuna kuvaan.

Meitä ei kuitenkaan kiinnosta miltä kuva näyttää vaan data jota kuvassa on. Lukemalla pikselin arvon voimme purkaa siitä UV:t seuraavasti.
V = Math.floor(pikselin_arvo / 4096.0)
U = (pikselin_arvo / 4096.0) - V
V = V / 4096.0
Näin meillä on jälleen samat UV:t (pienin pyöristyseroin), kuin alussa niitä kuvaan pakattaessa. Ja nyt voimme piirtää tekstuurin haluamastamme kuvasta.
X = U*tekstuurikuvan_leveys
Y = V*tekstuurikuvan_korkeus














Tekstuurikuva


Ja tein vielä ohjelman joka lukee UV arvot kuvasta piirtää kuvan teksturoimalla sen tekstuurikuvalla.














Pakatun UV datan mukaan piiretty kuva.


Koska itse renderointi on lineaarista tulee kuvaan pientä epätarkuutta, mutta ajatus kuitenkin näyttää toimivan. Seuraavaksi sitten on vuorossa enemmän itse koodia niin flashin kuin 3dsmax scriptinkin osalta. Tällä kertaa kuitenkin pysytellään näin teoriassa.

Blogi avattu

Tarkoitus tällä blogilla ei ole keskittyä yhteen tarkasti rajattuun alueeseen vaan tarkoitus on puhua laajemmalta alueelta vastaan tulevista ongelmista ja niiden ratkomisesta. Mikään tässä blogissa esitetty ratkaisutapa ei välttämättä ole oikea tai paras tapa, mutta se on eräs vaihtoehto kyseisen asian tekemiseen.