• No results found

Implementation av en renderare för Agency9

N/A
N/A
Protected

Academic year: 2021

Share "Implementation av en renderare för Agency9"

Copied!
23
0
0

Loading.... (view fulltext now)

Full text

(1)

2008:004 HIP

E X A M E N S A R B E T E

Implementation av en renderare för Agency9

Lasse Wedin

Luleå tekniska universitet

(2)

Förord

Detta examensarbete är gjort våren 2006 för Luleå Tekniska Universitet Institutionen i Skellefteå hos Företaget Agency9, Luleå. Företaget var i ett skede då de skulle utveckla en ny grafikmotor, vilken skulle innehålla en renderare. Denna rapport presenterar en möjlig design och

implementationen av denna renderare.

Jag vill tacka min handledare Tomas Karlsson, för all hjälp och feedback som han gett mej under mitt examensarbete. Jag vill även tacka min flickvän Katarina Alatalo, för att jag har fått bo hos henne under min tid i Luleå. Slutligen vill jag tacka Kashyar, Johan, Johan och Andreas för att att de bidragit med en munter stämning på kontoret.

(3)

Abstract

The visual complexity in real-time applications, like computer games, is increasing with customer demands on vissual apperance. This trend is supported by even better graphics cards from the hardvare industry. This must be taken into consideration when the architecture of a new graphics engine is to be designed. The engines renderer is responsible of generating a picture of scene data.

The renderer must cope with increasingly more complex scenes, and must be able to render them depending on available hardvare capasity. This report describes tha architecture and implementation of a basic renderer, with these demands i focus.

Sammanfattning

Den visuella komplexiteten i realtidsapplikationer, så som spel, ökar i och med kundes krav på nya grafiska upplevelser. Denna trend stöds av allt bättre grafikkort från hårdvaruindustrin. Hänsyn till denna förändring måste tas vid designen av dagens grafikmotorer. Grafikmotorns renderare

ansvarar för att generera en bild av scendata. Renderaren måste klara av att rendera allt mer komplexa scener, samt ha möjlighet att rendera dessa beroende på tillgänglig hårdvarukapacitet.

Denna rapport beskriver arkitetekturen och implementationen av en grundläggande renderare där dessa krav ställs i focus.

(4)

Innehållsförteckning

1Introduktion... 5

1.1Bakgrund...5

1.2 Syfte... 5

1.3 Ramar...5

2 Teori... 6

2.1 Renderings-pipeline... 6

2.1.1 Applikationssteget...7

2.1.2 Model- och View Transform (Geometristeget)...7

2.1.3 Lighting (Geometristeget)...8

2.1.4 Projection (Geometristeget)... 8

2.1.5 Clipping (Geometristeget)...9

2.1.6 Screen mapping (Geometristeget)...9

2.1.7 Rasteriseringssteget...9

2.1.8 Double Buffering... 10

2.1.9 Shaders... 10

2.2 Hash map:ar... 11

2.3 Strömmar... 11

3 Empiri... 11

Figur 3.2...13

3.1 Användning av rendering pipeline...13

3.2 Komunikation - klient, server... 14

3.3 Klientens bild av scenen... 14

3.4 Överföring av data... 15

3.5 Resurser... 15

3.5.1 Utöka antalet attribut i resursen... 16

3.5.2 Mesh-optimerare... 16

3.6 Instruktioner...17

3.6.1 Globala och lokala instruktioner...17

3.6.2 Sortering av instruktioner...18

3.7 Renderingskärna... 18

3.8 Inställningar från användaren... 19

3.9 External Context... 19

4 Resultat... 20

5 Diskussion... 21

5.1 Slutsats... 21

5.2 Förbättringar... 22

1.25.3 Vidareutveckling...22

5.4 Summering...22

Referenser...23

(5)

1 Introduktion

1.1 Bakgrund

Rendering är processen att, med datorns hjälp, skapa en bild utifrån given data. Realtidsrendering är processen att skapa dessa bilder i en sådan hög takt att de lurar användaren att han tar del av en dynamisk, process. Det övergripande målet med realtidsrendering är god kvalitet och hastighet. [1]

Vad som är god kvalitet skiljer sej mellan olika typer av applikationer. I en applikation som visualiserar kartor eller ritningar, kan korrekt återgivning vara ett krav, medan den totala visuella upplevelsen kan vara åtråvärd i ett fygsimulatorspel. [1] Hög hastighet eftersträvas då man vill att användaren ska uppleva applikationen som en filmsekvens snarare än en serie stillbilder.

Många av dagens realtidsrenderingsapplikationer krävar stöd från någon typ av 3D-grafikkort. [1]

Krav på renderingshastigheter och kvalitet har drivit på hårdvaruutvecklingen och gjort att det idag finns en uppsjö olika varianter a grafikkort, med varierande prestanda.

Datan som ska renderas, scenen, kan vara olika noggrannt beskriven. Risken finns att ett grafikkort inte kan rendera scenen lika utförligt som scendatan beskriver den. Därför är det nödvändigt att mjukvaran som renderar, renderaren, kan rendera en scen endast så avancerat som hårdvaran på den lokala stationen tillåter. [4]

1.2 Syfte

Syftet med arbetet är att visa att jag kan använda mina tidigare kunskaper till att lösa ett problem innom ett nytt område. Jag ska hitta en bra arkitektur för att kunna implementera en renderare som kan matas med renderingsdata i form av strömmar. [4] Syftet är också att företaget, Agency9, ska ta del av vad jag kommit fram till.

1.3 Ramar

Jag ska designa en arkitektur och implementera en prototyp av en renderare. Renderaren är den del av grafikmotorn som ser till att rita given data på skärmen. Renderaren ska vara en server, vilken ska ta emot order från en klient och därigenom rendera specifierad scen på skärmen.

