Tervehdys kaikille,
Olen kehittänyt tässä jonkin aikaa omaa 3D Engineä ja olen päätynyt vaiheeseen, jossa koitan optimoida hieman renderöintiä.
Tällä hetkellä olen kehittämässä pientä testi peliä joka hyödyntää omaa 3D engineäni. Törmäsin kuitenkin kysymykseen, että kuinka monta objektia kohtuu ok 3d engineen tulisi pystyä pyörittämään ruudulla realiaikaisesti, jotta fps pysyisi kohtuu hyvänä.
Testasin sen verran, että piirrän 3 erilaista objektia. Yksi objekti on sellanen jota ns. "kloonataan" eli ei luoda montaa objektia vaan yksi objekti jota sitten instanssi renderöidään esim. 1000 kertaa. Tässä kuitenkin törmäsin ongelmaan, että kaikkineen toimintoineen mitä per renderöinti tehdään kykenen renderöimään noin 3000 kpl ilman, että max framerate 100fps laskee.
Kloonattu objekti on yksinkertainen plane model ( 2 triangle ) johon renderöidään tekstuuri.
Onko tämä ok tulos vai tulisiko OpenGL pystyä paljon parempaan.
Näyttis on ATI HD 5850 ja prossu I7 3770k. Eli hardware on kohtu ok.
- Kiitos
Miten monta primitiiviä (tai verteksiä) nykyaikainen näyttökortti saa ruudulle tungettua on kiinni aika monenmonituisesta asiasta.
Joka tapauksessa, yksinkertaisesti teksturoituja kolmioita olettaisi jossain miljoonan kokoluokassa jouhevasti läpi menevän ilman kovin kummoisia optimointeja, kunhan VBO:t ovat käytössä, tekstuuria ei ladata joka kerta uusiksi näyttökortin muistiin, shaderiohjelmia ei käännetä jatkuvasti uudestaan ja mitään erityisen raskasta muuta ei tehdä samassa säikeessä renderöinnin kanssa.
Käytännössä kaikki tolkummat 3D-moottorit optimoivat myös renderöintijärjestystä (jolloin esim. samaa shaderi-ohjelmaa tai tekstuuria käyttävät objektit voidaan renderöidä peräkkäin vaihtamatta shaderia tai tekstuuria välissä), pitävät renderöintiä omassa säikeessään, jottei esimerkiksi fysiikan simulointi hidasta renderöintiä, ja tekevät ainakin yksinkertaisen occlusion cullingin, jolloin jätetään renderöimättä objekteja, jotka eivät kuitenkaan näkyisi ruudulla.
Tässä koodi joka renderöi ns. kloonatut objektit. Selkeenä optimointina olisi siirtää tuo updateCloneObjectMatrix -funktio suoraan näyttiksen puskuriin, koska
sijainti tiedot ovat staatisia. Jos otan ko. funktion pois ja renderöin objektit samaan sijaintiin, niin pääsen paljon parempiin tuloksiin.
Eli sadallatuhannella objektilla kesti noin 31ms renderöidä. Tämä tulos tarkoittaa siis vähän reilua 32fps:ää. Eli aika hidasta, mutta huomattavasti paremmin kuin se, että alkaa nykimään reilussa 3000. :). Toki tuo sinun miljoona on aika kaukana tuosta sadastatuhannestakin.
DWORD b = 0; DWORD a = GetTickCount(); for( int i = 0; i < _iNumberOfCloneModelObjects; i++ ) { glPushMatrix(); this->updateCloneObjectMatrix( i, cmoObject_ ); glDrawArrays( GL_TRIANGLES, 0, _moCurrentModelObject->getModel()->getMesh()->getNumberOfVertexes() ); glPopMatrix(); } b = ( GetTickCount() - a );
glPushMatrix() ja glPopMatrix() eivät kuulu OpenGL 3.0:n ja uudempien versioiden spekseihin. Hiukan vanhemmillakin korteilla, joilla ei ole kokonaista tukea OpenGL 3.0-speksifikaatiolle, matriisimanipulaatiot voisi hoitaa samalla tavalla kuin OpenGL 3.0:ssa. ATI HD 5850 taitaa kylläkin tukea OpenGL:ää versioon 4.3 asti.
Suosittelisin tekemään vähintään OpenGL 3.3:n spesifikaatioiden mukaan. Googlella löytyy iso läjä opasta, tosin hakulauseisiin kannattaa liittää mukaan "OpenGL 3", ettei päädy auttamattoman vanhentuneeseen oppaaseen.
Renderöidessäsi neliön kahdesta kolmiosta, ei kannata käyttää GL_TRIANGLESia, vaan GL_TRIANGLE_STRIPiä, jolloin neliön kaksi kolmiota voidaan määritellä neljällä verteksillä kuuden sijasta. Eli jos neliön verteksit olivat aikaisemmin:
[-1, -1, # ensimmäinen kolmio alkaa tästä: vasen alakulma -1, 1, # vasen yläkulma 1, 1, # oikea yläkulma 1, 1, # toinen kolmio alkaa tästä: oikea yläkulma 1, -1, # oikea alakulma -1, -1] # vasen alakulma
Voisivat ne muutoksen jälkeen olla olla:
[-1, -1, # ensimmäinen kolmio alkaa tästä: vasen alakulma -1, 1, # vasen yläkulma <- ensimmäisen kolmion 2. verteksi, toisen kolmion 1. verteksi 1, -1, # oikea alakulma <- ensimmäisen kolmion 3. verteksi, toisen kolmion 2. verteksi 1, 1] # oikea yläkulma <- toisen kolmion 3. verteksi
Suurempien meshien renderöinnissä on hyvä käyttää glDrawElements():iä, koska siinä määritellään meshin renderöinti indekseinä vertekseihin, joista primitiivit (tässä tapauksessa periaatteessa ne elementit) muodostuvat. Tällöin voidaan paremmin hallita verteksien jakoa ja vähentää käsiteltävien verteksien määrää.
Lisäys: Olen oikeastaan duunaamassa jossain määrin geneeristäkin opasta grafiikkaohjelmointiin, liitteinä löytyy WebGL-koodit. Jossain vaiheessa pitäisikin kirjoittaa jokin OpenGL 4.3:lla tehty efekti koodiesimerkkinä oppaan kylkiäiseksi.
Totta, että glPushMatrix ja glPopMatrix ovat vanhoja. Kuten jo aikasemmin huomioin matriisin voisi hyvin tallentaa näytönohjaimen muistiin ja näin aijon tehdä. Lisäksi minun tapauksessani ko. objektit renderöidään taustalle staatisiin sijainteihin. Tähän olisi yksi ratkaisu kopioida textuurit isompaan textuuriin ja renderöidä iso kuva yhelle planelle.
GL_TRIANGLE_STRIP ja glDrawElementsien käyttö on hyvä optimointi ehdotus. Tosin en näe tässä vaiheessa vielä niitä kovin suuriksi pullon kauloiksi. Jos miettii, että verteksi dataa on tosi vähän ei glDrawElementillä saavuta suurempaa nopeus eroa saattaa olla jopa hitaampi.
Ihmettelen vieläkin suuresti tuota miljoonan objektin yhtäaikaista renderöintiä. Mitä tulokseni kertoivat aikasemmin 31ms per objekti, niin luultavasti tuo glDrawElements ei asiaan paljoakaan vaikuta.
Edit:
Lisäksi olen pohdiskellut, että jos käytän glMultMatrixf miten, se käytännös eroaa nopeudeltaa esim. siinä, että syötän GLSL:lle matriisin uniformina.
Ne miljoonat kolmiot saavutetaan sillä ettei mitään objektin sijainteja lasketa välissä, eli mahdollisimman vähän matriisilaskuja. Käytännössä niissä on kai kyseessä esim yhtäjaksoinen "virtuaalimaailma", oli se sitten korkeuskartta tai muu. Ainut matriisilasku jonka aina joutuu tekemään on vertex-shaderillä se verteksin sijainnin kertominen projection-modelview-matriisilla. Ja sitä uniformia ei tarvitse kertoa shaderille kuin yhden kerran per frame.
Tietysti toinen seikka on verteksi ja fragment-shaderien optimointi haluttuun tarkoitukseen. Jos lasket varjot ja värit toisella tapaa, et välttämättä tarvitse edes välittää verteksin normaaleita ollenkaan. (Sivuseikkana voisi mainita esim. Skyrim:n, jossa normaalit luetaan tekstuurista. Perinteinen bump-map hyödyntää verteksin normaaleita, mutta täydellinen normal-map ei niitä käytä.)
OpenGL:llä on ilmeisemmin monia erilaisia tapoja toteuttaa instanssi piirto. Mikä olisi sellanen ns. hyvä tapa?
Itse katselin tätä tutorialia: http://ogldev.atspace.co.uk/www/tutorial33/
Onko tuo hyvä tapa, vai kannattaisiko minun käyttää jotain muuta? Esim. UniformBlocks -puskureita.
Minusta tuo linkin tutoriali vaikuttaa jokseenkin vanhalta/huonolta keinolta. Eli jotta mat4 -voidaa muodostaa, joudutaan leikkimään tuolla Divisorilla.
- Lisäksi olisi hyvä saada selvennystä alla olevaan keskusteluun:
kayttaja-3842 kirjoitti:
Lisäksi olen pohdiskellut, että jos käytän glMultMatrixf miten, se käytännös eroaa nopeudeltaa esim. siinä, että syötän GLSL:lle matriisin uniformina.
User137 kirjoitti:
Ja sitä uniformia ei tarvitse kertoa shaderille kuin yhden kerran per frame.
Myös glMultiMatrixf tarvii kertoa OpenGL:lle yhden kerran per frame.
Kyllä tuo uudelta oppaalta vaikuttaa, päätellen tuosta "#version 410". Divisoriin en ole tutustunut.
Yksi asia mitä aina näkee oppaissa, on että muuttujiin viitataan indeksinumeroilla, ja shaderissä se annetaan niille (location = 1) jne. Helpommalla pääsee minusta jos käyttää glGetAttribLocation() käännöksen jälkeen, ja kysyy ne shaderistä muuttujan oikeilla nimillä.
glMultiMatrixf() on CPU-operaatio. Se muuttaa vanhan OpenGL:n "kameraa". Vanhat shaderit kirjoitettiin ftransform-funktion kanssa, mutta nykyään CPU:n matriisilaskut tehdään ilman OpenGL-funktioita. Vertex-shaderissä on tehtävä tämä matriisilasku joka tapauksessa:
gl_Position = WVP * vec4(Position, 1.0);
Jos siltä haluaisi välttyä, niin sinulla ei voisi olla esim staattisia objekteja ja samalla tukea kameran kääntämistä. Tarviisi myös erillisen muistitaulukon lasketulle datalle.
Aihe on jo aika vanha, joten et voi enää vastata siihen.