Procedurell grottgenerator och eld i GLSL Marcus Widegren 14 januari 2012
Innehåll 2
Sammanfattning Jag har gjort en enkel procedurell grottgenerator i GLSL och C++. För belysning används en fackla, som också är gjort i GLSL. Grottan är en cylinder, vars geometri förändras i en vertex-shader med hjälp av en fraktal av Simplex Noise.
Kapitel 1 Implementation 1.1 Cylinder Innan vertex-shadern är grottan bara en rak cylinder. Detta görs enkelt genom att göra en dubbel for-loop med avseende på θ och h och sedan få koordinaterna för punkterna med: x = r cos θ y = r sin θ z = h N = ( x r y r 0 ) (1.1) Där N är ytans normal och r är radien. För att slippa flytta på grottan medan man rör sig framåt fungerar det så att när kameran gått mer än en längdenhet framåt i z-led flyttas kameran bak en längdenhet och texturkoordinaterna förskjuts framåt. På så sätt ser det ut som att man går framåt även fast man inte gör det. 1.2 Fraktal Det mesta i projektet som är procedurellt genererat är baserat på fraktaler av simplex noise. Fraktalen är en summa av simplex noise i flera frekvenser. Om inget annan anges är det funktionen nedan som menas som fraktal i resten av texten. for i = 0.0 octaves do i i + 1.0 noise+ = snoise(textur_koordinater 2 i ) 2 i end for noise+ = (octaves octaves ) snoise(textur_koordinater 2 octaves ) 2 octaves octaves anger hur många frekvenser som ska användas vid noiseberäkningen. Det behöver inte vara ett heltal, och går därför att justera gradvis för att t ex få mer detaljer när man ser närmare på en yta genom att öka antalet oktaver (octaves) desto närmare kameran punkten som renderas är. Med snoise menas GLSL-implementationen av Simplex Noise skriven av Stefan Gustavson. Hur många dimensioner variabeln textur_koordinater har bestämmer hur många dimensioner snoise körs med. 2
1.3 Displacement Displacement sker i vertex shadern med hjälp av simplex noise. Två fraktaler kontrollerar hur punkternas position ändras. En med hög frekvens som får väggarna att se ojämna ut samt en med lägre frekvens som får grottan att svänga i sidled. Båda dessa fraktaler har ett variabelt antal oktaver, som också bestäms med hjälp av simplex noise. Detta innebär att vissa delar av grottan är ganska jämna och släta medan andra delar är mer skrovliga. Normalerna räknas ut med hjälp av finita differenser. Displacement räknas ut för punkten som ska flyttas, samt två närliggande punkter sen räknas normalen ut med en kryssprodukt. Figur 1.1: Grottan med lågt oktavvärde (vänster) samt högt oktavvärde (höger). 1.4 Textur Stentexturen är baserad på en blandning av worley och simplex noise. Simplex-delen består av tre fasta frekvenser samt en fraktal vars oktaver ökar när man ser nära på en yta. (oktaverna är noll tills man kommer ganska nära) Ljusmodellen som används är vanlig phong, där ljusstyrkan avtar kvadratiskt med avseende på avståndet till ljuskällan. Ljusstyrkan beror också på hur stor elden för tillfället är. 1.5 Bump mapping För att få en ojämn yta utan att behöva ha hemskt många trianglar har jag implementerat bump mapping. Ett noise-värde tas fram med hjälp av en fraktal som representerar hur mycket positionen i den punkten på ytan ska förflytta sig i normalens riktning. Eftersom riktig displacement inte kan göras i en fragmentshader räknas istället en ny normal ut, efter hur ytan hade sett ut med sådan displacement. Figuren nedan visar detta lite tydligare. (tagen från Morten Mikkelsen) 3
Noise-funktionen jag använder räknar inte ut någon derivata, så det implementerades istället med hjälp av finita differenser. Med andra ord räknar jag inte bara ut noise-värdet i den nuvarande punkten på ytan, utan också i två närliggande punkter och använder sedan dessa tre positioner för att få fram en normal i punkten. Antalet oktaver som bump-mappen använder varierar med avseende på hur nära kameran punkten som renderas är samt varierar med en noise-funktion som beror på hur långt i grottan man har gått. Ett exempel på när bump mapping gör stor skillnad visuellt är spekulära ytor, till exempel isfläckarna i grottan. Figur 1.2: En isfläck utan (vänstra) och med (högra) bump mapping. 1.6 Fackla Facklan och elden är implementerade helt i GLSL, och hålls framför kameran genom att Vertex shadern helt enkelt inte flyttar på punkterna. Elden är uppbyggd av en fraktal i två dimensioner och avtar med ett smoothstep desto längre bort punkten som renderas är från eldens centrum. Y-positionen i eldens texturkoordinater flyttar sig uppåt med avseende på tiden, så att det mer liknar eld. 4
Elden multipliceras sedan också med ett simplex noise som enbart är beroende av tiden. Samma multiplikation sker också i grottans fragmentshader så att när elden flammar till så blir det även ljusare i grottan. Efter noise-värdet har räknats ut sätts färgerna såhär: red = noise green = noise 4 blue = smoothstep(0.6, 1.0, noise 0.5) noise 2 0.5 (1.2) Det ser däremot inte exakt så ut i koden, eftersom alpha-värdet måste räknas med där för att de yttre delarna av elden ska vara genomskinnliga. Facklan är helt enkelt en brun färg som avtar desto längre ifrån facklans centrum (en linje i detta fall) punkten är. Sedan multipliceras färgen med simplex noise, hur stor elden är just nu och hur långt ifrån eldens centrum punkten är. 1.7 Dimma Den mystiska dimman i grottan är implementerad med ett enkelt smoothstep med avseende på positionen i y-led. Dimman har konstant färg tills den är ganska långt bort från ljuskällan. Avtar den för snabbt med avseende på avståndet syns det tydligt att dimman helt enkelt är golvet med blå färg. 1.8 Isfläckar Ett tvådimensionellt simplex noise avgör var i grottan det är is. Isen visar bump-mappen väldigt väl, och får grottan att se lite mer intressant ut. Skillnaderna i rendering är att isen har en spekulär del i ljusmodellen och resultatfärgen är lite mörkare samt lite blå. 1.9 Övrigt Projektet är implementerat i OpenGL, och det mesta görs i GLSL. GLFW användes för att skapa fönster, hantera input från tangentbord, hålla koll på hur lång tid varje frame tar osv. Biblioteket GLM har använts för att hantera matriser då GLSL 3.30 kräver att man hanterar sådant manuellt. Projektet är skrivet i C++. För styrningen används sfäriska koordinater med glulookat. 5
Kapitel 2 Resultat 2.1 Prestanda Programmet, i sin nuvarande form, är väldigt krävande. Med ett Nvidia 9600GT renderas ungefär 10 bilder per sekund och med ett Nvidia GTX570 renderas ungefär 120 bilder per sekund. 2.2 Bilder 6
7
2.3 Video En demovideo finns här. (från och med den 15e januari) 8
Kapitel 3 Diskussion Mycket som just nu räknas flera gånger per sekund på GPU-sidan skulle kunna flyttas till CPUsidan (eller med t ex CUDA eller OpenCL) och bara räknas ut när det behövs. Till exempel displacement skulle kunna flyttas från vertex-shadern då det faktiskt inte behövs räknas ut i varje frame. Även stenmaterialet skulle kunna representeras som en bildtextur istället. (åtminstone den del av texturen som inte har dynamisk level of detail) Grottan blir just nu ganska snabbt tråkig, eftersom den saknar större variationer. Exempel på variationer skulle vara att stenmaterialet varierar mellan några olika sorter beroende på vart i grottan man är, variation i y-led med vatten under en viss nivå. Lösa stenar och sand hade även gett en mer dynamisk känsla till grottan, speciellt om stenarna till exempel flyttade på sig när man går över dem och sanden gör virvlar eller något liknande. Just nu räknas normaler ut med hjälp av finita differenser, vilket är ganska onödigt eftersom derivatan till simplex noise går att räkna ut billigt. 9
Kapitel 4 Referenser Simplex Noise, Stefan Gustavson. Bump mapping, Morten Mikkelsen. Phong-modellen, Wikipedia. 10