Algoritmer för gallring av dolda ytor Johannes Staffans Institutionen för informationsbehandling Åbo Akademi, FIN-20520 Åbo, Finland e-post: johannes.staffans@abo.fi
Referat Ett datorprogram som renderar tredimensionell grafik i realtid måste alltid vara optimerat för snabbhet. Ett av de bästa sätten att uppnå hög hastighet i renderingen är att helt lämna bort de delar av den tredimensionella världen som inte är synliga för användaren vid en viss tidpunkt. Om tillräckliga mängder polygoner kan gallras bort tillräckligt tidigt i renderingspipelinen kan stora ökningar i prestanda uppnås. Samtidigt vill man ju alltid presentera en realistisk bild åt användaren. Denna artikel kommer att kort presentera olika tillvägagångssätt för att gallra bort dolda ytor från renderingsprocessen. ACM-klassificering I.3.3 Picture/Image Generation, I.3.7 Three-Dimensional Graphics and Realism Innehåll 1. Introduktion 3 1.1 Problemet... 3 1.2 Grundläggande begrepp... 3 2. Klassiska algoritmer 4 2.1 Gallring av bortvända ytor... 4 2.2 Gallring enligt synvolymen... 4 2.2.1 Oktala träd... 5 2.3 Målarens algoritm... 5 2.4 Z-buffertalgoritmen... 6 3. BSP-träd och portalgallring 7 3.1 Introduktion till BSP-träd... 7 3.2 Skapande av trädet... 7 3.2.1 Optimeringar... 8 3.3 BSP-trädets egenskaper... 8 3.4 Problem associerade med BSP-träd... 9 3.5 Portalgallring... 9 4. Andra algoritmer 11 5. Slutsats 12 6. Referenser 12 2
1. Introduktion 1.1. Problemet Tänk dig ett grafikintensivt datorprogram såsom ett spel eller ett program för presentation av planerade men ännu inte uppförda byggnader och andra konstruktioner. Vi begränsar oss till sådana program som arbetar med tredimensionell grafik. Att rita ut en stor mängd tredimensionella solida eller halvgenomskinliga objekt både snabbt och visuellt realistiskt är ett stort problem för de här programmen och mycket tid och möda har satts ned på att tackla problemen. I takt med att programmen utvecklas och ökar i komplexitet ökar även kraven på effektiv rendering. I princip är prestandakraven bottenlösa då det gäller tredimensionell datorgrafik, det är till exempel alltid möjligt att lägga till fler och mer detaljerade objekt. Därför blir frågan om snabbhet i renderingen aldrig inaktuell. 1.2. Grundläggande begrepp En polygon är en detsamma som en avgränsad yta. Matematiskt sett kan ju en polygon ha hur många hörn som helst (därav namnet) men inom den tredimensionella grafikens område rör man sig nästan uteslutande med trianglar därför att tre punkter alltid definierar ett plan och polygonen de spänner upp alltid är konvex. Ett 3d-objekt är en samling polygoner. De flesta 3d-objekt skapas av en artist på förhand och importeras till programmet under körning men det går också att skapa 3dobjekt i realtid på matematisk väg eller utgående från till exempel en gråtonsbild, som anger vilket y-värde en given punkt skall ha. Slutresultatet är ändå oftast detsamma, ett konvext tredimensionellt objekt. Med synvolymen avser man den del av den tredimensionella datorvärlden användaren för tillfället kan se. I praktiken avgränsas synvolymen av sex plan; ett främre (som kan föreställas vara skärmens ruta), ett bakre samt fyra plan som man kan tänka sig går från ögat och genom skärmens kanter. Synvolymen blir alltså en pyramid som skärs av det främre och bakre planet (i praktiken använder man inte alltid ett bakre plan i till exempel mycket enkla världar). Normalen till en polygon är den normaliserade vektorprodukten av två av de tre kanterna. Enormt viktig för att exempelvis bestämma om en given polygon vetter bort från användarens öga eller om den är synlig. Normalen fås alltså genom vektorprodukten; skalärprodukten är ett annat mycket använt matematiskt verktyg. OpenGL och Direct3D är två så kallade API:n (Application Programming Interface) för tredimensionell grafik. Tack vare sådana här API:n behöver man inte bry sig om 3
de riktigt blodigaste detaljerna vad gäller renderingen utan kan koncentrera sig på själva ändamålet med programmet. OpenGL är som namnet avslöjar en öppen standard som har ett brett understöd inom industrin. OpenGL är därför portabelt till många olika plattformar medan Direct3D är Microsofts eget påhitt som fungerar endast på Windows-plattformen samt i Microsofts spelkonsol, X-Box. Oberoende av vilken API man använder är algoritmerna desamma, så vi skall inte öda mera tid på att jämföra dem. 2. Klassiska algoritmer Problemet med gallring av dolda ytor i en tredimensionell scen är mycket välstuderat inom datorgrafikens område [1]. Till följande tittar vi på några algoritmer som alltid brukar nämnas i sammanhanget. 2.1 Gallring av bortvända ytor Tänk dig en låda som ligger på golvet en bit bort. Du ser framsidan, locket och kanske lite av ena sidan, däremot så är botten, baksidan och så vidare bortvända från dig och således osynliga. Algoritmen för gallring av bortvända ytor bygger på observationen att om alla objekt i världen är slutna (d.v.s. konvexa) så är de polygoner som är vända bort från åskådaren inte synliga. Att alla objekt är slutna betyder att det inte får finnas några glapp eller hål mellan objektens polygoner. Kontrollen huruvida en given polygon är bortvänd från betraktarens öga görs enkelt genom att ta skalärprodukten mellan vektorn från ögat till polygonen och polygonens normalvektor. Om skalärprodukten är negativ pekar polygonen bort från ögat och kan genast gallras bort. Denna algoritm klarar av att gallra bort ungefär hälften av alla polygoner och är användbar som en del av en större helhet. Algoritmen, som på engelska kallas backface culling, finns numera ofta inbyggd i grafikhårdvaran och är en integrerad del av API:n som Direct3D och OpenGL. 2.2. Gallring enligt synvolymen En ganska naturlig lösning på problemet för gallring av osynliga ytor och objekt är att ta bort de delar av scenen som faller utanför betraktarens synvolym. Synvolymen beskrevs ovan. Algoritmen går ut på att kontrollera huruvida en given polygon befinner sig helt och hållet utanför synvolymen; om så är fallet kan polygonen gallras bort. Befinner sig en polygon bara delvis innanför synvolymen kan den osynliga delen klippas bort och avfärdas. Algoritmen kan effektiviseras genom att använda minimala uppspännande volymer för 3d-objekten i det inledande skedet av algoritmen. 4
Denna algoritm är av O(n)-komplexitet, det vill säga tiden ökar linjärt med antalet polygoner i scenen. Lyckligtvis kan vi genom att modifiera algoritmen uppnå betydligt högre prestandanivåer, främst genom att dela upp den tredimensionella världen i mindre delar. 2.2.1. Oktala träd Med hjälp av oktala träd kan man dela upp en tredimensionell rymd i mindre delar [2]. I kombination med gallring enligt synfältet kan man uppnå rätt stora prestandaökningar med hjälp av oktala träd. De kan också användas för mycket annat, exempelvis för att hantera kollisioner mellan objekt. Ett oktalt träd har sin början i en osynlig kub som man tänker sig omsluter hela den tredimensionella datorvärlden. Kuben är riktad enligt världens x-, y- och z-axlar, det vill säga kubens övre sida motsvarar världens positiva y-riktning och så vidare. Denna kub delas sedan upp i åtta mindre delar som bildar åtta nya noder i trädet. Såhär fortsätter man tills hela världen är uppdelad och alla världens polygoner finns i en viss nod i det oktala trädet. Man försöker alltid dela upp en nodkub i mindre delar, men om en subnod inte innehåller några polygoner är det ju ingen mening att vidare dela upp den delen av rymden. Vanligtvis har man en gräns för hur många, eller rättare sagt hur få, polygoner en nodkub skall innehålla för att man skall avbryta uppdelningen. Det finns även andra metoder, till exempel kan man ha en övre gräns för antalet noder. Själva gallringen går till så, att om en viss nod i det oktala trädet inte finns i synfältet så kan alla subnoder till den också gallras bort. Det är ganska enkelt att implementera oktala träd om gallring enligt synfältet redan är i användning. 5
2.3. Målarens algoritm Målarens algoritm representerar kanske den enklaste och mest naiva lösningen till visibilitetsproblemet. Man tänker sig att datorprogrammet agerar på samma sätt som en oljemålare kunde tänkas göra: bakgrunden ritas först och förgrunden ritas ovanpå denna. Det här innebär ju en hel del onödigt målande för en komplex scen, då ju objekten i förgrunden täcker de i bakgrunden. I ett datorprogram tillämpas målarens algoritm genom att alla polygoner sorteras enligt deras z-värde (avståndet till användarens öga). Polygonera ritas sedan ut i stigande z-ordning. Ett av problemen med algoritmen är att det inte alltid går att avgöra vilken polygon som skall ritas först; ett exempel på en typisk problemsituation ges i figur 1. Om två polygoner skär varandra uppstår också problem. Utöver detta visar sig målarens algoritm vara ganska långsam eftersom många polygoner som är blockerade från synfältet ändå ritas ut. 2.4. Z-buffertalgoritmen En enkel metod för att se till att rita bara de polygoner och delar av polygoner som ligger närmast användarens öga är z-buffertalgoritmen. Algoritmen bygger på att man förutom färgbufferten, som är lika stor som skärmen och innehåller information om färgen på den färdigt renderade bildens enskilda bildpunkter, även har en z-buffert som lagrar de enskilda bildpunkternas z-värde, det vill säga avstånd från användarens öga. När en ny bildpunkt skall ritas ut på skärmen görs en kontroll huruvida bildpunktens z-värde är större en det värde som från tidigare finns lagrat i z-bufferten på just den bildpunktens plats. Har den nya bildpunkten ett större z-värde överförs bildpunktens färginformation till färgbufferten och och dess z-värde skrivs över z-buffertens lagrade värde för den punkten. Ett mindre z-värde än det som färdigt finns lagrat i z- bufferten indikerar att bildpunkten ligger längre bort än den som redan ritats ut på den platsen och bildpunkten avfärdas. För varje uppdatering av skärmen töms Z-bufferten, det vill säga varje element i bufferten initialiseras till ett z-värde som representerar synfältets bortre plan. Denna tömning kan jämställas med tömningen av färgbufferten; vanligtvis kastar man ut färgbuffertens innehåll i samband med uppdateringen och ritar sedan en ny bild från början. Z-buffertalgoritmen är väldigt enkel, har en effektivitet på O(n) (där n representerar antalet polygoner som skall ritas ut) och fungerar för alla primitiver som man kan beräkna ett z-värde för. Det är värt att notera att denna algoritm tillåter polygonerna att ritas ut i vilken ordning som helst, vilket gör den ännu mer populär. För genomskinliga objekt är det ändå nödvändigt att rita ut polygonerna i strikt bak-till-framordning. 6
Algoritmen är enkel att implementera i hårdvara och används ofta som komplement till mer avancerade algoritmer i slutsteget av renderingspipelinen. Problemet med Z- buffertalgoritmen är att det i komplexa scener kan hända sig att en enskild bildpunkt blir utritad många gånger i onödan, då polygoner ligger bakom varandra till exempel. Man kan exempelvis tänka sig en situation där man i synfältet har tio kuber på rad bakom varandra i mitten av skärmen; om kuberna ritas ut med början längst bak kommer bildpunkterna i mitten av skärmen att ritas ut nio gånger innan kuben längst fram (den som i verkligheten är synlig) ritas ut. Z-komplexiteten blir alltså orimligt hög. 3. BSP-träd och portalgallring 3.1. Introduktion till BSP-träd Ett BSP (Binary Space Partitioning )-träd är egentligen inget mer än ytterligare ett sätt att partitionera rymden på. Två huvudvarianter finns; i den första är trädet riktat enligt den tredimensionella världens axlar medan den andra är baserad på scenens polygoner. Den förra varianten är rätt lik de oktala träden som diskuterades i 2.2.1 och vi skall här koncentrera oss på den senare versionen. Denna polygonbaserade variant beskrevs först i [3]. 3.2. Skapande av trädet En polygon i scenen väljs som trädets rot. Det plan som denna polygon entydigt definierar tänker man sig dela upp scenen i två delar. För båda noderna som uppstår upprepas proceduren; en ny polygon väljs som uppdelare och subrymden delas i två delar. Användande sig av rekursion fortsätter man tills alla polygoner finns i trädet. Se exempelscenen i figur 2. 7
Det är värt att notera att det finns flera varianter av BSP-träd. En variant lagrar till exempel polygonerna tillsammans med de uppdelande planen medan en annan lagrar polygonerna i de icke-solida löven. 3.2.1. Optimeringar Det är inte alldeles lätt att välja rätt polygon som uppdelare vid varje nod i BSPträdet. Det värsta man kan göra är att välja en polygon vars plan delar upp alla andra polygoner i två delar; plötsligt har vi fördubblat antalet polygoner i scenen! Resultatet av sådana här dåliga beslut är ett BSP-träd som visserligen är korrekt men som är mycket större än vad som är nödvändigt. Liksom hos andra trädstrukturer finns det en del olika egenskaper man kan välja att sätta tyngdpunkten för optimeringen på. Man kan till exempel välja att försöka balansera trädet så bra som möjligt och på så sätt uppnå ett minimalt djup hos trädet. Man kan också försöka se till att volymerna på båda sidorna om de uppdelande planen blir ungefär lika stora så att vi inte har stora volymskillnader hos noder som befinner sig på samma djup. En annan önskvärd egenskap hos BSP-träd är att de uppdelande planen är riktade enligt den tredimensionella världens axlar, något som kan underlätta då man traverserar trädet. Den viktigaste faktorn som man kan optimera vid skapandet av ett BSP-träd är dock antalet uppdelande plan som delar andra polygoner. Lyckas man minimera detta får man ett träd med minimalt antal polygoner, vilket rimligtvis är det träd som är snabbast att rita ut. Tyvärr är det svårt att uppnå en perfekt lösning på problemet; man borde egentligen pröva alla tänkbara kombinationer av uppdelare, ett problem med komplexiteten n!. Detta är naturligtvis inte tillämpbart i en tredimensionell värld som kan innehålla t.o.m. hundratusentals polygoner. Ett populärt sätt att approximera den perfekta lösningen är att i varje steg välja som uppdelare den polygon som delar upp det minsta antalet andra polygoner just i det steget. 3.3. BSP-trädets egenskaper Polygonbaserade BSP-träd har vissa goda egenskaper, till exempel kan man enkelt traversera trädet börjande från polygonerna längst bort och röra sig mot polygonerna som ligger närmast användaren. Man kollar först på vilken sida av rotplanet blickpunkten (kameran) befinner sig, vilket inte är svårare än en enkel jämförelse mellan en punkt och ett plan. Sedan går man över till den sida av rotplanet som befinner sig längre bort och upprepar proceduren rekursivt. På det här sättet uppnår man en strikt bak-till-fram-ordning bland polygonerna och kan utnyttja målarens algoritm. BSP-träd kan också utnyttjas tillsammans med gallring enligt synvolymen (se sektion 2.2). Man kollar helt enkelt om planet som utgörs av trädets rotnod skär synvolymen; om inte så befinner sig synvolymen helt och hållet på ena sidan av rotnoden och hela den bortre delen av trädet kan omedelbart gallras bort. Återigen använder man sig av 8
rekursion för att komma fram till vilken del av BSP-trädet som finns i synvolymen och därför bör ritas ut. Man kan också med fördel använda BSP-träd vid till exempel kollisionshantering. Trädet har förvånansvärt många tillämpningsområden inom den tredimensionella grafikens område. 3.4. Problem associerade med BSP-träd Det största problemet är naturligtvis att det är så kostsamt att skapa ett BSP-träd. På grund av detta lämpar sig BSP-träden i princip bara för statiska världar. Man kan dock med hjälp av olika metoder kombinera den statiska världen, representerad av ett BSPträd, med dynamiska objekt som rör sig inne i världen, till exempel med hjälp av Z- bufferten. Ett annat problem med BSP-träd är att Z-komplexiteten kan bli väldigt hög för komplexa scener. En BSP-implementation som använder sig av målarens algoritm, det vill säga traverserar BSP-trädet bak-till-fram och ritar ut polygonerna längs vägen, kan i värsta fall rita över samma bildpunkt många gånger. Detta är acceptabelt för enkla scener men inte för de komplexa världar som används i till exempel tredimensionella datorspel. En lösning på detta problem är att man för varje cell i ett cellbaserat BSPträd, där polygonerna är lagrade i löven (d.v.s. cellerna), kalkylerar dess PVS (Potentially Visible Set, potentiellt synliga mängd). Denna mängd representerar de andra celler som faktiskt är synliga från varje cell i BSP-trädet och lagras i en datastruktur associerad med cellen. När man vill rita ut världen sedd från en viss cell i BSP-trädet gallrar man först bort de av PVS:ens celler som befinner sig utanför synvolymen och sedan ritar man ut de celler som ligger både i PVS:en och i synvolymen. Problemet med detta tillvägagångssätt är att PVS-algoritmen är konservativ, det vill säga den anger alltid antalet synliga celler i överkant. I praktiken ritar man alltid ut omkring det dubbla antalet polygoner jämfört med vad som är strikt nödvändigt. Detta beror på att det i PVS:en för kamerans cell ingår alla andra celler som är synliga från någon punkt i kameracellen, inte bara kamerans punkt. Att kalkylera en PVS för varje cell är också rätt tidskrävande. En analytisk metod för att åstadkomma detta presenteras i [4]. 3.5. Portalgallring En något nyare variant av cellbaserad gallring är så kallad portalgallring, som är användbar i arkitektoniska modeller. Det finns en mängd olika algoritmer för portalgallring men gemensamt för dem alla är insikten att väggarna i en arkitektonisk modell ofta blockerar många andra delar av modellen, sett från en cell som utgörs av till exempel en korridor eller ett rum. Fönster och dörrar som sammanbinder näraliggande celler kallas portaler. Alla objekt som finns i en cell samt cellens väggar lagras i en 9
datastruktur som hör ihop med cellen. I samma datastruktur lagrar vi också information om näraliggande celler och portalerna som leder till dem. En potentiellt synlig mängd (PVS) kan kalkyleras för varje cell. För detta ändamål finns det ett antal olika algoritmer som är mer eller mindre bra. En annan metod, som beskrivs av Luebke och Georges i [5], kräver nästan inget förarbete alls, bara den datastruktur som beskrevs ovan. En enkel rekursiv algoritm används för att rendera scenen: 1. Hitta cellen V där användaren (ögat, kameran) befinner sig. 2. Låt P vara en rektangel som till att börja med är lika stor som skärmen. 3. Rita ut cellen V:s geometri, gallra enligt volymen som utgår från ögat och korsar rektangeln P:s kanter. 4. Gå genom rekursion till portalerna för cellerna intill cellen V. För varje portal, projicera portalen på skärmen och hitta den x-y-rektangel som motsvarar projektionen. Kalkylera snittet mellan rektangeln och P. 5. För varje snitt: om snittet är tomt betyder det att cellen som finns på andra sidan den portalen är osynlig och kan gallras bort. Om snittet inte är tomt kan den andra cellens innehåll gallras bort enligt den synvolym som utgår från ögat och genom det rektangulära snittet. 10
6. Om snittet inte var tomt så kan den intilliggande cellens grannar vara synliga. Hoppa alltså till steg 3 (använd snittrektangeln som P). Redan utritade objekt kan märkas så att de inte ritas en gång till. Denna algoritm illustreras i figur 3, där synvolymen stegvis förminskas. 4. Andra algoritmer Gallring av dolda ytor är ett område inom den tredimensionella grafiken där forskningen är rätt intensiv. Härav följer att det finns en stor mängd algoritmer som alla har sina olika starka och svaga sidor. De algoritmer som hittills presenterats är bara toppen av isberget. En intressant algoritm som bygger på hierarkisk visibilitet (HV) presenteras i [6]. Den tredimensionella miljön representeras av ett oktalt träd (se sektion 2.2.1.). Bilden ritas sedan ut i ungefärlig fram-till-bak-ordning (noderna närmast ögat behandlas först). Noder som faller utanför synvolymen gallras bort direkt. Projiceringen av en nod jämförs mot den hierarkiska z-bufferten. Idén är att om en nods framsida har högre z-värden än de som finns i den hierarkiska z-bufferten så kan noden (tillsammans med alla subnoder) gallras bort. En detaljerad beskrivning av algoritmen skulle tyvärr bli alltför lång för denna uppsats. HV-algoritmen kan modifieras för att ta tillvara på en egenskap som återfinns i väldigt många datorprogram som rör sig i tredimensionella miljöer: tidskoherens. Man kan anta att de noder som är synliga vid någon viss uppritning av bilden antagligen också är synliga vid nästa uppritning (frame-to-frame coherency). Noder som visat sig vara synliga vid den förra uppritningen av scenen ritas ut normalt med hjälp av Z- bufferten. Z-bufferten läses sedan in från hårdvaran och en hierarkisk Z-buffert byggs upp utgående från den. HV-algoritmen fyller sedan ut tomrummen i scenen. En annan algoritm som är värd att noteras är Hierarchial Occlusion Map (HOM)- algoritmen [7]. Denna algoritm är i likhet med HV-algoritmen bildbaserad, det vill säga den använder sig av den utritade bilden för att bestämma vilka objekt som är dolda. I korthet går algoritmen ut på att några objekt väljs som ockluderare (naturliga val är stora objekt som ligger nära ögat). Dessa renderas till en hierarkisk ockluderingskarta. Denna karta kan användas för att bestämma om något annat objekt är synligt eller inte. 5. Slutsats Som vi har sett finns det ett otal olika tillvägagångssätt för att lösa ett till synes rätt enkelt problem; rendera så få polygoner som möjligt utan att realismen blir lidande. Många algoritmer blir aldrig implementerade i kommersiella produkter. Det är dock 11
värt att tänka på att detta problem är något man helt enkelt är tvungen att ta itu med om man vill göra ett datorprogram som renderar tredimensionell grafik. Därför är forskningen på det här området aldrig ointressant. 6. Referenser [1] Foley, J. D., A. van Dam, S. K. Feiner, J. H. Hughes: Computer Graphics Principles and Practice, Second Edition, Addison-Wesley, 1990. [2] Ayala, D., P. Brunet, R. Juan, I. Navazo: Object representation by means of nonminimal division quadtrees and octrees, ACM Transactions on Graphics, volym 4 nummer 1, januari 1985. [3] Fuchs, H., Z. M. Kedem, B. F. Naylor: On Visible Surface Generation by A Priori Tree Structures, Computer Graphics (SIGGRAPH 80 Proceedings), vol. 14, nummer 3, sid 124-133, juli 1980. [4] Teller, Seth, Visibility Computations in Densely Occluded Polyhedral Environments, FD-avhandling (http://graphics.csail.mit.edu/~seth), University of California at Berkeley, 1992. [5] Luebke, David P., Chris Georges, Portals and Mirrors: Simple, Fast Evaluation of Potentially Visible Sets, Proceedings 1995 Symposium on Interactive 3D Graphics, sid 105-106, april 1995. [6] Greene, Ned, Michael Kass, Gavin Miller, Hierarchial Z-Buffer Visibility, Computer Graphics (SIGGRAPH 93 Proceedings), sid 231-238, augusti 1993. [7] Zhang, Hansong, Effective Occlusion Culling for the Interactive Display of Arbitrary Models, FD-avhandling (http://www.cs.unc.edu/~zhangh/dissertation.pdf), Department of Computer Science, University of North Carolina at Chapel Hill, 1998. 12