Den typ av renderare som jag ska implementera är redan väl känd och implementerad sedan

tidigare.[4] Jag kommer inte lägga ner energi på att hitta en smartare lösning, utan istället ägna mej åt att designa och implementera en renderare av befintlig typ.

Jag kommer att begränsa mej till att implementera en renderare, vars kärna inte är kapabel att rendera mer än en enkel scen. Trots detta, ska arkitekturen tillåta påbyggnad med mer avancerade strömmar och kärnor.

(6)

2 Teori

2.1 Renderings-pipeline

En scen består av en mängd primitiver, så som prickar linjer och trianglar. Genom att bygga ihop flera primitiver till en geometrisk figur kan man skapa mer avancerade objekt. Ett exempel på ett sådant objekt är en mesh, vilken är en tredimensionell figur som består av flera primitiver.

För att generera en bild på skärmen, måste samtliga meshar passera genom den så kallade

renderingspipelinen. Pipelinen kan liknas vid en motor som utifrån given geometridata och givna inställningar renderar en bild, vilken kan presenteras på skärmen [1].

Pipelinen brukar delas in i 3 steg, applikations-, geometri- och rasteriseringssteget. Varje steg består vanligtvis av en pipeline i sej vilken består av flera inbördes setg. Applikationssteget är vanligen implementerat i mjukvara medan geometri- och rasteriseringssteget vanligen sköts av ett 3D- grafikkort. [ref] Det som är gemensamt för alla steg är att de blir matade med en dataström, behandlar denna data och lämnar utdata. Vilken utdata ett steg lämnar från sej beror, förutom på indatat, även på hur pipelinen är inställd.

Figur 2.1.1: En chematisk bild över renderingspipelinen. De gula rektanglarna symboliserar varje steg. De blå pilarna visar primitivens väg genom pipelinen. Texten i rött till höger visar

inställningar vid de olika stegen.

En enkel beskrivning av pipelinens struktur, samt en beskrivning av vad varje steg utför sår

Stored mesh data

Model- and View transformmation Lighting

Projection Clipping

Screen mapping

Rasterization

Frame buffert Triangle setup

Application stage

Geometry stage

Rasterizer stage [M-transform]

[V-transform]

[P-transform]

Lights Material

Viewport

Textures

Vertex ShaderFragment Shader

(7)

beskrivet nedan. För att illustrera vad som sker i pipelinen, följer vi en primitiv genom pipelinen, från applikationsdata till pixlar på skärmen.

2.1.1 Applikationssteget

I applikationssteget lagras vertexdata. Varje vertex är ett hörn på en geometrisk figur, så kallad primitiv, och innehåller information om sin position, men kan även innehålla ytterligare

information. Ytterligare information för varje vertex kan vara t.ex. normal och texturkoordinat, vars funktion förklaras senare. Vertexarna har sin position bestämd relativt origo. Man säger att meshen och dess vertices bor i modelspace (eller objectspace). Primitiver är de geometriska figur som man kan använda för att bygga upp mer avancerade objekt, t.ex meshar. Extempel på primitiver är punkter, linjer och trianglar.

Geometristeget och rasteriseringssteget har en mängd inställningar som går att sätta. Innan data skickas till geometristeget ska dessa inställningar sättas till önskade värden.

Den lagrade vertexdatan skickas till geometristeget som en ström av vertices, tillsammans med en beskrivning på hur dessa vertexar ska tolkas rent geometriskt. Datan kan, bland annat, tolkas som enskilda punkter, som en linjes ändpunktpar eller som hörn i trianglar. [3]

Data: En ström av vertices. Var och en har position och möjligen annan data.

2.1.2 Model- och View Transform (Geometristeget)

Model- och view- transformerna är två 4x4 matriser som blivit bestämda under applikationssteget.

Model-transformen gör det möjligt att sätta position och orientering av en mesh. Det som sker är att varje vertex i strömmen transformeras enlnigt matrisen så att dess position flyttas och dess normal vrids. Modelmatrisen kan även användas för att skala och cheera meshen. Model-transformen flyttar vertexarna från model space till world space. [1][3]

View-transformen gör det möjligt att filma (betrakta) meshen ur ett önskat perspektiv. Det som sker är att varje vertex i strömmen transformeras enligt matrisen så att dess position flyttas och dess normal vrids. Meshen kan på så vis se ut att vara filmad från en given position och vinkel.

Egentligen betraktas alla meshar från origo i riktning längs den negativa z-axeln. Det view-

transformen gör, är att flytta och vrida världen inför en fixerad kamera i origo, snarare än att flytta runt kameran i världen. Resultatet blir dock detsamma. View-transformen flyttar vertexarna från worl space till eye space (eller camera space).

[1][3]

Ofta multipliceras de två matriserna ([view][model]) innan vertexmängden multipliceras med dem, vilket gör att transformationen går smabbare. [1][3]

Data: En ström av vertices i eye space. Samtliga positioner har blivit translaterade och vridna så att de hamnat på den position och orientering som modeltransform beskriver samt translaterade och vridna så att de ser ut att vara filmade från en virtuell kamera.

(8)

2.1.3 Lighting (Geometristeget)

Lighting går ut på att förändra färgen på meshens yta beroende på ljuskällors relation till meshen, och meshens material. Kort sagt gör ljussättningen så att de ytor som är vinklade mot en ljuskälla blir mer upplysta än de som är vända bort från ljuskällan. Det finns flera olika metoder att beräkna ljussättningen av en mesh[1]. Här följer en enkel beskrivning av hur en den kända metoden gouraud shading[2] fungerar:

