3D projektio Flashilla.
Tiedän, olen jo tässä blogissa käsitellyt tätä aihetta, mutta viimeksi esittelin alkeellisemman joskin helposti ymmärrettävissä olevan tavan projisoida 3d pisteitä 2d tasolle. Tällä kertaa kirjoitan "oikeasta" perspektiiviprojektiosta projektiomatriisin avulla.
Itse perspektiiviprojektiomatriisi on harvinaisen monimutkainen ja vaikeasti ymmärrettävä kasa epämääräisiä numeroita. Mistä numerot tulevat ja miksi käyttää juuri niitä laskuja, jotka tässä esittelen on toissijaista tietoa. Tätä tietoa ei tarvitse ellei aio tehdä jotain todella vänkää projektiomatriisilla. Pinnan alla piilevästä matematiikasta kiinnostuneille suosittelen tätä saittia http://www.codeguru.com/cpp/misc/misc/math/article.php/c10123. Tässä artikellissa keskitytään siihen miten oman projektiomatriisi saadaan implementoitua omaan koodiin.
Kameran "näkökenttä" eli view frustum on pyramiidin muotoinen alue (volume), jonka sisällä on kaikki se kama jonka kamera näkee. Ulkopuolelle jäävä roju clipataan pois, jolloin puhutaan view frustum cullingista. Emme paneudu tähän aiheeseen nyt, mutta halusin mainita millä nimellä aiheesta löytää lisää tietoa.
Kuvassa näkyy kameran view frustum.
Perspektiiviprojektiomatriisin avulla projisoidessa ei projisoida pisteitä 2d tasolle vaan uuteen "alueeseen" jota kutsutaan canonical view volume (tälle on varmasti jokin pyöristyttävä suomennoskin). Alue on laatikko kokoa [-1,-1,0] - [1,1,1]. Eli arvot tulevat välille -1.0 - 1.0 paitsi Z arvo, jonka on välillä 0.0 - 1.0. Canonical view volume edustaa "3d-monitoria". 2D X ja Y arvot saadaan mapattua ruudun kordinaatteihin kertomalla X resoluution leveydellä ja Y resoluution korkeudella.
Flashissa voimme käyttää projektiomatriisia näin.
var xy:Vector3D = Utils3D.projectVector(m_projectionMatrix, v1);
Eli Utils3D luokan projectVector projisoi vektorin annetulla matriisilla. Tuloksena on Vector3D, jonka X ja Y arvot ovat 3d-pisteen paikka 2d avaruudessa. Itse funktio, joka luo m_projectionMatrix matriisin näyttää tältä.
private function createProjectionMatrix():void
{
m_center = new Point( 400, 300);
m_focalLength = m_screenWidth * 0.5 /
Math.tan( m_fieldOfView * 0.5 );
m_center = new Point( m_screenWidth / 2,
m_screenHeight / 2 );
m_projectionMatrix = new Matrix3D();
m_projectionMatrix.identity();
var fov:Number = Math.PI * m_fieldOfView / 180.0;
var a:Number = 1.0 / (m_screenWidth / m_screenHeight) /
Math.tan( fov * 0.5 );
var b:Number = 1.0 / Math.tan( fov * 0.5 );
var c:Number = m_zFar / (m_zFar - m_zNear);
var d:Number = 1.0;
var e:Number = -c * m_zNear;
var pm:Vector. = m_projectionMatrix.rawData;
pm[0] = a; pm[1] = 0; pm[2] = 0; pm[3] = 0;
pm[4] = 0; pm[5] = b; pm[6] = 0; pm[7] = 0;
pm[8] = 0; pm[9] = 0; pm[10] = c; pm[11] = d;
pm[12] = 0; pm[13] = 0; pm[14] = e; pm[15] = 0;
m_projectionMatrix.rawData = pm;
}
//-----------------------------------------------------------------------
Melko pelottavan näköinen funktio, mutta onneksi tämän täydellistä ymmärtämistä ei tarvita. Lähinnä tärkeitä asioita ovat Field Of View ja ruudun koko.
Eli jos nyt teemme 3d mallin ja projisoimme sen tällä matriisilla saisimme mallin joka on hyvin pieni. Kaikki verteksit olisivat -1 - 1 alueella.
Verteksit pitää nyt skaalaa sopivaksi kulloiseenkin resolutioon. Eli verteksin 2d X arvo on X * (resoX/2) ja Y arvo on Y * (resoY/2). Resolutio jaetaan kahdella koska verteksien arvot ovat -1.0 - 1.0 eikä 0.0 - 1.0.
Näin mallisi on oikean kokoinen. Tosin on turhaa jakaa ja kertoa arvoja joka verteksille erikseen, koska tämä voidaan tehdä suoraan matriisissa. Kts. uusi createProjectionMatrix() funktio.
private function createProjectionMatrix():void
{
m_center = new Point( 400, 300);
m_focalLength = m_screenWidth * 0.5 /
Math.tan( m_fieldOfView * 0.5 );
m_center = new Point( m_screenWidth / 2,
m_screenHeight / 2 );
m_projectionMatrix = new Matrix3D();
m_projectionMatrix.identity();
var fov:Number = Math.PI * m_fieldOfView / 180.0;
var a:Number = 1.0 / (m_screenWidth / m_screenHeight) /
Math.tan( fov * 0.5 ) * m_center.x;
var b:Number = 1.0 / Math.tan( fov * 0.5 ) * m_center.y;
var c:Number = m_zFar / (m_zFar - m_zNear);
var d:Number = 1.0;
var e:Number = -c * m_zNear;
var pm:Vector. = m_projectionMatrix.rawData;
pm[0] = a; pm[1] = 0; pm[2] = 0; pm[3] = 0;
pm[4] = 0; pm[5] = b; pm[6] = 0; pm[7] = 0;
pm[8] = 0; pm[9] = 0; pm[10] = c; pm[11] = d;
pm[12] = 0; pm[13] = 0; pm[14] = e; pm[15] = 0;
m_projectionMatrix.rawData = pm;
}
//-----------------------------------------------------------------------
Nyt mallisi on heti oikean kokoinen voit rendaa sen ruudulle.
Esimerkkiohjelma renderoi kuvan mukaisen mallin ruudulle projisoiden pisteet yllä olevan projektiomatriisin kanssa.
