Mälardalens högskola Västerås, Maj 2006 Institutionen för Datavetenskap och Elektronik, IDE Student: Alexander Andersson E-post:



Relevanta dokument
Grafiska pipelinens funktion

Shaders. Renderingssystem. Renderingssystem. Renderingssystem. Hårdvara för 3D-rendering. Hårdvara för 3D-rendering

Shaders. Gustav Taxén

TDIU01 - Programmering i C++, grundkurs

F5: Högnivåprogrammering

F5: Högnivåprogrammering

Programmering i C++ En manual för kursen Datavetenskaplig introduktionskurs 5p

Kompilering och exekvering. Föreläsning 1 Objektorienterad programmering DD1332. En kompilerbar och körbar java-kod. Kompilering och exekvering

Programmering B med Visual C

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Felsökning. Datatyper. Referenstyper. Metoder / funktioner

Bankkonto - övning. Övning 2 Skriv en metod, geträntan, som returnerar räntan.

Programmering med Java. Grunderna. Programspråket Java. Programmering med Java. Källkodsexempel. Java API-exempel In- och utmatning.

Introduktion. Klasser. TDP004 Objektorienterad Programmering Fö 2 Objektorientering grunder

Classes och Interfaces, Objects och References, Initialization

(Man brukar säga att) Java är... Denna föreläsning. Kompilering av Java. Historik: Java. enkelt. baserat på C/C++ Allmänt om Java

Agenda. Arrayer deklaration, åtkomst Makron Flerdimensionella arrayer Initiering Strängar Funktioner och arrayer. Övningar nu och då

Objektorienterad Programmering (TDDC77)

Innehåll. Introduktion till objektorientering. OOP (objektorienterad programmering) Objekt, instanser, klasser

I Skapa Hej.java och skriv programmet. I Kompilera med javac Hej.java. I Rätta fel och repetera tills du lyckas kompilera ditt program

Grafiska pipelinen. Edvin Fischer

Inledning. Vad är ett datorprogram, egentligen? Olika språk. Problemlösning och algoritmer. 1DV433 Strukturerad programmering med C Mats Loock

EDAA20 Programmering och databaser. Mål komprimerat se kursplanen för detaljer. Checklista. Föreläsning 1-2 Innehåll. Programmering.

Programmera i C Varför programmera i C när det finns språk som Simula och Pascal??

Programmeringsteknik med C och Matlab

Introduktion till arv

LUNDS UNIVERSITET. Parallell exekvering av Float32 och INT32 operationer

Föreläsning 2 Objektorienterad programmering DD1332. Typomvandling

1 Klasser och objektorientering Vad är objektorientering?

Lite om felhantering och Exceptions Mer om variabler och parametrar Fält (eng array) och klassen ArrayList.

Vem är vem på kursen. Objektorienterad programvaruutveckling GU (DIT011) Kursbok Cay Horstmann: Big Java 3rd edition.

Idag. Javas datatyper, arrayer, referenssemantik. Arv, polymorfi, typregler, typkonvertering. Tänker inte säga nåt om det som är likadant som i C.

Procedurell renderingsmotor i Javascript och HTML5

Grundläggande programmering med C# 7,5 högskolepoäng

Introduktion till programmering D0009E. Föreläsning 1: Programmets väg

Teknik för avancerade datorspel!

Föreläsning 2 Programmeringsteknik och C DD1316. Mikael Djurfeldt

Innehåll. Användardefinierade typer. Användardefinierade typer Kategorier. Konstruktorer. Konstruktorer Två sätt att skriva initiering av medlemmar

Procedurell grottgenerator och eld i GLSL. Marcus Widegren

Classes och Interfaces, Objects och References Objekt-orienterad programmering och design (DIT952) Niklas Broberg, 2016

PROV. 12 Egenskaper (provavsnitt)

Lab5 för prgmedcl04 Grafik

Klassdeklaration. Metoddeklaration. Parameteröverföring

Statistik över heltal

Föreläsning 5: Introduktion av pekare

Att använda pekare i. C-kod

Övningar Dag 2 En första klass

Föreläsning 5-6 Innehåll. Exempel på program med objekt. Exempel: kvadratobjekt. Objekt. Skapa och använda objekt Skriva egna klasser

Arv: Fordonsexempel. Arv. Arv: fordonsexempel (forts) Arv: Ett exempel. En klassdefinition class A extends B {... }

Parameteröverföring. Exempel. Exempel. Metodkropp

Föreläsning 5-6 Innehåll

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Exempelduggan. Luffarschack. Koda spel

C++ - En introduktion

Minnestilldelning (allokering) och frigörande (avallokering) av minne

Teknik för avancerade datorspel!

Datatyper och kontrollstrukturer. Skansholm: Kapitel 2) De åtta primitiva typerna. Typ Innehåll Defaultvärde Storlek

Programmering, grundkurs, 8.0 hp HI1024, HI1900 etc., Tentamen TEN1. Måndagen den 10 januari 2011,

Universe Engine Rapport

TDIU01 - Programmering i C++, grundkurs

Tentamen i. Programmering i språket C

EDAA20 Programmering och databaser. Mål komprimerat se kursplanen för detaljer. Om att lära sig programmera. Föreläsning 1-2 Innehåll.

Tentamen. Datalogi I, grundkurs med Java 10p, 2D4112, Lördagen den 30 november 2002 kl , salar E33, E34

Pekare. Pekare. Varför använder vi pekare? Vad är en pekare? Pekare. Deklaration/initiering av pekare

Dynamiskt minne. Vad är dynamiskt minne Motivering Hur gör man i C Övningar

Programmering A. Johan Eliasson

Procedurella Grottor TNM084. Sammanfattning. Alexander Steen

Arrayer (vektorer) Murach s: kap Elektronikcentrum i Svängsta AB

Introduktion till Datalogi DD1339. Föreläsning 2 22 sept 2014

Innehållsförteckning

Användarhandledning Version 1.2

Uppgiften är att beskriva en kvadrat i ett Java program. En första version av programmet skulle kunna se ut så här:

Det finns många flaggor till g++,

Laboration 3 HI1024, Programmering, grundkurs, 8.0 hp

Objektorienterad Programmering (TDDC77)

Digitalitet. Kontinuerlig. Direkt proportionerlig mot källan. Ex. sprittermometer. Elektrisk signal som representerar ljud.

Programmering i C++ EDA623 Objektorienterad programutveckling. EDA623 (Föreläsning 5) HT / 33

Kort om klasser och objekt En introduktion till GUI-programmering i Java

TUTORIAL: KLASSER & OBJEKT

Programmering = modellering

Enkla datatyper minne

LÖSNINGSFÖRSLAG Programmeringsteknik För Ing. - Java, 5p

Johan Karlsson Datavetenskap för teknisk kemi, 10p, moment 1 Datavetenskap Umeå Universitet. Tentamen

MATLAB. Python. Det finns flera andra program som liknar MATLAB. Sage, Octave, Maple och...

Beräkningsvetenskap föreläsning 2

Typkonvertering. Java versus C

Imperativ programmering. Föreläsning 4

Föreläsning 2. Operativsystem och programmering

732G Linköpings universitet 732G11. Johan Jernlås. Översikt. Repetition. Muddy. Funktioner / metoder. Punktnotation. Evalueringsordning

Laboration - Shaders

Tentamen TNM061, 3D-grafik och animering för MT2. Tisdag 3/ kl 8-12 TP51, TP52, TP54, TP56, TP41, TP43. Inga hjälpmedel

Värmedistribution i plåt

Föreläsningsanteckningar, Introduktion till datavetenskap HT S4 Datastrukturer. Tobias Wrigstad

Objektorienterad programmering D2

Föreläsning 10. Pekare (Pointers)

Tentamen ID1004 Objektorienterad programmering October 29, 2013

Grunderna i C++ T A. Skapad av Matz Johansson BergströmLIMY

Alla filer som bearbetar PHP script ska avslutas med ändelsen.php, exempelvis ska en indexsida till en hemsida heta index.php

Föreläsning 11. Arrayer. Arrayer. Arrayer. Lagrar flera värden av samma typ Kan vara primitiva typer eller objekt. Kan ha en array av t.

F4. programmeringsteknik och Matlab

ITK:P1 Föreläsning 1. Programmering. Programmeringsspråket Java. Stark typning Explicit typning Strukturerat Hög säkerhet

Inledande programmering med C# (1DV402) Tärningarna ska kastas

Transkript:

Mälardalens högskola Västerås, Maj 2006 Institutionen för Datavetenskap och Elektronik, IDE Student: Alexander Andersson E-post: aan02016@student.mdh.se, aan02016@hotmail.com Telefon: 0707-418478 Handledare: Martin Skogevall Examinator: Thomas Larsson Analys och implementation av pixel- och vertex-shaders

Sammanfattning I denna rapport behandlas ett av datorgrafikens relativt nya koncept, nämligen shaders. Med shaders är det helt upp till programmeraren att bestämma hur bilder ska realtidsrenderas, tidigare har detta varit förutbestämt i grafikkortets hårdvara och har därmed inskränkt på kreativiteten. Men med shaders har denna funktionalitet ersatts men en programmerbar enhet, en GPU som är grafikkortets egna processor. Förutom att denna GPU kan programmeras har den en trevlig bieffekt, nämligen att den avlastar CPUn som kanske har andra viktiga uppgifter att utföra. Föreställ en flod som förgrenar sig, ena bifloden leder till CPUn och den andra till GPUn. Examensarbetet har haft till uppgift att introducera ämnet ifråga eftersom datorgrafiklaborationerna vid Mälardalens Högskola ska uppdateras, därför har rapporten en mer undervisande karaktär än en rent traditionell teknisk. Största delen handlar om hur shaders programmeras, även källkod för olika avancerade grafikalgoritmer visas. Sidan 2 av 54