Ett antal ljuskällor kan bli bestämda i applikationssteget. Dessa har bland annat en färg och en riktning. [3]

Ett material kan bli bestämt i applikationssteget, vilket talar om hur ljuset påverkar meshens yta.

Materialet har attribut som ambient, diffuse och specular, vilka beskriver dess förmåga att reflektera ljuset från ljuskällorna i scenen. Abient är förmågan att reflektera bakgrundsljus, diffuse är

förmågan att reflektera ljus som kommar att spridas i alla riktningar från materialets yta och specular är materialets glansighet. [3]

Ljuskällorna blir precis som alla primitiver transformerade via model- och view matrix. Ljusen och vertexarna har alltså blivit transformerade via samma transformer och sägs bo i samma space. De inbördes avstånden och vinklarna mellan ljus och vertices är samma som i worldspace så

beräkningarna kan ske i eye space.

För varje vertex sker en beräkning av ljusets reflektion mot materialet vid denna. Resultatet är två färgvärden vilka kallas för diffuse och specular. Diffuse beskriver hur upplyst ytan är vid vertexed medan specular beskriver hur mycket ytan glänser vid vertexen. För att utföra ljusberäkningarna är man beroende av ljusets position, riktning och färg, vertexens position och normal samt materialets egenheter. De två färgvärdena sparas som data för varje vertex eftersom de behövs när shadingen slutförs i rasteriseringssteget.

Data: En ström av vertexar i eye space. Samtliga vertexar har två färgvärden (diffuse och specular), vilka talar om ljuset reflekteras mot dem.

2.1.4 Projection (Geometristeget)

En projektion görs genom att verje vertex transformeras enligt en paralell- eller perspektiv- projektionstransform. Vid båda transformationer transformeras en ursprunglig vy-volym kallad view frustum med dess vertices till en bestämd volym kallad canonical view volume. View frustum kan ses som den volym som kameran blickar över. Om man använder sej av paralell projektion är den ursprungliga vy-volymen ett rätblock medan den i fallet av perspektiv projektion är

pyramidformad med avhuggen rektangulär top samt rektangulär bas. Canonical view volymen är vanligen en enhetskub och har sina extrempunkter i (1, 1, 1) och (-1, -1, -1).

Vid paralell transformering förblir paralella linjer paralella även efter transformationen. I fallet av persspektiv projektion kommer objekt som ligger närmere kameran att se större ut än lika stora objekt som ligger längre bort, precis som vi skulle uppleva det i verkligheten.

Projektionstransformen är en 4x4-matriser vilken bestäms i applikationssteget. Trasformen flyttar vertexarna från eyespace till clip space. Detta steg kallas projektionssteg, trotts att det bara är en tansformation från en volym till en annan. [1]

(9)

Data: En ström av vertexar vertexar i clip space. Märk väl att vertexarna fortfarande har kvar sina z-värden.

2.1.5 Clipping (Geometristeget)

Samtliga primitiver testas mot cononical view volumes sex väggar. De primitiver som är utanför kan ignoreras, eftersom de är utanför den volym som kameran ser. Primitiver som befinner sej helt innom volymen passerar helt oförändrade. Om en primitiv är delvis innom och delvis utanför volymen klipps den del som är utanför bort, rent geometriskt. Vid behov skapas nya vertices, vid de ställen där den klipta primitiven tangerar volymens vägg.

Förutom att clipping sker automatiskt mot volymens sex väggar, kan man explicit ange ytterligare plan att klippa primitiver mot under applikationssteget.

Data: En ström vertices tillhörande de primitiver som ligger innom cononical view volume. Om inga ytterligar clipping planes har angets, motsvarar dessa alltid de vertices som ligger innom view frustum.

2.1.6 Screen mapping (Geometristeget)

Samtliga vertices transformeras till screen-koordinater. Värden på hur canolical view volume ska mappas till skärmen har bestämts under applikationssteget.

Data: En ström med vertices positionerade på de x, y positioner som de har på skärmen. Varje vertex har fortfarande kvar sitt z-värde. Z = [-1,1]

2.1.7 Rasteriseringssteget

I rasteriseringssteget bestäms färgvärdet på varje pixel som de transformerade primitiverna

renderar. För varje primitiv skapas ett antal beståndsdelar kallade fragment, vilka bildar en sträcka om primitiven har två vertexar, och en yta om den har tre. Dessa kan ses som förstadiet till pixlar.

Informationen hos varje fragment är en linjärinterpolation av den information som ligger lagrad i primitivens vertexar.

Informationen i varje fragment används för att beräkna vilket färgvärde fragmentet ska få. I geometristeget sattes tidigare en färg för varje vertex, enligt en metod som kallas gouraud

shading[2]. Denna färgdata kommer precis som övrig data att interpoleras över primitiven och på så vis ge upphov till en mjuk färgövergång över denna.

Vid detta steg sker även textureringen, där ett eller flera lager av mönster mappas på primitivens yta. Vilka texturer som ska användas har blivit bestämt i applikationssteget. Vid användning av texturer har vi även skickat med en eller flera uppsättningar texturkoordinater, vilka talar om hur respektive textur ska mappas på primitiverna rent geometriskt.

(10)

Figur 2.1.7.1: Bilden illustrerar hur en texturmappning går till. Till vänster är en tvådimensionell textur, vilken mappas på en tetraed via texturkoordinater. De olikfärgade prickarna visar hur varje texturkoordinat mappas till en vertex.

Samtliga fragment ger, var och en, upphov till en pixel med ett visst färgvärde vilken lagras i en array, kallad color buffer. I denna buffer kommer primitiverna att ritas precis som de senare kommer att uppträda på skärmen.

