Oculus Rift -ohjelmointi
Juttu on alun perin julkaistu Skrollin numerossa 2015.2.
Virtuaalitodellisuuteen sopivat silmikkonäytöt ovat kovaa kyytiä tulossa markkinoille. Mutta kuinka niitä virtuaalimaailmoja sitten luodaan? Se ei onneksi ole vaikeaa.
Esittelemme matalan tason lähestymistavan virtuaalitodellisuuden ohjelmointiin käyttäen OculusVR:n julkaisemaa LibOVR-kirjastoa. Pohjana käytämme OpenGL-ohjelmointiartikkelisarjassa (Skrolli 2013.2–2014.2) luotua grafiikkamoottoria. Mikäli OpenGL-ohjelmointi ei ole ennestään tuttua tai kaipaat muistin virkistystä, kyseisten artikkelien lukeminen on suositeltavaa.
Stereoskooppisessa renderöinnissä on kaksi päävaihetta. Ensin renderöidään kummankin silmän näkymät tekstuureihin ja sen jälkeen yhdistetään ne ruudulla esitystä varten. Joissakin esitystavoissa vaiheet voidaan yhdistää ja renderöidä näkymät suoraan oikeaan paikkaan ruudulla, mutta Oculus Riftillä tämä ei ole mahdollista.
Jotta syvyysvaikutelma välittyisi käyttäjälle, näkymät on piirrettävä hieman eri kohdista. Katselupisteiden etäisyyden tulisi suunnilleen vastata katsojan silmien välistä etäisyyttä – muuten katsoja voi tuntea olonsa epämukavaksi.
LibOVR tarjoaa C-kielisen, oliopohjaisen rajapinnan. Kaikkien funktioiden, tietotyyppien ja vakioiden nimissä on etuliite ovr. Funktioiden nimistä ilmenee myös mihin oliotyyppiin ne liittyvät. Esimerkiksi ovrHmd-tietotyyppi kuvaa silmikkonäyttöä, ja siihen liittyvien funktioiden nimet ovat muotoa ovrHmd_DoSomething.
Ennen käyttöä kirjasto pitää alustaa kutsumalla ovr_Initialize-funktiota. Se palauttaa alustuksen onnistumista kuvaavan totuusarvon. Ohjelmistonkehityspakettiin sisältyvän taustaohjelman tulee olla käynnissä, tai alustus epäonnistuu. Linux-jakelut eivät ole vielä paketoineet LibOVR:ää, joten käynnistyksen joutuu virittämään itse.
Alustuksen jälkeen silmikkonäyttöä kuvaavan olion voi luoda funktiolla ovrHmd_Create. Funktio ottaa parametrina silmikkonäytön indeksin: arvo 0 tarkoittaa ensimmäistä järjestelmästä löytyvää silmikkonäyttöä. Jos funktio palauttaa null-osoittimen, tarkoittaa se, ettei yhtään silmikkonäyttöä löytynyt. Ohjelma voi tällöin vaihtaa tavalliselle näytölle sopivaan piirtotapaan. Listauksessa 1 on esitetty alustukseen tarvittavat toimet.
Oculus Riftin laaja kuvakulma perustuu näyttöpaneelin kuvaa vääristäviin linsseihin. Vääristymä on epälineaarinen siten, että lähempänä näkökentän keskipistettä pikselit ovat pienempiä ja tarkkuus suurempi. Jotta käyttäjä näkisi kuvan oikein, on ruudulle piirrettäessä käytettävä käänteistä vääristymää.
LibOVR:n ensimmäisissä versioissa vääristymä oli itse tuotettava shader-ohjelmalla ohjelmointioppassa esitettyjen kaavojen mukaan. Kaiken saaminen täsmälleen oikein oli melkoisen hankalaa. Versiosta 0.3.0 alkaen on tarjolla helpompi rajapinta, jossa kirjastolta voi pyytää valmiin kolmioverkon tekstuurikoordinaatteineen. Tämä tapahtuu ovrHmd_CreateDistortionMesh-funktiolla. Parametrina annetaan osoitin ovrDistortionMesh-olioon, johon funktio täyttää pyydetyt arvot. Funktiota täytyy kutsua erikseen kummallekin silmälle.
Valon eri aallonpituudet taittuvat linssissä hieman eri tavalla tuottaen erivärisiä reunuksia erityisesti näkökentän reuna-alueilla olevien esineiden ympärille. Näiden kromaattisten virheiden korjaamiseksi vääristymäverkon tekstuurikoordinaatit voi halutessaan pyytää erikseen eri värikomponenteille. Listaus 2 näyttää kuinka vääristymädata saadaan LibOVR:ltä ja kuinka sitä voi käyttää OpenGL:ssä.
Koska katsojan havaitsema pikselitiheys kulmayksikköä kohti vaihtelee, piirtoon tarvittavan näkymän koko ei vastaa näyttölaitteen erottelutarkkuutta. Jos kuva-ala halutaan hyödyntää kokonaan, tarvittava tarkkuus on hieman suurempi. Tässä tulee avuksi funktio ovrHmd_GetFovTextureSize, joka palauttaa parametrina annettavaa näkökenttää vastaavan tekstuurin koon.
Virtuaalitodellisuuteen uppoutumisen viimeistelee käyttäjän pään asennon seuranta. Se käynnistetään kutsumalla ovrHmd_ConfigureTracking-funktiota. Parametrina annetaan toivottuja sekä vaadittuja seurantaominaisuuksia kuvaavat bittimaskit. Funktion palauttama totuusarvo kertoo, onnistuiko seurannan käynnistys.
Seurannan ollessa käynnissä nykyisen tilan saa selville funktiolla ovrHmd_GetTrackingState. Paluuarvona saatava tietorakenne sisältää muunmuassa pään asennon ja sijainnin. Ne yhdistetään pelihahmon sijaintiin ja silmän siirrokseen lopullisen kameramatriisin muodostamiseksi.
Kirjaston alustuksen yhteydessä asennon seurannan nollakohta asetetaan noin metrin päähän seurantakameran eteen, katse kameraa kohti. Koska käyttäjän pää todennäköisesti ei ole lepoasennossa juuri tässä kohtaa ja käyttäjä saattaa myös vaihtaa asentoa pelaamisen aikana, on ohjelman syytä tarjota mahdollisuus nollaukseen nappia painamalla. Toiminto on helppo toteuttaa kutsumalla funktiota ovrHmd_RecenterPose.
Vaikka Oculus Rift tuottaakin varsin uskottavan vaikutelman kolmiulotteisesta ympäristöstä, aivan kaikkia ihmisen näkökyvyn ominaisuuksia se ei pysty jäljittelemään. Kaksiulotteisen näyttöpinnan kaikki pisteet ovat samalla etäisyydellä silmästä, joten koko kuva näkyy tarkkana. Syväterävyystehosteen käyttäminen silmikkonäytön kanssa ei ole kuitenkaan suositeltavaa, sillä nykyisillä laitteilla ei pysty seuraamaan käyttäjän katseen suuntaa.
Silmikkonäyttöä hyödyntävissä sovelluksissa kannattaa suosia peliohjaimen käyttöä, koska käyttäjä ei pysty näkemään käsiään ja näppäimistöä. Jos näppäimistön käyttö on nappien määrän tai hiiriohjauksen takia välttämätöntä, napit kannattaa sijoittaa lähelle toisiaan helposti löydettävään paikkaan. Useimmissa näppäimistöissä on F- ja J-näppäimissä kohoumat, joiden avulla ne löytää myös silmikkonäytön peittäessä näkökentän.
Pään asennon seurantaa käytettäessä pystysuuntaiset katselukontrollit kannattaa ottaa pois käytöstä ja käyttää yksinomaan asennon seurannasta saatavaa dataa.
Jotkin tehosteet, kuten varjokartat ja ympäristökarttoihin perustuvat heijastukset, vaativat apunäkymän piirtoa tekstuuriin ja sen käyttämistä osana varsinaisen näkymän piirtoa. Apunäkymää ei kannata turhaan piirtää erikseen eri silmille, vaan valmistelutoimet voi suorittaa yhteisesti. Tämä kannattaa ottaa huomioon grafiikkamoottorin suunnittelussa.
Listaus 1: LibOVR:n alustus
if(ovr_Initialize())
{
ovrHmd hmd = ovrHmd_Create(0);
if(hmd)
{
// Kaikki kunnossa
}
else
{
// Silmikkonäyttöä ei kytketty
}
}
else
{
// Alustus epäonnistui
}
Listaus 2: Vääristymäverkon pyytäminen LibOVR:ltä
ovrDistortionMesh mesh;
ovrEyeType eye = ovrEye_Left; // Tai ovrEye_Right
ovrFovPort fov = hmd->DefaultEyeFov[eye]; // Oletusnäkökenttä on hyvä lähtökohta
ovrHmd_CreateDistortionMesh(hmd, eye, fov, ovrDistortionCap_Chromatic, &mesh);
ovrDistortionVertex &v0 = mesh.pVertexData[0];
glVertexAttribPointer(VERTEX, 2, GL_FLOAT, false, sizeof(ovrDistortionVertex), &v0.ScreenPosNDC.x);
glVertexAttribPointer(RED_TEXCOORD, 2, GL_FLOAT, false, sizeof(ovrDistortionVertex), &v0.TanEyeAnglesR.x);
glVertexAttribPointer(GREEN_TEXCOORD, 2, GL_FLOAT, false, sizeof(ovrDistortionVertex), &v0.TanEyeAnglesG.x);
glVertexAttribPointer(BLUE_TEXCOORD, 2, GL_FLOAT, false, sizeof(ovrDistortionVertex), &v0.TanEyeAnglesB.x);
// Muista kutsua ovrHmd_DestroyDistortionMesh(&mesh); kun dataa ei enää tarvita
Teksti: Mikko Rasa
Kuvat: Sakari Leppä, Mikko Rasa
Lue myös: Oculus Rift DK2 – keinotodellisuuden toinen versio