Innehållsförteckning ANALYS OCH IMPLEMENTATION AV PIXEL- OCH VERTEX-SHADERS... 1 SAMMANFATTNING... 2 INNEHÅLLSFÖRTECKNING... 3 1 INTRODUKTION... 5 1.2 BAKGRUND TILL EXAMENSARBETE... 5 1.3 SYFTE MED EXAMENSARBETE... 5 1.4 DEFINITIONER... 6 2 BAKGRUND... 6 2.1 TRADITIONELL MODELL... 6 2.2 NY MODELL... 7 2.3 ÖVERSATT TILL PIPELINE-MODELLEN... 7 2.4 UTVECKLING... 9 3 GPU & PROGRAMMERBAR PIPELINE... 10 3.1 GRUNDLÄGGANDE ARKITEKTUR... 10 3.2 LITE TEKNISK DATA... 12 4 ATT KOMMA IGÅNG MED SHADERS TUTORIALSTYLE... 12 4.1 UNDERSÖKNING AV HÅRDVARA... 12 4.1.1 Installera glew... 12 4.1.2 Test av hårdvara... 12 4.2 INITIERA/AVSLUTA SHADERS... 13 4.2.1 Skapa och kompilera en shader... 14 4.2.2 Skapa och länka ett program... 14 4.2.3 Debugning... 15 4.2.4 Avsluta... 16 4.3 DATAKOMMUNIKATION... 16 4.3.1 Gränssnitt mellan OpenGL och shaders... 16 4.3.2 Gränssnitt mellan OpenGL och vertexshader... 17 4.3.3 Gränssnitt mellan vertexshader och pixelshader... 18 4.3.4 Översiktsbild över datakommunikationsvariabler... 19 4.4 GRUNDLÄGGANDE SYNTAX... 19 4.4.1 Typer och variabler... 20 4.4.1.1 Initiera variabler... 20 4.4.1.2 Initiera variabler av sammansatta typer... 20 4.4.1.3 Typkonvertering... 21 4.4.1.4 Komponentvisa operationer... 21 4.4.1.5 Komponentvisa uttryck... 22 4.4.2 Satser... 22 4.5 INBYGGD FUNKTIONALITET... 23 4.5.1 Transformationer... 23 4.5.2 Texturer... 24 4.5.3 Färg... 25 5 EXEMPEL PÅ MÖJLIGA ALGORITMER... 26 5.1 PHONGSHADING... 26 5.1.1 Lokal belysningsmodell utan fysikaliska begrepp... 26 5.1.1.1 I ambient... 27 5.1.1.2 I diffuse... 27 5.1.1.3 I specular... 27 5.1.2 Implementation... 28 Sidan 3 av 54

5.1.2.1 Vertexshader... 28 5.1.2.2 Pixelshader... 28 5.1.3 Resultat... 29 5.2 ANIMERAD FLAGGA... 29 5.2.1 Implementation... 29 5.2.1.1 Vertexshader... 29 5.2.1.2 Pixelshader... 30 5.2.2 Resultat... 30 5.3 HEIDRICH OCH SEIDEL ANISOTROPIC SHADING... 31 5.3.1 Implementation... 32 5.3.1.1 Kromad tekanna med fejkad tangentriktning... 33 5.3.1.1.1 Resultat... 33 5.3.1.2 Julgranskula av glänsande tyg... 33 5.3.1.2.1 Resultat... 34 5.3.1.3 Realistisk tekanna... 35 5.3.1.3.1 Resultat... 35 5.3.2 Vertexshader... 36 5.3.3 Pixelshader... 36 5.4 MULTITEXTURING... 37 5.4.1 Resultat... 38 6 RESULTAT... 39 7 FRAMTIDEN... 39 8 SLUTSATS... 40 REFERENSER... 41 BILAGOR... 41 1 EN ENKEL WRAPPER FÖR SHADERS... 41 2 ENKEL WRAPPER FÖR TEXTURER... 47 3 ETT LABORATIONSFÖRSLAG... 50 A. Skapa en solid teapot i glut... 51 B. Initiera och länka shaders... 51 C. Transformation och färgsättning... 52 D. Diffus hörnpunktsbelysning... 52 E. Laborationsskelett... 52 Sidan 4 av 54

1 Introduktion I detta kapitel beskrivs bakgrund och syfte med examensarbetet. En del nya termer introduceras som kommer att underlätta läsningen för resterande del av rapporten. 1.2 Bakgrund till examensarbete Datorgrafik är ett snabbt växande område där realtidsrendering blir allt vanligare. Realtidsrendering innebär att rendering och beräkningar genomförs under körning, vilket leder till att användaren interaktivt kan kommunicera med datorn och utifrån det renderas en ny bild. Motsvarigheten till realtidsrendering är exempelvis animerade filmer, där bilderna som visas i sekvens är renderade i förväg. Denna modell tillåter dock inte användaren att kommunicera och påverka resultatet vilket gör det omöjligt att använda i ett interaktivt spel. Att rendera i realtid är mycket kostsamt och krävande för en dator som beroende på implementation kan ha andra uppgifter, exempelvis kollisionsdetektering. Denna modell att låta datorns CPU sköta alltifrån rendering till funktioner på applikationsnivå är oacceptabelt med dagens realism. På senare år har renderingen överlåtits till grafikkortets hårdvara. Till en början via förutbestämd funktionalitet men på senare år (2002 och framåt) har detta utökats till att bli programmerbart via en Graphical Processing Unit (GPU) eller grafikprocessor på svenska. GPUn är grafikkortets egna processor som utnyttjas för just rendering, därmed kan CPUn avlastas och realtidsrendring blir möjlig i större utsträckning. Även om GPUn kan avlasta CPUn är den största fördelen att renderingen inte längre är bunden till en förutbestämd funktionalitet. Denna modell har orsakat likriktning bland exempelvis spel och andra grafiska program som ställer höga krav på just datorgrafik. I den programmerbara modellen är det snarare programmerarens fantasi som är begräsningen. I många fall kanske den egna fantasin är mindre än vad grafikkortstillverkare kan åstadkomma i hårdvara, kanske framförallt för nyinvigda grafikprogrammerare, men fördelarna med en programmerbar GPU är enorma och den enda vägen att idag lära sig grafikprogrammering. Hur denna utveckling från den förutbestämda modellen till den nuvarande programmerbara modellen har fortskridit och vad den har lett till kommer att diskuteras i detta examensarbete. Huvuddelen av rapporten handlar dock om hur den programmerbara modellen kan utnyttjas. Dels hur ett mjukvaruinterface (OpenGL) kan konfigurera GPUn och dels hur själva GPUn programmeras (vertex- och pixel-shaders). Exempel på möjliga realtidsrenderingar kommer att visas och i detalj förklaras. För att kunna ta del av examensarbetet krävs erfarenhet av 3Dgrafik och dess terminologi. Även grundläggande kunskaper om OpenGL krävs, hur det används för att rita enkla primitiver och hur transformationer genomförs. 1.3 Syfte med examensarbete På Mälardalens högskola har studenter möjligheter att läsa tre kurser inom datorgrafik; Tillämpad Datorgrafik A, Datorgrafik gk C samt Datorgrafik fk D. Tillämpad datorgrafik introducerar ämnet i fråga och ger en ökad förståelse för raster- och vektorgrafik. Grundkursen har till stor del handlat om att designat en 3dmotor nedifrån och upp medan fortsättningskursen handlat om kurvade ytor. Båda dessa två kurser har varit i bruk sedan 1996 och har inte blivit uppgraderade sedan dess. Med önskemål efter förfrågan har detta examensarbete tagit formen att bli en introduktion till pixel- och vertex-shaders som senare ska ligga till grund för framtagandet av nytt laborationsmaterial. Med detta nyligen Sidan 5 av 54

introducerade ämne kommer datorgrafikkurserna få en mer uppifrån- ned-struktur som passar bättre för datorgrafikens snabba framfart. 1.4 Definitioner Tabell 1.1 Vanliga begrepp inom datorgrafik. Textbegrepp Shader Shaders Framebuffert Vertex Pixel Fragment Fragmentshader Pixelshader OpenGL Direct3D DirectX GLSL GPU Förklaring En vertexshader eller en pixelshader Det gemensamma namnet för en vertexshader och en pixelshader Minne som representerar vad som visas på skärmen En hörnpunkt Minsta beståndsdel i datorgrafik, har färg och position som attribut En kandidat till en pixel, blir en pixel först efter en del tester genomförts. Kan dessutom ha fler attribut Ett mer korrekt uttryck för en pixelshader Det slarviga namnet för en fragmentshader, är dock det vardagliga och ett accepterat uttryck Tillhandahåller datorgrafikrelaterade funktionalitet via hårdvara, har till uppgift att utnyttja ett grafikkorts funktionalitet Microsofts version av OpenGL Samling av microsoftkomponenter. Här ingår Direct3D men även andra komponenter som stödjer ljud, nätverk osv Programmeringsspråk för shaders Graphical Processing Unit, grafikkortets egna processor Tabell 1.2 Matematiska symboler som används i rapporten. Matematiska symboler an b aob abb Förklaring Komponentvis multiplikation Linjär multiplikation, även kallad skalärprodukt Kryssprodukt 2 Bakgrund I inledningen av rapporten nämndes det att den förutbestämda funktionaliteten har ersatts med hårdvaruenheter som är programmerbara. För att ge några konkreta exempel på vad det innebär utvecklas det vidare i detta kapitel. 2.1 Traditionell modell I OpenGL skickas varje hörnpunkt genom en bestämd geometri-pipeline. Därefter interpoleras det mellan hörnpunkterna i rastersteget för beräkning av exempelvis position och färg. Dessa två steg är bestämda i förväg och förhållandet mellan hörnpunkterna som skickas in och värdet som skrivs ut i framebufferten beror på i vilket tillstånd OpenGL befinner sig i. I denna modell kan inte användaren per pixel påverka det utgående resultatet i framebufferten. Begränsningen beror på vad OpenGL har för möjliga tillstånd. Se figur 2.1 nedan. Sidan 6 av 54