Primitiverna kommer alltid att ritas i den ordning som de skickades in i applikationssteget. Detta gör att en primitiv som ligger längre bort från kameran (högre z-värde) kan överlappa en primitiv som ligger närmere (lägre z-värde). Detta är givetvis oönskat då det ser onaturligt och fel ut.

Lösningen på problemet är att använda sej av z-bufferten (eller djupbufferten). Z-bufferten har samma storlek och form som color bufferten. När varje fragment skrivs till colorbufferten, skrivs samtidigt ett värde till z-bufferten på samma x-y-koordinat. Värdet innehåller fragmentets z-värde, dvs. hur långt bort från kameran fragmentet befinner sej. Vid utritning av en ny pixel i

colorbufferten, testas först hurvida pixelns z-värde är större eller mindre än det som redan är skrivet på samma plats i z-bufferten. Om z-värdet är mindre, ligger pixeln på denna primitiv närmare kameran. Det gamla color och z-värdet skrivs då över av motsvarande värden i den nya pixeln. Om z-värdet är större, ligger pixeln bakom och ingen åtgärd tas. På så vis kan vi garantera att alla pixlar som presenteras på skärmen är de som ligger närmast kameran.

Hur en ny pixel ska ta hänsyn till z-värdet, går att ställa in även på andra sätt, men inställningen ovan är av intresse när det gäller att dölja pixlar som är gömda bakom synliga pixlar.

Data: Färgvärden i colorbufferten och djupvärden i z-bufferten, vilka motsvarar den färdiga bilden.

2.1.8 Double Buffering

För att undvika att användaren ser hur primitiverna ritas ut på skärmen använder vi oss av ett system kallat double buffering. Systemet består av två ritytor – en som användaren ser på skärmen, kallad front buffer och en som utritning sker till, kallad back buffer. När all utritning är färdig till back buffer byts innehållet i buffrarna, användaren ser nu den nyuppdaterade datan och ny utritning kan ske till back buffern.

2.1.9 Shaders

Många grafikkort kan ljussätta primitiverna automatiskt genom gouraud shading. Andra metoder att shade:a kan uppnås genom att steg i pipelinen byts ut mot programkod som användaren själv specifierat. Dessa program kallas vertex- och fragment shader. En vertexshader implementeras genom att model view – transform, lighting och projection byts ut mot användarens egen programkod. En fragmentshader implementeras genom att fragmentsteget på liknande sätt implementerar en fragmentshader varvid rasteriseringssteget byts ut.

(11)

2.2 Hash map:ar

Hash map:ar är ett effektiv datastruktur där nycklar och tillhörande värden lagras parvis i en array.

När ett värde läggs till hashmappen, skickar man med en nyckel. Denna nyckel används senare för att söka rätt på tillhörande värde. Samtliga värden lagras i en arrray där värdets position sätts utifrån nyckeln. Detta sker genom att nyckeln genererar en hashkod vilken bestämmer värdets position.

Hashkoden genereras av en användarspecifierad funktion och beror på datan i objektet.

Om man lägger till ett nyckel-värde-par vars nyckel redan finns inlagd i hashmappen, ersätts det gamla paret av det nya. Det kan på så vis inte finnas två lika nycklar i hashmappen.

2.3 Strömmar

För att beskriva en mesh bestående av (en eller) flera trianglar behövs mängder av data av olika typer. Sådan data kan vara positioner, normaler, texturkoordinater, färgvärden, animationsdata mm.

[3]

Varje datamängd kan beskrivas med en endimensionell array, vilken kallas stream. [4] En mesh måste åt minstonde ha en positionsström men kan även beskrivas med hjälp av fler strömmar.

3 Empiri

För att ta reda på vilka möjligheter det fanns att påverka renderingen av en scen, studerade jag renderingens kärna- renderings-pipelin:en. Efter att jag visste vad som kunde påverkas tog jag reda på hur klienten beskrev en scen. Målet var att kunna mappa klientens bild av scenen mot pipelin:en så att scenen kunde renderas via klientens order. Eftersom klienten ska föra över mycket data till servern var det fördelaktigt att inte föra över samma data flera gånger, utan istället spara denna på serversidan, för att senare referera till dessa resurser via instruktioner. Efter att resurser och instruktioner förts över från klienten till servers, renderas scenen av en renderingskärna, vilken pratar direkt ner mot pipelin:en. I empirin beskriver jag mer i detalj hur denna design växte fram.

Min design ledde till renderar arkitekturen illustrerad nedan, vilken i sej är enkel men tillåter utökning med mer avancerade mesh:ar med fler streams, fler resurser samt fler renderingskärnor.

Det över schemat förklarar i vilket sammanhang renderaren är implementerad.

(12)

Figur 3.1: Figuren visar i vilket sammanhang renderaren är designad och implementerad. User application är kod utanför grafikmotorn. Display är någon typ av grafiskt output vilket är beredd att ta emot bilder från renderaren. Klient ger, utifrån en scengraf, servern order om vad som ska ritas. Servern är kan ha flera renderare och varje renderare kan ha flera kärnor. Användaren kan sätta vissa inställningar utan att gå via klienten.

Figur 3.2: Figuren visar en schematisk bild över hur jag valde att designa renderarens arkitektur.

ResourceManager tar emot optimerar/konverterar och lagrar resurser skickade från klienten.

Insruction manager tar emot instruktioner från klienten, vilka förkalarar hur resurserna ska användas för att rendera den scenen. Kernel är själva renderingskärnan vilken har i uppgift att rendera bilden utifrån de lagrade resurserna och instruktionerna. User settings är inställningar som användaren kan använda utan att gå via klienten.

User Application

Display Klient

Server

Renderer Renderer

Renderer

Kernel Kernel

Kernel

Renderer

ResourceManager