Figur 2.1 Resultatet står i förhållande till OpenGLs tillstånd. I ovanstående exempel renderas två olika bilder, men det noterbara är att efter glbegin() exekverat har vi överlåtit renderingen till OpenGL. Pixelns färg inom triangeln står i direkt relation till OpenGL:s tillstånd. Denna modell med den fixa pipelinen begränsar användarens kreativitet. 2.2 Ny modell En mer önskvärd ansats vore att inte överlåta rendering till OpenGL efter glbegin(). Detta är vad pixel- och vertexshaders handlar om. Med pixelshaders kan användaren explicit ange hur varje pixel ska genereras. Även alla hörnpunkters attribut, som påverkas av de olika transformationsmatriserna, kan manipuleras i en sk vertexshader. All denna extraktion från OpenGL medför att användaren själv måste ange transformationer, belysningsberäkningar och texturmappning. Första tanken kanske insisterar på att det är ett straff för att vi ersatt den traditionella pipelinen, någonting onödigt, men det är precis detta som är meningen. Programmeraren får mer kontroll. Detta kan tyckas vara krävande för användaren, men språket som används är specialdesignat för ändamålet och källkoden blir oftast väldigt kort och enkel. GPU:ns programspråk, från användarens synvinkel, har liksom CPU:ns utvecklas genom åren via kompilatorer. Allt från assembler till dagens högnivåspråk som har en C/C++ liknande syntax, mer om detta i kapitel 4. 2.3 Översatt till pipeline-modellen De som är vana med grafikprogrammering och kanske framförallt OpenGL har förmodligen också sett hur data skickas genom en sk pipeline. För att tydliggöra resonemanget ovan kompletteras modellerna med en förenklad traditionell bild av pipelinestegen. Vanligtvis brukar en pipeline beskrivas med boxar och pilar däremellan. Den modellen duger oftast för att få en aning om vad processen innebär, det förklarar dock inte i detalj vad som faktiskt sker. Vi nöjer oss med att nämna att streams initierar kernels. Kernels bearbetar in-streams som en funktion och producerar out-streams. Out-streams konsumeras direkt av efterföljande kernel som in-streams. Mappning mellan in-streams och out-streams i en kernel är förutbestämt vilket gör det utmärkt för att implementera i hårdvara. Detta resonemang kanske inte revolutionerade synen på dessa boxar och pilar men förklarar en del termer som kan dyka upp i sammanhanget. För en betydligt mer ingående beskrivning rekommenderas [1]. Sidan 7 av 54

Figur 2.2 De två pipeline-modellerna. Noterbart är att den fixa pipelinen kan användas även om hårdvaran stödjer den programmerbara modellen. De olika modellerna kan dock inte användas samtidigt [2]. Som redan nämnts innebär den programmerbara modellen vissa skyldigheter för programmeraren. Varje hörnpunkt som skickas genom den fixa pipelinen genomgår olika transformationer, ljusberäkningar osv. Med en vertexshader exekverande på GPUn måste programmeraren själv tillhandahålla kod för den funktionaliteten. Nedan listas de vanligaste exemplen på vad som måste ersättas i en vertexshader. Transformation av hörnpunkter Transformation av normaler Normalisera normaler Ljusberäkningar Texturmanipulationer Likaväl som kod måste ersättas, finns funktionalitet som inte kan ersättas eftersom det saknas vetskap om andra hörnpunkter. Exempel på det är elimination av baksidor och klippning, som bilden i figur 2.2 visar är detta en del av den förutbestämda funktionaliteten. En fragmentshader opererar på varje fragment och dess tillhörande data. Precis som i vertexshadern har programmeraren vissa skyldigheter. Några vanliga exempel nedan. Använda data från interpolerade värden Läsa från texturer Skriva till framebufferten Fog Funktionalitet som inte fragmentshadern ersätter från den fixa pipelinen är exempelvis blending, logiska operatorer, ljusberäkningar. Sidan 8 av 54

Informationen är hämtad från [3] och är inte komplett. För en fullständig list över vad som måste ersättas och inte kan ersättas i respektive shader rekommenderas just [3]. 2.4 Utveckling Möjligheten att påverka hörnpunkters attribut (position, färg, normal och texturkoordinater) introducerades först av DirectX 8 år 2001 i form av vertexshader version 1.0. Det kom även en version 1.1 under samma DirectX med vissa uppgraderingar. Möjligheten att påverka pixlar i en pixelshader kom i DirectX 8.1 år 2001 och där utvecklades under en kort tid fem olika versioner (1.0-1.4). Begränsningarna var många och programmeringsspråket för dessa shaders var på instruktionsnivå vilket försvårade för användare [2]. Året efter kom DirectX 9.0 vilket stödde shadermodel 2.0 [2], hårdvarufunktionalitet brukar beskrivas relativt Direct3Ds shadermodel. Med shadermodel 2.0 hade funktionaliteten standardiserats, nu fanns det inte längre fem olika pixelshaderversioner och två olika vertexshadersversioner utan ett gemensamt samlingsnamn. Med shadermodel 2.0 togs också det väsentligt stora steget mot användarvänlighet eftersom programmeringsspråket var C- liknande. Självklart fanns det också mer teknisk funktionalitet i shadermodel 2.0. Exempelvis var det möjligt att skriva längre program (fler instruktioner per renderingspass) vilket ledde till att mer komplicerade renderingsalgoritmer var möjliga. År 2004 kom shadermodel 3.0 (senaste versionen) med DirectX 9.0c [4]. Samma år släpptes OpenGL 2.0 och där fanns shaders med i standarden. Anledningen till att OpenGL inte har diskuterats ovan är att de inte har samma tillvägagångssätt för att tillhandahålla uppgraderingar. Microsoft som tillhandahåller DirectX släpper sina uppgraderingar på ett matematiskt diskret sätt, medan OpenGL har en kontinuitet i sina uppgraderingar via sk extensions. Meningen med extensions är att grafikkortstillverkare kan utöka OpenGL med ny funktionalitet. Om funktionalitet gillas av OpenGL brukar den på ett standardiserat sätt finnas med i nästa version. Kom ihåg att shadermodel egentligen är ett mått på aktuell hårdvara. En kort sammanfattning om förhållandet mellan DirectX och shadermodel visas i tabell 2.1, referens [4]. Som parentes bör det nämnas att enligt [4] kom DirectX 9.0 år 2003, men det korrekta skall vara 2002. Kanske beror detta på en kinesisk variant, övrig information är dock helt korrekt. Tabell 2.1 Historisk återblick. DirectX version Exempel på grafikkort År Shadermodel 1.x 8 GeForce 3 2001 Shadermodel 2.0 9.0 GeForce FX 2002 Shadermodel 3.0 9.0c GeForce 6 2004 För att återknyta till shadermodel 3.0 och dess utveckling visas i tabell 2.2 de viktigaste skillnaderna mellan shadermodel 2.0 och 3.0 [5]. Noterbart är att det återigen är hårdvara som diskuteras, tabellen nedan kan ses som miniprestanda för respektive modell. Om ett grafikkort stödjer flera hundratusen instruktioner klassas det ändå som shadermodel 3.0. Tabell 2.2 Skillnad mellan vertex-shadermodel 2.0 och 3.0. Vertexshader 2.0 3.0 Beskrivning Dynamiska instruktioner 256 65535 Maximala antalet instruktioner som kan exekveras i ett pass Hoppinstruktioner Nej Ja Hoppa över kodavsnitt Sidan 9 av 54

Access till textur Nej Ja Avläsning av texturer Instansiering Nej Ja Rita flera objekt med ett anrop Tabell 2.3 Skillnad mellan pixel-shadermodel 2.0 och 3.0. Pixelshader 2.0 3.0 Beskrivning Dynamiska instruktioner 96 65535+ Maximala antalet instruktioner som kan exekveras i ett pass Hoppinstruktioner Nej Ja Hoppa över kodavsnitt Back-face reg Nej Ja Rita framsidor och baksidor i ett pass Även om shadermodel 3.0 innehåller mer funktionalitet betyder det inte direkt bättre grafik (självfallet bortsett från vissa algoritmer som i sig kräver ny funktionalitet). Detta är en vanlig missuppfattning, kanske glatt påhejad av datorspel- och grafikkorts- tillverkare. Skillnaden versionerna emellan är snarare att kod kan optimeras av användaren med exempelvis hoppinstruktioner. Även mer avancerande shaders kan skrivas eftersom shadermodel 3.0 klarar av fler dynamiska instruktioner än shadermodel 2.0. Detta var en parentes i sammanhanget men kan vara viktigt att känna till denna missuppfattning. 3 GPU & programmerbar pipeline Detta kapitel ger ökad förståelse för aktuell hårdvara. All teknisk specifikation som redovisas refererar till Nvidias GPU GeForce 6800 eftersom den hårdvaran används i examensarbetet. Valet av GPU är dock mindre viktigt. Det som avses är att ge en fingervisning om en modern GPUs prestanda samt hur en grafisk pipeline fungerar i hårdvara. 3.1 Grundläggande arkitektur För att få en uppfattning om GPUns placering och hur GPUn kommunicerar med övriga delar av datorsystemet visas nedan en bild. Data som redovisas är relativt men ger en bild om ett typiskt modernt datasystem. Referens [6]. Figur 3.1 Översiktsbild över ett modernt datorsystem. Ovanstående bild ger en överblick av systemet men det intressanta är hur den grafiska pipelinen erhåller och bearbetar indata till utdata. Nedan visas en förenklad bild av förloppet Sidan 10 av 54