InstructionManager

Kernel

User settings

API Pipeline Display User orders

Klient Resources and Instructions

(13)

Figur 3.2

3.1 Användning av rendering pipeline

Eftersom jag begränsat mej till att göra en grundläggande renderare intresserade jag mej bara av de pipeline-inställningar som kändes nödvändiga för att kunna generera en enkel scen på skärmen.

Följande kändes absolut nödvändigt att kunna göra:

”Cleara” color- och z-buffertarna

För att kunna rita en ny bild måste all grafik i bilden innan suddas bort. Ett alternativ är att rita över hela den gamla bilden med ny grafik. Z-bufferten måste tömmas eftersom nya pixlar ska få hindras att ritas ut beroende på gamla djupvärden som ligger kvar i z-bufferten. (se teori -

rasteriseringssteget)

Mata pipelin:en med mesh:ar

Scenen renderas genom att pipelin:en matas med primitiver, mesh:ar, vars egenheter ändras av de inställningar som pipelinen har för tillfället. Pipelinen ska kunna matas med meshar, vilka endast har positioner eller meshar av mer avancerade slag.

Sätta modeltransform

Primitivernas position och orientering kan bestämmas genom att sätta denna transform. Med hjälp av model transformen kan även ljuskällor positioneras och orienteras.

Sätta viewtransform

Den kamera som filmar scenen har en position och är riktad att filma i en viss riktning. Dessa kan bestämmas genom att sätta denna transform.

Sätta projectiontransform

Kamerans lins kan ställas genom att bestämma denna transform. Genom att förändra transformen kan man skapa olika linser till kameran. Med hjälp av perspektiv projektion kan man få lika stora objekt att se olika stora ut beroende på deras inbördes avstånd till kameran.

Sätta ljuskällor

Ljuskällor i scenen möjliggör att objekt kan bli skuggsatta på ett naturtroget sätt. Ljuskällorna kan flyttas och riktas om med hjälp av modeltransform.

Sätta material

Hur primitivesn yta påverkas av ljus i scenen går att bestämma genom att sätta material. Materialets matthet och glansighet går att bestämma.

Sätta textur

Genom att sätta textur går det att applicera en bild på ytan av en primitiv. Det går att applicera ett flertal texturer på en och samma primitiv, men jag har begränsat mej till en.

(14)

mapping.

3.2 Komunikation - klient, server

Det var nu klart på vilka sätt man behövde påverka pipelin:en för att rendera en grundläggande scen. Frågan var nu hur klienten skulle komunicera med servern, renderaren, för att rendera scenen.

För att ha möjlighheten att byta renderare, ska renderings orderna från klient till server vara generell. Klienten ska alltså inte ta någon hänsyn till vilken renderare den pratar till. Samtliga implementerade renderare ska sedan kunna lyssna på dessa instruktioner och rendera önskad scen.

Varje renderare i sej ska ha sitt eget sätt att tolka informationen och prata mot sitt API(Application Program Interface) ner mot renderings pipelinen.

1.2 3.3 Klientens bild av scenen

Klienten är baserad på ett filformat COLLADA[5], vilket är gjort för att beskriva virtuella scener i 3D. Min exjobbskollega, Johan Lindbergh, som har arbetat med klienten, kom fram till ett antal objekt vilka stämde över ens med kontentan av vad COLLADA använder för att beskriver en grundläggande scen. Dessa objekt, beskrivna nedan, har lagt grunden för språket från klient till server:

Mesh

Beskriver en mesh genom ett antal streams. Olika strams kan innehålla data om position, normal, texturkoordinal, animationsdata mm. En mesh måste åt minstonde ha en positions-stream. Varje stram har en tillhörande array vilken talar om i vilken ordning datan ska hämtas när meshen byggs upp.

Transform

Beskriver en transform som en 4x4 matris, vilken kan användas för att positionera och orientera objekt, så som meshar, ljus och kameror. Den kan även användas för att bestämma kamerans lins.

Light

Beskriver en ljuskälla, som lyser i en viss färg och riktning. Här finns möjlighet att ställa hur mycket ljuset frörsvagas beroende på avständ, möjlighet att begränsa ljusets utbredning från den riktning det är riktat. Genom att ställa dessa attribut på olika sätt, kan man åstadkomma olika typer av ljus som:

Pointlight: ljus som sprider sej från en position i alla riktningar utan begränsning.

Spotlight. ljus som sprider sej från en position i en given riktning och är begränsad så att den kastar en ljuskägla.

Directional light: ljus som utgår från en position som kan tänkas befinna sej oändligt långt bort och därför kastar sina strålar paralellt i en given riktning.

Ambient light: bakgrundsljus som kan tänkas ha studsat oändligt många gånger och därför lyser lika starkt över hela scenen.

(15)

Transformed Light

Beskriver ett ljus och hur detta är transformerat, genom att referera till en transform och ett ljus.

Material

Beskriver utseendet på meshens yta. Här går det att bestämma på vilket sätt materialet återger ljuset från scenens ljuskällor. De tre sätt på vilka materialet kan återge ljus från ljuskällorna kallas:

Ambient, Diffuse och Specular. Dessa beskrivs nedan:

Ambient: materialets förmåga att reflektera scenens bakgrundsljus.

Diffuse: materialets förmåga att rflektera ljus som träffar ytan för att sedan reflekteras i alla riktningar.

Specular: materialets förmåga att reflektera ljus som studsar direkt mot ytan för att sedan studsa i en riktning, vilket är samma sak som dess förmåga att glänsa.

Materialets yta kan även ha egenskapen att ge ifrån sej ljus oberoende av ljuskällor, kallat emmission. Detta ljus syns alltså även i en totalt ljuskällefri scen.

För varje av ovanstående 4 komponenter kan man sätta en färg som gäller just denna återgivning.

Det är även möjligt att referera till en 2D-textur, vilken har samma uppgift som färgen men möjliggör varierad återgivning över ytan.

2D-Textur

En rektangulär bild bestående av ett antal pixlar med olika färg. Klienten anger denna via ett filnamn. Detta fungerar bra nu när klient och server ligger på samma station, men måste ändras om man vill ha klient och server på olika stationer.

3.4 Överföring av data

För att rendera varje ny bild, måste klienten skicka över nya renderingsorder. Det blir ett stort informationsflöde då systemet måste vara kapabelt att visa många nya bilder per sekund. Då server och klient, i framtiden, ska kunna vara lokaliserande på olika stationer, är det intressant att hålla nere informationsflödet.

Klienten har ett sätt att beskriva scenen som inte nödvändigtvis stämmer över ens med renderarens sätt att prata vidare mot pipelinen. Varje renderare måste kunna tolka informationen och översätta den till det format som passar bäst för dess API. Denna översättning vill man göra så sällan som möjligt, då den tar tid.

En lösning på dessa problem presenteras i stycket 3.5 och 3.6.

3.5 Resurser

Ett sätt att minska flödet, är att inte skicka samma information flera gåneger. Om det går att identifiera information som är mer eller mindre konstant, kan vi först skicka över denna från

(16)

Jag gjorde arkitekturen sådan att klienten kan skicka över alla ovan specifierade COLLADA-objekt som resurscontainrar, vilka förutom ovan angivna attribut även innehåller ett ID. Dessa tas emot av renderaren och översätts till ett internt format. Vid översättningen gäller det att se till hur

informationen i resurscontainrarna kan översättas för att enklast kunna mata renderingspipelin:en med de nödvändiga instruktionerna vid rendering.

Colladaobjekten, som Johan identifierat, stämde mycket väl över ens med de nödvändiga pipeline- instruktioner som jag identifierat. De flesta av containrarna kunde översättas till resurser genom att kopieras, rakt av. Mesh:en var dock nödvändig att optimera, eftersom den skickats från klienten i ett generellt format och måste skickas till renderarens pipeline i ett specifikt format. Jag skapade en ResourceManager för att sköta jobbet med att översätta och lagra resurserna. Mer information om meshen fins i stycket 3.5.2.

Samtliga resurser lagras hos renderaren i en hash map där nyckeln är ID:t och värdet är resursen.

Om en resurs läggs in tillsammans med en befintlig nyckel, skrivs den gamla resursen över av den nya. Containrarna är bara till för att föra över information från klienten till servern. Dessa kastas efter att informationen översats och lagrats av renderarens resourcemanager.

3.5.1 Utöka antalet attribut i resursen

Den data som nu kan lagras i containrarna räcker bara till att rendera en grundläggande scen. I framtiden vill man kunna föra över fler typer av data för att möjliggöra rendering av mer komplexa scener, utan att för den sakens skull skriva om de implementerade klasserna: ResourceContainer, ResourceManager, eller RenderingKernel.

Genom att definiera resurser som hash map:ar gör vi implementationen framåtkompatibel. Metoden går till så att en ResourceContainer innehåller en hash map med strängar som nycklar och vilket objekt som helst som värde. På så vis kan en och samma resurs hålla hur många olika attribut som helst, vilka kan hämtas enkelt ur hash map:en genom att kalla på det via dess namn.

ResourceManager kan sedan optimera mesh:arna enligt resursens angivna streams och

RenderingKernel kan rendera via de strömmar och attribut den önskar. Min grundläggande kärna frågar, t.ex. endast efter position-, normal- och texturkoordinatströmarna. Implementationen gör att renderaren inte behöver skrivas om trotts mer avancerade instruktioner frän klienten.

3.5.2 Mesh-optimerare

Översättning: Datan i mesh container:n läses igenom, optimeras och lagras som en mesh- resurs vilken är lättläst av renderarens API. Optimeringen går ut på att, utifrån givna resurser och index, bygga upp en mesh som ser likadan ut men består av minimalt antal vertex:ar. Optimeringen gör också så att mesh:en lagras med ett antal source array:er och en index array vilken refererar till samtliga source array:er. Detta eftersom API:n läser en mesh på detta sätt.

Optimeringsalgoritmen går kort till så att samtliga vertex:ar i in-meshen stegas igenom och lagras, om den inte en likadan finns lagrad redan. En vertex anses vara likadan om alla attribut är likadana.

Efter att alla unika vertexar lagras byggs meshen upp genom att en lista fylls med referenser till mängden unika vertex:ar.

(17)

Figur 3.5.2.1: Bilden visar ett exempel på vad som händer när en mesh optimeras. Till vänster är meshen illustrerad som den är beskriven i MeshContainern. Till höger meshen som den är

beskriven efter att den optimerats av ResourceManager. De gula figurerna föreställer polygoner.

De röda prickarna är vertex:ar och de röda pilar är vertexnormaler. Märk väl att vertexarna med svarta streckade linjer har samma position. Optimeringsalgoritmen lagrar endast en av vertexarna som är lika. Båda högra vertexparet som binder ihop B och C har samma position och samma vertexnormal vilket gör att B:s nedre högra hörn och C:s övre vänstra hörn kommer att referera till samma vertex efter optimeringen. Vi har på så vis gått ifrån att beskriva meshen med 11 vertex:ar till 10.

3.6 Instruktioner

Via ResourceManager kan klienten lagra resurser i renderaren, nu återstår bara att kunna använda dessa. Genom att skicka en lista med instruktioner kan klienten specifiera vad som ska ritas ut av de resurser som tidigare skickats över. Denna data kallar jag för renderingsinstruktioner och består uteslutande av referens-ID:n till resurserna.