samt en förklaring av varje pipelinesteg. Detta ger en bättre bild än den som redovisades i avsnitt 2.3. Figur 3.2 Bearbetning från hörnpunkter till pixlar i framebufferten. CPU: Förser pipelinen med indata. Vertexshader: Programmerbar enhet som arbetar med hörnpunkter. Transformationer och ljusberäkning genomförs. Nytt för GeForce 6-serien är att vertexshadern har access till texturer, vilket gör det möjligt för exempelvis displacement-mapping. Med x6 menas att sex stycken vertexenheter används (GeForce 6800Ultra). För jämförelse kan det nämnas att Microsofts Xbox har två stycken. Bakgrunden till att fler vertexenheter kan användas för parallellism är att hörnpunkters attribut är oberoende av varandra. Cull/Clip/Setup: Hörnpunkter grupperas till primitiver. Objekt som inte är synbara kastas och klippning utförs på objekt som skär vypyramiden. I setupsteget beräknas hörn och planekvationer som skickas vidare till rasteringssteget. Rasterization: Uträkningen av vilka pixlar som primitiven täcker. För prestanda jämförs även Z-värdet för att kunna kasta pixlar på ett tidigt stadium. Fragmentshader: Programmerbar enhet som arbetar med fragment. Syftet är att beräkna färg för varje fragment. I GeForce 6-serien arbetar fyra stycken fragmentenheter på fyra pixlar i taget (sk quads). Vilket leder till att sexton stycken fragment blir indata till nästa steg. Z-Comparison/Blending: Jämför Z-värdet och utför blending. Utgående data är en pixel. Notera skillnaden mellan ett fragment och en pixel. Ett fragment blir en pixel först när fragmentet genomgått en rad tester och når framebufferten. Som nämnts ovan ger detta en förenklad bild. Informationen är hämtad från [6]. För en mer detaljerad beskrivning, se just [6]. Sidan 11 av 54

3.2 Lite teknisk data Klockhasighet: 425MHz Minneshastighet: 550MHz Vertexshader: 600 000 hörnpunkter/ms Fragmentshader: 12 800 000 pixlar/ms Flyttalsprecision: 32-bitar Referens [6]. 4 Att komma igång med shaders Tutorialstyle Avsikten med detta kapitel är att visa vad som behövs för att själv komma igång med att skriva shaders. Det kan ses som en lathund som täcker in allt från hårdvara till kod på både applikationsnivå och shadernivå. Det som kommer diskuteras är högnivåprogrammering via OpenGL 2.0 eftersom det har använts i examensarbetet. För högnivåprogrammering krävs antingen stöd för shadermodel 2.0 eller shadermodell 3.0. Om inte hårdvarustöd finns och extensions önskas användas visas bara kort hur detta är möjligt. 4.1 Undersökning av hårdvara Det som krävs utan att behöva använda extensions är alltså OpenGL 2.0. För att kontrollera detta kan GLEW(The OpenGL Extension Wrangler Library) laddas ner från http://glew.sourceforge.net/ och därefter installeras. GLEW är framförallt ett hjälpmedel för extensions och senare versioner av OpenGL för att kunna använda ny funktionalitet utan att behöva uppdatera lib- och h- filer. Dessutom ingår en del funktioner för att utreda aktuell hårdvara. 4.1.1 Installera glew Innan hårdvaran kan undersökas föreslås att GLEW installeras. Det görs genom att kopiera rätt filer till rätt kataloger. Observera att det kanske inte är nödvändigt att använda GLEW beroende på om lib- och h- filerna är uppdaterade. o bin/glew32.dll till [systemroot]/system32 o lib/glew32.lib till [Visual C++]/lib o include/gl/glew.h [Visual C++]/include/GL o include/gl/wglew.h [Visual C++]/include/GL Nu är glew installerat. 4.1.2 Test av hårdvara Skapa ett nytt projekt i Visual C++ eller liknande. Det antas förtrogenhet med hur projekt skapas och hur glut eller gl används. Inkludera filerna och lägg till koden enligt nedan. Det är Sidan 12 av 54

viktigt att glew.h inkluderas innan glut.h om just glut används, annars genererar GLEW ett preprocessorfel. #include <gl\glew.h> #include "glut.h" // alternativt #include <gl\glut.h> #pragma comment(lib, "glew32.lib" ) // Inkludera glew32.lib... void main()... // Initiera glut glewinit(); if (glewissupported("gl_version_2_0")) cout<< Stöd för OpenGL 2.0 <<endl; Hårdvara kan också undersökas genom anrop till glgetstring(gl_version) som returnerar en sträng. Prova dessutom GL_RENDERER, GL_VENDOR och GL_EXTENSIONS för ytterligare information. Stödjer hårdvaran OpenGL 2.0 betyder det att shaders kan programmeras via högnivåspråk och att initeringen kan genomföras på ett standardiserat sätt, om inte måste antingen drivrutinerna till grafikkortet uppdateras eller extensions användas. Prova först att uppdatera drivrutinerna. Gå till grafikkortstillverkarens hemsida och ladda ner drivrutiner. Om osäkerhet uppstår om tillverkare kan det undersökas genom anrop till glgetstring(gl_vendor). Troligtvis ATI eller NVIDIA eftersom de är de två vanligaste. Installera drivrutinerna och prova återigen om koden som diskuterades ovan ger ett annat utfall. Lyckas uppdateringen betyder det att grafikkortet stödjer OpenGL 2.0 och shaders kan användas. Om utfallet fortfarande är detsamma måste stöd för extensions undersökas. Det krävs att du har stöd för två extensions, nämligen GL_ARB_vertex_shader och GL_ARB_fragment_shader. Prova följande kod. #include <gl\glew.h> #include "glut.h" // alternativt #include <gl\glut.h>... #pragma comment(lib, "glew32.lib" ) // Inkludera glew32.lib... void main()... // Initiera glut glewinit(); if (GLEW_ARB_vertex_shader && GLEW_ARB_fragment_shader ) cout<< Stöd för shaders <<endl; Observera att om grafikkortet stödjer extensions men inte OpenGL 2.0 finns det inte någon garanti för hur avancerade shaders som är möjliga. 4.2 Initiera/avsluta shaders I detta avsnitt beskrivs hur shaders initieras och avslutas på applikationsnivå. Programmeraren måste själv tillhanda kod för hur initieringen ska gå till väga. OpenGL har alltså ingen klar funktion för detta. För flexibilitet erbjuds istället olika funktioner som måste användas på rätt sätt. I detta avsnitt visas just hur dessa funktioner måste anropas. I bilaga 1 finns klara klasser med tillhörande kod för hur shaders initieras och avslutas i Sidan 13 av 54

OpenGL 2.0. Koden erbjuder i objektorienterad anda enkla funktionsanrop utåt, där det enda användaren behöver göra för att initiera shaders är att anropa en funktion där källkodsfiler för shaders tas som inargument. Om grafikkortet bara stödjer shaders via extensions är initieringen snarlik den i OpenGL 2.0. Begränsningen med extensions har självklart ingenting att göra med initieringen, enbart den kod som ska exekvera på GPUn. I koden finns kommentarer för extensions om vilka funktioner som måste ersättas och vad de ska ersättas med. Det som diskuteras i detta avsnitt är delvis hur ett shaderprogram länkas ihop men framförallt förklaras ingående de olika funktionerna som används i bilaga 1. Det rekommenderas att granska koden i bilaga 1 för att se hur de olika funktionerna kan användas trots att de finns beskrivna i detta avsnitt. Att granska kodexempel är ett utmärkt sätt att lära sig. Noterbart är ett det finns fler funktioner att tillgå utöver de som använts i implementationen, se [7]. Innan funktionerna beskrivs kan det nämnas att ett shaderprogram består av shaders, vanligtvis en vertexshader och en pixelshader (se figur 4.1). Shaders skapas och kompileras var för sig och länkas därefter ihop av ett shaderprogram. Likheterna är många med vilket programspråk som helst där olika källkodsfiler länkas ihop till ett program. All den information som presenteras i subrubrikerna nedan är hämtad från [7]. 4.2.1 Skapa och kompilera en shader Det första som görs är att bestämma vilken typ av shader som ska skapas. Det finns två alternativ, antingen en vertexshader eller en pixelshader. Funktionen nedan skapar en shader av typ type och returnerar en handtag till shadern. -type: GL_VERTEX_SHADER GL_FRAGMENT_SHADER -return: Ett handtag till shadern uint glcreateshader(enum type); Nästa steg är att associera källkod till det nyss skapta handtaget. Det görs med följande funktion. - shader: handtaget - count: Antalet strängar källkoden består av. - string: Array av strängar - length: Array av strängarnas längd, NULL om nollterminerade strängar. void glshadersource(uint shader,sizei count,const char **string,const int *length); I det sista steget kompileras koden. - shader: handtaget void glcompileshader(uint shader); 4.2.2 Skapa och länka ett program Innan shaders som skapades i förra avsnittet kan användas måste dem bindas till ett program. Ett program skapas med följande funktion. -return: Ett handtag till programmet uint glcreateprogram(void); När programmet skapas är det tomt, d.v.s. ingen shader finns inlagd. En shader läggs till med följande funktion. Sidan 14 av 54