3.6.1 Globala och lokala instruktioner

Vissa instruktioner refererar till resurser, vilka påverkar flera meshar i scenen medan andra refererar till resureser som endast påverkar en enskilld mesh. De instruktioner som refererar till resurser som påverkar flerea meshar kallar jag fört globala instruktioner, medan jag kallar de som refererar som refererar till enskillda meshar för lokala instruktioner. Samtliga resurser hanteras av renderarens instruction manager.

De globala instruktionerna bestämmer kamerans instälningar och ljussättningen i scenen.

Ljussättningen är i sin tur en lista med trandformerade ljus. En global instruktion kan se ut på följande vis:

B

C A

Optimize B

C A

B

C A

B

C A

(18)

De lokala instruktionerna är en order att rendera en mesh med givna attribut. En lokal instruktion kan se ut på följande vis:

LocalInstruction = (modelTransformId, materialId, meshId)

När listan med globala och lokala instruktioner anländer till InstructionManager läses denna igenom, instruktion för instruktion. För varje påträffad local instruction, skapas en ny instruktion vilken innehåller både global och local data. Den lokala datan kommer att bestå av samma referenser som den påträffade lokala instruktionen och den globala kommre att sättas till samma referenser som i den senast påträffade globala instruktionen. På så vis blir alla lokala instruktioner under en global instruktion påverkad av denna.

<bild instruktion som skapas av global + lokal instruktion>

3.6.2 Sortering av instruktioner

För att rendering ska kunna utföras snabbare kan man sortera instruktionerna beroende på vilka resurser de refererar till. Under rendering måste renderingspipelinen ställas in mellan varje batch.

En batch består, i princip, av en eller flera meshar med samma inställningar. Vissa inställningar är mer tidskrävande att byta än andra. Teckniken går ut på att sortera instruktionerna först efter de attribut som är mest tidskrävande och sedan efter allt mindre tidskrävande attribut. När

instruktionerna sedan ska användas för att rendera mesharna kommer de mest tidskrävande inställningsbytena att ske minst antal gånger medan de allt mindre krävande inställningsbytena kommer att ske allt fler gånger. [2] Att implementera denna sortering är något som finns med på att göra listan.

3.7 Renderingskärna

Efter att renderaren fyllts med resurser från klienten samt efter att instruktioner på hur dessa resurser ska användas för att rendera en scen, har vi allt som behövs för att rendera scenen med hjälp av renderingspipelinen. För att noggrannare kunna specifiera hur scenen ska renderas kan denna renderas via olika renderingskärnor, vilka går att byta likt ”plugins”. Varje renderingskärna har tillgång till samma resurser och instruktioner, men väljer själv hur den renderar scenen. Med hjälp av flera kärnor, kan man välja bland en uppsättning olika avancerade kärnor för att matcha grafikkortets prestanda. På så vis renderas scenen med så hög kvalitet som det är möjligt på den lokala datorn.

Det är även möjligt att låta en upsättning data och instruktioner löpa genom flera kärnor för att rendera en bild som är en kombination av dessa. (Se figur 4.1)

Vid renderingen av en mesh läser kärnan bara de strömmer den är intresserar av. Antag att en mycket noggrann mesh med många strömmar ska renderas på en dator med ett sämre grafikkort. En möjlighet skulle här vara att välja en enkel kärna för att rendera den avancerade meshen utan att ta hänsyn till alla strömmar. Meshen kommer inte att se lika snygg ut, men kan i alla fall renderas trotts dess komplexitet.

I den grundläggand kärna som jag implementerade, läser jag instruktionerna från instruktionslistan som skapats av InstructionManager. Varje instruktion innehåller, som beskrivet, ett antal referenser

(19)

vilka refererar till redan lagrade resurser. För varje instruktion ställer jag in pipelinens inställningar, enligt resurserna, i följande ordning:

ProjectionTransform ViewTransform TransformedLights Material

ModelTransform

Sedan skickar jag meshen till pipelinen med de strömmar som finns tillgängliga.

Det är onödigt att byta inställinigar om de ändå är lika för nästkommande mesh. Chansen att inställningar är lika mellan meshar är ännu större om jag hade implementerat sortering av

instruktioner (se stycke 3.6.2). Vitsen är att ett gäng meshar ska ritas ut med samma instälningar så att man byter inställningar så sällan som möjligt.

3.8 Inställningar från användaren

I stycket 3.1 ”Användning av rendering pipeline” nämnde jag några instruktioner som jag ansåg absolut nödvändiga att använda för rendera en grundläggande scen. Klientens språk till renderaren kan inte använda sej av alla dessa instruktioner. Anledningen till detta är att klienten förser servern med en specifikation på vad som ska ritas ut. Klienten har däremot inget att göra med var bilden ritas, när det ritas eller vilken kärna som ska utföra jobbet. Detta gjorde att jag fick en uppsättning kommandon som används direkt från användaren utan att gå via klienten. Kommandona var:

SetViewport(ScreenRegion)

Metoden bestämmer hur colorbuffern ska mappas mot fönstret vars den ritas ut.

ClearBuffers()

Metoden clearar color och frame bufferten.

SetScissors(ScreenRegion)

Sätter den del av output-fönstret dit pixlar kan skrivas vid rendering.

Render(renderingskärna)

Metoden beordrar en rendering av den specifierade scenen med given renderingskärna.

3.9 External Context