-program: programmets handtag -shader: shaderns handtag void AttachShader(uint program,uint shader); Det som bör nämnas är att en shader varken behöver ha någon källkod eller vara kompilerad för att bli inlagd i programmet. Det som återstår är att länka och använda programmet. Det görs i följande ordning med följande funktioner. -program: programmets handtag void gllinkprogram( uint program ); -program: programmets handtag void gluseprogram( uint program ); Att länka programmet är ett välkänt begrepp. Däremot är meningen med den sista funktionen inte helt självklar. Det som görs är att den exekverbara koden installeras i renderingskedjan. 4.2.3 Debugning Eftersom debugning inte är lika enkelt i shaders som i ett högnivåspråk erbjuder OpenGL 2.0 funktioner för detta. Här visas de viktigaste funktionerna och dess parametrar för hur fel kan upptäckas. För att undersöka en shaders status efter kompilering kan följande funktion användas. -shader: shaderns handtag -pname: COMPILE_STATUS INFO_LOG_LENGTH (finns fler) params: returvärde som beror på pname void glgetshaderiv(uint shader,enum pname,int *params); Om pname är COMPILE_STATUS returneras (värdet i params) TRUE om kompileringen lyckades, annars FALSE. Om pname är INFO_LOG_LENGTH returneras längden på den sträng som beskriver resultatet av kompileringen. Om kompileringen misslyckades kan det vara intressant att undersöka loggen som genererades. Det görs som följer. -shader: shaderns handtag -bufsize: maximala storleken på infologgen -length: verklig storlek -infolog: Loggen void GetShaderInfoLog(uint shader,sizei bufsize,sizei *length,char *infolog ); På liknande sätt som kompileringen kan undersökas, kan också statusen på länkningen av programmet kontrolleras. Det görs med följande funktion. -program: programmets handtag -pname: LINK_STATUS INFO_LOG_LENGTH (finns fler) params: returvärde som beror på pname void GetProgramiv( uint program,enum pname,int *params); Loggen som produceras av länkningen kan också undersökas. -program: programmets handtag -bufsize: maximala storleken på infologgen Sidan 15 av 54

-length: verklig storlek -infolog: Loggen void GetProgramInfoLog(uint program,sizei bufsize,sizei *length,char *infolog ); 4.2.4 Avsluta I ovanstående avsnitt diskuterades hur shaders initierades. Här visas hur shaders avskiljs från programmet och hur programmet själv avslutas. För att avskilja en shader från ett program används följande funktion. -program: programmets handtag -shader: shaderns handtag void gldetachshader(uint program,uint shader); Även om shadern har avskiljts från programmet har shadern kvar sina attribut. Det innefattar att shadern har källkod associerad till sig och att den fortfarande är kompilerad. För att ta bort en shader används funktionen nedan. En shader kan inte tas bort om shadern i fråga är allokerad av ett program. Om en shader försöks ta bort och fortfarande är allokerad tas shadern automatiskt bort när den inte längre är allokerad av ett program. -shader: shaderns handtag void gldeleteshader(uint shader); Det finns även en motsvarande funktion för att ta bort ett program. Om programmet inte används i något sammanhang tas programmet bort direkt. Om programmet används tas programmet bort automatiskt när det inte längre används. Alla shaders som är allokerade till programmet avskiljs enligt gldetachshader(). -program: programmets handtag void DeleteProgram(uint program); 4.3 Datakommunikation Detta avsnitt är viktigt och ger en beskrivning av hur de olika programdelarna kommunicerar. OpenGL-applikationen kommunicerar med shaders och shaders kommunicerar med varandra och den fixa pipelinen. Det kommer att presenteras några rader källkod för shaders trots att det diskuteras först i nästa avsnitt. Källkoden kan för tillfället ignoreras, målet är att beskriva hur data passas mellan olika nivåer. Noterbart är att om extensions önskas användas ska ändelsen ARB läggas till sist i varje funktion som presenteras här. Hela detta avsnitt bygger på [7], de viktigaste delarna har valts ut. 4.3.1 Gränssnitt mellan OpenGL och shaders Både vertex- och pixel-shadern kan erhålla data från OpenGL via variabler som är deklarerade som uniform. Uniforms används för att ange data som gäller för en primitiv, en frame eller för hela scenen. De kan alltså inte användas för att ange attribut för en hörnpunkt i taget. Uniforms är endast läsbara och finns tillgängliga i både vertex- och pixel-shadern. Ovanför main()-funktionen i vertex- och pixel-shadern anges namnet på den tänkta variabeln. Anledningen till att de måste vara deklarerade ovanför main()-funktionen är att uniforms måste vara globala, de kan inte användas inom funktioner. Sidan 16 av 54

VertexShader PixelShader uniform vec3 cameraposition; uniform vec3 cameraposition; void main() void main() Innan cameraposition kan anta något värde måste OpenGL-applikationen ha minnespoitionen för variabeln. Minnespositionen kan erhållas med följande funktion. Notera att programmet måste vara länkat för denna information. -program: programmets handtag -name: variabelns namn -return : minnesposition för variabeln int glgetuniformlocation(uint program, const char * name); För att sedan ange värde för variabeln finns olika funktioner för detta beroende på variabel som ska initieras. I detta exempel med en vektor med tre flyttalskomponenter används följande funktion. Se sidan 81 i specifikationen för OpenGL 2.0 för fler funktioner. -location: minnesposition för variabeln -a,b,c: flyttal void gluniform3f(int location,float a,float b,float c); För exemplet ovan med variabeln cameraposition skulle funktionerna anropas enligt nedan. float pos[3]; int memorypos; int program; void renderscene() gluniform3f(memorypos,pos[0],pos[1],pos[2]); void main() // initiera glut och shaders och länka programmet innan åtkomst till cameraposition. memorypos = glgetuniformlocation(program, cameraposition ); 4.3.2 Gränssnitt mellan OpenGL och vertexshader Som nämndes i förra avsnitten kan inte uniforms påverka en enskild hörnpunkt. För detta måste attribute användas. Attributes är endast läsbara och endast tillgängliga i vertexshaders. Ovanför main()-funktionen i vertexshadern anges namnet på den tänkta variabeln. Precis som med uniforms måste attributes vara globala. De kan inte användas inom funktioner. VertexShader attribute float data; void main() Sidan 17 av 54

Precis som med uniforms måste minnespositionen för variabeln erhållas innan den kan användas i OpenGL. -program: programmets handtag -name: variabelns namn -return : minnesposition för variabeln int glgetattriblocation(uint program,const char *name ); För att tilldela ett värde till en attribute finns precis som med uniforms olika funktioner. Funktionerna har samma uppbyggnad som med uniforms men olika namn. I detta exempel när data är av typen float kan följande funktion användas. -location: minnesposition för variabeln -a: flyttal void glvertexattrib1f(int location,float a); Nedan visas hur attributes kan användas i OpenGL för att passa data till vertexshadern. int memorypos; int program; void renderscene() glbegin(gl_quads); glvertexattrib1f(memorypos,7.4f); glvertex3f(-1.0f,-1.0f,0.0f); glvertexattrib1f(memorypos,3.9f); glvertex3f(1.0f,-1.0f,0.0f); glvertexattrib1f(memorypos,2.1f); glvertex3f(1.0f,1.0f,0.0f); glvertexattrib1f(memorypos,8.8f); glvertex3f(-1.0f,1.0f,0.0f); glend(); void main() // initiera glut och shaders och länka programmet innan åtkomst till data memorypos = glgetattriblocation(program, data ); 4.3.3 Gränssnitt mellan vertexshader och pixelshader En viktig och användbar mekanism inom datorgrafik är interpolation. Data interpoleras mellan en primitivs hörnpunkter som sedan kan avläsas i pixelshadern. Önskas interpolation av viss data kan det anges med nyckelordet varying. För att vertexshadern ska kunna kommunicera med pixelshadern måste den interpolerade variabeln vara deklarerad både i vertex- och pixel-shadern på ett identiskt sätt. Interpolationen sker automatiskt, det enda som måste anges är nyckelordet varying. Variabler som är varying är skrivbara och läsbara i vertexshadern men endast läsbara i pixelshadern. Noterbart är att varyings precis som uniforms och varyings måste vara globala, de kan inte användas inom funktioner. Sidan 18 av 54

VertexShader PixelShader varying position; varying position; void main() void main() // Initiera position // Läs av position Variabeln position interpoleras och kan per pixel anta ett värde i pixelshadern. 4.3.4 Översiktsbild över datakommunikationsvariabler Som komplement till ovanstående avsnitt visas för tydlighetens skull en överblick över tillgängliga datakommunikationsvariabler. Tabell 4.1 Lathund för datakommunikation Skrivs? Läses? Används? uniform OpenGL Vertexshader & pixelshader För data som inte förändras under en frame eller fler attribute OpenGL Vertexshader För data som gäller för enskilda hörnpunkter varying Vertexshader Pixelshader För interpolation av data 4.4 Grundläggande Syntax Här beskrivs hur pixel- och vertex- shaders programmeras. Syntaxen dem emellan är identisk och syntaxen mellan C och GLSL är snarlik, sånär som vissa detaljer. Istället för att redovisa hela språkets syntax, som ändå är en mappning från C, beskrivs viktiga skillnader. Det blir enklare att programmera vid tveksamma syntaxsituationer om utgångspunkten är C. För den som inte är van med C kan det vara svårt att ta till sig vissa delar eftersom endast skillnader diskuteras här. Det är ingen mening att beskriva hur en if-sats är uppbyggd om den fungerar på samma sätt som i C. En mer detaljerad beskrivning för språket ges i [3]. Observera att det shadingspråk som används är GLSL. GLSL eller GLslang är en förkortning för OpenGL shading language och är det programmeringsspråk som används för att skriva shaders i OpenGL 2.0. Andra API:ers shadingspråk är exempelvis Direct3D som använder HLSL och Glide som använder Cg. Informationen i detta avsnitt bygger på [3], återigen har de viktigaste delarna valts eftersom detta kapitel har i avsikt att fungera som en lathund. Sidan 19 av 54

4.4.1 Typer och variabler I GLSL finns ingen implicit typkonvertering, språket är därmed typsäkert. Inget stöd för pekare finns eftersom det helt enkelt inte behövs, inte ännu. Pekare är kraftfullt och ett måste i rätt sammanhang, men inte här. Nedan listas vilka variabler som finns i GLSL. Tabell 4.2 Typer i GLSL Void För funktioner utan returvärde Bool För villkor, har stöd för true och false Int 16-bitars heltal med tecken. Stöd för bitoperationer saknas (>> & osv) Float Flyttal med tecken vec[x] Flyttalsvektor med [2 3 4] komponenter bvec[x] Villkorsvektor med [2 3 4] komponenter ivec[x] Heltalsvektor med [2 3 4] komponenter mat[x] Flyttalsmatris med [2 3 4] rader och kolumner sampler[x]d Handtag för access till texturer med dimensionen [1 2 3] samplercube Handtag för access till cubemap texturer sampler[x]dshadow Handtag för access till shadowmap texturer med dimensionen [1 2] Noterbart är att strängar och tecken inte stöds eftersom det saknar relevans i sammanhanget. Förutom de inbyggda typerna finns det precis som i C stöd för att deklarera sina egna typer. Det görs med ordet struct. Ingen typedef behövs, dock är nyckelordet reserverat av GLSL för framtida bruk. struct nytyp float b; nyvariabel;... nytyp nyvariabel2; 4.4.1.1 Initiera variabler Variabler kan initieras vid deklarationen, det går dessutom att initiera fler variabler med ett kommatecken emellan. Variabler som deklareras med nyckelordet const måste initieras. int a = 5,b = 3,c; const float d = 5.4; De variabler som inte kan initieras är de som är avsedda för datakommunikation. Kom ihåg att uniforms och attributes initieras av OpenGL och varyings programmeras av vertexshadern. Följande kod är alltså inte tillåten. uniform float data = 5.0; // Fel! Uniforms måste initieras från OpenGL 4.4.1.2 Initiera variabler av sammansatta typer En sammansatt typ är exempelvis vec3 och för att tilldela värden till en sådan typ används konstruktorer. Variabler kan alltså inte initieras som i C enligt a 1, a 2,, a n. GLSL är väldigt flexibelt med hur konstruktorer kan kombineras och det är svårt att syntaktiskt göra fel. Om lvalue har samma typ som konstruktorn som appliceras på rvalue är koden kompilerbar. Detta kan tyckas självklart men exemplen nedan visar att viss förvirring kan Sidan 20 av 54

uppstå. Ett exempel är när det anges fler eller färre fält innanför parenteserna till en konstruktor än vad själva namnet på konstruktorn antyder. vec3 v1 = vec3(1.0,0.0,0.0); ivec2 v2 = ivec2(4,5); ivec2 v3 = ivec2(6,7); ivec4 v4 = ivec4(v2,v3); // Korrekt! v4 = (4,5,6,7) ivec2 v6 = ivec2(1,2,3,4); // Korrekt! 3 och 4 ignoreras struct foo vec3 v; bar = foo(vec3(v1)); ivec4 v7 = ivec4(1); // Korrekt! ekvivalent med ivec4 v7 = ivec4(1,1,1,1); 4.4.1.3 Typkonvertering Som nämnts ovan är GLSL typsäkert. Det finns inget stöd för implicit typkonverting. För typkonverting används konstruktorer. float f1 = 7; // Fel! Ingen implicit typkovertering float f2 = (float)7; // Fel! Konstruktor måste användas float f3 = float(7); // Korrekt! f3 är nu en float med värdet 7.0 vec4 v1 = vec4(1.0,2.0,3.0,4.0); vec2 v2 = vec2(v1); // Korrekt! v2 = (1.0,2.0) 4.4.1.4 Komponentvisa operationer GLSL erbjuder åtkomst till inbyggda typers olika komponenter. En vanlig float eller int består inte av olika komponenter, de är komponenter i sig själva. De som har komponenter är de sammansatta typerna som vec3 eller mat3. De finns olika sätt att accessa komponenter beroende på ändamålet. Följande ändelser kan användas för vektorer. - x,y,z,w Användbara för punkter och normaler (1) - r,g,b,a Användbara för färg (2) - s,t,p,q Användbara för texturer (3) Notera att dessa ändelser endast är till för tydlighet och saknar funktionell betydelse. Exempelvis kan (1) användas för att beskriva en färgvektor eller (2) för en normal. Det går även att använda hakparenteser som fungerar på det mest intuitiva sättet, exempelvis syftar normal[1] på den andra komponenten. Uttrycket innanför hakparenteserna kan vara ett heltaluttryck. Indexering utanför vektorns adressrymd leder till obestämt tillstånd. Precis som med konstruktorer erbjuder GLSL ett flexibelt sätt att kombinera komponentändelser för att skapa nya variabler. Dessa uttryck är en aning krångliga och det kan vara bättre att använda konstruktorer. Några vanliga kompileringsfel är exempelvis när en komponentändelse som är utanför variabelns adressområde används eller när komponentändelser inte kommer från samma mängd. vec3 v1 = vec3(4.0,2.0,1.0); Sidan 21 av 54

v1.w = 3.0; v1.xyb = vec3(5.0,4.0,8.0); // Fel! Utanför adressområdet // Fel! Blandning av komponentändelser vec2 v2 = v1.xy; // Korrekt! v2 = (4.0,2.0) vec3 v3 = v1.zyx; // Korrekt! v3 = (1.0,2.0,4.0) vec3 v4 = v1.xxy; // Korrekt! v4 = (4.0,4.0,2.0) v1.xz = vec2(3.0,5.0); // Korrekt! v1 = (3.0,2.0,5.0) v1.y = v2(0); // Korrekt! v1 = (3.0,4.0,5.0) Det finns även stöd för åtkomst av enstaka komponenter i matriser. Dessa uttryck är mer intuitiva än de med vektorer. Indexering utanför matrisens adressrymd leder till obestämt tillstånd mat3 matris; matris[2] = vec3(2.0,5.0,1.0); matris[2][1] = 3.0; // Korrekt! Den tredje kolumnen tilldelas // Korrekt! Den andra komponenten i tredje kolumnen tilldelas 4.4.1.5 Komponentvisa uttryck Operatorer kan verka på vektorer och matriser på samma sätt som är möjligt i C via nyckelordet operator. vec3 v1,v2,v3,v4; float f1; v1 = v2 + v3; v4 = v2 + f1; 4.4.2 Satser Satser i GLSL har exakt samma syntax som i C, med några undantag. En hoppfunktion har tagits bort och en lagts till. Den som tagits bort är goto, och den som lagts till är discard. Nyckelordet discard är endast tillgängligt i pixelshadern och avslutar abrupt arbetet på aktuell pixel utan att skriva till några buffertar. Nedan ett exempel hämtat från just [3]. if (intensity < 0.0) discard; Även funktioner skiljer sig på några punkter. Precis som i C måste en main-funktion finnas, men egendefinierade funktioner måste ange de formella parametrarnas attribut enligt nedan. in - Den formella parametern är indata. Om inget annat anges är en parameter implicit in. out - Den formella parametern är utdata. Kan ersättas med return. inout - Den formella parametern är både indata och utdata. void shade(in vec3 color, in float i, out vec3 result) result = color*i; vec3 shade(vec3 color,float i) return(color*i); Sidan 22 av 54

Det finns även stöd för överlagring av funktioner. void compare(vec3 a, vec3 b); void compare(float a,float b); 4.5 Inbyggd funktionalitet Detta avsnitt är förmodligen de viktigaste eftersom källkod för shaders till stor del består av inbyggda variabler och funktioner. Även om vissa delar i den fixa pipelinen har ersatts med programmerbara enheter måste det finnas ett interface däremellan. Vertexshadern kommunicerar med pixelshadern via den fixa pipelinen. Pixelshadern måste i sin tur kommunicera med efterföljande pipelinesteg. Kommunikationen sker genom att skriva till och läsa från diverse inbyggda variabler. Vertex- och pixel- shadern har dessutom tillgång till olika inbyggda variabler som representerar OpenGLs tillstånd. Endast de mest användbara och vanligaste variablerna och funktionerna beskrivs. Som vanligt ger [3] en mer detaljerad bild. 4.5.1 Transformationer I vertexshadern ställs ett krav på koden för att den ska fungera. Hörnpunktens position måste alltid transformeras. Positionen för hörnpunkten sätts i OpenGL via exempelvis glvertex3f(), men då bara i världskoordinater. Transformation från världskoordinater till önskvärda projicerade ögonkoordinater är upp till programmeraren och kan göras på olika sätt. -gl_projectionmatrix: mat4 som beskriver projektionsmatrisen -gl_modelviewmatrix: mat4 som transformerar från världskoordinater till ögonkoordinater -gl_vertex: Världskoordinater som sätts i OpenGL -gl_position: Den transformerade koordinaten, måste bli skriven gl_position = gl_projectionmatrix * gl_modelviewmatrix * gl_vertex; Att multiplicera projektionsmatrisen med modelviewmatrisen är onödigt för varje hörnpunkt. Dessa två matriser ändras ju inte innanför ett glbegin()-block. De finns för att de ska kunna användas separat, inte ihop med varandra. För att förklara varför det är önskvärt att kunna använda dem separat kan ett exempel inom ljusberäkningar diskuteras. Eftersom ljusberäkningar oftast görs i ögonkoordinater kan det vara önskvärt att ha en egen varyingvariabel som beskriver positionen för en hörnpunkt i ögonkoordinater. Den kan då transformeras i vertexshadern med enbart modelviewmatrisen och användas för ljusberäkningar i pixelshadern. Detta exempel var en parentes i sammanhanget, nedan visas ett mer optimerat sätt att transformera en hörnpunkt till projicerade ögonkoordinater. -gl_modelviewprojectionmatrix: mat4 som beskriver projektionsmatrisen multiplicerad med modelviewmatrisen -gl_vertex: Världskoordinater som sätts i OpenGL -gl_position: Den transformerade koordinaten, måste bli skriven gl_position = gl_modelviewprojectionmatrix * gl_vertex; Sidan 23 av 54