Det område som den färdigrenderade bildens skickas till, kallas context. Genom att skicka bilden till ett external context, behöver inte renderaren och det fönster dit resultatet renderas till, känna till varandra. Denna finess implementerade jag genom att main, användarens applikation, känner till både klienten, servern och fönstret. När main ber fönstret att uppdatera sej, sätts detta fönster till current contex. Det gör att all utritning kommer att ske till detta fönster. Sedan sker direkt ett anrop tillbaka till main, ett så kallat callback. Main anropar i sin tur renderaren och låter den göra sitt jobb. Efter att renderaren är klar firgörs fönstret och är alltså inte current context längre. Nu kan

(20)

4 Resultat

Renderaren kan rendera en grundläggande tredimensionell scen bestående av mesh:ar och ljus, vilken kan bli betraktade via en kamera. Mesh:arnas utséende påverkas av ljusen i scenen samt deras material.

Renderaren fungerar som en server, vilken tar emot resurser och instruktioner från klienten.

Renderaren kan tolka klientens generella resurser och instruktioner, för att senare prata ner mot ett specifikt grafik-API.

Renderarens kärna är ett plugin, vilket går att byta. Rendering av samma scen från flera kärnor till samma slutliga bild är möjlig.

En mesh med ett flertal strömmar kan renderas av en simpel kärna vilken då bara lyssnar på en delmängd av alla strömmar. Alla kärnor tillåter på så vis mer avanceraede meshbeskrivningar, utan förändrad output.

Nedan presenteras några skärmdumpar vilka visar bilder som renderaren producerat.

(21)

Figur 4.1: De 3 störe objekten är debugmeshar, vilka består av 4 trianglar vardera. De tre strecken visar scenen 3 koordinatakxlar. De små prickarna är scenens ljuskällor. Scenen är renderar med den grundläggande kärnan samt med en debugkärna vilken visar axlarna och

lamporna.

Figur 4.2: Samma scen som i figur 4.1. Bilden är skapad genom att scenen är renderad med debugkärnan plus 3 olika kärnor, vilka vardera

ansvarar för att rendera 1/3 av skärmen.

Vänstra segmentet är renderat med en vireframekärna, mitten med ordinarie kärna och

högra segmentet med en genomskinlighetskärna.

Figur 4.2: Karaktären Hope, renderad utan textur, ljussatt med 3 lampor. Lamporna är de 3

små prickarna.

5 Diskussion

5.1 Slutsats

Det är möjligt att implementera en renderingsserver vilken får renderingsorder från en server i form

(22)

hårdvaran på den lokala stationen eller för att bygga upp en bild där en scen renderas i flera pass genom olika kärnor.

Stream-arkittekturen gör att en mesh kan beskrivas olika nogrannt genom att ange ett varierat antal data-strömmar.

Hash mappar är ett smidigt sätt att göra en implementation framåtkompatibel.

5.2 Förbättringar

Meshoptimeringsfunktionen i ResourceManager kan göras mer generell. I nuläget tar den bara hänsyn till strömarna vilka beskriver position, normal och texturkoordinat.

Instruktionerna kan sorteras enligt stycket 3.6.2, vilket syftar till att förbättra rendderingshastigheten.

Serverinterfacet mot klienten kan utvärderas och göras tydligare.

1.3 5.3 Vidareutveckling

Fler renderare kan kan skapas vilka använder sej av andra API:er.

En arkitektur för att hantera en renderares output på ett generellt sätt kan skapas. I nuläget syftar alltid outputen till att rendera pixlar till skärmen.

En renderingskärna som hanterar shaders kan implementeras.

5.4 Summering

En renderare kan bli matad med meshar vars antal strömmar varierar, samt övriga instruktioner vars utförlighet varierar. Jag har implementerat en renderare, vilken kan rendera en grundläggand scen, genom att bara lyssna på de strömmar den är intresserad av. Arkitekturen tillåter att renderaren blir matad med mer avancerad data utan att behöva implementeras om. Genom att mata arkitekturen med mer avancerad data samt byta kärna kan mer avancerade scener renderas i framtiden.

(23)

Referenser

[1] ”Real-Time Rendering, Second Edition”, Tomas Akenine-Möller Eric Haines, 2002 [2] ”Continued shading of curved surfaces”, Gouraud H., June 1971

[3] ”OpenGL programming guide”, OpenGL Architecture Review Board, 2005 [4] ”GPU Gems 2”, Matt Pharr, 2005

[5] URL: www.collada.org

References

Related documents

upp och försäkra sig om att arbetsför- delningen anpassas och inte orsakar ohälsa och olycksfall. Vem gör detta? I arbetsmiljölagen är det självklart att arbetsgivaren har det

Skillnaderna mellan de olika grupperna är enligt undersökningen försumbara och Thorén (2012:29) kommer fram till att andraspråkseleverna använder färre partikelverb men inte till

Effektiviteten av arbetet spelar en viktig roll för en butiks verksamhet (Pinedo, 2008), butik ökar i omsättning och personal är medvetna om sina arbetsuppgifter, arbetsschema

Jag vet ofta hur jag skall lösa ett problem, men så måste jag gå tillbaka till mina anteckningar och hitta lämplig formel eller så vet jag nästa steg, men jag vet inte hur jag

När samhället och ekonomin förändras drabbas vissa människor av om- ställningsproblem. Men vem, och hur? Och vem får vara med och påverka hur omställningen ska se ut? Denna

För att få ett bredare och mer pålitligt mätresultat hade det förmodligen behövts utföras fler test på de olika webbläsarna för att få en klarare bild kring hur väl React

”Missväxten i Frankrike är en orsak till revolutionen, i oktober 1789 marscherade Paris kvinnor till kungen och drottningens slott Versailles och krävde att derasS.

Västtornets första våning, östra kammaren, mot norr.Tv ingången till västra kammaren, t h utgången till läktaren.. Tv ingången till västra tomkammaren, t h utgången