Som vanligt finns det alltid ytterligare ett sätt. Det gäller även transformation av hörnpunkter. I GLSL finns en funktion som garanterar att hörnpunkter transformeras på det mest optimerade sättet. Det rekommenderas att alltid använda denna funktion. gl_position = ftransform(); Att skriva till gl_position är alltså ett krav. Annan funktionalitet som exempelvis transformation av normaler är frivillig men ett måste om normaler ska användas för exempelvis ljusberäkningar. Nedan visas hur normaler transformeras. -gl_normalmatrix: Transponeringen av inversen av den översta 3x3- modelviewmatristen -gl_normal: En primitivs normal i världskoordinater, sätts i OpenGL -normal: Den transformerade normalen vec3 normal = gl_normalmatrix*gl_normal; 4.5.2 Texturer Andra attribut till hörnpunkter som initieras i OpenGL är exempelvis texturkoordinater. För att texturer ska kunna användas måste en speciell varying-variabel initieras. Den interpoleras sedan eftersom den är deklarerad som varying. -gl_multitexcoord[x]: Texturkoordinater till textur nr [0-7], sätts i OpenGL -gl_texcoord[x]: Interpolerade texturkoordinater för textur x, avläses sedan i pixelshadern gl_texcoord[0] = gl_multitexcoord0; För att sedan läsa av aktuell texel i pixelshadern används en variabel som kallas sampler. Samplers nämndes kort i avsnitt 4.4.1 Typer och variabler men ett exempel behövs för att förklara hur dessa används. Betänk följande exempel där en tvådimensionell textur används. VertexShader PixelShader uniform sampler2d mytexture; void main() void main() gl_texcoord[0] = gl_multitexcoord0; vec4 texel = texture2d(mytexture,gl_texcoord[0].st); I pixelshadern finns en variabel mytexture som är deklarerad som en sampler2d. Att variabeln dessutom är en uniform är högst väsentligt eftersom vi på applikationsnivå vill ange vilken textur vi syftar på. Hur detta fungerar i OpenGL visas i kapitel 5. Det väsentliga i GLSL är vetskap om hur ett handtag till en textur skapas, och det görs genom den översta raden i pixelshadern. För att läsa av texturens data används följande funktion. -sampler: Handtag till en textur, i detta fall en sampler2d -pos: vec2 som beskriver texturkoordinater, notera i exemplet hur gl_texcoord[0] som är en vec4 omvandlas till en vec2 -return: Texturens data vec4 texture[1 2 3]D(sampler[1 2 3]D sampler, vec2 pos); Sidan 24 av 54

4.5.3 Färg Ytterligare ett exempel på ett attribut till en hörnpunkt är färgen. Att sätta färgen per hörnpunkt och sedan interpolera resultatet används i gouraudshading. Med shaders går det som bekant att göra på ett bättre sätt eftersom ekvationer kan räknas ut per pixel. Det kan ändå vara användbart i vissa situationer. -myfrontcolor: Egen beräknad färg -gl_frontcolor: varying vec4 som anger färgen för en hörnpunkt. Används för att färgsätta en framsida, sker automatiskt gl_frontcolor = myfrontcolor; -mybackcolor: Egen beräknad färg -gl_backcolor: varying vec4 som anger färgen för en hörnpunkt. Används för att färgsätta en baksida, sker automatiskt gl_backcolor = mybackcolor; Om färgen önskas som initieras i OpenGL används gl_color i vertexshadern istället för de egna definerade myfrontcolor eller mybackcolor. För att sedan läsa av gl_frontcolor eller gl_backcolor i pixelshadern används en variabel med samma namn, gl_color. Dock är det inte samma variabel, gl_color i vertexshadern är av typen attribute och gl_color i pixelshadern är av typen varying. För att sedan sätta färgen till aktuell pixel i pixelshadern skrivs variabeln gl_fragcolor. Ett exempel förenklar beskrivningen. VertexShader PixelShader void main() void main() gl_frontcolor = vec4(0.4,0.1,0.5,1.0); gl_fragcolor = gl_color; En mer fysikalisk beskrivning av ljus är att beskriva materialegenskaperna för en yta och ljusegenskaperna för en ljuskälla. I OpenGL sätts materialegenskaperna med kommandot glmaterialfv() och ljusegenskaper med gllightfv(). Dessa går sedan att läsa av i vertexoch pixel- shadern. Nedan listas några användbara strukturer som finns att tillgå. struct gl_materialparameters vec4 emission; vec4 ambient; vec4 diffuse; vec4 specular; float shininess; ; uniform gl_materialparameters gl_frontmaterial; uniform gl_materialparameters gl_backmaterial; Dessa två uniforms förklarar nog sig själva. gl_frontmaterial för framsidor och gl_backmaterial för baksidor. struct gl_lightsourceparameters vec4 ambient; vec4 diffuse; vec4 specular; vec4 position; vec4 halfvector; Sidan 25 av 54

vec3 spotdirection; float spotexponent; float spotcutoff; float spotcoscutoff; float constantattenuation; float linearattenuation; float quadraticattenuation; ; uniform gl_lightsourceparameters gl_lightsource[gl_maxlights]; Notera de översta elementen som är samma som gl_materialparameters. Vid ljusberäkningar används den komponentvisa multiplikationen av materialets parametrar och ljuskällans parametrar. Andra användbara attribut är exempelvis position som beskriver ljuskällans position i ögonkoordinater. Ljuskällor behöver alltså inte explicit transformeras. struct gl_lightproducts vec4 ambient; vec4 diffuse; vec4 specular; ; uniform gl_lightproducts gl_frontlightproduct[gl_maxlights]; uniform gl_lightproducts gl_backlightproduct[gl_maxlights]; Istället för att multiplicera ljuskällors färgegenskaper med ytors materialegenskaper som ändå inte förändras finns en klar struktur som innehåller den komponentvisa multiplikationen av materialets parametrar och ljuskällans parametrar. gl_frontlightproducts[0].ambient ekvivalent med gl_frontmaterial.ambient*gl_lightsource[0].ambient gl_frontlightproducts[0].diffuse ekvivalent med gl_frontmaterial.diffuse*gl_lightsource[0].diffuse gl_frontlightproducts[0].specular ekvivalent med gl_frontmaterial.specular*gl_lightsource[0].specular 5 Exempel på möjliga algoritmer Detta kapitel avser att visa hur algoritmer kan implementeras i vertex och pixelshaders. Varje avsnitt inleds med en grundlig förklaring av algoritmen och dess tillhörande matematiska bakgrund. Källkoden har i viss mån kommenteras men bör vara enkel att följa med den inledande förklaringen. Koden på applikationsnivå redovisas inte. 5.1 Phongshading Avsikten med phongshading är beräkna den speglande reflektionen som kan uppstå på ytor med rätt materialegenskaper. Algoritmen är en empirisk modell av Phong Bui Thong från 1975 och har först på senare år blivit möjlig i realtidssammanhang. 5.1.1 Lokal belysningsmodell utan fysikaliska begrepp I detta avsnitt förklaras de olika komponenterna i en lokal belysningsmodell [2]. Sidan 26 av 54

I total = I ambient + I diffuse + I specular 5.1.1.1 I ambient Ljuset som träffar en yta i en lokal belysningsmodell beror enbart på ljuskällors attribut. Ingen hänsyn tas till ljus som andra ytor avger. Approximationen för andra ytors inverkan benämns I ambient och är en konstant som beräknas: b c I ambient = l ambient N m ambient l ambient = Ljuskkällans RGB@modell för bakgrundsbelysning m ambient = Materialets RGB@ modell för bakgrundsbelysning 5.1.1.2 I diffuse Den diffusa komponenten som beräknas med Lamberts cosinuslag har störst anknytning till verkligheten. Lambert cosinuslag säger att det reflekterande ljuset för matta ytor bestäms av cosinus för vinkeln mellan ytans normal och ljusvektorn. Om normalen och ljusvektorn är enhetsvektorer kan Lamberts cosinuslag uttryckas som skalärprodukten mellan dessa vektorer. Ljuset som reflekteras sprids likformigt i alla riktningar och är därmed vyoberoende. I diffuse = cos ` α a b c b jk jk cb A l diffuse N m diffuse = NO LA ldiffuse N m diffuse l diffuse = Ljuskkällans RGB@ modell för bakgrundsbelysning m diffuse = Materialets RGB@ modell för bakgrundsbelysning c 5.1.1.3 I specular Syftet med den speglande komponenten är att ge ytan ett blänkande utseende. I specular beräknas som cosinus mellan den reflekterande vektorn r, som är ljusvektorn roterad α grader runt normalen, och vyvektorn. I motsats till I diffuse är komponenten alltså vyberoende. jk b jk jk c jk jk R = 2A NO LA N@ L I specular = cos b shininess β c A l N m specular specular b c = b = V jk jk cshininessb O R A lspecular N m specular l specular = Ljuskkällans RGB@ modell för bakgrundsbelysning m specular = Materialets RGB@ modell för bakgrundsbelysning c För att kontrollera hur skinande komponenten ska upplevas används shininess som anger materialets benägenhet att sprida ljus. Sidan 27 av 54

5.1.2 Implementation Nedan följer källkod för hur phongshading kan implementeras med vertex- och - pixelshaders. 5.1.2.1 Vertexshader varying vec4 position; varying vec3 normal; void main() position = gl_modelviewmatrix*gl_vertex; normal = gl_normalmatrix*gl_normal; gl_position = ftransform(); För att åstadkomma phongshading krävs interpolation av positionen (var på primitiven vi befinner oss) och normalen. Dessa används sedan av ljusekvationerna i pixelshadern. Positionen som interpoleras befinner sig i betraktarens koordinatsystem efter att modelviewmatrisen applicerats. 5.1.2.2 Pixelshader varying vec4 position; varying vec3 normal; void main() vec3 l = vec3(normalize(gl_lightsource[0].position - position)); vec3 n = normalize(normal); vec3 v = normalize(vec3(-position)); vec3 r = -reflect(l,n); vec4 ambient = gl_frontlightproduct[0].ambient; vec4 diffuse = max(0.0,dot(n,l))*gl_frontlightproduct[0].diffuse; vec4 specular = pow(max(0.0,dot(v,r)),gl_frontmaterial.shininess)*gl_frontlightproduct[0].specular; gl_fragcolor = ambient + diffuse + specular; Källkoden är trivial och beskriver den lokala belysningsmodellens ekvationer. Notera att vyvektorn v är lika med positionen på primitiven med omvänd riktning. Det har sin förklaring i att vi befinner oss i ögonkoordinater. Enligt [3] returnerar reflect(l,n) vektorn L jk b jk jk c jk jk @ 2A NO LA N vilket är @ R i den lokala belysningsmodellen. Noterbart är även att ljusvektorn beräknas per pixel vilket indikerar att en närbelägen punktformad ljuskälla används. Sidan 28 av 54

5.1.3 Resultat Figur 5.3 Tekanna enligt den lokala belysningsmodellen. 5.2 Animerad flagga I detta avsnitt beskrivs hur en animerad flagga kan simuleras. Det väsentliga är att visa hur hörnpunkternas position kan förändras och inte att ge en realistisk effekt. 5.2.1 Implementation Nedan visas källkod för hur en animerad flagga kan skapas med vertex- och -pixelshaders. 5.2.1.1 Vertexshader uniform float angle; void main() vec4 temppos = gl_vertex; temppos.y += (sin((temppos.x + angle)*4.0)*0.05) * (min(abs(temppos.x) + temppos.x*0.5 + 0.1,1.0)); temppos.z += sin((temppos.x + angle)*4.0)*0.03; temppos.x += sin((temppos.y + angle)*2.0)*0.02; gl_texcoord[0] = gl_multitexcoord0; gl_position = gl_modelviewprojectionmatrix * temppos; Sidan 29 av 54

Ekvationerna bygger på en känsla om vad som betyder vad i en ekvation och saknar därmed fysikalisk anknytning. b c db c e f1 pos,angle = sin pos + anglea amplitud A global displacement ` a b ` a c f2 pos = min abs pos + posa 0.5 + 0.1,1.0 f1(pos,angle) får flaggan att röra sig i en sinusformad våg. Pos kan ses som startposition medan angle, som erhålls från applikationen, får flaggan att röra sig. Amplitud och global_displacement anger hur kraftig svängingen ska verka. f2(pos) ger en effekt av att flaggan rör sig kraftigare på ena sidan (en flagga rör sig mindre desto närmare flaggstången). 5.2.1.2 Pixelshader uniform sampler2d tex; void main() vec4 texcolor = texture2d(tex,gl_texcoord[0].st); gl_fragcolor = texcolor; Aktuell pixel får sitt värde från en textur. En textur av den svenska flaggan. 5.2.2 Resultat Figur 5.4 En enkel animerad flagga. Sidan 30 av 54

5.3 Heidrich och Seidel anisotropic shading I den lokala belysningsmodellen förutsätts att ytan för en primitiv är perfekt polerad. Ingen hänsyn tas till små mikroskopiska förändringar av ytnormalen som exempelvis uppträder på polerad metall. I den perfekt polerade modellen sägs ytan vara isotropic medan en yta som har små förändringar av ytnormalen benämns anisotropic. Figur 5.5 illustrerar detta fenomen. I Heidrich och Seidels [8] anisotropiska modell antas oändligt små tangentfiber löpa över en yta för att simulera normalens förändring. Problemet är dock att för ett tangentfiber (kan ses som ett hårstrå) finns ingen traditionell normal, se figur 5.6. Det optimala vore att integrera ljusekvationen med normalen som parameter. Men för prestanda används den mest signifikanta normalen (N ms ) till ljusekvationerna. N ms för den diffusa termen fås genom att projicera ljusvektorn på normalplanet. Detta leder till störst skalärprodukt med ljusvektorn (kom ihåg att skalärprodukten anger hur mycket två vektorer har gemensamt) [9]. jk jk jk N = L@ proj jk L = L jk L jk jk O T @ j f T jk jk k A T jk T O T jk N L jk bjk jk c jk f @ LOT AT jk jk = d N O N jk bjk jk c e d jk L @ LO T AT O L jk jk jk s @ LO N jk bjk jk c jk L @ LOT AT s b c2 1@ L jk OT jk f w I diffuse = N jk O L jk = b c2 = s1@ L jk jk O T h l js w h L jk @ L jk O T jk i b c i jk AT f w b c2m k 1@ L jk O T jk b c ew f = jk AT f b c2 1@ L jk jk O T O L jk = s b c2 1@ L jk OT jk g w f = Sidan 31 av 54

Som ekvationen visar behövs inte normalen explicit beräknas. Istället används tangenriktningen t. På liknande sätt kan termen för den speglande komponenten beskrivas. Då används den reflektionsvektor som har kortast avstånd till vyvektorn (ligger i det plan som tangentriktningen och vyvektorn spänner upp) [9]. b cshininess fd jk = proj jk V + proj V jk e d jk O proj R jk jk + proj R jk eg shininess jk = T N T N I specular = V jk O R jk fd e db O R jk jk c jk bjk jk c eg jk shininess O T AT + RO N A N = b = V jk jk c jk bjk jk c jk O T AT + VO N A N h b = V jk jk c jk j OT AT + h b c2 = 1@ L jk jk js OT s w $ T jk OT jk = 1, N jk OT jk = 0 b c2 1@ V jk O T jk w b c2 As1@ V jk jk OT ih A N jk b ko @ L jk jk c jk j OT AT + w i b c k b @ V jk jk c jk jk O T A LOT s b c2 1@ L jk OT jk shininess w A N jk i k shininess Gemensamt för den diffusa och speglande ekvationen är att endast tangentriktningen T jk är okänd. Detta gör det möjligt att använda texturer för uppslagning av data. RGB i texturen kan då tolkas som XYZ för T jk. Dock har tangentriktningen för implementationerna som följer beräknats analytiskt. = 5.3.1 Implementation I detta avsnitt visas tre olika anisotropiska effekter. Källkod för vertex- och pixel-shaders är gemensam sånär som tangentriktningen och presenteras i slutet på detta avsnitt. Sidan 32 av 54

5.3.1.1 Kromad tekanna med fejkad tangentriktning För effekten nedan används ljusvektorn reflekterad runt den falska interpolerade isotropiska normalen för att simulera tangentriktningen, figur 5.9. Valet av T jk saknar helt analytisk bakgrund och var till en början bara ett försök. Den redovisas här för att visa att alla effekter inte måste ha direkt anknytning till verkligheten. 5.3.1.1.1 Resultat Figur 5.10 Tekanna med fejkad tangentriktning. 5.3.1.2 Julgranskula av glänsande tyg Till skillnad från den kromade tekannan har denna effekt mer anknytning till verkligheten. Föreställ en julgranskula med trådar av glänsande tyg från pol till pol. På applikationsnivå har en egen genererad sfär används eftersom det ger möjligheten att kunna beräkna tangentriktningen. Någonting som inte glut-biblioteket erbjuder via funktionsanropet glutsolidsphere(). Sfären är uppbyggd med sfäriska koordinater som beskrivs nedan. Idén och inspirationen om att simulera en julgranskula med sfäriska koordinater är hämtad från [10]. Sidan 33 av 54

x = ρa sin b φ c A sin ` θ a y = ρa sin b φ c A cos ` θ a z = ρacos b φ c Två tangentriktningar kan sedan beräknas genom att partiellt derivera med avseende på de två vinklarna θ och φ. θ anger vinkeln i xz-planet och φ i yz-planet. Tangent 1 Tangent 2 x φ y φ z φ b c f ` a = ρa cos φa sin θ b c f ` a = ρa cos φa cos θ b c f =@ ρa sin φ x θ y θ z θ b c f ` a = ρa sin φa cos θ b c f ` a =@ρa sin φa sin θ f = 0 5.3.1.2.1 Resultat Figur 5.13 Julgranskula. Sidan 34 av 54