Implementatie in Java van een Architectuur voor Real-time 3D applicaties

Maat: px
Weergave met pagina beginnen:

Download "Implementatie in Java van een Architectuur voor Real-time 3D applicaties"

Transcriptie

1 Faculteit Ingenieurs Wetenschappen Vakgroep Informatietechnologie Voorzitter: Prof. Dr. Ir. Paul Lagasse Implementatie in Java van een Architectuur voor Real-time 3D applicaties door Bert Van Semmertier Promotor: Prof. Dr. Ir. Herman Tromp Scriptiebegeleider: Dr. Kris De Schutter Scriptie ingediend tot het behalen van de academische graad van Licentiaat in de Informatica Academiejaar

2 Faculteit Ingenieurs Wetenschappen Vakgroep Informatietechnologie Voorzitter: Prof. Dr. Ir. Paul Lagasse Implementatie in Java van een Architectuur voor Real-time 3D applicaties door Bert Van Semmertier Promotor: Prof. Dr. Ir. Herman Tromp Scriptiebegeleider: Dr. Kris De Schutter Scriptie ingediend tot het behalen van de academische graad van Licentiaat in de Informatica Academiejaar

3 Toelating tot bruikleen De auteur geeft de toelating deze scriptie voor consultatie beschikbaar te stellen en delen van de scriptie te kopiëren voor persoonlijk gebruik. Elk ander gebruik valt onder de beperkingen van het auteursrecht, in het bijzonder met betrekking tot de verplichting de bron uitdrukkelijk te vermelden bij het aanhalen van resultaten uit deze scriptie. Bert Van Semmertier, mei 2006

4 Implementatie in Java van een Architectuur voor Real-time 3D applicaties door Bert Van Semmertier Scriptie ingediend tot het behalen van de academische graad van Licentiaat in de Informatica Academiejaar Promotor: Prof. Dr. Ir. Herman Tromp Scriptiebegeleider: Dr. Kris De Schutter Faculteit Ingenieurs Wetenschappen Universiteit Gent Vakgroep Informatietechnologie Voorzitter: Prof. Dr. Ir. Paul Lagasse Samenvatting Een algemeen gekende moeilijkheid bij de ontwikkeling van een volwaardige render-engine is scene-management. Hoe snel videokaarten vandaag de dag ook mogen zijn, het is en blijft gewenst zo weinig mogelijk data naar de videokaart te sturen. Geen twee 3D-scenes zijn bovendien hetzelfde, wat het probleem nog een stuk complexer maakt. Binnenlokaties vereisen doorgaans andere render-technieken dan buitenlokaties en scene-management dient op verschillende manieren te gebeuren. Over elk van die rendertechnieken zijn al veel studies gepubliceerd. Maar wat veelal ontbreekt, is hoe deze verschillende technieken kunnen gecombineerd worden in één applicatie. De meeste render-engines specialiseren zich bijvoorbeeld in het renderen van binnenlokaties en leggen zichzelf de beperking op dat er geen complexe buitenlokaties kunnen gerenderd worden. Dit legt uiteraard een serieuze beperking op de variatie aan omgevingen die gecreëerd kunnen worden. In dit werk stel ik een architectuur voorop die het op een robuuste manier mogelijk maakt volstrekt willekeurige renderers tegelijk te combineren met elkaar in één scene én waartussen de overgang volstrekt seamless is. In de vooropgestelde architectuur wordt een scene opgebouwd uit onafhankelijk gedefiniëerde gebieden, die ik sectoren noem, die aan elkaar gelinkt worden via portals. Het is de bedoeling hierbij dat elke sector gebruik maakt van een eigen, unieke renderer, BSP- en Octree-renderers zijn momenteel geïmplementeerd, die de data in de specifieke sector zo efficiënt mogelijk zal renderen.

5 In de tekst wordt een volledige beschrijving gegeven van de architectuur. Evenals hoe zichtbaarheid van elke sector bepaald wordt, hoe we zoveel mogelijk beperken wat we renderen en de beschrijving van de SceneGraph-structuur. De resource-framework, die het beheer doet van gebruikte texturen, modellen en shaders in een applicatie, wordt gedetailleerd besproken. Ter afsluiting worden een aantal belangrijke uitbreidingen op de architectuur besproken. Hier komen zaken aan bod als: particle renderers, dynamic clothing, projective texturing, verschillende real-time shadow rendering technieken, refraction/reflection en water-rendering. Dit alles met een volledig beschrijving van de source-code, vergezeld van eventueel gebruikte GLSL-shaders en uitgewerkte voorbeelden. Trefwoorden Render-engine, portal, BSP, Octree, Java, OpenGL, 3D, rendering, shaders, GLSL, materialframework, sector, sectormanager, real-time shadows, water-rendering, SceneGraph.

6 Inhoudsopgave I Introductie 9 1 Inleiding 10 2 Basisprincipes van een OpenGL applicatie Wat is OpenGL? Verduidelijking van enkele gebruikte termen in de tekst Assenstelsels en spaces Inleiding De Modelview- Projection- en Texture-matrix Transformatie tussen assenstelsels View Frustum Introductie Bepaling van de View Frustum Color-, Depth- en Stencil-buffer Color-buffer Depth-buffer Stencil-buffer II De Libera Engine 22 3 De filosofie 23 4 Beschrijving architectuur SectorManager BSP SectorManager Octree SectorManager Galaxy SectorManager Sector BSP Sector Octree Sector Galaxy Sector Hoe een nieuwe Sector(Manager) schrijven? Portal Spiegels Monitoren RenderEngine

7 4.4.1 Bepalen root-sector Zichtbaarheidsbepaling Berekenen bounding-box en view-frustum ShadowRenderer SceneNode MovableObject Renderen van de SceneGraph Culling van SceneNodes Collision detection Code-voorbeelden Resource-framework Inleiding Texture-manager De klasse Texture De klasse TextureManager Materials Material MaterialManager Material-scripts Inleiding Voorbeelden Shaders Introductie tot shaders Per-pixel lighting Per-pixel lighting met specular component Cel-shading Normal-mapping MeshLoader (Sub)Mesh OBJLoader Een nieuwe MeshLoader schrijven FontManager Implementatie in de engine Logmanager Config-bestanden III Uitbreidingen 76 6 Particle engine Inleiding Billboarded particles Particles met point sprites Implementatie in de engine

8 7 Dynamic clothing Simulatie van een koord Simulatie van clothing Implementatie in de engine Projective texturing 92 9 Real-time shadow rendering Shadow Mapping Gebruikte GLSL-shader Shadow mapping via een FBO (Frame Buffer Object) Spotlights Directional lights Point-lights via Cubic shadowmapping Soft-shadows via PCF-filter Shadow Volumes Bepalen silhouette edges Bepalen shadow-volumes Renderen van de schaduwen Shadow volumes met GLSL Soft-shadows door Light-jittering Implementatie in de engine Reflection / Refraction Reflection plane Werkwijze Implementatie in de engine Cubic environmental mapping Refraction plane Werkwijze Implementatie in de engine Water-rendering Werkwijze Water-rendering en FBO (Frame Buffer Object) Implementatie in de engine IV Besluit Nabeschouwing V Bijlage: UML-diagrammen 138

9 Lijst van figuren 2.1 De vlakken van een Frustum OpenGL Transformatie pipeline View Frustum Alignering van twee portals SectorManager voorbeelden SectorManager voorbeelden Een portal, met textuur, dat dienst doet als een spiegel Frustums voor portals Boundingbox van een portal Een voorbeeld van een SceneGraph SceneGraph met en zonder pivot-node Boundingboxen en BoundingSphere Een voorbeeld scene Eenzelfde model gerenderd d.m.v. verschillende material-scripts in de RenderEngine Schema van de OpenGL pipeline Een gekleurde driehoek OpenGL pipeline, grafisch voorgesteld Diffuse lighting Phong specular lighting Blinn specular lighting Normal mapping Normal map Bitmapped Font Een voorbeeld log Particles Billboarding De OpenGL Modelview matrix Modelview matrix voor Billboarding Modelview matrix voor Billboarding met schaling Enkele particle-renderers in een scene waaronder fakkels en regen Een koord, samengesteld uit punten verbonden met veren Een eenvoudig model voor textiel Een robuuster model voor textiel

10 7.4 Dynamic clothing in een BSP-scene Projective texturing Matrices en Projective Texturing Projective texturing en real-time shadows in een BSP-scene Verschillende ShadowRenderers in de engine Shadow mapping Shadow mapping voor een punt-licht en een aantal objecten in een scene Object en lichtbron in een scene Silhouette edges van het object Belichte polygoon Schaduw-volume van het object Stencil buffer voor Shadow volumes Gesloten schaduw-volume van het object Schaduw volumes d.m.v. GLSL shader Schaduw volumes d.m.v. GLSL shader Vertices verplaatsen weg van het licht Een ReflectionPlane in een scene Real-time cubic environmental mapping in een BSP-scene Zes View-Frustums gedefinieerd rond een object voor environmental cube mapping Refraction Voorbeeld Normal- en DUDV-map Een refraction plane in een scene De fresnel-term in de praktijk Water-rendering schematisch voorgesteld Waterplanes in verschillende scenes Main UML Sector(Manager) UML ShadowRenderer en Portal UML MovableObject UML Sequentie diagram van de render-pass Sequentie diagram voor culling van SceneNodes

11 Deel I Introductie 9

12 Hoofdstuk 1 Inleiding Mijn afstudeerwerk als Licentiaat in de Informatica, gaat over de implementatie van een architectuur voor real-time 3D applicaties in Java. Een algemeen gekende moeilijkheid bij de ontwikkeling van een volwaardige render-engine is scene-management. Hoe snel videokaarten vandaag de dag ook mogen zijn, het is en blijft gewenst zo weinig mogelijk data naar de videokaart te sturen. Geen twee 3D-scenes zijn bovendien hetzelfde, wat het probleem nog een stuk complexer maakt. Binnenlokaties vereisen doorgaans andere render-technieken dan buitenlokaties en scene-management dient op verschillende manieren te gebeuren. Over elk van die rendertechnieken zijn al veel studies gepubliceerd. Maar wat veelal ontbreekt, is hoe deze verschillende technieken kunnen gecombineerd worden in één applicatie. De meeste render-engines specialiseren zich bijvoorbeeld in het renderen van binnenlokaties en leggen zichzelf de beperking op dat er geen complexe buitenlokaties kunnen gerenderd worden. Dit legt uiteraard een serieuze beperking op de variatie aan omgevingen die gecreëerd kunnen worden. In dit werk stel ik een architectuur voorop die het op een robuuste manier mogelijk maakt volstrekt willekeurige renderers tegelijk te combineren met elkaar in één scene én waartussen de overgang volstrekt seamless is. In de vooropgestelde architectuur wordt een scene opgebouwd uit onafhankelijk gedefiniëerde gebieden, die ik sectoren noem, die aan elkaar gelinkt worden via portals. Het is de bedoeling hierbij dat elke sector gebruik maakt van een eigen, unieke renderer, BSP- en Octree-renderers zijn momenteel geïmplementeerd, die de data in de specifieke sector zo efficiënt mogelijk zal renderen. Belangrijk hierbij is dat deze procedure volledig transparent dient te zijn voor de gebruiker. Hij moet geen weet hebben van het feit dat er verschillende renderers bestaan in een omgeving. We houden ook voor ogen dat het erg eenvoudig moet zijn nieuwe renderers toe te voegen aan de engine, zodat in de toekomst meer soorten sectoren en renderers kunnen gebruikt worden in een scene. De correcte werking van de architectuur tracht ik aan te tonen door hier een implementatie van te maken in een volwaardige render-engine met ondersteuning voor real-time shadows, 10

13 scene-graph-based rendering en een robuuste material-framework. De vrijheid die deze architectuur biedt aan de gebruiker om complexe scenes op te bouwen, gaf aanleiding tot de naam van de engine: Libera. In wat volgt, geef ik een gedetailleerde beschrijving van de architectuur van Libera en hoe die geïmplementeerd wordt. Dit alles wordt voorzien van Java source-code, implementaties van gebruikte GLSL-shaders, duidelijke tekeningen, screenshots en voorbeelden. De screenshots komen rechtstreeks uit Libera en de tekeningen zijn allen van mijn hand, ter ondersteuning van de verschillende processen die aan bod komen. Java was een vereiste en de implementatie van de engine was een goede test in hoeverre Java geschikt is voor de ontwikkeling van geavenceerde real-time 3D-applicaties. Libera werd ontwikkeld met de OpenGL API, d.m.v de Java-OpenGL binding LWJGL (LightWeight Java Game Library, Om source-code en scripts duidelijk te onderscheiden van de eigenlijke tekst werd gekozen voor een volgend formaat: Voorbeeld-code in Java en/of pseudocode wordt geschreven in een enkele kader met rechte hoeken: public void foo ( String text ) //Commentaar z i e t er als volgt uit. dosomething ( ) ; / Een groot blok commentaar / GLSL-shaders worden geschreven in een dubbele kader met rechte hoeken: uniform vec4 auniformvector ; void main () //Commentaar z i e t er als volgt uit. gl Position = ftransform ( ) ; Material-scipts worden geschreven in een enkele kader met schaduw-rand: Wood //Commentaar z i e t er als volgt uit. tex data/mytexture.bmp s c r o l l

14 De volledige source-code en alle demo s worden gereleased als OpenSource project onder een BSD-licentie. De scriptie is als volgt ingedeeld: Deel I Een korte beschrijving van enkele basis-principes van een OpenGL applicatie: assenstelsels, ruimtes, matrices, View-frustums en buffers. Deel II De volledige beschrijving van de architectuur van Libera en informatie over hoe een scene opgebouwd en gerenderd wordt d.m.v. de verschillende sectoren en portals wordt in dit deel beschreven? Evenals hoe zichtbaarheid van elke Sector bepaald wordt, het renderen van schaduwen en beschrijving van scene-beheer d.m.v. de SceneGraph-structuur. De resource-framework, die het beheer doet van gebruikte texturen, modellen en shaders in een applicatie, wordt gedetailleerd besproken. Ter afsluiting van dit deel wordt een voorbeeld uitgewerkt dat beschrijft hoe je zelf een scene kan opbouwen in een eigen applicatie d.m.v. deze engine. Deel III Dit deel behandelt de uitbreidingen die gemaakt zijn op de architectuur. Hier komen zaken aan bod als: particle renderers, dynamic clothing, projective texturing, verschillende realtime shadow rendering technieken, refraction/reflection en water-rendering. Dit alles met een volledig beschrijving van de implementatie, vergezeld van eventueel gebruikte GLSL shaders en uitgewerkte voorbeelden. Deel IV bevat het besluit Deel V bevat de UML- en sequentie-diagrammen behorende bij de engine. Hoewel er geen diepgaande wiskundige analyse gemaakt wordt van het probleem, is vectoren matrix-rekenen onlosmakelijk verbonden aan 3D-programming. Kennis van een aantal elementaire zaken uit de Lineaire Algebra worden dan ook verwacht van de lezer. Merk tenslotte op dat dit een render-engine is, geen game-engine. Alle methoden om aan scene-beheer en rendering te doen zijn aanwezig, maar er wordt geen aandacht gegeven aan sound, collision detection, physics en networking. Dit zijn zaken die volledig los staan van de render-engine (maar maken wel allen onderdeel uit van de game-engine).

15 Hoofdstuk 2 Basisprincipes van een OpenGL applicatie 2.1 Wat is OpenGL? OpenGL (Open Graphics Library) maakt onderdeel uit van de grafische hardware in je PC en is een grafisch systeem dat speciaal gemaakt is voor het maken van interactieve driedimensionale toepassingen. Merk op dat het niet meer is dan een bibliotheek met instructies om je videokaart aan te spreken: OpenGL biedt geen support voor input-verwerking, GUI, collisiondetection, schaduw-rendering, model-rendering, level-rendering, culling, scene -management, etc... dit zijn zaken die je als programmeur zelf moet implementeren d.m.v. een programmeertaal naar keuze en de OpenGL API. OpenGL is een interface die hardware- en software-onafhankelijk is, dit betekent dat een OpenGL applicatie theoretisch kan draaien op gelijk welke software/hardware die er te vinden is, als er maar een OpenGL-implementatie bestaat voor dat platform. Een OpenGL applicatie kan draaien op een ATI-, nvidia-kaart, op een Windows-, Mac-, Linux- of Solarissysteem. Er bestaan ondertussen zelfs OpenGL-implementaties voor mobiele-telefoons en PDA s. Sinds de eerste release van OpenGL in 1992 worden regelmatig revisies en uitbreiding gereleast, versies 1.0, 1.1, 1.2, 1.3, 1.4, 1.5 zijn reeds de revue gepasseerd. In september 2004 werd de huidige versie, 2.0, beschikbaar gesteld wat onder andere GLSL (OpenGL Shading Language) introduceerde. GLSL maakt het mogelijk voor programmeurs de OpenGL pipeline te programmeren in een elegante taal (lijkt erg goed op C), dit met volledige ondersteuning door OpenGL. Naast de standaardbibliotheken bestaat de mogelijkheid OpenGL uit te breiden d.m.v. extensions. Die worden geïmplementeerd door hardware-fabrikanten die instructies toevoegen aan hun videokaarten. Als blijkt dat de extentie na verloop van tijd erg veel gebruikt wordt, kunnen ze ge evalueerd worden door de ARB (Architecture Approval Board) en toegevoegd worden aan de standaard OpenGL specificatie. Een greep uit reeds gereleaste extenties: blending, texture mapping, lighting, fog, occlusion culling, fragment- en vertex-programs. 13

16 Opmerkelijk is dat OpenGL gebruik maakt van een Client/Server-architectuur. Dit betekent dat de computer die de grafische data verwerkt niet dezelfde moet zijn als die computer die de data zal weergeven. Een OpenGL programma kan je m.a.w zonder probleem over een netwerk laten draaien; als een pc niet in een netwerk ligt zal die zowel client als server zijn. 2.2 Verduidelijking van enkele gebruikte termen in de tekst Vertex Een punt in de ruimte dat wordt voorgesteld door een tripel (x, y, z) die de plaatsing bepaalt op respectievelijk de X-, Y- en Z-as. Onthoud dat OpenGL intern vertices zal behandelen als homogene coördinaten, van de vorm (x, y, z, 1). Pixel Een beeldpunt op het scherm dat wordt voorgesteld door een getal die de kleur-informatie bevat. Indien we 24-bits kleur gebruiken zal 8-bit gebruikt worden om respectievelijk de hoeveelheid rood, groen en blauw (8 x 3 = 24) te beschrijven. De hoeveelheid pixels op het scherm hangt af van de gebruikte resolutie. OpenGL zal elke frame in je 3D-wereld omzetten naar pixels, dit gebeurt door het toepassen van een transformatie d.m.v. een serie matrices, zie Transformatie tussen assenstelsels (p.17). Fragment Zoals vermeld, zal OpenGL je 3D-scene omzetten naar pixels op je scherm. Vooraleer een beeldpunt een pixel wordt, zullen we spreken over een fragment. Denk aan een fragment alsof het een tijdelijke pixel is. Het is namelijk goed mogelijk dat, voordat alle data in de scene verwerkt is, een fragment overschreven wordt door een ander fragment. Of: meerdere fragmenten kunnen kandidaat zijn om een pixel te worden op een bepaalde plaats. Welk fragment gekozen wordt om een volwaardige pixel te worden, hangt af van de huidge toestand van de OpenGL-pipeline. 2.3 Assenstelsels en spaces Inleiding In een OpenGL applicatie worden typisch verschillende assenstelsels (met bijhorende ruimtes) gedefinieerd, d.m.v. een matrix: Tangent-space Tangent-space staat ook bekend als Texture-space en defineert voor elke vertex in een object een apart orthogonaal assenstelsel, bepaald door de Normal-, Tangent-, BiTangent-vectoren. De Normal-vector is de normaal van de polygoon waar de vertex deel van uitmaakt, de Tangent is een vector die de richting aangeeft waarin de X- component van de textuur-coordinaat stijgt. De BiTangent verkrijgt men door het cross-product te nemen tussen de Normal- en Tangent-vector: B = (N T)

17 Object-space Elk object in een 3D wereld heeft een eigen lokaal assenstelsel dat bepaald wordt tijdens het modelleren van het object. Een rotatie van een object gebeurt altijd rond de oorsprong van het assenstelsel in zijn Object-space. World-space World-space an sich bestaat niet in OpenGL maar veel programmeurs houden ervan dit concept voor ogen te houden in hun applicatie omdat dit het meest intuïtief is. In world-space is een (uniek) assenstelsel bepaald dat het centrum van de virtuele wereld voorstelt en toelaat om objecten relatief ten opzichte van elkaar te positioneren t.o.v één centraal punt, de oorsprong. Objecten in World-space plaatsen doe je door die te manipuleren met behulp van gltransform(), glrotate() en glscale(). View-space Het assenstelsel bepaalt hier de positie van de camera in de scène op zo een manier dat de camera te allen tijde langs de negatieve Z-as kijkt. Alle objecten staan hier relatief t.o.v. van de camera, die hier steeds in het nulpunt (0, 0, 0) staat. Deze ruimte staat ook bekend als de Camera-space. Clip-space In deze ruimte beschrijft het assenstelsel nog steeds de positie van de camera maar de x, y en z coördinaten van de punten worden genormaliseerd over het [-1, 1] interval. Dit is nodig om de z-buffer (zie onder) een zo hoog mogelijke nauwkeurigheid te garanderen. Screen-space Dit is de twee-dimensionale ruimte die men verkrijgt door de punten in de 3D-scene te projecteren op het computer scherm. Deze ruimte beschrijft de uiteindelijke positie van de vertices op het scherm, relatief t.o.v. de linker onderhoek van de viewport De Modelview- Projection- en Texture-matrix Twee belangrijke concepten in OpenGL zijn de ModelView- en Projection-matrices. Modelview-matrix De ModelView matrix is een 4x4 matrix die zowel de positie van de camera als de positie van de individuele objecten in de scène beschrijft. Standaard is dit een identiteitsmatrix: M = Via deze matrix kan je vertices in world-space transformeren naar view-space (en ze dus relatief plaatsen t.o.v. de positie van de camera). (view x, view y, view z, 1) = (world x, world y, world z, 1) M Objecten manipuleren in je scene doe je door de Modelview matrix aan te passen, beschouw bijvoorbeeld een ModelView-matrix van de volgende vorm: M =

18 Hierin stelt de vector in de laatste kolom (0, 0, 150, 1) de positie van de oorsprong (en dus de plaatsing van de camera in World-space) voor. Stel dat we een vertex plaatsen in de ruimte d.m.v. glvertexf(0.0f, 0.0f, 1.0f) dan zal die, in View-space, op positie (0, 0, 151, 1) komen te staan. (0, 0, 151, 1) = (0, 0, 1, 1) Wanneer we nu de camera verplaatsen naar positie (0, 0, 100, 1) zal de vector op positie (0, 0, 101, 1) geplaatst worden in view-space (0, 0, 101, 1) = (0, 0, 1, 1) Merk op dat het dus geen verschil maakt om de camera 50 units naar voor te plaatsen of het punt 50 units naar achter om een illusie van beweging op te wekken. Vandaar dat in OpenGL de ModelView-matrix de camera of de positie van je elementen in je scene zal aanpassen, afhankelijk van hoe je de data in de ModelView-matrix interpreteert. Als dit laatste niet direct duidelijk is, geen zorgen, het concept Modelview-matrix alleen al zorgt voor veel kopzorgen bij de meeste beginnende OpenGL-programmeurs. Op een dag wordt het allemaal duidelijk. Hiermee is meteen duidelijk dat het concept camera eigenlijk niet bestaat in OpenGL: de camera verplaats je door de ModelView-matrix aan te passen, en dus onrechtstreeks verplaats je de objecten in View-space. Onthoud hier echter dat het verplaatsen van het assenstelsel niet de eigenlijke waarden van de vertices zal veranderen! Voor collision-detection of Frustum culling bijvoorbeeld zal je maatregelen moeten nemen om dit op te vangen... Wens je punten te transformeren van View- naar World-space (bijvoorbeeld als je de positie van de camera in World-space wilt kennen) volstaat het de locatie van het punt in View-space (merk op: voor de camera is dit altijd (0, 0, 0)) te vermenigvuldigen met de inverse van de modelview matrix: Projection-matrix (world x, world y, world z, 1) = (view x, view y, view z, 1) M 1 De Projection-matrix beschrijft hoe vertices getransformeerd zullen worden naar Clip-space, net zoals de ModelView-matrix bepaalt hoe pixels getransformeerd worden naar View-space, en dus hoe vertices uiteindelijk op het scherm zullen geplaatst worden. We onderscheiden twee soorten projection-matrices: perspectief en orthogonaal: - Perspectief projectie Deze projectie benadert de realiteit het best. Objecten die verder van de camera staan zullen kleiner gerenderd worden zodat je als gebruiker een correct dieptezicht ervaart.

19 - Orthogonale projectie Deze projectie kent geen perspectief-correctie. Orthogonale projectie wordt vooral gebruikt in grafische ontwerppakketten (zoals AutoCAD) of om in 2D te renderen in OpenGL. De projectiematrix zal een View-Frustum ( ViewFrustum (p.27)) definiëren, zoals afgebeeld in figuur 2.1 (p.17). De projectie-matrix heeft steeds de volgende vorm: P = 2n r+l r l 0 0 2n t b r l 0 t+b t b 0 (f+n) 2fn f n f n Met daarin: n: de positie van de near-plane. f: de positie van de far-plane. l: de positie van de left-plane. r: de positie van de right-plane. t: de positie van de top-plane. b: de positie van de bottom-plane. In een typische applicatie zal de Projection-matrix ongewijzigd blijven doorheen het programmaverloop terwijl de ModelView-matrix vele keren per frame zal gewijzigd worden. Naast de ModelView- en Projection-matrix bestaat ook een Texture-matrix die bepaalt hoe een textuur zal gerenderd worden en die toelaat de textuur-coördinaten van een object eenvoudig aan te passen. Om een textuur te laten scrollen in X-richting volstaat het de Texture-matrix te vermenigvuldigen met een richtingsvector en het object te renderen Transformatie tussen assenstelsels Figuur 2.1: De vlakken van een Frustum Het is belangrijk in te zien dat elke vertex steeds een vaste sequentie van transformaties zal doorlopen vooraleer het een zichtbare pixel wordt op het scherm. Als programmeur mag je dus niet vergeten rekening te houden in welke ruimte je de data wenst te interpreteren en waar die eigenlijk gedefiniëerd staat. Belichting in OpenGL gebeurt bijvoorbeeld steeds in view-space, omdat belichting soms afhankelijk is van de positie en oriëntatie van de camera (bijvoorbeeld voor Specular Lighting).

20 Figuur 2.2: OpenGL Transformatie pipeline Dit wordt vooral belangrijk wanneer je begint aan shader-programming, waar je als programmeur zelf die transformaties zal moeten doorvoeren. Een andere veel gemaakte beginners-fout is de ModelView- en Projectie-matrices verkeerd gebruiken. In OpenGL moet je expliciet vermelden welke matrix de huidige matrix is, veel mensen vergeten dit en manipuleren bijvoorbeeld de Projection-matrix om een translatie door te voeren, met totaal onverwachte resultaten als gevolg. 2.4 View Frustum Introductie Hoe snel videokaarten vandaag de dag ook mogen zijn, het is onnodig elementen naar je videokaart te sturen die uiteindelijk niet op het scherm zullen komen (omdat ze bijvoorbeeld achter de camera liggen). Vaak zijn 3D-werelden zodanig groot dat we er steeds maar een fractie van zien op elk bepaald moment, methodes om stukken van je scene te cullen ( weg te laten ) dringen zich op. Het behoeft geen uitleg dat het voor complexe 3D-scenes noodzakelijk is enkel maar het strikt noodzakelijke te renderen. Het is dus zeer interessant om te kunnen bepalen wat de gebruiker kan zien in de 3D-wereld op een bepaald moment, om te beslissen om delen van je 3D-omgeving niet in aanmerking te laten komen ze door de render-pipeline te sturen. Hiervoor moeten we een idee kunnen vormen van het gebied waarin vertices moeten liggen zodat ze uiteindelijk op het scherm zullen geprojecteerd worden. Dit gebied wordt de View Frustum genoemd en heeft de vorm van een afgeknotte piramide, zie figuur 2.3 (p.19) Zoals eerder vermeld, bepaalt de PROJECTION-matrix hoe vertices geprojecteerd worden op het scherm; die matrix lijkt dus uitstekend geschikt om de Frustum te bepalen Bepaling van de View Frustum Door handig gebruik te maken van matrix-rekenen en de figuur in Transformatie tussen assenstelsels (p.17) voor ogen te houden, is het bijzonder eenvoudig de Frustum te bepalen. We weten dat in Clipspace de x, y en z coordinaten van de vertices genormaliseerd liggen in het interval [-1, 1], in Clip-space wordt de View-frustum dus doodgewoon bepaald door een kubus met zijden [-1, 1] rond de oorsprong. Echt nuttig is dit nog niet aangezien we de World-space coördinaten van de Frustum moeten kennen vooraleer we kunnen testen indien een object al dan niet in de ViewFrustum ligt. De hoekpunten van de kubus transformeren we naar de World-space via een aantal inverse transformaties:

21 (a) View Frustum in Clipspace (b) View Frustum in World-space Figuur 2.3: View Frustum (world x, world y, world z, 1) = (clip x, clip y, clip z, 1) P 1 M 1 Gebruikmakend van die informatie kunnen we de vergelijking van de 6 vlakken, die de Frustum bepalen, opstellen. De vergelijking van een vlak wordt wiskundig gegeven door: ax + by + cz + d = 0 met (a, b, c) de normaal van het vlak en d de afstand van het vlak tot de oorsprong. Een vlak, waarin drie gegeven punten liggen, kunnen we als volgt definieren (komt uit de klasse Plane van de RenderEngine): public Plane ( Vector3 v1, Vector3 v2, Vector3 v3) Vector3 v3v1 = Vector3. Minus(v1, v3 ) ; Vector3 v3v2 = Vector3. Minus(v2, v3 ) ; //De normaal (a, b, c ) van het vlak. normal = Vector3. Cross (v3v1, v3v2 ) ; normal = Vector3. Normalize ( normal ) ; position = v1 ; //Afstand d van het vlak constant = ( f l o a t ) Vector3. Inner ( normal, position ) ; Wanneer de zes vlakken van de frustum gekend zijn, kunnen we testen of bepaalde voorwerpen hier al dan niet inliggen. Testen indien in een punt zich in de Frustum bevindt, kan volgens de volgende methode (pseudocode): f o r ( AllPlanesOfTheFrustum )

22 // Bereken de afstand van het punt (met coordinaten (x, y, z )) // tot het vlak. // De vector (a, b, c ) i s de normaal van het huidige vlak. afstand = a x + b y + c z + d ; i f ( afstand < 0) //Punt l i g t achter het vlak ( en dus niet in de Frustum ). //Stop algoritme break ; Testen indien in een balk zich in de Frustum bevindt, kan als volgt (pseudocode): for ( AllPlanesOfFrustum ) // Controleer a l l e punten van de balk d.m. v. bovenstaand algoritme. PointInFrustum ( punt1 ) ; PointInFrustum ( punt2 ) ; PointInFrustum ( punt3 ) ; PointInFrustum ( punt4 ) ; PointInFrustum ( punt5 ) ; PointInFrustum ( punt6 ) ; PointInFrustum ( punt7 ) ; PointInFrustum ( punt8 ) ; //Als we hier geraken l i g t de balk niet in de frustum! return f a l s e ; return true ; Testen voor een bol met straal r gebeurt zo (pseudocode): f o r ( AllPlanesOfTheFrustum ) // Bereken de afstand van het middelpunt (met coordinaten (x, y, z )) // tot het vlak. afstand = a x + b y + c z + d ; i f ( afstand < r ) //Stop algoritme break ; 2.5 Color-, Depth- en Stencil-buffer OpenGL kent drie buffers die ter beschikking staan tot de gebruiker om invloed te hebben op wat er uiteindelijk gerenderd zal worden Color-buffer De color-buffer bevat de kleur-informatie van de pixels op het scherm. In OpenGL kan je verhinderen dat er geschreven wordt naar de color-buffer, terwijl de depth-buffer wel geëvalueerd

23 wordt. Dit kan nuttig zijn in een aantal specifieke situaties, bijvoorbeeld bij Shadowmapping, zie Shadow Mapping (p.96) Depth-buffer De depth-buffer bevat de diepte informatie van de pixels op het scherm. Vooraleer een fragment op het scherm verschijnt, zal zijn depth-waarde vergeleken worden met de waarde op die plaats in de depth-buffer. Als blijkt dat de depth-value groter is (en dus achter een andere pixel ligt) komt dat fragment niet in aanmerking om te renderen op het scherm. Zonder de depth-buffer zouden objecten door elkaar gerenderd worden zonder een notie van diepte en zouden zaken die achter een muur liggen toch zichtbaar kunnen zijn. Aangezien de depth-buffer geen oneindige precisie heeft (traditioneel slechts 16- of 24-bits per pixel) kan er depth-fighting ontstaan tussen pixels die te dicht bij elkaar liggen en er niet meer beslist kan worden welke van de twee pixels voor de andere ligt. De nauwkeurigheid van de depth-buffer is afhankelijk van de afstand tussen de near- en far-planes van de View-Frustum. Je dient dit in beschouwing te nemen bij het renderen van je scene en een afweging maken tussen je view-distance en depth-precision is noodzakelijk Stencil-buffer De Stencil-buffer is een 8-bit buffer die toelaat op een per-pixel basis te beslissen of een pixel op het scherm mag getekend worden. Aan elke pixel wordt een gehele waarde gehecht waarop je later tests kan uitvoeren. Indien je bijvoorbeeld wenst dat enkel op de linker-helft van het scherm gerenderd wordt, kan je de linker-helft van de Stencil-buffer opvullen met 1-en en enkel renderen naar die pixels met een Stencil-waarde 1. Geavanceerdere toepassingen van de Stencil-buffer zijn het renderen van reflecterende oppervlakken (zie Spiegels (p.32)) en Shadow-volume rendering (zie Shadow Volumes (p.104)).

24 Deel II De Libera Engine 22

25 Hoofdstuk 3 De filosofie Een belangrijk probleem bij elke 3D-engine is ervoor zorgen dat enkel het strikt noodzakelijke gerendert wordt naar het scherm. Niettegenstaande dat de videokaarten van vandaag miljoenen polygonen per seconde kunnen verwerken, is en blijft het overbodig polygonen door de pipeline te sturen die uiteindelijk toch niet op het scherm zullen komen omdat ze buiten de View-Frustum liggen of, belangrijker, omdat ze afgeschermd worden door een ander object in de scene. De grote moeilijkheid hierbij is dat geen twee 3D-scenes hetzelfde zijn. Het spreekt voor zich dat voor binnenlokaties, waar meestal veel muren zijn die andere objecten onzichtbaar kunnen maken, andere culling-technieken zullen gebruikt moeten worden dan in buitenlokaties, waar je meestal tot aan de horizon kan kijken. Een bekende techniek die kan helpen bij het cullen van grote gebieden in je scene is het gebruik maken van portals. Kort gezegd geldt dat je aan een portal geometrie kan linken dat enkel gerenderd wordt wanneer de portal zichtbaar is. Omdat het over het algemeen erg eenvoudig en goedkoop is om te testen of een portal zichtbaar is, is dit een goede methode om ruwweg grote delen van een scene te cullen. Dit is meteen de eigenlijke focus van het thesis-onderwerp: het ontwerp en implementatie van een robuuste en flexibele Portal-based render-engine in Java. Niet alleen werd verwacht een portal-engine te implementeren, het moest mogelijk zijn Sectoren te definieren die elk gebruik (kunnen) maken van een andere SectorManager en renderer, bijvoorbeeld een BSPrenderer. Bovendien moeten nieuwe SectorManagers eenvoudig toe te voegen zijn, zonder dat dit invloed heeft op je werking van de RenderEngine en elke Sector moet kunnen bestaan in zijn eigen, lokaal assenstelsel. Concreet betekent dit laatste dat je als level-designer geen rekening hoeft te houden waar het gebied uiteindelijk zal komen in de scene, dit wordt volledig afgehandeld door de Portal-engine. Kort gezegd: een wereld in Libera wordt opgebouwd door Sectoren (en hun bijhorende SectorManagers) die gelinkt worden aan elkaar via portals. De RenderEngine zorgt ervoor dat dit alles correct en zo efficiënt mogelijk gerenderd wordt. Binnen zo een SectorManager kan je je verbeelding volledig de vrije loop laten en een unieke renderer schrijven, vermist zij zich toch gedragen als volledig op zichzelf staande blokken. Meer informatie hierover wordt beschreven in de hierop volgende hoofdstukken. 23

26 Het resultaat is alvast erg bemoedigend: buitenlokaties kunnen seamless gelinkt worden aan binnenlokaties terwijl elke sector toch zo efficiënt mogelijk gerenderd wordt door zijn specifieke SectorManager. Bovendien worden sectoren gedefiniëerd in een lokaal assenstelsel, waardoor instanciëring zonder probleem mogelijk is (een bepaalde sector kan op verschillende plaatsen gerenderd worden, dit terwijl er maar 1 instantie van in het geheugen opgeslaan zit), dit is vooral nuttig voor het renderen van spiegels en monitors door middel van portals of als je een zelfde Sector meerdere malen wilt renderen in een scene. Ook kunnen aan verschillende sectoren eenvoudig unieke eigenschappen gelinked worden zoals een ShadowRenderer of Fog, zonder dat die weerslag hebben op andere sectoren in de scene. Daarbovenop gebeurt culling en collision detection specifiek voor een SectorManager, zodat we die optimaal kunnen maken voor die bepaalde scene. Elke SectorManager handelt die opdracht volledig autonoom af, zonder dat hij weet moet hebben van eventuele andere Sector- Managers (als die al bestaan), wanneer hij hiertoe opgdragen worden door de RenderEngine.

27 Hoofdstuk 4 Beschrijving architectuur Hierna volgt een gedetailleerde beschrijving van de architectuur, de geïmplementeerde klassen en hun gebruik. Er wordt besproken hoe je eenvoudig een scene kan opbouwen door middel van de engine en hoe je zelf een SectorManager kan schrijven. Voor het UML-diagram dat de architectuur beschrijft, verwijs ik je graag naar Deel V van deze tekst. 4.1 SectorManager De taak van een SectorManager is het renderen van een bijhorende Sector op een zo goed mogelijke manier af te handelen. Het beschikt over volgende attributen om het renderen tot een goed einde te kunnen brengen: Portals Een List van de portals in de scene. Door middel van die portals is het mogelijk andere SectorManagers te linken aan deze SectorManager en kan zichtbaarheid naar andere Sectoren bepaald worden. Sector De Sector die door de SectorManager zal gerenderd worden. De SectorManager zal de rauwe vertex-data, die opgeslaan zit in een Sector, renderen volgens zijn eigen renderer. TransformMatrix Zoals vermeld worden Sectoren gedefiniëerd in een eigen assenstelsel, maar het moet bekend zijn hoe deze SectorManager geplaatst staat t.o.v van de andere Managers in de scene. Die informatie wordt opgeslaan in de TransformMatrix. TransformMatrix is een 4x4 matrix die met de ModelView-matrix zal vermenigvuldigd worden vooraleer de Sector gerenderd wordt. Door middel van die gemodificeerde ModelView-matrix zal het mogelijk zijn de Sector, die gedefinieerd staat in een lokaal-assenstelsel, op de correcte plaats te renderen in Camera-space. De waarde in de TransformMatrix is afhankelijk van de positie en oriëntatie van de portal, waaraan de SectorManager gelinkt staat, en de TransformMatrix van de SectorManager waaraan hij gelinkt is. 25

28 Wanneer je een SectorManager linkt aan een andere moet die matrix dus aangepast worden. De berekening hiervan gebeurt volledig automatisch zonder dat je hier als programmeur rekening mee moet houden. De RenderEngine zal als volgt de TransformMatrix updaten indien nodig: //We berekenen de hoek tussen de twee portals. //De hoek tussen twee vectoren wordt gegeven door de arccosinus van // het Inner product van die vectoren. angle = (Math. acos ( Vector3. Inner ( portal1. normal, Vector3. Inverse ( portal2. normal ) ) ) ) ; //We translateren de oorsprong van de ANDERE sector naar de l o c a t i e //van de portal. translationvector=vector3.add( translationvector, portal1. position ) ; //We translateren de portal in de ANDERE sector naar de l o c a t i e van //de portal in de huidige sector. translationvector=vector3.add( translationvector, portal2. position ) ; //We slaan de rotatie en translatie vector op in de transformmatrix //voor later gebruik. transformmatrix = new Matrix4 ( ) ; transformmatrix=utils. translationmatrix ( Vector3. Inverse (p. getposition ( ) ) ) ; transformmatrix=matrix4. mult ( mat, Utils. rotationmatrixy ( rot ) ) ; transformmatrix=matrix4. mult ( mat, Utils. translationmatrix (p. getposition ( ) ) ) ; transformmatrix=matrix4. mult ( mat, Utils. translationmatrix ( trans ) ) ; transformmatrix=matrix4. mult ( mat, new Matrix4 ( other. transformmatrix ). transpose ( ) ) ; Figuur 4.1: Alignering van twee portals

29 ViewFrustum Dit is een frustum, zie ViewFrustum (p.27), uniek voor deze SectorManager. Aangezien elke SectorManager een Sector zal renderen dat gedefiniëerd staat in een lokaal assenstelsel, is het noodzakelijk dat hij beschikt over een eigen Frustum. Wanneer een SectorManager gevraagd wordt zich te renderen zal een Frustum bepaald worden in dat lokale-assenstelsel en zal zo aan Frustum-culling gedaan worden. Zie RenderEngine (p.34) voor een gedetailleerde uitleg hierover. SceneNode Dit is de root van de SceneGraph behorende bij de SectorManager. Deze wordt gebruikt om objecten te linken aan een SectorManager die op hun beurt bijgehouden in een SceneGraphstructuur, zie SceneNode (p.39) voor meer uitleg hierover. ShadowRenderer Aan een SectorManager kan een (optionele) ShadowRenderer gelinkt worden. Deze renderer zal schaduwen renderen voor je scene en eventuele objecten die in de SceneGraph staan. ShadowMapping en ShadowVolumes (zowel Z-pass en Z-fail methoden) worden ondersteund. Voor een gedetailleerde bespreking van de ShadowRenderer verwijs ik je naar ShadowRenderer (p.38). In Libera zijn reeds een aantal SectorManager geïmplementeerd en klaar voor gebruik. In wat volgt zal ik deze kort bespreken BSP SectorManager Een BSPSectorManager zal de sector renderen d.m.v. een BSP-renderer. BSP (of Binary Space Partitioning) is een techniek die de data van een scene recursief verdeeld in sub-scenes, volgens een aantal goedgekozen vlakken, en die structureert in een boomstructuur. Aan de hand van die boom kan erg eenvoudig bepaald worden welke vlakken we kunnen zien vanaf de positie van de camera. BSP-rendering was bijzonder nuttig een aantal jaren geleden, wanneer rendering uitsluitend op de CPU gebeurde en het erop aan kwam zoveel mogelijk polygonen te kunnen cullen. Vandaag, nu we videokaarten hebben die miljoenen polygonen per seconde kunnen verwerken, heeft het een beetje zijn nut verloren, maar het formaat wordt niettemin nog veel gebruikt. Wanneer een PVS (Possible Visibility Set) gebruikt wordt, kan op een nog betere manier zichtbaarheid van grote delen van een level bepaald worden. Een BSP-renderer is over het algemeen de optimale manier voor het renderen van binnenlokaties waar er veel muren zijn die helpen een goede BSP-boom op te bouwen. Het compileren van een BSP-boom gebeurt in een pre-processing fase en resulteert in een.bsp file die de BSP SectorManager kan parsen. Een uitstekende en gratis editor die toelaat BSP-levels te maken, is GTKRadiant. Libera biedt ondersteuning voor het laden en renderen van Quake3 BSP-levels en maakt

30 gebruik van Frustum-culling, PVS en een face-cache om de scene zo optimaal mogelijk te renderen Octree SectorManager De Octree-sectormanager zal een heightmap inlezen (zowel 8- en 16-bit versies worden ondersteund) en de data van het level opslaan in een Octree-Structuur. Het bouwen van een octree gebeurt door de geometrie van de scene te omringen door een zo klein mogelijke kubus en die recursief te verdelen in 8 kleinere kubussen tot je op een punt komt dat in elke sub-kubus een vooraf bepaald aantal polygonen zit. Na het uitvoeren van het algoritme zal in de Octree alle level-data verdeeld staan in een boomstructuur. D.m.v. Frustum culling kan nu zeer eenvoudig voor alle sub-kubusjes bepaald worden of die zichtbaar zijn of niet. Als een kubus niet zichtbaar is, wordt de level-data die erin opgeslaan staat ook doodgewoon niet gerenderd. Octree-based frustum culling is een eenvoudige maar erg efficiënte techniek voor het renderen van groot uitgestrekte scenes, zoals landschappen. Het grote voordeel aan Octree-rendering is dat dit gebruikt kan worden voor virtueel elk mogelijk scene : verdeel vooraf gewoon alle vertices op in sub-gebieden, naargelang hun positie in de wereld, en pas er tijdens de render-fase Frustum-culling op toe. (a) Een heigthmap (b) Een Octree sector met zichtbare Octree-Nodes Figuur 4.2: SectorManager voorbeelden De landschappen in de OctreeSectorManager worden momenteel gerendert volgens een brute-force methode, maar door OctreeSectorManager uit te breiden kan een eigen renderer geschreven worden die het terrein bijvoorbeeld rendered d.m.v. Geometrical Mipmapping of R.O.A.M. Uiteraard heeft dit geen weerslag op de RenderEngine of eventuele andere Sector- Managers in de scene. In Hoe een nieuwe Sector(Manager) schrijven? (p.29) beschrijf ik hoe je dit het best kan doen.

31 4.1.3 Galaxy SectorManager Een GalaxySectorManager is dummy-manager die een lege sector rendert met enkel een Skybox en de SceneGraph. Deze sector is ideaal voor het bouwen van een snelle demo-applicatie. Een voorbeeld van hoe je een eigen SectorManager kan schrijven staat in Code-voorbeelden (p.45) 4.2 Sector Sector is een abstracte klasse en stelt de eigenlijke data voor van die sector. Het bevat de vertices, indices, faces, normalen, textuurcoördinaten van alle statische data in de scene. In een Sector wordt de data opgeslaan in een standaard formaat. Een BSP-Sector zal bij inlezen van een BSP-bestand de data vertalen naar dit formaat, die dan op zijn beurt geïnterpreteerd kan worden door een SectorManager naar keuze. Een sector zal zichzelf niet renderen, daarvoor moet je een SectorManager selecteren die de data in de sector zal verwerken en dan zo efficiënt mogelijk renderen. In wat volgt, beschrijf ik kort de Sectoren die reeds geïmplementeerd zijn in Libera BSP Sector Een BSPSector zal een.bsp file inlezen, parsen, opslaan en klaarmaken voor het renderen door middel van een BSPSectorManager. Enkel Quake3 BSP files worden ondersteund. Beziercurven en Quake3shaders worden niet gerenderd, maar support kan in principe toegevoegd worden door de BSPSector uit te breiden met de gewenste functionaliteit Octree Sector Een Octree Sector zal een heightmap inlezen en zo opslaan dat een Octree SectorManager de data kan opslaan in een Octree-datastructuur om die dan efficiënt te kunnen renderen. Heightmaps in.raw formaat worden ondersteund, zowel in 8bit en 16bit mode, opgeslaan in BIG-ENDIAN of LITTLE-ENDIAN formaat Galaxy Sector Een GalaxySector is een dummy-sector behorende bij een GalaxySectorManager, die geen data inleest. Het maakt gewoon een lege scene waar, via de SectorManager, objecten in geplaatst kunnen worden aan de SceneGraph Hoe een nieuwe Sector(Manager) schrijven? De flexibiliteit van de RenderEngine zit grotendeels in het feit dat het geen probleem is naar believen nieuwe SectorManagers te schrijven en die zonder moeite kunnen gebruikt worden in een reeds bestaande scene. Hou voor ogen wat het nut is van een Sector (Beschrijft een Sector) en een SectorManager (Rendert de data in de Sector) en het is erg eenvoudig een eigen Sector(Manager) te schrijven.

32 (a) Octree sector (b) BSP sector (c) Galaxy sector met Normalmapping-test Figuur 4.3: SectorManager voorbeelden Stel, we zijn ambitieus, en we willen proceduraal terrein kunnen renderen d.m.v. bijvoorbeeld NURBS zodat we niet meer moeten leven met de beperkingen van heightmaps (Geen overhangs, grote memory-footprint, beperkt detail). We zouden dan als volgt te werk kunnen gaan (uiteraard zal ik hier geen implementatie geven van een werkende NURBS-renderer, maar een beschrijving van het framework en zaken waar je moet op letten): Een nieuwe Sector dient de abstracte klasse Sector uit te breiden public class NURBS Sector extends Sector public NURBS Sector () //Doe i n i t i a l i s a t i e hier. public loaddata () / Hier zal je de NURBS data moeten transformeren naar het interne formaat van de engine, die enkel werkt met vertices en indices. Voor een NURBS renderer betekent dit waarschijnlijk dat je de controle punten zal opslaan als vertices en die naar wens verwerken i n j e SectorManager / Een nieuwe SectorManager dient de abstracte klasse SectorManager uit te breiden public class NURBS SectorManager extends SectorManager //De Sector die de SectorManager zal renderen. NURBS Sector sector ; public NURBS SectorManager( String name)

33 super (name ) ; sector = new NURBS Sector ( ) ; generateportals ( ) ; public void generateportals () //Voeg hier een manier toe om portals toe te voegen //aan je scene. Dit kunnen eventueel hand placed portals // zijn... / De volgende twee functies zijn de meest belangrijke en bevatten de code die gebruikt wordt om te renderen. Ik heb een s t r i k t onderscheid gemaakt tussen een Root en niet Root Sector. Soms i s het immers wenselijk een niet root sector te renderen met minder features ( bijvoorbeeld zonder schaduwen of shaders ) / //Deze methode wordt opgeroepen als de SectorManager de root i s. public void rendersectorasroot (Camera c, Frustum f, boolean debugmode) rendermynurbs ( ) ; //Render de elementen in de SceneGraph. // elementen geculled worden. getrootscenenode ( ). render ( frustum ) ; Via de Frustum zullen de //Deze methode wordt opgeroepen als de SectorManager niet de root i s. public void rendersectoraschild (Camera c, Frustum f, boolean debugmode) rendermynurbs ( ) ; getrootscenenode ( ). render ( frustum ) ; private void rendermynurbs () // S c h r i j f hier je code die het NURBS terrein zal renderen. Als alles correct geïmplementeerd werd, kan je deze nieuwe SectorManager op net dezelfde manier gebruiken in een scene zoals de reeds geïmplementeerde managers. SectorManagers linken, gelinkte SectorManagers cullen, de SceneGraph managen, de Sector correct transformeren in de 3D-wereld,... wordt allemaal correct afgehandeld door de RenderEngine en de gebruiker moet daar geen rekening mee houden.

34 4.3 Portal Een portal wordt voorgesteld door een onzichtbare rechthoek (in de Engine kan wel een textuur aan een Portal gehangen worden) waaraan een SectorManager gelinkt wordt. Die SectorManager zal dan enkel gerenderd worden wanneer de Portal in kwestie zichtbaar is door de camera. Het grote voordeel aan Portals is dat ze eenvoudig te implementeren zijn, flexibel zijn en het erg goedkoop is te testen of een Portal al dan niet in de ViewFrustum ligt. In de RenderEngine zal voor elke Portal een BoundingSphere (een bol die de Portal omvat) gedefinieerd worden om de Frustum-test te versnellen, we hoeven dan immers maar te testen tegen 1 punt i.p.v. alle hoekpunten van de Portal. Als grotere nauwkeurigheid gewenst is, kan je hierna nog testen op de hoekpunten van de portal, maar meestal is de BoundingSphere-test meer dan voldoende. In de Engine worden spiegels en monitoren gedefinieerd als een extentie van de klasse Portal, met dit verschil dat daarbij de Stencil-buffer zal gebruikt worden om de scene achter de spiegel of monitor correct te renderen. In het geval van een spiegel wijst de portal gewoon naar zijn eigen SectorManager. Portals zijn steeds rechthoeken maar met een minimum aan creativiteit hoeft dat geen beperking te zijn. Je kan immers zonder probleem geometrie toevoegen aan de scene die een deel van de portal overlapt om een niet rechthoekige doorgang te simuleren. De Stencil-buffer gebruiken kan ook bijzonder nuttig zijn om te renderen naar een willekeurige vorm (bijvoorbeeld voor een ronde spiegel). Figuur 4.4: Een portal, met textuur, dat dienst doet als een spiegel Spiegels Zoals vermeld, zijn spiegels portals die wijzen naar eenzelfde SectorManager, maar hoe slagen we erin de Sector aan de andere kant van de spiegel correct te renderen? De sector moet immers gespiegeld worden naargelang de oriëntatie en plaatsing van de spiegel, hiervoor moet de ModelView-matrix aangepast worden d.m.v. een reflectie matrix. Een spiegel toevoegen aan een scene gebeurt als volgt: Portal mirror ; mirror = new Mirror (new Vertex (135, 9, 120), new Vertex (135, 9, 120), new Vertex ( 120, 9, 120), new Vertex ( 120, 9, 120)); mirror. setotherside ( mysector ) ;

35 In deze engine werd ervoor gekozen dat een spiegel nooit andere spiegels zal renderen. Een spiegel in een spiegel zal gerenderd worden als een zwart vlak. Berekening van de reflectie-matrix Een reflectie over de vlakken gevormd door het orthogonaal assenstelsel in world-space is bijzonder eenvoudig, we hoeven hiervoor enkel maar de assen te inverteren. Een reflectie over het XZ-vlak wordt bijvoorbeeld gegeven door de ModelView-matrix te vermenigvuldigen met volgende matrix (we inverteren gewoon de Y-coördinaten): M = Uiteraard is dit niet direct bruikbaar, aangezien we wensen te spiegelen over een willekeurig vlak. De reflectie over een willekeurig vlak met normaal n wordt gegeven door: (n.x) 2 + (n.y) 2 + (n.z) 2 2 (n.x) (n.y) 2 (n.x) (n.z) 0 2 (n.x) (n.y) (n.y) M = 2 + (n.x) 2 + (n.z) 2 2 (n.y) (n.z) 0 2 (n.x) (n.z) 2 (n.y) (n.z) (n.z) 2 + (n.x) 2 + (n.y) Deze methode verwacht wel dat de spiegel in de oorsprong van het assenstelsel staat, we moeten dus het assenstelsel verplaatsen naar de positie p van de spiegel, daar reflecteren en dan terug translateren naar de oorspronkelijke oorsprong. De volledige reflectie matrix wordt m.a.w. gegeven door: REFL = p.x p.y p.z M p.x p.y p.z Wanneer we de SectorManager achter de spiegel willen renderen, volstaat het de ModelViewmatrix te vermenigvuldigen met de matrix REFL. De methode public void reflectcamera ( Plane plane ) van de klasse Mirror, maakt die implementatie reeds. Dynamische spiegels Roterende spiegels worden ondersteund in Libera, hiervoor moet elke frame de reflectionmatrix opnieuw berekend worden. De methode public void rotatemirror ( f l o a t angle ) ; van de klasse Mirror, maakt dit mogelijk.

36 4.3.2 Monitoren Een monitor wordt op net dezelfde manier gerenderd als een normale portal maar de stencilbuffer wordt gebruikt om te zorgen dat er enkel gerenderd wordt naar het gewenste oppervlak, niet daarbuiten. Een monitor toevoegen aan een scene gebeurt als volgt: Portal monitor ; monitor = new Monitor (new Vertex (247.5 f, 10, 380), new Vertex (247.5 f, 100, 380), new Vertex (247.5 f, 10, 280), new Vertex (247.5 f, 100, 280)); monitor. setotherside ( mysector ) ; 4.4 RenderEngine De klasse RenderEngine is het hart van Libera. Het staat in voor de zichtbaarheidsbepaling van SectorManagers, het efficiënt renderen van Sectoren maar ook camera-management gebeurt hier en verschillende parameters (fullscreen, wireframe, viewportsize, etc...) om de render-pipeline aan te passen staan hier gedefinieerd. RenderEngine beschikt over een lijst waar de SectorManagers in de scene opgeslaan staan, aan de hand daarvan (en de positie van de camera in de scene ) zullen gedurende de render-fase de correcte sectormanagers gerenderd worden. De Render-fase bestaat uit volgende componenten: Pre-render fase Hierin wordt de root-sector (de sector waar de camera zich in bevindt en van waaruit de zichtbaarheid van andere portals zal bepaald worden) gezocht en zal berekend worden welke SectorManagers zichtbaar zijn vanuit de root-sector. Ook zal voor elke sector een unieke Frustum bepaald worden om zoveel mogelijk te kunnen cullen. Render fase De zichtbare SectorManagers worden gerenderd, evenals de SceneGraphs, gebruikmakende van de Frustums die bepaald werden in de pre-render fase. De SectorManagers worden front-to-back gerenderd (startende vanuit de root-sector) om zo veel mogelijk te vermijden dat gerenderde sectoren later toch overtekend worden door andere. Post-render fase Dit is een post-processing fase en een ideale plaats om post-process effecten zoals Bloom, HDR-lighting of Motionblur te implementeren Bepalen root-sector Als gebruiker moet je steeds een initiële root-sector opgeven, aan de hand van die Sector zal gedurende het verloop van het programma de root-sector eventueel geüpdatet worden. De start-sector vastleggen gebeurt d.m.v. volgende methode van RenderEngine: public void setroot ( SectorManager sector ) ;

37 Gedurende de pre-render fase zal steeds getest worden of de camera zich in een andere sector bevindt, dit gebeurt als volgt (pseudo-code): for ( allportalsintherootsector ) // Transform camera into Local space of the sector. Vector3 pos = Matrix4. mult ( inversetransformmatrix, camerapos ) ; // I f the camera i s within the boundaries of a Portal i f ( currentportal. pointinbox ( pos )) // Set the rootsector to the SectorManager on the otherside // of the Portal. root = tempportal. otherside ; Onthoud dat SectorManagers allemaal gedefinieerd staan in een eigen assenstelsel, dus het is belangrijk in te zien dat hier de camerapositie (die in World-space gedefinieerd staat) steeds getransformeerd moet worden naar de Local-space van de RootSector, anders heeft die vergelijking, wiskundig gezien, geen zin. Zoals vermeld in SectorManager (p.25) beschikt elke SectorManager over een TransformatieMatrix T, die bepaalt waar die sector uiteindelijk zal komen in World-space. Om de camera te transformeren naar de Local-space van de sector volstaat het de camera-positie te vermenigvuldigen met de inverse van de tranformatie-matrix van die specifieke SectorManager: (local x, local y, local z, 1) = (world x, world y, world z, 1) T Zichtbaarheidsbepaling Vanuit de root-sector wordt elke frame bepaald welke Sectoren zichtbaar zijn, op een manier die het midden houdt tussen een iteratief en recursief algoritme (pseudocode): //Een belangrijke counter die ervoor zal zorgen dat // zelfde sectormanagers niet meer dan eenmaal geevalueerd //worden int counter = 0; // Verwijder a l l e Sectoren uit de render l i j s t. rendercontainer. clear ( ) ; //Voeg de root sector toe ( daar beginnen we immers te renderen ). rendercontainer. add( root ) ; while ( counter < rendercontainer. s i z e ()) //Neem de eerstvolgende niet geevalueerde SectorManager. current = rendercontainer. get ( counter ) ; for ( allportalsinthecurrentsector ) //Test de zichtbaarheid

38 i f ( Portal v i s i b l e ) //Als de SectorManager gelinkt aan de portal zichtbaar // is, bereken dan zijn frustum. CalculateFrustum ( ) ; rendercontainer. add( current ) counter++; Berekenen bounding-box en view-frustum Aangezien je door een portal maar zelden de sector, aan de andere zijde, volledig kan zien, is het nuttig moeite te doen de scene achter een portal zoveel mogelijk te cullen. Achterliggende SectorManagers zullen we dus cullen d.m.v steeds kleiner wordende Frustums, naargelang de grootte van de portals, zoals afgebeeld in figuur 4.5 (p.36) Figuur 4.5: Frustums voor portals Hiervoor bepalen we voor elke SectorManager die gerenderd wordt een Frustum die de portal in kwestie zo goed mogelijk zal omsluiten. In wat volgt zal ik beschrijven hoe je eenvoudig zo een frustum kan vinden. In grote lijnen gaat het algoritme als volgt: 1. Zoek de coördinaten van de portal in ScreenSpace. 2. Bepaal de boundingbox van de portal in ScreenSpace.

39 3. Un-project de hoekpunten van de boundingbox zodat we een Frustum verkrijgen in World-space die door de boundingbox wordt bepaald. 4. Gebruik de verkregen Frustum om de SectorManager te renderen. Coördinaten in Screen-space De hoekpunten van een portal zijn gekend, we transformeren die sequentieel van World-space naar Camera-space en Clip-space (P is de ProjectionMatrix, MV is de ModelviewMatrix): (clip x, clip y, clip z, clip w ) = (worldx, world y, world z, clipw) MV P Aan de hand van de Clipspace-coördinaten kunnen we de Screen-Space coördinaten als volgt verkrijgen: Sla de kenmerken van de viewport op: glgetfloat (GL11.GL VIEWPORT, buffer ) ; Normaliseer de coördinaten: Bereken Screen-space coordinaten: ndc = (clip x /clip w, clip y /clip w, clip z /clip w ) f l o a t w = buffer. get (2) 0.5 f ; f l o a t h = buffer. get (3) 0.5 f ; f l o a t x = ( ndc. x + 1.0F) w + buffer. get ( 0 ) ; f l o a t y = ( ndc. y + 1.0F) h + buffer. get ( 1 ) ; (a) Een portal in Screen-Space (b) Boundingbox van de portal Figuur 4.6: Boundingbox van een portal Na uitvoering van dit algoritme verkrijgen we de portal in ScreenSpace (zie figuur 4.8(a) (p.42)), echter voor het bepalen van een Frustum hebben we een rechthoek nodig. Er rest ons enkel nog de BoundingBox van de Portal in Screen-space te zoeken (zie figuur 4.8(b) (p.42)):

40 BoundingBox [ 0 ]. x = Math.max( screenspace [ 0 ]. x, screenspace [ 2 ]. x ) ; BoundingBox [ 0 ]. y = Math. min( screenspace [ 0 ]. y, screenspace [ 1 ]. y ) ; BoundingBox [ 3 ]. x = Math. min( screenspace [ 1 ]. x, screenspace [ 3 ]. x ) ; BoundingBox [ 3 ]. y = Math.max( screenspace [ 2 ]. y, screenspace [ 3 ]. y ) ; BoundingBox [ 1 ]. x = BoundingBox [ 3 ]. x ; BoundingBox [ 1 ]. y = BoundingBox [ 0 ]. y ; BoundingBox [ 2 ]. x = BoundingBox [ 0 ]. x ; BoundingBox [ 2 ]. y = BoundingBox [ 3 ]. y ; Aan de hand van die coördinaten kunnen we de Frustum als volgt bepalen (we maken hierbij gebruik van GLU): GL11. glmatrixmode (GL11.GL PROJECTION) ; GL11. glloadidentity ( ) ; GLU. glupickmatrix (x, y, width, height, view ) ; GLU. gluperspective (60.0 f, viewportw/viewporth, nearplane, farplane ) ; 4.5 ShadowRenderer Het is mogelijk met elke SectorManager een unieke (optionele) ShadowRenderer te gebruiken, dit omdat bepaalde schaduw-technieken beter geschikt zijn voor bepaalde scenes dan andere. Een ShadowRenderer toevoegen aan een scene is bijzonder eenvoudig gehouden en gaat doorgaans als volgt: ShadowRenderer s r = new ShadowVolumeZFailRenderer ( ) ; sr. addlight ( lightnode ) ; sr. addlight ( lightnode2 ) ; sector. setshadowrenderer ( sr ) ; De ShadowRenderer zal aan de hand van de MovableObjects in zijn SceneGraph en de meegegeven Lights schaduwen renderen in de scene. Standaard zullen de meeste objecten schaduwen casten (behalve Particles) maar je kan, voor performantie redenen, een object eenvoudig uitsluiten door de volgende methode van MovableObject: public void setshadowcaster ( boolean value ) ; Objecten die geen shadowcaster zijn, zullen geen schaduwen casten, maar kunnen wel schaduwen ontvangen van andere objecten. Schaduwen renderen voor de Sector zelf is eveneens mogelijk, maar dit wordt enkel ondersteund door de ShadoRenderers die gebruik maken van Shadow Mapping. Zie onder voor een opsomming van de beschikbare ShadowRenderers in Libera. ShadowRenderer abstraheert het renderen van schaduwen, wat het erg eenvoudig maakt andere ShadowRenderers toe te voegen. Momenteel zijn volgende ShadowRenderers geïmplementeerd: Shadow Volumes d.m.v. Z-pass methode Shadow Volumes d.m.v. Z-fail methode (ook gekend als Carmack s reverse)

41 Shadow Mapping voor Spotlights Shadow Mapping voor Directional Lights Cubic Shadow Mapping voor Pointlights Soft-shadows via een PCF Voor een gedetailleerde beschrijving van deze Shadowrenders verwijs ik je graag naar Realtime shadow rendering (p.96). 4.6 SceneNode Alle elementen in een scene die geen deel uitmaken van de statische level-data worden in de RenderEngine geordend in een Scene-Graph. Het gebruik van een scene-graph heeft grote voordelen voor het renderen van complexe, dynamische omgevingen. Zo is het mogelijk een Object te linken aan een ander Object, zoals een wapen of licht aan een personage, die mee zullen bewegen wanneer dat personage verplaatst wordt. Een Scene-graph is een boomstructuur waarin SceneNodes opgeslaan staan die in moederdochter relatie staan met elkaar, aan elke SceneNode wordt een MovableObject gelinkt die gerenderd wordt wanneer de SceneNode zichtbaar is. Wanneer op een SceneNode een transformatie wordt toegepast (translatie, rotatie, schaling) zal die ook toegepast worden op alle kinderen van die SceneNode. Elke SectorManager beschikt standaard over een root-scenenode waaraan andere SceneNodes kunnen gelinkt worden. Elke SceneNode houdt zijn translatie, rotatie, schaling bij en zal bij het renderen van de SceneNode het assenstelsel zodanig aanpassen dat de voorwerpen correct gerenderd worden. Een voorbeeld van een Scene-graph in de RenderEngine zou er kunnen uitzien zoals in figuur 4.7 (p.40) Merk op dat het in de RenderEngine niet mogelijk is items te renderen zonder die aan de SceneGraph van een SectorManager toe te voegen MovableObject Zoals vermeld, kunnen enkel MovableObjects in SceneNodes opgeslaan worden, misschien behoeft dat concept wat verduidelijking vooraleer verder te gaan. Een MovableObject is een abstrahering van wat een voorwerp in een 3D-scene kan zijn en wordt gedefinieerd door de abstracte klasse MovableObject. Door die abstrahering is het mogelijk willekeurig wat in de SceneGraph op te slaan en die toch correct te cullen tegen de view-frustum. Momenteel zijn volgende MovableObjects geïmplementeerd (meer uitleg voor elk van hen is te vinden in de respectievelijke hoofdstukken), maar niets houd je tegen MovableObject naar believen uit te breiden met extra implementaties (bijvoorbeeld een Spline- of Bezier-curve renderer). Billboard Een billboard is een rechthoek met een textuur die steeds gericht zal staan naar de camera.

42 Figuur 4.7: Een voorbeeld van een SceneGraph Dynamic clothing Een MovableObject dat dynamische stof in real-time zal renderen. Entity Een entity is een 3D-mesh waaraan een material wordt gehangen. Particle system Rendert particles zoals: vuur, regen, rook, fonteinen,... Light Een licht-object Renderen van de SceneGraph Bij het renderen van deze SceneGraph zal de boom diepte-eerst, recursief doorlopen worden en bij elke SceneNode een correcte assenstelsel-transformatie doorvoeren vooraleer de MovableObject (en zijn kinderen) te renderen. Na renderen wordt de Matrix-stack hersteld. Het renderen van de afgebeelde SceneGraph gaat als volgt: [Start in Root-SceneNode] //Evalueer kind1 van root glpushmatrix(); gltranslatef(20, 0, 0); Object1.render(); //Evalueer kind1 van Object1 glpushmatrix(); gltranslatef(1, 0, 20); glrotatef(90, 0, 1, 0);

43 Object3.render(); glpopmatrix(); //Restore Matrixstack //Evalueer kind2 van Object1 glpushmatrix(); glscalef(2, 2, 2); Object4.render(); //Evalueer kind1 van Object4 glpushmatrix(); gltranslatef(0, 10, 0); Object5.render(); glpopmatrix(); //Object5 done glpopmatrix(); //Object4 done glpopmatrix(); //Object1 done //Evalueer kind2 van root glpushmatrix(); Object2.render(); glpopmatrix(); Merk op dat intern de volgorde van transformatie steeds als volgt gaat 1. Scale 2. Rotate 3. Translate Dit heeft één belangrijke implicatie: voorwerpen kunnen enkel rond hun lokale oorspong geroteerd worden, niet rond een willekeurig punt! Uiteraard is dit niet gewenst en een goede oplossing hiervoor is gebruik maken van Pivot -SceneNodes. Pivot-nodes zijn SceneNodes waar een null-object wordt meegegeven als MovableObject en die dus enkel dienen om een transformatie van het assenstelsel door te voeren. Stel dat je een kubus wilt laten roteren rond een bol (op positie (50, 10, 0)) in het XZ-vlak met een straal van 10 eenheden, dan kan je als volgt tewerkgaan: SceneNode pivot ; SceneNode cubenode ; SceneNode spherenode ; MovableObject cube, sphere ; public void setupscene () cube = new Entity (cubemesh, cubematerial ) ; sphere = new Entity ( spheremesh, spherematerial ) ; //Geef een null object om een lege SceneNode te // creeren.

44 spherenode = new SceneNode ( sphere, sphere ) ; spherenode. translate (new Vector3 (50, 10, 0 ) ) ; pivot = new SceneNode ( pivot, null ) ; spherenode. getrootscenenode ( ). addchildnode ( pivot ) ; cubenode = new SceneNode ( cube, cube ) ; cubenode. translate (new Vector3 (0, 0, 10); pivot. getrootscenenode ( ). addchildnode ( cubenode ) ; public void update () // Roteer het punt elke frame. pivot. rotatey (0.01 f ) ; (a) Zonder pivot-node (b) Met pivot-node Figuur 4.8: SceneGraph met en zonder pivot-node Culling van SceneNodes Om de SceneNodes correct te cullen moeten extra maatregelen genomen worden: een transformatie van het assenstelsel verplaatst immers enkel de oorsprong maar verandert niet de eigenlijk waarden van de vertices van het object in kwestie. Om een SceneNode correct te cullen tegen de View-Frustum moeten dus de coördinaten van de vertices van dat object gekend zijn in de ruimte waar die Frustum gedefinieerd is, in local-space van de bijhorende SectorManager. Omdat voor objecten met veel vertices het erg duur zou zijn bij elke transformatie van het assenstelsel de waarden van de vertices aan te passen en elke vertex tegen de Frustum te testen kunnen we, wanneer we een (meestal minimale) graad van onnauwkeurigheid toelaten, voor elk object een BoundingBox definiëren en enkel de (acht) hoekpunten van de BoundingBox te transformeren en die testen tegen de Frustum. Een BoundingBox voor een 3D-object is een balk die het object in kwestie zo goed mogelijk omvat, in Libera wordt de BoundingBox voor een Entity als volgt bepaald (bepaling van de BoundingBox kan aangepast worden voor elk soort MovableObject): public BoundingBox getboundingbox () int numberofverts = getsubmesh ( ). vertices. capacity () / 3;

45 SubMesh submesh = getsubmesh ( ) ; f l o a t sx = 15000; f l o a t sy = 15000; f l o a t sz = 15000; f l o a t lx = 15000; f l o a t ly = 15000; f l o a t lz = 15000; Vector3 center = new Vector3 ( ) ; //Bereken het centrum van het object. for ( int i = 0; i < numberofverts ; i++) // Add the current vertex to the center variable Vector3 temp = new Vector3 (submesh. vertices. get ( i 3), submesh. vertices. get (( i 3)+1), submesh. vertices. get (( i 3)+2)); i f (temp. x < sx ) sx = temp. x ; i f (temp. y < sy ) sy = temp. y ; i f (temp. z < sz ) sz = temp. z ; i f (temp. x > lx ) lx = temp. x ; i f (temp. y > ly ) ly = temp. y ; i f (temp. z > lz ) lz = temp. z ; Vector3 v1 = new Vector3 ( sx, sy, sz ) ; Vector3 v2 = new Vector3 ( lx, ly, lz ) ; Vector3 d i f f = Vector3. Minus(v2, v1 ) ; center = Vector3.Add(v1, Vector3. DevideByScalar ( diff, 2)); f l o a t w = Math. abs ( center. x lx ) ; // breedte f l o a t h = Math. abs ( center. y ly ) ; // hoogte f l o a t d = Math. abs ( center. z lz ) ; // diepte f l o a t radius = w; // Straal van BoundingSphere i f (h > radius ) radius = h ; i f (d > radius ) radius = d ; // Pythagoras radius = ( f l o a t )Math. sqrt ( radius radius + radius radius ) ; return new BoundingBox( new Vector3 ( center. x w, center. y + h, center. z + d), new Vector3 ( center. x + w, center. y h, center. z d), center, radius ) ;

46 Om de Frustum-test nog wat te versnellen, kunnen we een Bounding-sphere (een bol die het object zo goed mogelijk omvat) definiëren voor elk object en eerst testen tegen de BoundingSphere vooraleer te testen op de BoundingBox. Een bol testen tegen de Frustum is een stuk goedkoper (we hoeven maar 1 punt te testen: het middelpunt van de bol) maar ook een stuk onnauwkeuriger (vooral voor lange, smalle objecten), dus we kunnen het Frustum-culling algoritme als volgt aanpassen (pseudocode): // First test the boundingsphere i f ( BoundingSphereInFrustum () == true ) // I f sphere visible, test the box i f ( BoundingBoxInFrustum () == true ) renderobject ( ) ; e l s e // I f the BoundingSphere isn t visible, the boundingbox w i ll certainly //not be visible, so stop testing the object. Voor zichtbare objecten testen we nu telkens tegen 9 punten (1 voor de bol, 8 voor de box) maar voor scenes met veel objecten behoeft het geen betoog dat deze manier van Frustum culling een snelheids-winst levert. (a) Bounding box (b) Bounding sphere (c) Bounding boxes in een scene Figuur 4.9: Boundingboxen en BoundingSphere Omdat voor elke SectorManager in een scene een unieke Frustum wordt bepaald, kunnen objecten achter een portal erg goed geculled worden, zodat opnieuw enkel objecten gerenderd en geüpdatet worden (erg nuttig bijvoorbeeld relatief dure update-operaties voor Particle engines en Dynamic clothing) wanneer ze effectief zichtbaar zijn door de portal. 4.7 Collision detection Collision detection wordt niet ondersteund door de RenderEngine, maar de mogelijkheid die toe te voegen is er en het lijkt me nuttig hier even bij stil te staan. Reeds bij het ontwerp van de architectuur werd immers in het achterhoofd gehouden hoe we Frustum-culling

47 en collision-detection zouden kunnen afhandelen. SectorManagers staan allen gedefiniëerd in een eigen, lokaal, assenstelsel en collision-detection doen op de world-coördinaten van de vertices van het level zal dus niet lukken (SectorManagers worden immers gerenderd relatief t.o.v zijn portals en eventuele andere SectorManagers). Om Frustum culling tot een goed einde te brengen zal, zoals ik reeds heb uitgelegd, de frustum steeds getransformeerd worden naar het lokaal-assenstelsel van de SectorManager in kweste en daar bepalen welke zaken al dan niet zichtbaar zijn. Voor collision detection zullen we gelijkaardig te werk gaan. Om collision detection voor de camera te doen, bijvoorbeeld om te vermijden dat door muren kan gelopen worden, volstaat het dus de camera-positie te translateren naar het lokaalassenstelsel van de SectorManager in kwestie en daar testen of er collision gebeurd is. Collision-detection is meestal enkel nodig in de root-sector, de architectuur van de RenderEngine laat dus toe om grote delen van een level zowiezo al te schrappen voor het testen van collisions zonder bijkomende tests, wat uiteraard een enorme troef is. De architectuur laat bovendien toe een collision-manager te schrijven specifiek voor een bepaalde SectorManager: collision-detection in een BSP-sector gebeurt anders dan in een Octree-sector. Zo kan je niet alleen collision-detection beperken tot één bepaalde SectorManager (zoals je root-sector) maar ook steeds de meest efficiënte collision-methode gebruiken voor je scene. Collision detection zou dan in grote lijnen als volgt kunnen gebeuren: Er komt een aanvraag voor camera-verplaatsing in de RenderEngine. Transformeer camera naar Object-space van de root-sector. Geef het gewenste doel aan de CollisionManager van de root-sector een check of er een collision zal optreden. De CollisionManager geeft een nieuwe camera-positie terug (bij collision kan dit hetzelfde zijn als de input). Transformeer camera-positie terug naar world-space en plaats de camera in je scene door je ModelView-matrix desgewenst aan te passen. 4.8 Code-voorbeelden Een scene maken doe je het meest eenvoudig door het overerven van de abstracte klasse AbstractApp. In dit voorbeeld zal een eenvoudige BSPSectorManager gebruikt worden, met een kubus, een Skybox en een ShadowRenderer, die via een portal gelinkt wordt aan een OctreeSectorManager. Dit is alles om een scene te renderen, de RenderEngine zal alles correct afhandelen achter de schermen! public class MyFirstApp extends AbstractApp //De BSP SectorManager private BSP SectorManager f i r s t ;

48 //De Octree SectorManager private Octree SectorManager second ; //De skybox SkyBox skybox ; //Een.OBJ f i l e lader. OBJLoader loader ; //De Entity dat onze kubus zal voorstellen. private Entity cube ; //De SceneNode voor onze kubus. SceneNode cubenode ; //Een l i c h t Light l i g ht ; //Een SceneNode voor het l i c h t. SceneNode lightnode ; //De ShadowRenderer ShadowRenderer s r ; public MyFirstApp( String str ) super ( str ) ; / In de engine wordt een verschil gemaakt tussen het i n i t i a l i s e r e n van de data en het opbouwen van de scene. Dit omdat het dan mogelijk i s de data van een sector op voorhand te laden, maar de opbouw van de sector en het plaatsen van de objecten in een latere fase te doen. / public void initdata () //Onze materialen. MaterialManager. getsingleton ( ). parsematerialfiles ( materials /shadowvolume. material ) ; //Maak een skybox skybox = new SkyBox (6000); //Maak een zacht wit l i c h t. l i g h t = new Light (GL11.GL LIGHT0, new Vector3 (0.8 f, 0.8 f, 0.8 f ), new Vector3 (0.8 f, 0.8 f, 0.8 f ) ) ; public void setupscene () //Maak een nieuwe BSP SectorManager, en laad de map met naam map2 // in het. config bestand. Zie het onderdeel over Config bestanden, //voor meer uitleg hierover. f i r s t = new BSP SectorManager ( map2 ) ; //Maak een nieuwe Octree SectorManager. second = new Octree SectorManager ( sa2 ) ;

49 loader = new OBJLoader ( ) ; // Laad de kubus. cube = new Entity ( loader. load ( models/cubenormals. obj ), MaterialManager. getsingleton ( ). getmaterial ( Brick ) ) ; ///// // Belangrijk : dit i s noodzakelijk voor Z Fail Stencil Shadows! // Zie hiervoor het onderdeel over Shadow Volumes. ///// cube. replaceedgeswithtriangles ( ) ; //Maak een SceneNode voor de kubus. cubenode = new SceneNode ( cube, cube ) ; // Plaats hem in te scene cubenode. translate (new Vector3 (75, 30, 0 ) ) ; //Voeg hem toe aan de SceneGraph van f i r s t f i r s t. getrootscenenode ( ). addchildnode ( cubenode ) ; //Een SceneNode voor het l i c h t lightnode = new SceneNode ( l i g h t, l i g ht ) ; // Plaats hem in de scene lightnode. translate (new Vector3 (100, 50, 0 ) ) ; //Update ed worldposition ( belangrijk voor lichten ) lightnode. updateworldposition ( ) ; //Hang de portal met index 0 in f i r s t aan de portal //met index 0 in second. f i r s t. link ( second, 0, 0); // Plaats de camera in de scene engine. positioncamera (100, 50, 100, 20, 50, 0, 0, 1, 0); //Bepaal de root sector. engine. setroot ( f i r s t ) ; //Maak een Z Fail schaduw renderer. s r = new ShadowVolumeZFailRenderer ( ) ; //Voeg lichten toe. sr. addlight ( lightnode ) ; //Hang aan f i r s t. f i r s t. setshadowrenderer ( sr ) ; public void render () engine. clear ( ) ; engine. positioncamera ( ) ; skybox. render ( engine. camera ) ; engine. render ( ) ;

50 Figuur 4.10: Een voorbeeld scene De gemaakte scene ziet er uit als in figuur 4.10 (p.48) Wens je meer invloed op de scene, is het beter een reeds geïmplementeerde SectorManager uit te breiden en die te gebruiken in je applicatie, als volgt: Create eigen BSPSectorManager: public class MyBspSectorManager extends BSP SectorManager SkyBox skybox ; Entity cube ; SceneNode cubenode ; OBJLoader loader ; public MyBspSectorManager( String name) super (name ) ; //Maak een cube en hang aan de scenegraph. cube = new Entity ( loader. load ( models/cubenormals. obj ), MaterialManager. getsingleton ( ). getmaterial ( Brick ) ) ; cubenode = new SceneNode ( cube, cube ) ; getrootscenenode ( ). addchildnode ( cubenode ) ;

51 Gebruik de SectorManager in je applicatie: public class DemoApp extends AbstractApp public SectorManager sector ; SkyBox skybox ; public DemoApp( String str ) super ( str ) ; public void initdata () FontManager. buildfont ( data/font tahoma. png, 7); skybox = new SkyBox (5000); angle = 0.05 f ; public void setupscene () //Gebruik je gewijzigde BSP SectorManager zoals voorheen. sector = new MyBspSectorManager( Hall ) ; engine. positioncamera (0, 100, 450, 0, 100, 0, 0, 1, 0); engine. setroot ( sector ) ; public void render () engine. clear ( ) ; engine. positioncamera ( ) ; engine. render ( ) ;

52 Hoofdstuk 5 Resource-framework 5.1 Inleiding Een absolute noodzaak in een volwassen RenderEngine is een manier om efficiënt je resources (Texturen, Shaders, 3D-meshes,...) te beheren. Texturen en meshes worden meestal vele keren opnieuw gebruikt in een scene, het is uiteraard wenselijk dat we de data maar éénmaal in het geheugen lezen en werken met instanties van die data in de applicatie. De enige manier om dit goed te doen is door de data centraal te beheren; in Libera is dat de taak van de Texture-manager en Material-manager. 5.2 Texture-manager We onderscheiden twee belangrijke klassen: Texture en TextureManager. Ze verhouden zich tegenover elkaar zoals een Sector en SectorManager dat doen: Texture bevat de kenmerken van een Textuur (breedte, hoogte, bits-per-pixel,...), TextureManager doet het beheer van texturen zoals laden of binden van een textuur De klasse Texture Texture is een eenvoudige klasse die voor elke ingeladen textuur een aantal kenmerken bijhoudt, zoals de dimensies van de afbeelding en het formaat waarin die werd opgeslaan. De open-source image-bibliotheek DevIL wordt gebruikt om een Texture in te laden, dus een grote variatie van formaten wordt ondersteund. Voor een lijst van de ondersteunde formaten verwijs ik je graag naar de officiele site van DeVIL ( De klasse TextureManager De texture-manager beheert de texturen in een HashMap datastructuur. Een Hashmap is performant, het voert in constante tijd zijn get() en put() operaties uit, maar is vooral handig omdat aan een Textuur een naam gelinkt kan worden waarmee die later kan geïdentificeerd worden. Bij het inladen van een Textuur dient die identificatie opgegeven te worden, dikwijls is dit het file-path naar het bestand. Aan de hand van die naam zal gekeken worden of het bestand reeds in de HashMap voorkomt, als dat het geval is, wordt de aanvraag de textuur in te laden verworpen, zonder dat de gebruiker hier iets van merkt. 50

53 De texture-manager maakt gebruik van de Lightweight-pattern en er is te allen tijde maar één instantie beschikbaar in je programma-scope en die opvragen gebeurt op volgende manier: TextureManager. getsingleton ( ) ; Intern gebeurt het volgende: TextureManager instance ; public s t a t i c TextureManager getsingleton () i f ( instance==null ) instance = new TextureManager ( ) ; return instance ; Handmatig een textuur inladen gebeurt d.m.v. volgende methode: TextureManager. getsingleton ( ). load ( String ID, String Path, boolean clamp ) ; Merk op dat dit volledig geautomatiseerd kan gebeuren door gebruik te maken van Materialscipts, Material-scripts (p.52). Een textuur opvragen, gebeurt op volgende manier: TextureManager. getsingleton ( ). gettexture ( String ID ) ; Een textuur binden om het te gebruiken als texturemap voor een object, doe je als volgt: TextureManager. getsingleton ( ). gettexture ( String ID ). bind ( ) ; 5.3 Materials Een Material staat een trapje hoger dan een Texture en laat toe met groter detail en flexibiliteit te beschrijven hoe een object zal gerenderd worden. Een Material kan zich beperken tot een enkele textuur, maar het kan evengoed een mesh renderen d.m.v. multitexturing, geanimeerde texturen en vertex- en/of pixel-shaders. Die kenmerken worden beschreven in Material-scipts en worden ingeladen bij het laden van je applicatie. Voor meer informatie hierover verwijs ik je naar Material-scripts (p.52). Ook hier wordt weer een onderscheid gemaakt tussen een beschrijvende klasse Material en een beherende klasse MaterialManager Material Een Material houdt de kenmerken van een material bij, dit gaat van rotatiesnelheid en scrollsnelheid (in het geval van geanimeerde texturen) over het al dan niet transparent zijn tot de vertex- en fragment-shader code die moet gebruikt worden bij het renderen van dat specifiek object. Om een Material te kunnen gebruiken, moet die eerst gebonden worden, als volgt:

54 mymaterial. set ( ) ; Hierin zullen eventuele texturen gebonden worden of, wanneer shaders gebruikt worden, shader-programma s geïnitaliseerd worden voor gebruik. Om een Material te updaten (in het geval van een geanimeerde textuur) gebruik: mymaterial. update ( ) ; Na gebruik moet je een Material opruimen door volgende methode: mymaterial. reset ( ) ; MaterialManager Ook de MaterialManager maakt gebruik van de LightWeight-pattern en het opvragen van de instantie gebeurt gelijkaardig aan de TextureManager: MaterialManager. getsingleton ( ) ; Net als de TextureManager zal de MaterialManager de gekende Materials opslaan in een HashMap datastructuur zodat een material nooit meer dan éénmaal wordt geinitialiseerd en eenvoudig kan opgevraagd worden door middel van zijn ID: MaterialManager. getsingleton ( ). getmaterial ( String ID ) ; 5.4 Material-scripts Inleiding Om het mogelijk te maken dat Materials niet hardcoded moeten worden in je programma, en dus extern gewijzigd kunnen worden zonder dat hercompilatie noodzakelijk is, werd het principe van Material scripts toegevoegd aan de engine. Een material-script is een simpel tekst-bestand, dat geparsed en geladen wordt tijdens het opstarten van je applicatie, waarin kenmerken van een material kunnen beschreven worden. Zo een material kan op de klassieke manier gebruikt worden in je applicatie. Materialscripts worden in een bestand geplaatst met extentie.material, dat als volgt wordt geladen: MaterialManager. getsingleton ( ). parsematerialfiles ( dir /mat. material ) ; Na uitvoeren van deze instructie zal de volledige material file geparset worden en zullen de gedefinieerde Materials, indien deze correct worden geformuleerd, ingeladen worden in de MaterialManager voor verder gebruik Voorbeelden Als eerste voorbeeld toon ik een script dat een Material zal aanmaken dat enkel een textuur zal mappen op het voorwerp waar de Material aan gelinkt wordt. Een script bestaat uit twee componenten, de header en body:

55 Header Dit is de naam van je material die zal gebruikt worden door de MaterialManager om de Material te kunnen gebruiken in je applicatie. Body Dit stuk staat tussen accolades en is de beschrijving van je material. Mat1.material //Maak een Material met de naam texwood texwood // Start body // Definieer een textuur door het keyword tex, gevolgd //door de l o c a t i e van de textuur. tex data/wood.bmp //Einde body De material gebruiken op een object in je scene kan als volgt gebeuren (Opmerking: In het volgend code-fragment staat een nieuwe klasse OBJLoader, zie OBJLoader (p.71) voor meer informatie hierover): Entity cube ; OBJLoader loader ; MaterialManager. getsingleton ( ). parsematerialfiles ( mat. material ) ; loader = new OBJLoader ( ) ; cube = new Entity ( loader. load ( models/cubenormals. obj ), MaterialManager. getsingleton ( ). getmaterial ( texwood ) ) ; //Wanneer je deze Entity cube hangt aan een SceneNode zal het model // gerenderd worden met de gewenste material. Merk op dat indien we later zouden beslissen dat de kubus in onze scene geen Hout-textuur mag krijgen maar bijvoorbeeld een Steen-textuur, dan volstaat het de Materialscript aan te passen zonder dat je programma gehercompileerd moet worden. Een iets geavanceerder script zou een Glass -material kunnen zijn waarbij we de textuur halfdoorzichtig maken: Mat2.material //Maak een Material met de naam texglass texglass // Start body // Definieer een textuur tex data/glass.bmp //Blend de textuur door het keyword blend // toe te voegen met twee argumenten a en b. // Intern zal de i n s t r u c t i e glblendfunc (a, b) gebruikt worden. blend GL ONE GL ONE //Einde body

56 Het is mogelijk texturen te animeren d.m.v. een script: Mat3.material ScrollingClouds //Voeg textuur toe tex data/ fog.bmp // Blending blend GL ONE GL ONE //Animeer de textuur : s c r o l l de textuur // 0.2 units in richting en 0.1 units // in Y richting. Elke frame zal de p o s i t i e //van de textuur aangepast worden door de //OpenGL TextureMatrix aan te passen naargelang // die parameters. s c r o l l Mat4.material RotatingMagicStuff //Voeg textuur toe tex data/magic.bmp // Roteer de textuur 0.1 graden. Elke frame zal de textuur // roteren volgens de gegeven hoek. rotate 0.1 //Maak de textuur dubbel zo groot scale 2 2 Scroll x y, rotate x en scale x y kunnen naar believen gecombineerd worden met elkaar. En, last but not least, GLSL-shaders kunnen zonder problemen gebruikt worden in materialscripts. Een model renderen d.m.v. bijvoorbeeld een Cel-shader, gebeurt met volgende material: Mat5.material CelShader //Voeg textuur toe tex data/mytexture.bmp // Definieer de shader door het keyword shader, // gevolgd door het path naar je vertex en fragment shaders. shader cel. vert cel. frag

57 Merk op, in eenzelfde.material file kunnen zonder probleem meerdere scripts gedefinieerd worden, als volgt: AllMyMaterials.material texwood tex data/wood.bmp texglass tex data/glass.bmp blend GL ONE GL ONE ScrollingClouds tex data/ fog.bmp blend GL ONE GL ONE s c r o l l RotatingMagicStuff tex data/magic.bmp rotate 0.1 CelShader tex data/mytexture.bmp shader c e l l. vert c e l l. frag Voor een aantal voorbeelden van hoe dit eruit kan zien in je applicatie verwijs ik je graag naar figuur 5.1 (p.56). 5.5 Shaders GLSL shaders worden volledig ondersteund door de Material-Manager en omdat er relatief weinig geïmplementeerde GLSL-shaders te vinden zijn heb ik me voorgenomen een onderdeel van de tekst te wijden aan een gedetailleerde beschrijving van een aantal geïmplementeerde shaders die meegeleverd worden met de RenderEngine, gaande van (textured) per-pixel lighting over Cel-shading tot Normalmapping. In Libera zijn meer shaders meegeleverd, maar die allemaal beschrijven zou buiten het bestek van deze tekst vallen. Ik verwijs je graag naar de source-code indien je geïnteresseerd bent hoe een aantal effecten, zoals Gooch-shader en Parallax mapping, geïmplementeerd worden in GLSL.

58 (a) Enkel textuur, geen shader (b) Specular Per pixel lighting (c) Per pixel lighting met textuur (d) Geanimeerde vuur-textuur (e) Gooch-shader (f) Cel-shading Figuur 5.1: Eenzelfde model gerenderd d.m.v. verschillende material-scripts in de RenderEngine

59 5.5.1 Introductie tot shaders Om shaders te begrijpen moet je weten hoe de OpenGL pipeline werkt en welke stadia een vertex zal doorlopen vooraleer het een pixel wordt op het scherm. De fixed pipeline ziet er schematisch uit zoals figuur 5.2 (p.57) Figuur 5.2: Schema van de OpenGL pipeline Vertex transformatie Een vertex wordt hier voorgesteld door een aantal kenmerken, zoals: positie, kleur, zijn normaal, textuurcoördinaten, enz... Per vertex worden die attributen als input voor de pipeline gegeven. In deze stage van de fixed-pipeline gebeuren volgende acties: Vertex positie transformatie (Transformatie van Worldspace naar Screenspace) Berekenen belichting op een per vertex basis Generatie en transformatie van textuur coordinaten Samenstellen primitieven en rasterisatie Deze stage krijgt als input de getransformeerde vertices en connectiviteits informatie, dit laatste beschrijft hoe de vertices verbonden zijn met elkaar (lijn, driehoek of polygoon) Rasterisatie zal voor elke primitieve (lijn, driehoek, polygoon) de fragmenten en pixel-posities

60 bepalen, elk fragment beschikt over attributen die de kleur, normaal en textuurcoördinaten zullen opslaan. De output van stage2 in de pipeline bestaat uit twee zaken: De positie van de fragmenten in de frame-buffer De geïnterpoleerde waarden voor de kleur, normaal en textuurcoördinaten van het fragment, berekend uit de vertices van de primitive waar het deel van uitmaakt. Dit laatste puntje is erg goed zichtbaar als volgt: De OpenGL instructie om een gekleurde driehoek op het scherm te tekenen ziet er als volgt uit: //De vertices die hierop volgen maken deel uit van een // Triangle ( c o n n e c t i v i t e i t s informatie! ) glstart (GL TRIANGLE) ; //Vertex1 glcolor3f (0, 255, 0); glvertex3f ( 1, 0, 1); //Vertex2 glcolor3f (255, 0, 0); glvertex3f (0, 1, 1); //Vertex3 glcolor3f (0, 0, 255); glvertex3f (1, 0, 1); glend ( ) ; Elke vertex heeft een andere kleur, het uiteindeljke resultaat na doorlopen van de OpenGLpipeline ziet er uit als in figuur 5.3 (p.58), waarbij de kleurwaarden geïnterpoleerd werden over de fragmenten. Figuur 5.3: Een gekleurde driehoek Texturen en kleuren van fragmenten De input van deze stage zijn de geïnterpoleerde waarden van de fragmenten, de kleurwaarden van de fragmenten kunnen hier gecombineerd worden met een textuur. Ook fog wordt in deze

61 stage toegepast op een fragment. Als eindresultaat krijgen we voor elk fragment een kleurwaarde (Color value) en een dieptewaarde (Depth value). Raster operaties De inputs hier zijn: De locatie van de pixels Depth en Color waarden van de fragmenten Op elk binnenkomend fragment zullen volgende tests toegepast worden, fragmenten waarvoor alle tests slagen, zullen uiteindelijk op het scherm verschijnen als een pixel: Scissor test Alpha test Stencil test Depth test Als blending geactiveerd is, wordt ook deze operatie in deze stage uitgevoerd. Dit alles kan grafisch worden voorgesteld zoals in figuur 5.4 (p.59) Figuur 5.4: OpenGL pipeline, grafisch voorgesteld Shaders laten toe die pixed-function pipeline van OpenGL te vervangen zodat we meer invloed kunnen hebben op hoe vertices en fragmenten zullen gegenereerd worden. Momenteel bestaan twee soorten shaders:

62 Vertex shaders Vertex shaders zullen de Vertex transformatie -stage vervangen, in een vertex shader wordt code geschreven om volgende taken uit te voeren: Vertex transformatie. Onthoud dat we de originele Vertex transformatie -stage vervangen dus we moeten dit zelf doen wanneer we een vertex shader gebruiken! Transformatie en normalisatie (indien nodig) van de Normalen Generatie van textuur coordinaten Berekening per-vertex lighting Kleurberekingen Al deze operaties zijn uiteraard optioneel. De instructie gl Position = vec4 (a, b, c, d ) ; is verplicht, wil je iets te zien krijgen op het scherm tenminste. Fragment shaders Deze worden dikwijls, hoewel niet echt terecht (de operaties worden niet op pixels maar op fragmenten uitgevoerd), ixel-shaders genoemd en zullen de Texturen en kleuren van fragmenten -stage in de pipeline vervangen. Volgende zaken kunnen uitgevoerd worden in een Fragment-shader: Kleur bepaling op een per-pixel basis Berekenen textuurcoördinaten per-pixel Texturen toepassen Fog berekenen Berekenen normalen per fragment voor per-pixel lighting. In een fragment-shader moet steeds het codewoord gl FragColor = vec4 (a, b, c, d ) ; gebruikt worden die de uiteindelijke kleur van het fragment zal vastleggen. Merk op dat het niet nodig is altijd een Vertex- en Fragment-shader te combineren. Wanneer je enkel een Vertex-shader gebruikt, zal de OpenGL fixed-function pipeline de taken van de fragment-shader gewoon overnemen. Onthoud wel dat het niet mogelijk is bijvoorbeeld kleur te berekenen in een vertex-shader maar per-vertex lighting te laten uitvoeren door de fixed-function pipeline, bij het gebruik van een shader word je geacht alle instructies, die de shader vervangt, zelf uit te voeren. Twee uitstekende IDE s voor de ontwikkeling van GLSL-shaders zijn:

63 Shader designer, Typhoon Labs - Rendermonkey, ATI Per-pixel lighting Zoals hierboven beschreven gebeurt in de fixed OpenGL-renderpipeline de belichting op een per-vertex manier. In het geval van flat-shading krijgt de polygoon een egale kleur naargelang zijn oriëntatie, in het geval van smooth-shading wordt de kleurwaarde geïnterpoleerd over de polygoon. Omdat per-vertex lighting staat of valt bij de tessalation van je belichte object (uit hoeveel polygonen die bestaat) en het niet toelaat detail te belichten op een model zonder belachelijk hoge polygon-count, is er de mogelijkheid om belichting te berekenen op een per-pixel basis. Dit zorgt niet alleen voor realistischere belichting, maar is ook minder afhankelijk van de tessalation en laat toe detail te simuleren door gebruik te maken van BumpMapping, NormalMapping of Parallax Mapping. We bekijken een eenvoudige GLSL shader die een model zal renderen zonder textuur en per-pixel diffuse lighting: De diffuse component van invallend licht wordt voorgesteld als in figuur 5.5 (p.61) en stellen we wiskundig voor als: Diffuse = L c M c cos(θ) waarbij L c de kleur van het licht is, M c de kleur van het oppervlak waar het licht op invalt en θ the hoek tussen L (richtingsvector van het licht) en N (de normaal van het oppervlak). Figuur 5.5: Diffuse lighting DiffuseLighting.vert

64 //Varying variables worden gedeeld tussen // vertex en fragment shaders. varying vec3 normal ; varying vec3 lightdir ; void main () // Transformeer de normaal van de binnenkomende vertex //naar Eye space. normal = normalize ( gl NormalMatrix gl Normal ) ; // Belichting in OpenGL gebeurt in Eye space, transformeer //de p o s i t i e van de binnenkomende vertex naar Eye space, //door te vermenigvuldigen met de ModelView matrix, // zodat de v e r g e l i j k i ng wiskundig gezien zin heeft. vec4 worldpos = gl ModelViewMatrix gl Vertex ; //Bereken de richtingsvector van het l i c h t naar de binnen //komende vertex. Vergeet niet te normaliseren! // lightdir i s een varying variabele en kan gelezen worden //door de fragment shader voor verdere afhandeling. lightdir = normalize ( vec3 ( gl LightSource [ 0 ]. position worldpos ) ) ; // Transformeer de vertex naar Screen space. gl Position = ftransform ( ) ; DiffuseLighting.frag varying vec3 normal ; varying vec3 lightdir ; void main () //We berekenen het dot product tussen de normaal en // lightdir, dit levert ons de cosinus van de hoek // tussen de twee vectoren. Precies wat we nodig //hebben voor de berekening van de d i f f u s e belichting. f l o a t cosangle = max( dot ( normal, lightdir ), 0. 0 ) ; // Hier berekenen we de u i t e i n d e l i j k e kleur van het object //We gaan er hier vanuit dat de kleur van het fragment waar // het l i c h t op invalt wit i s. vec4 diffuselight = cosangle //Hoek gl LightSource [ 0 ]. d i f f u s e // Lichtkleur vec4 (1.0, 1.0, 1.0, 1. 0 ) ; // Materiaal kleur // Geef h e t binnenkomende fragment de gewens te k l e u r. gl FragColor = diffuselight ;

65 5.5.3 Per-pixel lighting met specular component Wanneer licht invalt op glanzende oppervlakken zal ze zich complexer gedragen en uit een extra component bestaan naast diffuse lighting, nl Specular Lighting. Dit effect is afhankelijk van zowel de richtingsvector van het invallend licht en de kijkrichting van de camera; het model voor de belichting moet hievoor aangepast worden. Er bestaan twee modellen voor Specular lighting: Phong en Blinn, dat een benaderde, maar snellere versie is van het Phong model. Phong Het Phong model kan grafisch voorgesteld worden zoals in figuur 5.6 (p.63). Hier is R de vector die verkregen wordt door de richtingsvector van het licht te reflecteren over het oppervlak waar het licht op invalt, R wordt als volgt berekend: R = 2N(L N) L E is de vector die vanaf het oppervlak richt naar de positie van de camera in de scene. De uiteindelijke specular-component wordt gegeven door de volgende formule: Specular = (R E) s L c M c waarin L c M c opnieuw respectievelijk de licht- en materiaalkleur zijn, s is een factor die bepaalt hoe reflecterend het oppervlak is en ǫ de hoek tussen E en R. Figuur 5.6: Phong specular lighting Blinn Zoals vermeld is het Blinn-model een benadering voor de bovenstaande methode. Hier is het niet meer nodig de reflecterende licht-vector te berekenen maar we benaderen die door een speciale half-vector H die het midden bepaalt tussen L en E. Het Blinn model kan grafisch voorgesteld worden zoals in figuur 5.7 (p.64). H is een stuk eenvoudiger (en dus sneller) te berekenen dan R: H = L + E

66 Figuur 5.7: Blinn specular lighting De Blinn specular-component wordt nu gegeven door: Specular = (N H) s L c M c Dit is het model dat we zullen gebruiken in onderstaande shader, wat een eenvoudige uitbreiding is van de DiffuseLighting shader, hierboven: SpecularLighting.vert varying vec3 normal ; varying vec3 lightdir ; varying vec3 halfvector ; void main () // Bereken de normaal normal = normalize ( gl NormalMatrix gl Normal ) ; //Bereken vertex p o s i t i e in view space. //!!!ONTHOUD!!! Belichting in OpenGL gebeurt in view space vec4 pos = gl ModelViewMatrix gl Vertex ; //Bereken licht richting naar de vertex lightdir = normalize ( vec3 ( gl LightSource [ 0 ]. position pos ) ) ; //Bereken de half vector. GLSL berekent die reeds z e l f! halfvector = normalize ( gl LightSource [ 0 ]. halfvector. xyz ) ; // Transformeer de vertex naar screen space. gl Position = ftransform ( ) ; SpecularLighting.frag varying vec3 normal ; varying vec3 lightdir ; varying vec3 halfvector ;

67 void main () // Diffuse component van het l i c h t. vec4 diffuselight ; // Specular component van het l i c h t. vec4 specularlight ; //Een constante die de hoeveelheid specular // l i c h t bepaalt. f l o a t shine = 64.0; vec3 n = normalize ( normal ) ; f l o a t cosangle = max( dot (n, normalize ( lightdir )), 0. 0 ) ; //Bereken d i f f u s e diffuselight = cosangle gl LightSource [ 0 ]. d i f f u s e ; //Bereken specular specularlight = pow(max( dot (n, normalize ( halfvector )), 0.0), shine ) gl LightSource [ 0 ]. specular ; //... en combineer de twee waarden voor onze u i t e i n d e l i j k e // belichting. gl FragColor = diffuselight + specularlight ; Met deze eenvoudige shaders die toelaten een model te renderen met per-pixel lighting kunnen we al iets doen. Vergeet echter niet dat een shader het stuk in de OpenGL-pipeline vervangt die onder andere zorgt voor texture-mapping; een model dat met deze shader zal gerenderd worden zal dus geen textuur hebben. We moeten hiervoor zelf support toevoegen in de shader. Dit is eenvoudig, als voorbeeld zal ik de SpecularLighting-shader uitbreiden voor textuur-ondersteuning: TexturedSpecularLighting.vert varying vec3 normal ; varying vec3 lightdir ; varying vec3 halfvector ; void main () // //We wensen de textuur coordinaten te gebruiken vanop layer 0! // gl TexCoord [ 0 ] = gl MultiTexCoord0 ; normal = normalize ( gl NormalMatrix gl Normal ) ; vec4 pos = gl ModelViewMatrix gl Vertex ; lightdir = normalize ( vec3 ( gl LightSource [ 0 ]. position pos ) ) ; halfvector = normalize ( gl LightSource [ 0 ]. halfvector. xyz ) ;

68 gl Position = ftransform ( ) ; TexturedSpecularLighting.frag varying vec3 normal ; varying vec3 lightdir ; varying vec3 halfvector ; // //Een verwijzing naar de gebruikte textuur // uniform sampler2d tex ; void main () vec4 diffuselight ; vec4 specularlight ; f l o a t shine = 64.0; vec3 n = normalize ( normal ) ; f l o a t cosangle = max( dot (n, normalize ( lightdir )), 0. 0 ) ; diffuselight = cosangle gl LightSource [ 0 ]. d i f f u s e ; specularlight = pow(max( dot (n, normalize ( halfvector )), 0.0), shine ) gl LightSource [ 0 ]. specular ; // Indexeer de textuur met de gegeven textuur // coordinaten en steek de kleurwaarde in texel. vec4 texel = texture2d ( tex, gl TexCoord [ 0 ]. st ) ; //Combineer de berekende lichtwaarde met de textuur // kleur. gl FragColor = ( diffuselight + specularlight ) texel ; Cel-shading Cel-shading is een techniek om wat je rendert een wat cartoony uitstraling te geven. De techniek is erg eenvoudig, we zullen fragmenten die belicht worden in een bepaalde hoek dezelfde kleurwaarde geven: CelShading.vert varying vec3 Normal ; void main () // Bereken de normaal

69 Normal = normalize ( gl NormalMatrix gl Normal ) ; // Transformeer de vertex gl Position = gl ModelViewProjectionMatrix gl Vertex ; CelShading.frag varying vec3 Normal ; void main () f l o a t cosangle ; vec4 color ; // Bereken cosinus van de hoek tussen de normaal en // licht richting. cosangle = dot ( vec3 ( gl LightSource [ 0 ]. position ), Normal ) ; //Geef vier mogelijke kleuren naargelang de hoek dat het l i c h t // invalt. i f ( cosangle > 0.95) color = vec4 ( 0. 9, 0. 9, 0. 9, 1. 0 ) ; e l s e i f ( cosangle > 0.5) color = vec4 ( 0. 7, 0. 7, 0. 7, 1. 0 ) ; e l s e i f ( cosangle > 0.25) color = vec4 ( 0. 4, 0. 4, 0. 4, 1. 0 ) ; e l s e color = vec4 ( 0. 2, 0. 2, 0. 2, 1. 0 ) ; //Geef het fragment de u i t e i n d e l i j k e kleur. gl FragColor = color ; Deze shader zal opnieuw geen textuur renderen, ondersteuning hiervoor toevoegen is eenvoudig en volledig gelijklopend met de TexturedSpecularLighting-shader Normal-mapping Wanneer we een klassieke 2D-textuur gebruiken is het niet mogelijk diepte voor te stellen. Een manier om dit effect bereiken, zonder extra geometrie toe te voegen, is normal mapping, waarbij we een extra textuur zullen gebruiken die de oneffenheden van dit oppervlak zal opslaan. Gebruikmakende van die textuur zullen we, op een per-pixel basis, het licht laten weerkaatsen op denkbeeldige onneffenheden in het model zodat de indruk opgewekt wordt dat er veel detail aanwezig is. Om dit te kunnen doen, moeten we voor elke texel van het model de normaal kennen, die informatie slaan we op in die extra textuur, de normal-map. De normal-map berekenen, gebeurt in een pre-processing fase en ziet er meestal uit zoals in figuur 5.9 (p.68). De kleur is typisch voor een Normal-map, de x-, y- en z-coördinaten van de normalen worden respectievelijk opgeslaan in de textuur als rood, groen, blauw. In onze pixel-shader zullen we voor elk binnenkomend fragment de bijhorende normal lezen en aan de

70 Figuur 5.8: Normal mapping hand van die normaal het licht berekenen dat valt op die pixel, via bovenstaande methoden. Hierbij moeten we rekening houden dat in een textuur de kleurwaarden worden beperkt tot het interval [0,1], terwijl de coördinaten van een normaal in het interval [-1,1] liggen. We zullen dus de coördinaten moeten hermappen, als volgt: x = 2.0 (roodwaarde 0.5) y = 2.0 ( groenwaarde 0.5) z = 2.0 (blauwwaarde 0.5) Figuur 5.9: Normal map Vooraleer ik verder ga, moet ik nog een erg belangrijk element aansnijden over Normal Mapping (dit geldt trouwens voor alle texture-based per-pixel render-technieken, zoals Parallax- en Relief-mapping). De normalen in een normal-map liggen opgeslaan in 1 vaste richting. Stel nu eens dat we die normal-map plakken op een vlak dat we plaatsen in een ruimte. Wanneer we dat vlak roteren, bijvoorbeeld rond de Y-as, zouden de normalen ook moeten draaien. Dit gebeurt echter niet aangezien de waarden van de normalen vastgelegd zijn in de normal-map. We moeten dit opvangen, anders zou onze normalmap maar werken in één bepaalde richting. De oplossing hiervoor is de coördinaten van ons vlak te transformeren naar dezelfde ruimte

71 waarin de normal-map definieerd staat: Tangent-space. In de introductie van deze tekst heb ik reeds kort het concept Tangent-space aangehaald maar het lijkt me nuttig hier nog eens op terug te komen: Voor elke vertex ons vlak is een aparte ruimte gedefinieerd, de Tangent space, die bepaalde wordt door drie vectoren: Tangent-, Normal- en BiTangent-vectoren. Als we die drie vectoren in een matrix plaatsen, hebben we een TBN-Matrix (Tangent BiTangent Normal): TBN = T x B x N x 0 T y B y N y 0 T z B z N z als je een punt in Tangent-space hiermee vermenigvuldigt, zal je het transformeren naar Object-space. Wij wensen echter het omgekeerde te doen (onze vertex transformeren van Object-space naar Tangent-space), hiervoor gebruiken we dus simpelweg de inverse van de TBN-matrix. We hebben nu een manier om onze vertices in Tangent-space te plaatsen, maar onze lichtbron staat nog steeds gedefinieerd in World-space. We moeten voor de positie van de lichtbron eveneens dezelfde transformatie doorvoeren. Merk op dat we dus voor elke vertex de TBN-matrix moeten kennen, gelukkig kunnen we dit berekenen in een pre-proces fase en opslaan in ons 3d-formaat. Die berekende TBN-matrices kunnen we hergebruiken zolang we het model enkel translateren in onze 3D-scene, bij een rotatie moeten de TBN-matrices opnieuw berekend worden! Merk op dat hier een optimalisatie mogelijk is: het is eigenlijk niet nodig de TBN-matrix te berekenen voor elke vertex, maar het volstaat dit te doen voor elke polygoon (de vertices van die polygoon zullen eenzelfde TBN-matrix hebben). We kunnen normal-mapping als volgt samenvatten: Bereken de inverse TBN-matrix voor elke polygoon van je model (dit moet enkel herberekend worden bij een rotatie). Bereken de licht-vector en transformeer die naar Tangent-space. Voor elk binnenkomend fragment lees je de normaal uit de normal-map en hermap het naar het interval [-1, 1]. Bereken het licht dat invalt op dat fragment. In de RenderEngine wordt de volgende GLSL-shader gebruikt voor Normalmapping: NormalMapping.vert varying vec2 texcoord ; varying vec3 direction ; uniform vec4 lightpos ;

72 //Onze tangent en bitangent vectoren uniform vec3 tangent ; uniform vec3 bitangent ; void main () // Transformeer vertex naar clip space. gl Position = gl ModelViewProjectionMatrix gl Vertex ; // gebruik texturing met textuurcoordinaten op layer 0. texcoord = gl MultiTexCoord0. xy ; // Bereken licht richting vec3 lightdir = vec3 ( lightpos gl Vertex ) ; // Creeer de TBN matrix mat3 tbnmatrix = mat3( tangent, bitangent, gl Normal ) ; // Converteer l i c h t naar Tangent space. direction = tbnmatrix lightdir ; NormalMapping.frag varying vec2 texcoord ; varying vec3 direction ; //Onze basis textuur uniform sampler2d texturemap ; // Onze normal map uniform sampler2d normalmap ; void main () //Lees onze basis textuur vec4 texcol = texture2d ( texturemap, texcoord ) ; //Lees normaal uit vec3 normal = texture2d (normalmap, texcoord ). xyz ; //Hermap onze normaal naar interval [ 1, 1] normal = ( normal 0.5) 2. 0 ; // Normaliseer de l i c h t r i c h t i n g vec3 lightd = normalize ( direction ) ; //Bereken diffuse component van het l i c h t via de // uitgelezen normaal. vec3 d i f f u s e = vec3 ( dot ( normal, lightd ) ) ; //Bereken textuur kleur met de licht waarde. gl FragColor = texcol vec4 ( diffuse, 1. 0 ) ;

73 5.6 MeshLoader Uiteraard is een 3D-renderer weinig interessant als het niet mogelijk is bestaande 3Dmodellen in een bepaald formaat te importeren in je applicatie. De klasse MeshLoader werd geschreven om die functionaliteit toe te voegen aan Libera. Ik heb geopteerd dit proces volledig te abstraheren zodat het in de toekomst zonder problemen mogelijk is een nieuwe Loader te schrijven voor een bepaald formaat. Momenteel is het enkel mogelijk modellen te importeren die opgeslaan werden in het Wavefront.obj formaat maar door een nieuwe implementate van de abstracte klasse MeshLoader te maken is het mogelijk bijvoorbeeld een.3ds importer te schrijven die volledig compatibel is met het intern formaat van de RenderEngine, meer informatie hierover verder op (Sub)Mesh Intern wordt een 3D-object opgeslaan als een Mesh die een verzameling van SubMeshes kan zijn (dit hangt uiteraard af van hoe je 3D-model is opgeslaan). Een SubMesh houdt de vertices, indices, textuur-coördinaten en normalen bij in Buffers, bij het schrijven van een MeshLoader zal je dus een manier moeten vinden je formaat te vertalen naar het formaat waarin een SubMesh wordt opgeslaan OBJLoader Momenteel is enkel een MeshLoader geïmplementeerd voor modellen in.obj formaat. Obj-files slaan de data gewoon op in tekst-formaat i.p.v. byte-formaat wat zorgt dat dit een intuïtief formaat is dat erg eenvoudig te parsen is. Het laat bovendien toe zowel vertices, indices, normalen en textuurcoördinaten van modellen op te slaan wat het een uitgelezen formaat maakt voor eenvoudige statische modellen Een nieuwe MeshLoader schrijven Zoals vermeld is het perfect mogelijk een nieuwe MeshLoader te schrijven (zoals een Loader voor het 3D Studio Max formaat.3ds) die volledig compatibel blijft met de RenderEngine, het volstaat hiervoor een nieuwe implementatie te maken van de methode public abstract Mesh load ( String path ) ; van de abstracte klasse MeshLoader, als volgt: public class MyLoader extends MeshLoader Mesh mesh ; public MyLoader () super ( ) ; public Mesh load ( String path ) mesh = new Mesh ( ) ;

74 // S c h r i j f hier je code die het gewenste formaat zal parsen //en opslaan in de Mesh, mesh. return mesh ; De loader gebruiken in je applicatie is bijzonder eenvoudig: MovableObject model ; MeshLoader loader ; loader = new MyLoader ( ) ; model = new Entity ( loader. load ( models/amodel. my3dformat ), amaterial ) ; //De gemaakte e n t i t y kan nu doodgewoon aan een SceneNode gehangen //worden en gerenderd worden in je scene. 5.7 FontManager Libera laat toe tekst te schrijven naar het scherm, bijvoorbeeld voor debug-mogelijkheden. Er werd geopteerd hiervoor bitmapped-fonts te gebruiken, omdat die erg flexibel zijn (het is erg eenvoudig later nieuwe lettertypes/-groottes te gebruiker) en eenvoudig te implementeren. Bij bitmapped fonts slaan we de karakters die we wensen te gebruiken op in een bitmap, in een gelijkmatig verdeeld rooster, zie figuur 5.10 (p.73). Voor elk karakter zullen we nu een OpenGL displaylist genereren (in ons geval 256, voor elke letter één ), waarin in elk van hen een quad zal zitten waarop we één karakter texture-mappen. Dit alles wordt opgeslaan in een array die we, wanneer we tekst wensen te renderen, indexeren aan de hand van de ASCIIwaarde van het karakter. We renderen de gewenste Displaylists in OpenGL Ortho-mode zodat tekst niet onderhevig is aan perspectief en steeds bovenop de scene ligt. Een uitstekende freeware (indien niet voor commercieel gebruik) bitmap-font maker is Bitmap Font Builder ( en wordt volledig ondersteund door de RenderEngine Implementatie in de engine In de RenderEngine wordt dit alles geïmplementeerd door de klasse FontManager. Om tekst te gebruiken in je applicatie, ga je als volgt te werk: //Laad je font in en creeer de displaylists. FontManager. buildfont ( data/font tahoma. png, 7); / Render tekst / // Zorg dat j e i n Ortho mode werkt. engine. setorthoon ( ) ; // S c h r i j f je tekst

75 Figuur 5.10: Bitmapped Font FontManager. glprint (0, // p o s i t i e in X richting 20, // p o s i t i e 20 in Y richting 1, // schaling 1 0, //In een bitmap font kan je twee lettertypes // opslaan, wij gebruiken de eerste. Dit i s een tekst, //De tekst 1.0 f, 0.0 f, 0.0 f ) ; // kleur ( rood in dit geval ). // Vergeet niet Orthomode terug af te zetten! engine. setorthooff ( ) ; Indien je tekst in world-space wilt renderen, zodat ze echt op een bepaalde plaats in je scene kan geplaatst worden, kan je als volgt tewerk gaan: FontManager. gltext (0, //X coordinaat 0, //Y coordinaat 0, //Z coordinaat 0, //In een bitmap font kan je twee lettertypes // opslaan, wij gebruiken de eerste. 1, // schaling Dit i s een tekst, //De tekst 5.8 Logmanager In de RenderEngine is de mogelijkheid voorzien om gedetailleerde logs uit te schrijven van gebeurtenissen in je applicatie. Zaken zoals initialisatie van componenten, resources die niet gevonden worden en errors worden daarin weggeschreven in HTML-formaat via een duidelijke kleurencode. Dit is optioneel en kan ingesteld worden in het.config-bestand, zie Config-bestanden (p.74). Er worden standaard een heleboel berichten naar de log geschreven (gewoonlijk is dit het bestand log.html) maar als gebruiker sta je vrij hier extra berichten aan toe te voegen. Iets schrijven naar de log, is eenvoudig en gebeurt als volgt:

76 Logger. getsingleton ( ). write ( [ABSTRACTAPP] There i s no root Sector, can t start rendering. Please use the \ setroot ( SectorMananger mananger );\ method during Scene setup, //De tekst Logger.ERROR) ; // Soort van bericht // ( Dit bepaalt de kleur in log ) Een voorbeeld-log staat afgebeeld in figuur 5.11 (p.74). Figuur 5.11: Een voorbeeld log 5.9 Config-bestanden Het eerste wat de RenderEngine zal doen bij opstarten, is het bestand configuration.xml inladen en parsen. Hierin staan een aantal elementen beschreven die noodzakelijk zijn voor de goede werking van de engine, zoals: Logging geactiveerd of niet. Lokatie van de texturen. De naam van de log file. De lokatie van de level-data.

77 Een.config bestand ziet er ongeveer zo uit: <configuration> <group name="settings"> <value key="loggingenabled">true</value> </group> <group name="paths"> <value key="datapath">data/</value> <value key="texturepath">textures/</value> <value key="mappath">maps/</value> <value key="materialpath">materials/</value> <value key="logpath">/</value> </group> <group name="filenames"> <value key="log">log.html</value> </group> <group name="leveldata"> <value key="central">centralhall.bsp</value> <value key="secondhall">secondhall.bsp</value> <value key="water">water.bsp</value> </group> </configuration> Extra groups en values kunnen hier desgewenst toegevoegd worden. Stel dat we een nieuwe group MyGroup met value MyValue aanmaken, dan kan je de bijhorende waarde als volgt inlezen in de RenderEngine: String value = Configuration. get ( MyGroup, MyValue ) ;

78 Deel III Uitbreidingen 76

79 In wat volgt, zal ik de extenties beschrijven aan de Portal-engine. Deze features behoren niet tot de core van het thesis-onderwerp maar het zijn zaken die ik nog heb kunnen implementeren gedurende de periode ik aan de engine gewerkt heb. Ze voegen veel toe aan de engine en het was een goede test in hoeverre de architectuur toeliet die uitbreidingen toe te voegen. Het bleek erg eenvoudig de bestaande architectuur van de engine uit de breiden met die functionaliteiten, wat erg bemoedigend is voor eventuele verdere uitbreiding aan de engine in de toekomst. Volgende onderwerpen komen aan bod: Particle engine Dynamic clothing Projective texturing Real-time shadowmapping Schaduwen d.m.v schaduw volumes Refraction / Reflection Environmental mapping Water-rendering

80 Hoofdstuk 6 Particle engine 6.1 Inleiding Een particle engine laat toe een grote variatie aan effecten (zoals vuur, rook en regen) te simuleren op een eenvoudige en intuïtieve manier. Dit tracht je te doen door een groep elementen op een zodanige manier onderling te laten bewegen t.o.v. elkaar zodat het gewenste effect overtuigend overkomt tot de gebruiker. De basiselementen in een particle engine zijn de particles die een punt in de ruimte beschrijven met een bepaalde plaats, snelheid en/of versnelling. Naargelang het effect we willen bereiken, wensen we die parameters aan te passen tot we een goed resultaat bekomen. Omdat elke particle apart beheerd en geüpdatet zal worden, moeten we verstandig omspringen met het aantal particles: als we frame achter frame particles toevoegen aan de scene gaat dit een nefaste invloed hebben op de performantie. Een oplossing hiervoor is particles recycleren: wanneer een particle een bepaalde leeftijd bereikt heeft, resetten we die particle en berekenen we een nieuw pad. Zo verkijgen we een simpele animatie met een vooraf bepaald aantal particles waarin we meer invloed hebben op de performantie van het systeem. We wensen dus extra parameters in te voeren die de huidige en maximale leeftijd van een element beschrijven. Een voorbeeldsituatie zou kunnen zijn zoals voorgesteld in figuur 6.1 (p.79) waarin drie particles afgebeeld staan met hun parameters: de vector p is de huidige positie, de vector v stelt de huidige snelheid voor en de vector a beschrijft de versnelling van de particle (in deze situatie zou a de zwaartekracht kunnen zijn die de particles naar beneden zal trekken). Het komt er nu simpelweg op neer om via elementair vector-rekenen de parameters naar believen aan te passen en de particles te renderen in je scene. Meer is er niet aan een particle engine maar het zal je verbazen wat een veelzijdigheid aan effecten je hiermee kan maken. De grote vraag blijft nu echter: hoe renderen we dit, uiteraard liefst zo efficiënt mogelijk? Zoals vermeld, zijn de elementen die we renderen d.m.v. een particle engine meestal simpele quads, met een, eventueel geanimeerde, textuur. Ik bespreek eerst de meest inuïtieve manier die de particles zal renderen d.m.v. billboards en een manier die gebruik maakt van Point-sprites. Dit is een OpenGL-extentie, niet alle videokaarten ondersteunen dit, maar het zal zorgen voor een mooie snelheidswinst. 78

81 6.2 Billboarded particles Figuur 6.1: Particles Een billboard is een Quad die steeds zal gericht staan in de richting van de camera, concreet: de normaal van het vlak zal steeds gericht staan naar de positie van de camera in de scene. Billboarding is een techniek die bijzonder vaak gebruik wordt en bestaat in verschillende variaties, zie ook figuur 6.2 (p.80): Spherical billboarding In deze techniek zal de billboard steeds naar de camera gericht staan, ongeacht de positie ervan. Cylindrical billboarding Hier zal de billboard steeds gericht worden naar de camera maar dit gebeurt rond een vast gekozen as. Meestal wordt als as de Y-as van het coördinatensysteem genomen zodat de billboard steeds recht blijft staan maar zal meedraaien indien de camera zich er rond beweegt. In dit artikel zal ik een variatie gebruiken op Spherical billboarding, die zal zorgen dat de billboards steeds in de richting zullen staan van de kijkrichting i.p.v. te wijzen naar de camera-positie. Het resultaat is niet volledig hetzelfde als het echte Spherical billboarding maar het gaat een stuk sneller om de oriëntatie aan te passen terwijl het resultaat meer dan overtuigend genoeg is voor een particle engine. Om te kunnen uitleggen hoe je de correcte oriëntatie kan vinden moet ik even teruggaan naar de inleiding van deze tekst, meer bepaald het stuk over de OpenGL ModelView-matrix. We weten dat de ModelView-matrix de positie en oriëntatie van zowel de camera als de objecten in de scene zal bepalen, als we even de ModelView-matrix voor ogen nemen, zie figuur 6.3 (p.80), denk ik dat het direct duidelijk zal zijn wat er gebeurt. De rode sub-matrix bepaalt de positie van de oorsprong van de huidige local-space relatief t.o.v. de camerapositie (We weten dat wanneer de ModelView matrix een identiteitsmatrix is, de camera gepositioneerd staat in de oorsprong van de world-space en kijkt langs de negatieve Z-as). De groene submatrix bepaalt de rotatie en schaling van dat lokaal assenstelsel. Wanneer we de groene submatrix vervangen door een identiteitsmatrix zullen we dus elke eventuele rotatie ongedaan maken en zullen de elementen steeds georiënteerd staan langs de Z-as, precies

82 (a) Spherical billboarding, zijaanzicht (b) Cylindrical billboarding, zijaanzicht (c) Spherical billboarding, bovenaanzicht (d) Cylindrical billboarding, bovenaanzicht (e) Fake Spherical billboarding Figuur 6.2: Billboarding Figuur 6.3: De OpenGL Modelview matrix

83 wat we wensen te bereiken voor onze billboards! Aan de positie wordt niets gewijzigd; die informatie staat in het rode deel dat we ongemoeid laten. Zie figuur 6.4 (p.81). Figuur 6.4: Modelview matrix voor Billboarding De oplettende lezer zal meteen een probleem zien: bij het aanpassen van de groene matrix zal ook de eventuele schaling verloren gaan. We moeten dus na het vervangen van de groene sub-matrix de eventuele schaling (s x, s y, s z ) herstellen. Vooraleer een billboard te renderen, volstaat het dus de huidige ModelView matrix aan te passen zoals in figuur 6.5 (p.81) Figuur 6.5: Modelview matrix voor Billboarding met schaling Vergeet na het renderen van de billboards de oude ModelView-matrix niet te herstellen. 6.3 Particles met point sprites Billboards zijn een eenvoudige manier om particles te renderen die gealigneerd staan met de kijkrichting maar we zitten nog steeds met het feit dat we particles renderen d.m.v Quads, wat betekent dat we per particle 4 vertices door de OpenGL-pipeline moeten sturen. Het zou mooi zijn indien we een particle zouden kunnen bepalen door 1 positie met een vaste oriëntatie. Dit is precies waar Pointsprites voor dienen. Point-sprites zijn gedefinieerd in een OpenGL-extentie en laten toe een Quad met een textuur te definiëren d.m.v. 1 vertex met die beperking dat ze steeds zal georiënteerd staan naar de camera. Die betekent niet alleen dat we niet meer zelf de Modelview-matrix moeten aanpassen maar dat we dus 4 keer minder vertices door de pipeline moeten sturen voor onze Particle-renderer! Nadeel is dat oudere videokaarten deze extentie niet ondersteunen zodat je voor die mensen zal moeten kunnen terugvallen op Billboarded-particles. Point-sprites zijn bijzonder eenvoudig om mee te werken. De eigenschappen (zoals de grootte en texture environment) van de pointsprites stellen we als volgt in: glpointparameterfarb ( GL POINT FADE THRESHOLD SIZE ARB, 60.0 f ) ; glpointparameterfarb ( GL POINT SIZE MIN ARB, 1.0 f ) ; glpointparameterfarb ( GL POINT SIZE MAX ARB, 100 ) ; gltexenvf ( GL POINT SPRITE ARB, GL COORD REPLACE ARB, 1);

84 En we renderen onze Point Sprites: //Bind een textuur glbindtexture (GL TEXTURE2D, myid) ; //Render d.m. v. pointsprites glenable ( GL POINT SPRITE ARB ) ; glbegin ( GL POINTS ) ; for ( int i = 0; i < MAX PARTICLES; i++ ) glvertex3f ( p a r t i c l e s [ i ]. position. x, p a r t i c l e s [ i ]. position. y, p a r t i c l e s [ i ]. position. z ) ; glend ( ) ; gldisable ( GL POINT SPRITE ARB ) ; 6.4 Implementatie in de engine Figuur 6.6: Enkele particle-renderers in een scene waaronder fakkels en regen. Om het feit te kunnen opvangen dat sommige gebruikers geen PointSprites ondersteunen en om de nadelen van PointSprites te kunnen vermijden (bijvoorbeeld indien je echt grote Quads wenst te gebruiken) wordt een abstracte particles renderer AbstractParticleSystem gedefinieerd, die zelf een MovableObject is, die het renderen van Particles zal abstraheren. Verder werden abstracte extenties geschreven, AbstractBillboardParticleSystem en Abstract- PointSpriteParticleSystem, die een frame-work vormen voor het renderen van particles d.m.v. respectievelijk Billboards en PointSprites. Wens je zelf een Particle-system te schrijven, dan zal je de gewenste Abstracte klasse moeten overerven en de abstracte methode update () herimplementeren. Als voorbeeld bespreek ik de implementatie van een imaginair Billboarded particle-system:

85 public class MyParticleSystem extends AbstractBillboardParticleSystem //De constructor met de Billboard die we wensen te gebruiken //om de p a r t i c l e s te renderen. public MyParticleSystem ( Billboard bb) super (bb ) ; MAX PARTICLES = 20; p a r t i c l e s = new Particle [MAX PARTICLES] ; // Init p a r t i c l e s for ( int i = 0; i < MAX PARTICLES; i++ ) Vector3 randomvector = Vector3. multiplybyscalar ( new Vector3 (0, 1, 0), getrandomminmax(0.005 f, 0.01 f ) ) ; p a r t i c l e s [ i ] = new Particle (new Vector3 (0.0 f, 0. 0 f, 0. 0 f ), randomvector, 1, 1, 1, 1.0 f ) ; p a r t i c l e s [ i ]. lifetime = 1500; p a r t i c l e s [ i ]. age = getrandomminmax(0, p a r t i c l e s [ i ]. lifetime ) ; // Implementatie van de abstracte methode update () public void update () for ( int i = 0; i < MAX PARTICLES; i++ ) // S c h r i j f hier je code die de positie, snelheid, age,... //van je p a r t i c l e s zal updaten. //Een p a r t i c l e renderer i s een MovableObject, we moeten dus // ook de methode getboundingbox () implementeren. public BoundingBox getboundingbox () return new BoundingBox ( ) ; In de Engine zijn reeds een hele reeks particle systems geïmplementeerd, klaar voor gebruik: FireParticleSystem FountainParticleSystem

86 MagicParticleSystem RainParticleSystem SmokeParticleSystem WaterfallParticleSystem Een Particle System is een MovableObject en moet dus opnieuw aan de SceneGraph gelinkt worden om te gebruiken in je scene. Als voorbeeld gebruik ik de FireParticleSystem in een eenvoudige testscene: SectorManager space ; SkyBox skybox ; //De billboard dat gebruikt wordt om het vuur te renderen Billboard f ; //De particle system AbstractParticleSystem f i r e ; //De SceneNode voor de ParticleSystem SceneNode firenode ; //Een fakkel object Entity holder ; //SceneNode voor de fakkel SceneNode holdernode ; public void setup () skybox = new SkyBox (5000); loader = new OBJLoader ( ) ; Mesh m = loader. load ( models/holder. obj ) ; holder = new Entity (m, amaterial ) ; //Maak een nieuwe Billboard van grootte 4, met materiaal Flare f = new Billboard ( Flare, 4); //Maak een nieuw particle system f i r e = new FireParticleSystem ( f ) ; space = new BSP SectorManager ( mysector ) ; engine. positioncamera ( 220, 80, 50, 20, 20, 0, 0, 1, 0); engine. setroot ( space ) ; //Hang a l l e s aan de Scenegraph holdernode = new SceneNode ( holder, holder ) ; holdernode. translate (new Vector3 ( 50, 80, 85)); firenode = new SceneNode ( f i r e, f i r e ) ; holdernode. addchildnode ( firenode ) ; space. getrootscenenode ( ). addchildnode ( holdernode ) ; public void update () //update de particle system f i r e. update ( ) ;

87 public void render () engine. clear ( ) ; engine. positioncamera ( ) ; skybox. render ( engine. camera ) ; engine. render ( ) ;

88 Hoofdstuk 7 Dynamic clothing Physics zijn niet meer weg te denken uit een moderne engine, vooral dynamic clothing is een goede manier om leven te blazen in een 3D-scene. Een cloth-object werd toegevoegd aan de engine die een eenvoudige cloth-simulator implementeert. Cloth is een MovableObject dus moet in de SceneGraph van de SectorManager geplaatst worden vooraleer het gerenderd zal worden. Om te kunnen uitleggen hoe dynamic clothing geïmplementeerd werd, moeten we eerst kijken hoe een koord fysisch gemodelleerd wordt, vervolgens zullen we de stap maken naar clothing. 7.1 Simulatie van een koord We kunnen een koord zien als een serie punten die met elkaar verbonden zijn met veren. We laten de punten bewegen evenredig met een eventuele kracht die erop inwerkt (dit kan de zwaartekracht zijn maar ook een krachtvector op een bepaald punt om dit te verplaatsen). Voor elk punt van de draad bepalen we de totale kracht die inwerkt op dat punt door simpelweg de afstand tot zijn twee buren te bepalen. Als die afstand groter wordt dan een bepaalde grenswaarde dan zorgen we dat die afstand behouden wordt door de extentie van de veer te berekenen afhankelijk van de lengte van de veer en de normale lengte van de veer. Figuur 7.1: Een koord, samengesteld uit punten verbonden met veren In pseudocode kunnen we een erg eenvoudig model voor een koord als volgt beschrijven: ZwaarteKracht = Vector3 (0, 9,87, 0); for ( AllePuntenVanDeKoord ) afstand1 = afstandtoteerstebuur ; extentie1 = afstand1 normalelengte ; afstand2 = afstandtottweedebuur ; 86

89 extentie2 = afstand2 normalelengte ; temp = new Vector3 ( ) ; temp. x = ( EersteBuur / afstand1 extentie1 ) + (TweedeBuur / afstand2 extentie2 ) + temp. y = ( EersteBuur / afstand1 extentie1 ) + (TweedeBuur / afstand2 extentie2 ) + Zwaartekracht ; temp. z = ( EersteBuur / afstand1 extentie1 ) + (TweedeBuur / afstand2 extentie2 ) + huidigpunt. x = huidigpunt. x + temp. x ; huidigpunt. y = huidigpunt. y + temp. y ; huidigpunt. z = huidigpunt. z + temp. z ; Dit model rendert een koord dat onderhevig is aan de zwaartekracht en realistisch zal reageren op positie veranderingen van individuele punten. Voor een nog realistischer resultaat kan je een wind-compontent toevoegen aan het model die een zacht briesje kan simuleren door een extra-kracht vector te laten inwerken op de punten van het koord. Collision-detection kan indien gewenst ook toegevoegd worden door, vooraleer de positie van een punten te updaten, te testen of het punt een bepaald object niet binnendringt. 7.2 Simulatie van clothing Nu we een eenvoudig model hebben voor een dynamisch koord kunnen we de stap eenvoudig zetten naar dynamic clothing. Een stuk textiel kunnen we immers zien als een serie dynamische koorden die verweven zijn met elkaar, zoals in figuur 7.2 (p.87) Figuur 7.2: Een eenvoudig model voor textiel Voor elk punt van de patch zullen we, zoals bij het koord, de totale krachtvector berekenen maar nu kijken we naar de 4 buren van elk punt (indien die bestaan), als volgt:

90 ZwaarteKracht = Vector3 (0, 9,87, 0); for ( AllePuntenVanDePatch ) for ( AlleBurenVanHetPunt ) Veer = Buur huidigpunt ; Lengte = AfstandTussenBuur ( ) ; schaal = ( Lenghte normalelengte ) / normalelengte ; Veer = Veer (1/ Length ) ; kracht = Veer schaal ; kracht = kracht + ZwaarteKracht ; //Doe een c o l l i s i o n test indien gewenst. DoCollision ( ) ; huidigpunt. x = huidigpunt. x + kracht. x ; huidigpunt. y = huidigpunt. y + kracht. y ; huidigpunt. z = huidigpunt. z + kracht. z ; Het bovenstaande model zal echter nog niet echt overtuigend reageren (het textiel zal soms onrealistisch in elkaar vallen ) doordat de punten van te weinig buren afhankelijk zijn; de simulatie kan een stuk beter gemaakt worden door de punten te laten afhangen van zijn 8 buren, zoals in figuur 7.3 (p.88) Figuur 7.3: Een robuuster model voor textiel Hoe meer buren in beschouwing genomen worden hoe realistischer het textiel zal reageren, maar hoe trager de simulatie wordt natuurlijk. In deze engine wordt gekeken naar de 8 buren, met reeds een erg bemoedigend resultaat. Voor een optimaal resultaat kan je de 24 dichtste buren in beschouwing nemen.

91 Ook hier kunnen we de simulatie verbeteren door een wind-vector in te voeren of collision detection te berekenen voor elk punt van de cloth. Een goed model voor textiel, dat reageert op wind, zou deze kunnen zijn (merk op dat we hier de driehoeken, meer bepaald hun normalen, moeten kennen waaruit het textiel is opgebouwd): Vector3 Wind = new Vector3 (1, 0, 0); f o r ( AlleDriehoekenVanDePatch ) //Bereken de normaal van de huidige driehoek ( dit moet elke //frame opnieuw gebeuren! ) Vector3 Normaal = BerekenDeNormaalVanDeDriehoek ( ) ; //Bereken het dot product tussen de wind vector en de normaal // ( Dit heeft als resultaat de cosinus van de hoek tussen die twee // vectoren ) f l o a t dot = DotProduct (Normaal, Wind ) ; Vector3 kracht = Normaal dot ; //Pas de verkregen krachtvector toe op de drie punten // die tot de driehoek behoren. In de RenderEngine is de mogelijkheid ingebouwd om een collision-test te doen tegen een bol: //Onze movement vector verkregen door de 8 buren te evalueren r = Vector3.Add(c, movementvector ) ; / Vooraleer de update daadwerkelijk door te voeren zullen we een c o l l i s i o n test doen. / //Bereken de afstand van het punt tot het middelpunt ( pos ) van //de bol f l o a t distance = Vector3. Distance ( r, pos ) ; //Als die afstand > ( radius van de bol + marge ), geen c o l l i s i o n. i f ( distance > radius + 5) //update de vertex p o s i t i e zoals gewoonlijk. vertices. put ( indexc, r. x ) ; vertices. put ( indexc+1, r. y ) ; vertices. put ( indexc+2, r. z ) ; //Er i s een c o l l i s i o n! e l s e //Bereken vector van middelpunt bol naar de vertex. direction = Vector3. Minus( r, pos ) ; // Verplaats de vertex volgens die vector zodat ze net buiten de

92 // bol komt te liggen. direction = Vector3. multiplybyscalar ( Vector3. DevideByScalar ( direction, distance ), radius + 5); r = Vector3.Add( pos, direction ) ; //Update de vertex met de aangepaste p o s i t i e. vertices. put ( indexc, r. x ) ; vertices. put ( indexc+1, r. y ) ; vertices. put ( indexc+2, r. z ) ; 7.3 Implementatie in de engine Figuur 7.4: Dynamic clothing in een BSP-scene Zoals vermeld, wordt in de Cloth-simulation in deze engine voor elk punten gekeken naar de de 8 buren. Enkel rechthoekige stukken cloth worden ondersteund en het is mogelijk aanhechtingspunten en aanhechtingsrechten te bepalen waaraan je het textiel kan ophangen in je scene. Dynamic clothing toevoegen in je applicaties is eenvoudig en gaat als volgt: MovableObject cloth ; SceneNode clothnode ; //... cloth = new Cloth (10, 10, 10, mymaterial, new Vector3 (0, 0, 0), null, new Vector3 (0, 1, 0), new Vector3 (0, 0, 1 ) ) ; //Hang het t e x t i e l op aan zijn bovenste r i j punten

93 cloth. setfixrow ( 0 ) ; // Cloth i s een MovableObject, dus hang j e ook gewoon // aan een SceneNode. clothnode = new SceneNode ( cloth, cloth ) ; getrootscenenode. addchildnode ( clothnode ) ; //... en je hebt Dynamic Clothing in je scene.

94 Hoofdstuk 8 Projective texturing Projective texturing is een techniek waar we een textuur projecteren op een scene zoals een diaprojector dit zou doen. We gebruiken hiervoor een extra texture-layer en textuurcoördinaten die we verkrijgen door een projectie uit te voeren. Projective texturing kan gebruikt worden om spotlichten te simuleren maar wordt ook gebruikt voor Real-time Shadowmapping. Figuur 8.1: Projective texturing Wanneer we een textuur willen projecteren over een scene dienen we een ModelView- en Projection-Matrix te definiëren die de positie en view-frustum van de projector zal bepalen. Gebruikmakende van die matrices zullen we de OpenGL ModelView- en Projection-matrices aanpassen zodat de camera komt te staan op de plaats van de projector en zullen we van die 92

95 positie een textuur projecteren over het volledige scherm. Probleem is natuurlijk dat we in onze shader de textuurcoördinaten genereren in View-space van de camera, terwijl we de projectietextuur moeten indexeren in de clip-space van de projector. We zullen dus een transformatie moeten doorvoeren op die textuurcoördinaten zodat we indexering correct kunnen uitvoeren. Een afbeelding maakt ongetwijfeld duidelijker wat hiermee bedoeld wordt, figuur 8.1 (p.92). Vooraleer ik verder ga, bekijken we eerst eens de relatie tussen de verschillende matrices die we gebruiken bij Projective Texturing. Om de textuur correct te indexeren wensen we dus de transformatie door te voeren die de gele pijl aanduidt in figuur 8.2 (p.93) Figuur 8.2: Matrices en Projective Texturing De beste manier om dit te doen is door de textuur-matrix, TM, als volgt aan te passen (we volgen de pijlen op te tekening in de omgekeerde volgorde en passen de bijpassende transformaties toe): TM = P p MV p MV 1 c waarbij: P p de Projection-matrix is van de Projector. MV p de ModelView-matrix is van de Projector. MV c de ModelView-matrix is van de Camera. Wanneer we de textuurcoördinaten kennen in Clip-space van de projector dienen we nog 1 transformatie door te voeren. De coördinaten staan nu immers in het interval [-1, 1] terwijl we een textuur indexeren in het [0,1] interval. We gebruiken volgende matrix:

96 TRANS = om de textuurcoordinates te mappen naar het interval [0,1], als volgt: TM = TM TRANS Nu hebben we de correcte textuurmatrix en kunnen we de textuur projecteren over de scene. In de RenderEngine gebruik ik hiervoor volgende GLSL-shader: ProjectiveTexturing.vert varying vec4 projcoord ; void main () // Genereer textuur coordinaten gl TexCoord [ 0 ] = gl MultiTexCoord0 ; gl TexCoord [ 1 ] = gl MultiTexCoord1 ; // Transformeer vertex naar View space vec4 realpos = gl ModelViewMatrix gl Vertex ; // Genereer textuurcoordinaten in clip space //van de projector door te vermenigvuldigen met //de gewijzigde TextuurMatrix ( die aanpassing gebeurt // buiten de shader ). projcoord = gl TextureMatrix [ 1 ] realpos ; // Transformeer vertex naar Clip space. gl Position = ftransform ( ) ; ProjectiveTexturing.frag uniform sampler2d basetexture ; uniform sampler2dshadow projtex ; varying vec4 projcoord ; void main () //Constante die bepaalt hoe transparant de projectie zal zijn. const f l o a t transparancy = 0. 5 ; // Indexeer een eventuele basis textuur. vec4 texel = texture2d ( basetexture, gl TexCoord [ 0 ]. st ) ;

97 // Indexeer de projectie textuur met de textuurcoordinaten // in clip space van de projector. vec3 rvalue = vec3 ( shadow2dproj ( projtex, projcoord ). r + transparancy, shadow2dproj ( projtex, projcoord ). g + transparancy, shadow2dproj ( projtex, projcoord ). b + transparancy ) ; rvalue = clamp ( rvalue, 0.0, 1. 0 ) ; vec3 coordpos = projcoord. xyz / projcoord. w; //Zorg dat de textuur niet buiten de ViewFrustum van de projector // getekend wordt. i f ( coordpos. x >= 0.0 && coordpos. y >= 0.0 && coordpos. x <= 1.0 && coordpos. y <= 0.9 && projcoord. z > 0.0) //Combineer kleur basistextuur met kleur projectietextuur gl FragColor = texel vec4 ( rvalue. x, rvalue. y, rvalue. z, 1. 0 ) ; e l s e //Als coordinaat buiten de view frustum ligt, toon dan enkel de // kleur van de basistextuur. gl FragColor = texel ; Een voorbeeld van projective texturing in Libera is afgebeeld in figuur 8.3 (p.95) Figuur 8.3: Projective texturing en real-time shadows in een BSP-scene

98 Hoofdstuk 9 Real-time shadow rendering Het renderen van schaduwen is een vak op zich, er bestaan reeds vele technieken met elk hun specifieke voor- en nadelen. Het is moeilijk de beste techniek te bepalen want dit is onlosmakelijk verbonden met de scene waarvan je shaduwen wilt casten. De twee bekendste technieken zijn ongetwijfeld Shadow Mapping en Shadow Volumes, waarvan er opnieuw verschillende variaties bestaan. In deze engine werden Shadow Mapping, Cubic Shadow mapping en Shadow Volumes (Z-fail en Z-pass) geïmplementeerd als ShadowRenderer-object, zie ( ShadowRenderer (p.38). Het doel van dit hoofdstuk is deze technieken in detail te bespreken, hoe ze geïmplementeerd werden in deze engine en hoe je ze kan gebruiken in een applicatie. Onthoud echter dat het renderen van schaduwen, gelijk welke techniek je gebruikt, een dure opdracht is. De scene waarvoor je schaduwen wilt renderen moet veelal tweemaal gerenderd worden (bij Cubic shadowmapping zelfs 7 keer!) dit aantal verdubbelt per licht in je scene, maatregelen moeten dus genomen worden om je applicatie werkbaar te houden, zoals: Beperk het aantal lichten in je scene (althans die lichten die schaduwen werpen ). Beperk het aantal voorwerpen die schaduwen werpen in je scene. Render enkel schaduwen voor objecten die binnen een bepaalde straal van de camera staan. Update de schaduwen niet bij elke frame Shadow Mapping Shadow mapping is een image-based techniek die voor het eerst beschreven werd in het artikel Casting curved shadows on curved surfaces van Lance Williams en dateert uit De techniek is eenvoudig en leent zich perfect tot het uitvoeren in hardware d.m.v. je GPU, aangezien we enkel gebruikmaken van texturen en de depth-buffer. 96

99 (a) ShadowMapping voor een Spot-light (b) Soft shadows via een PCF (c) Cubic ShadowMapping voor een Point-light in een BSPSector (d) Shadow Volumes via Z-Pass methode Figuur 9.1: Verschillende ShadowRenderers in de engine

100 Shadow Mapping leunt zeer goed aan bij wat we intuïtief aanvoelen over hoe shaduwen onstaan in een reële situatie: trek voor elk punt in de omgeving een lijn naar de lichtbron, als die lijn een ander object snijdt zal het bewuste punt in schaduw liggen, anders niet. Dit laatste is eigenijk hetzelfde als kijken naar de scene vanop de locatie van de lichtbron en kijken welke punten zichtbaar zijn vanop die positie, onzichtbare punten zullen in schaduw liggen. Gelukkig bestaat er al een techiek om de zichtbaarheid van punten te testen: depth-testing. Punten die zichtbaar zijn vanaf de positie van het licht zullen slagen voor de depth-test op de positie van de lichtbron, andere niet. Wanneer we dit voor ogen houden is het erg eenvoudig Shadow Mapping te verstaan, stel je een scene voor met 1 lichtbron: 1. Render de scene vanuit de positie van de lichtbron en sla de depth-buffer op in een textuur, de shadowmap. 2. Projecteer die shadowmap vanaf de positie van het licht over de scene, zie Projective texturing (p.92). 3. Render de scene vanuit de positie van de camera, zorg dat Depth-writing geactiveerd is want we zullen de depth-values van die scene gebruiken in de volgende stap. 4. Converteer elk fragment naar het coördinatensysteem van de lichtbron en vergelijk de depth-value van het fragment met die value die opgeslaan staat in de shadowmap, op die plaats. Aan de hand van die vergelijking kunnen we bepalen of het fragment al dan niet in shaduw ligt. Een afbeelding, figuur 9.2 (p.99), maakt ongetwijfeld een stuk duidelijker wat er juist gebeurt Gebruikte GLSL-shader ShadowMapping is het eenvoudigst te doen d.m.v. een shader. De gebruikte GLSL-shader in de RenderEngine, ziet er als volgt uit en lijkt erg goed de GLSL-shader voor Projective Texturing, mits een aantal kleine wijzigingen: ShadowMapping.vert varying vec4 projcoord ; void main () gl TexCoord [ 0 ] = gl MultiTexCoord0 ; gl TexCoord [ 1 ] = gl MultiTexCoord1 ; vec4 realpos = gl ModelViewMatrix gl Vertex ; projcoord = gl TextureMatrix [ 1 ] realpos ; gl FrontColor = gl Color ;

101 Figuur 9.2: Shadow mapping gl Position = ftransform ( ) ; ShadowMapping.frag uniform sampler2d basetexture ; uniform sampler2dshadow shadowmap ; uniform vec4 color ; varying vec4 projcoord ; void main () const f l o a t transparancy = 0. 5 ; vec4 texel = texture2d ( basetexture, gl TexCoord [ 0 ]. st ) ; f l o a t rvalue = shadow2dproj (shadowmap, projcoord ). r + transparancy ; rvalue = clamp ( rvalue, 0.0, 1. 0 ) ; vec3 coordpos = projcoord. xyz / projcoord. w;

102 i f ( coordpos. x >= 0.0 && coordpos. y >= 0.0 && coordpos. x <= 1.0 && coordpos. y <= 0.9 && projcoord. z >=0.0) gl FragColor = texel rvalue gl Color ; e l s e gl FragColor = texel transparancy gl Color ; Zoals vermeld, leent deze techniek zich uitstekend tot implementatie op hardware-niveau vooral omdat er in OpenGL een aantal hulpmiddelen beschikbaar zijn om die depth-test tot een goed einde te brengen en er speciaal textuurformaat bestaat om de depth-values van de scene in op te slaan in een hoge precisie (16 of 24 bits). Voordelen: Kennis over hoe de scene opgebouwd is, is niet nodig. Shadow Mapping is een imagebased techniek (we gebruiken de data die gerenderd wordt in de depth-buffer) en kan gebruikt worden voor willekeurige scenes. Eén enkele textuur per licht nodig. Nadelen: Aliasing bij gebruik van kleine shadow-maps. Artifacts door het projecteren van de shadowmap op vlakken die parallel met de projectierichting staan. De scene moet verschillende keren gerenderd worden. Kwaliteit van de shaduwen is sterk afhankelijk van de afstand van het licht tot de scene en de afstand tussen de near- en far-planes van de ViewFrustum van de lichtbron Shadow mapping via een FBO (Frame Buffer Object) Een FBO laat toe rechtstreeks te renderen naar het textuurgeheugen op de videokaart, wat een snelheidswinst oplevert aangezien de textuur niet meer vanuit het RAM-geheugen naar de videokaart moet verplaatst worden. Deze functionaliteit werd toegevoegd als een extentie aan OpenGL, dus je mag er niet vanuit gaan dat alle videokaarten dit ondersteunen. Een FBO is een off-screen buffer, wat wil zeggen dat alles wat gerenderd wordt naar een FBO geen invloed zal hebben op de Color-, depth en stencil-buffer en we dus geen glclear() moeten uitvoeren achteraf (wat opnieuw een snelheidswinst is). Een FBO kan gebruikt worden om te renderen naar zowel een RGB- als Depth-texture.

103 In de RenderEngine kan shadowmapping gebeuren via een FBO, indien gewenst. Shadowmapping via een FBO gebeurt in grote lijnen, als volgt: 1. Maak een FBO-object: id = BufferUtils. createintbuffer ( 1 ) ; EXTFramebufferObject. glgenframebuffersext ( i d ) ; fbid = id. get ( 0 ) ; 2. Creëer een render-target (in dit geval is dit een Depth-texture): depthid = initrendertexture (GL11.GL DEPTH COMPONENT, GL11.GL DEPTH COMPONENT) ; 3. Schakel Read- en Drawbuffer uit (verplicht wanneer we naar een depth-texture wensen te renderen!): EXTFramebufferObject. glbindframebufferext ( EXTFramebufferObject. GL FRAMEBUFFER EXT, fbid ) ; GL11. glreadbuffer (GL11.GL NONE) ; GL11. gldrawbuffer (GL11.GL NONE) ; 4. Bind de render-target aan de FBO: EXTFramebufferObject. glframebuffertexture2dext ( EXTFramebufferObject. GL FRAMEBUFFER EXT, EXTFramebufferObject.GL DEPTH ATTACHMENT EXT, GL11.GL TEXTURE 2D, depthid, 0); EXTFramebufferObject. glbindframebufferext ( EXTFramebufferObject. GL FRAMEBUFFER EXT, 0); 5. Controleer of er geen errors zijn: int status = EXTFramebufferObject. glcheckframebufferstatusext ( EXTFramebufferObject. GL FRAMEBUFFER EXT) ; i f ( status == EXTFramebufferObject.GL FRAMEBUFFER COMPLETE EXT) System. out. println ( FBO OK ) ; e l s e System. out. println ( FBO ERROR ) ; 6. Maak frame-buffer klaar: EXTFramebufferObject. glbindframebufferext ( EXTFramebufferObject. GL FRAMEBUFFER EXT, fbid ) ; GL11. glclear (GL11.GL DEPTH BUFFER BIT) ; 7. Render depth-values in de scene naar de depth-texture in FBO: render ( ) ;

104 8. Stop het renderen naar de FBO: EXTFramebufferObject. glbindframebufferext ( EXTFramebufferObject. GL FRAMEBUFFER EXT, 0); 9. Gebruik de depth-texture net zoals we dat met een klassieke textuur zouden doen Spotlights De manier waarop de schaduwen zullen geprojecteerd worden, hangt af van Projection-matrix van de licht-frustum. Een Perspective Projection-matrix zal een spotlicht simuleren: Voorbeeld Projection-matrix voor een spot-licht: GL11. glmatrixmode (GL11.GL PROJECTION MATRIX) ; GL11. glloadidentity ( ) ; GLU. gluperspective (60.0 f, 1.0 f, 50.0 f, f ) ; Directional lights Uiteraard is bovenstaande methode niet goed om zonlicht voor te stellen (de schaduwen die de zon werpt zijn min of meer parallel aan elkaar). Een oplossing hiervoor zou kunnen zijn de lichtbron erg ver van de scene te plaatsen, maar dit is niet praktisch aangezien het detail in Shadowmap dan veel te klein wordt. Een betere oplossing is i.p.v. een perspectief-matrix te gebruiken een ortho-matrix te gebruiken als projection-matrix. Shaduwen zullen zo parallel geprojecteerd worden zonder dat de lichtbron onwezenlijk ver van de scene moet staan: GL11. glmatrixmode (GL11.GL PROJECTION MATRIX) ; GL11. glloadidentity ( ) ; GL11. glortho (0, shadowmapsize / 4, 0, shadowmapsize / 4, 10, 256); GL11. gltranslatef ( shadowmapsize / 2 / 4, shadowmapsize / 2 / 4, 0); Point-lights via Cubic shadowmapping Op het eerste zicht lijkt dat Shadow mapping niet mogelijk is voor point-lights maar we kunnen de techiek eenvoudig uitbreiden door de scene te renderen vanuit 6 Frustra (langs positieve en negatieve X-, Y- en Z-as) en de shadowmaps opslaan in een CubeMap. We passen de gekende techniek nu simpelweg toe voor elke licht-frustum. Zie figuur 9.3 (p.103). In Libera is deze techniek geïmplementeerd als ShadowMapCubicRenderer Soft-shadows via PCF-filter Zoals vermeld is Shadowmapping erg gevoelig voor aliasing, we kunnen dit gedeeltelijk opvangen door de shadowmap te filteren d.m.v. een PCF (Percentage Closest Filtering), waarbij we voor elk beschaduwd fragment het gemiddelde berekenen van de 8 omliggende fragmenten. We kunnen de bovenstaande GLSL-shader voor shadowmapping eenvoudig uitbreiden met een PCF:

105 Figuur 9.3: Shadow mapping voor een punt-licht en een aantal objecten in een scene ShadowMappingPCF.vert varying vec4 projcoord ; void main () vec4 realpos = gl ModelViewMatrix gl Vertex ; gl TexCoord [ 0 ] = gl MultiTexCoord0 ; gl TexCoord [ 1 ] = gl MultiTexCoord1 ; projcoord = gl TextureMatrix [ 1 ] realpos ; gl FrontColor = gl Color ; gl Position = ftransform ( ) ; ShadowMappingPCF.frag uniform sampler2d basetexture ; uniform sampler2dshadow shadowmap ; varying vec4 projcoord ; void main () const f l o a t transparency = 0. 5 ; vec3 shadowuv = projcoord. xyz / projcoord. q ; f l o a t mapscale = 1.0 / 512.0;

106 vec4 texel = texture2d ( basetexture, gl TexCoord [ 0 ]. st ) ; vec4 shadowcolor = shadow2d( shadowmap, shadowuv ) ; //Bereken u i t e i n d e l i j k e shaduwkleur door het gemiddelde te nemen v // van de buur fragmenten. shadowcolor += shadow2d( shadowmap, shadowuv. xyz + vec3 ( mapscale, mapscale, 0 ) ) ; shadowcolor += shadow2d( shadowmap, shadowuv. xyz + vec3 ( mapscale, mapscale, 0 ) ) ; shadowcolor += shadow2d( shadowmap, shadowuv. xyz + vec3 ( mapscale, 0, 0 ) ) ; shadowcolor += shadow2d( shadowmap, shadowuv. xyz + vec3( mapscale, mapscale, 0 ) ) ; shadowcolor += shadow2d( shadowmap, shadowuv. xyz + vec3( mapscale, mapscale, 0 ) ) ; shadowcolor += shadow2d( shadowmap, shadowuv. xyz + vec3( mapscale, 0, 0 ) ) ; shadowcolor += shadow2d( shadowmap, shadowuv. xyz + vec3 ( 0, mapscale, 0 ) ) ; shadowcolor += shadow2d( shadowmap, shadowuv. xyz + vec3 ( 0, mapscale, 0 ) ) ; shadowcolor = shadowcolor / 9. 0 ; shadowcolor += transparency ; shadowcolor = clamp ( shadowcolor, 0.0, 1. 0 ) ; i f (shadowuv. x >= 0.0 && shadowuv. y >= 0.0 && shadowuv. x <= 1.0 && shadowuv. y <= 0.9 ) gl FragColor = texel shadowcolor ; e l s e gl FragColor = texel ; 9.2 Shadow Volumes Daar waar shadowmapping sterk aanleunt tegen hoe we zelf het proces van schaduwvorming ervaren, zou je Shadow-volumes kunnen beschrijven als een wiskundige abstractie van het proces. Voor een aantal mensen zal dit proces een stuk minder intuïtief zijn maar aangezien de techniek gebruik maakt van de Stencil buffer, en we voor elk model schaduwvolumes genereren, kunnen we shaduwen maken die pixel-perfect zijn, zonder enige vorm van aliasing. Nadeel is dat de techniek erg reken-intensief is en het zeker niet in alle situaties kan gebruikt worden (Sillhouete berekening is niet altijd mogelijk en/of te duur).

107 Voordelen: Pixel-perfect. Geen aliasing, zelfs niet voor lichten die ver van de scene staan. Self-shadowing wordt zonder probleem ondersteund. Nadelen: CPU intensief, tenzij we shadowvolume-extrusion doen in een vertex-shader, maar dan moeten we voor elk model extra geometrie definiëren (soms dubbel zoveel). We moeten kennis hebben van de geometrie waarvoor we shaduwen wensen te casten, niet bruikbaar voor willekeurige scenes. Hoge fillrate, elke schaduwvolume moet door de pipeline gestuurd worden. Moeilijk om soft-shadowing te gebruiken met Shadow Volumes. De sleutel tot een correcte werking van het Shadow Volume algoritme is het vinden van de correcte ShadowVolume. De Shadow Volume van een object is de ruimtelijke figuur die men krijgt door de silhouette-randen van een object, gezien van de lichtbron, uit te breiden, van de lichtbron weg. De silhouette-randen zijn die randen die bijdragen tot de vorm van de schaduw van het object. Alles in de scene dat in de Shadow Volume ligt, zal uiteindelijk in schaduw zitten. Bij verplaatsen van licht of object moet de Shaduw-volume opnieuw berekend worden. Dit is een intensieve opdracht voor de CPU, zeker voor objecten met veel polygonen, daarom wordt op het einde van dit hoofdstuk een manier gegeven die de shadowvolumes kan bepalen via een shader, zodat de werklast verlegd wordt naar je GPU, die voor zulke opdrachten vele malen sneller is dan een CPU Bepalen silhouette edges Beschouw een scene zoals voorgesteld in figuur 9.4 (p.106). Zoals vermeld zijn de silhouetteedges die randen die de vorm van de schaduw zullen bepalen. De silhouette van het object staat afgebeeld in figuur 9.5 (p.106). Hoe vinden we die randen voor een willekeurig object en een willekeurige lichtbron? Wanneer we eens rustig nadenken over wat een silhouetterand is, is dit probleem zeer eenvoudig op te lossen. Intuitief gezien moet een rand, opdat het een silhouette-rand kan zijn (en kan bijdragen aan de vorm van de schaduw), liggen tussen een polygoon die gericht staat naar de lichtbron en één die weggedraaid staat van de lichtbron, niet meer niet minder. En nu iets wiskundiger: een polygoon is belicht als de hoek tussen de normaal van de polygoon en de vector, bepaald van de lokatie van de lichtbron naar de lokatie een vertex van de polygoon, een hoek maakt die kleiner is dan 90 graden.

108 Figuur 9.4: Object en lichtbron in een scene Figuur 9.5: Silhouette edges van het object

109 Figuur 9.6: Belichte polygoon Om de silhouette-randen te kunnen bepalen, moeten we dus voor alle polygonen van het object bepalen of die gericht staan naar de lichtbron. Indien dat het geval is en een buurpolygoon staat niet gericht naar de lichtbron dan zal de rand, die de twee polygonen delen, een silhouette rand zijn. We kunnen het algoritme als volgt beschrijven in pseudocode: f o r ( AllePolygonenVanHetModel ) i f ( PolygoonIsBelicht ()) //Voeg de randen ( een paar van vertices ) waaruit de polygoon bestaat // toe aan een verzameling. VoegAanStackToe ( rand ) ; // Controleer of de randen ( of hun inverse ) die toegevoegd werden reeds //voorkwamen in de verzameling, indien dat het geval is, verwijder dan // die bewuste rand ( en zijn dubbel ) van de stack. ControleerStack ( rand ) ; Na uitvoering van dit algoritme zullen de randen die voorkomen in de verzameling, silhoutteranden zijn. Een beperking van bovenstaand algoritme zou kunnen zijn dat het object gesloten moet zijn: er mag geen polygoon bestaan die geen buur-polygoon heeft voor elke rand. Echter,

110 dit vormt meestal geen probleem aangezien het sowieso gewenst is dat een object gesloten is indien je grafische glitches wilt vermijden, maar je moet er dus wel rekening mee houden. Let op: dit betekent niet dat een object convex moet zijn! Dit algoritme werkt zonder probleem voor zowel convexe- als concave-voorwerpen, ze moeten enkel gesloten zijn zodat je niet in het model kan kijken Bepalen shadow-volumes Nu we de silhouette randen kennen, rest ons enkel nog de Schaduw volumes te bepalen en zijn we klaar voor het eigenlijke werk: schaduwen renderen. We zullen de schaduw-volume vormen door de silhouette randen, die we verkrijgen, over een bepaalde afstand te verplaatsen, weg van de lichtbron, zie figuur 9.7 (p.108) Figuur 9.7: Schaduw-volume van het object Een rand verplaatsen van de lichtbron weg, gebeurt als volgt: //De p o s i t i e van het l i c h t Vector3 lightpos ; // Positie van de eerste vertex van de rand Vector3 pos1 ; // Positie van de tweede vertex van de rand Vector3 pos2 ; int afstand = 100; //Bepaal de vectoren tussen de lichtbron en de vertices // van de rand. Vector3 v1 = Vector3. Minus( pos1, lightpos ) ; Vector3 v2 = Vector3. Minus( pos2, lightpos ) ;

111 // Verleng die gevonden vector over de gewenste afstand newpos1 = Vector3. multiplybyscalar (v1, afstand ) ; newpos2 = Vector3. multiplybyscalar (v2, afstand ) ; Gebruik nu deze vertices, en die van de oorsprongkelijke silhoutte-randen om de schaduwvolume te renderen Renderen van de schaduwen Nu de theorie duidelijk is, kunnen we de stap zetten naar de praktijk en onze schaduwen renderen. Zoals vermeld, wordt gebruik gemaakt van de Stencil-buffer om de schaduwen te renderen, die gebruikt wordt om te tellen hoe vaak we een schaduw volume binnendringen en terug verlaten. Dit tellen kan op twee manieren gebeuren en staan gekend als de Z-pass en Z-fail methoden, met elk hun specifieke voor- en nadelen. Z-pass In de Z-pass methode gebruiken we volgende stappen om de waarden in de Stencilbuffer aan te passen. Merk op dat we twee render-passes nodig hebben voor het opvullen van de stencil buffer: Render je volledige scene alsof het volledig in de schaduw zou staan (bijvoorbeeld door alle lichten in je scene uit te schakelen) Enable de Stencil buffer, en schakel Color- en Depth-writes uit, zodat niet meer naar de color- en depth-buffer geschreven wordt (die is immers opgevuld met de data van onze beschaduwde scene ) Render de front-faces van de schaduwvolumes en verhoog de waarde van de stencil buffer voor elk punt dat slaagt voor de depth-test. Render de back-faces van alle shaduwvolumes en verlaag de waarde voor elk punt in de stencil buffer dat niet slaagt voor de depth-test. Herstel color- en depth-writes Render de volledige scene opnieuw, maar nu belicht, en enkel op die plaatsen waar de stencil buffer 0 is. Deze stap zal alle punten in de color-buffer (die de volledige schaduw-scene bevat) overschrijven met een belichte versie wanneer die buiten de schaduwvolumes vielen. Merk op dat we de stencil buffer verhogen voor die punten waarvoor de depth-test slaagt, vandaar dus de naam Z-pass methode. Dit alles is niet zo intuitief, maar figuur 9.8 (p.110) kan helpen om alles wat duidelijker te maken. In de afbeelding zie je een test-scene met zichtbare schaduwvolumes en de uiteindelijke waarden in de stencilbuffer. Merk op dat wanneer we een schaduwvolume binnendringen (vanuit het standpunt van de camera) we de waarde van de stencil buffer verhogen voor de punten die slagen voor de depth test. Alles goed en wel, we hebben pixel-perfect shadows in onze scene, maar eenmaal we met

112 Figuur 9.8: Stencil buffer voor Shadow volumes

113 onze camera een schaduw-volume binnengaan (en dus onze Near-clipping plane een schaduwvolume snijdt) zal de Z-pass methode mislukken en de stencil buffer verkeerd opvullen. Omdat we een front-face zullen hebben van het Schaduw-volume dat de near-plane snijdt, zonder bijhorende back-face, zal dit resulteren in een effect dat bekend staat als inverted shadows. Uiteraard is dit voor situaties waar we zeker zijn dat de camera nooit een schaduwvolume kan binnendringen (bijvoorbeeld als de camera altijd boven de scene blijft hangen) perfect aanvaardbaar, maar voor een willekeurige scene is het duidelijk dat we een oplossing moeten vinden. Deze werd gevonden en staat gekend als de Z-fail methode (of Carmack s Reverse, naar John Carmack de legendarische 3D-programmeur wegens zijn aandeel in dit algoritme) en wordt met succes bijvoorbeeld gebruikt in zijn Doom3-engine. Z-Fail De Z-fail methode gaat als volgt: Render je volledige scene alsof het volledig in schaduw zou staan (bijvoorbeeld door alle lichten in je scene uit te schakelen) Enable de Stencil buffer, en schakel Color- en Depth-writes uit, zodat niet meer naar de color- en depth-buffer geschreven wordt. Render de back-faces van de schaduwvolumes en verhoog de waarde van de stencil buffer voor elk punt dat niet slaagt voor de depth-test. Render de front-faces van alle schaduwvolumes en verlaag de waarde voor elk punt in de stencil buffer dat slaagt voor de depth-test. Herstel color- en depth-writes Render de volledige scene opnieuw, maar nu belicht en enkel op die plaatsen waar de stencil buffer 0 is. Zoals je ziet, vereist dit slechts een kleine aanpassing aan de Z-pass methode maar het legt wel een extra vereiste op aan de schaduw-volumes: ze moeten volledig afgesloten worden, zoals getoond in figuur 9.9 (p.112). De eenvoudigste manier om je schaduw-volumes af te sluiten is de schaduwvolumes te genereren zoals hierboven uitgelegd maar de bestaande geometrie van het object te gebruiken om de schaduwvolumes af te sluiten, als volgt: Sorteer de polygonen van het object, waarvoor we schaduw-volumes berekenen, in twee groepen: diegene die gericht staan naar de lichtbron, en diegene die niet gericht staan naar de lichtbron. Gebruik de polygonen die gericht staan naar de lichtbron om de voorkant van de schaduwvolumes af te sluiten. Verplaats nu de vertices van de polygonen die gericht staan naar het licht weg van de lichtbron (zoals je doet voor de vertices van de schaduwvolume) en gebruik de getransformeerde polygonen om de schaduw-volume achteraan af te sluiten.

114 Figuur 9.9: Gesloten schaduw-volume van het object

115 Verderop geef ik een methode om shaders te gebruiken om je gesloten schaduw-volumes te berekenen. Zoals vermeld, zal de Z-fail methode het Near-plane clipping probleem oplossen maar het introduceert meteen een ander probleem: Far-plane clipping, maar die is gelukkig eenvoudiger op te lossen/vermijden. Het probleem met de Far-plane is dat een schaduwvolume deze niet mag snijden, dit kunnen we op twee manieren oplossen: 1. Zorg dat je schaduw-volumes niet te lang worden door ze af te snijden aan de Far-plane, maar dat is een ietwat moeilijke bewerking. 2. Verplaats je Far-plane tijdelijk over een grote afstand wanneer je de stencil-buffer opvult en herstel de oude waarde later. We hebben nu een methode die pixel-perfect schaduwen rendert, waar we geen near-plane clipping problemen meer hebben en er dus geen beperkingen meer zijn met betrekking tot de positie van de camera. De Z-Fail methoide wordt met succes toegepast in de Doom3-engine van ID-software en vele andere 3D-engines die gebruik maken van Stencil shadows Shadow volumes met GLSL Het zal direct duidelijk zijn dat het genereren van de Schaduw-volumes erg belastend is voor de CPU en het zou mooi zijn indien we die dure opdracht kunnen laten uitvoeren door de GPU, die gespecialiseerd is in zulke vertex-transformaties. Dit lijkt d.m.v. een vertex-shader perfect mogelijk. Echter een vertex-shader kan enkel bestaande vertices manipuleren, ze kan onmogelijk nieuwe vertices maken. We moeten hier rekening mee houden en die extra geometrie vooraf reeds toevoegen aan het model. We moeten alle edges van de polygonen van het object waarvoor we schaduw-volumes voor wensen te berekenen, vervangen door een quad, die we dan zullen uitrekken in de vertexshader. Het toevoegen van die extra geometrie gebeurt niet tijdens run-time maar het is duidelijk dat er veel meer vertices door de pipeline gestuurd zullen worden dan voorheen, dit kan een probleem vormen voor high-polygon models. Beschouw figuur 9.10 (p.113) waarbij we een edge vervangen hebben door een quad (merk op dat de tekening niet echt correct is omdat de Quad eigenlijk een lijn is (overstaande vertices vallen samen)). Figuur 9.10: Schaduw volumes d.m.v. GLSL shader In de vertex-shader zullen we nu de quads uitrekken zodat die de schaduwvolume zullen vormen. Merk op dat het met die methode niet meer nodig is expliciet de schaduwvolumes af te sluiten aangezien dit nu automatisch gebeurt!

116 Hoe zorgen we nu dat we de juiste quads zullen uitrekken in de vertex-shader? We zullen hiervoor de normalen van de vertices van de quad gelijkstellen aan de normalen van de polygonen waartoe toe de vertices behoren, zie figuur 9.11 (p.114) en die zo doorgeven aan de vertex-shader. Figuur 9.11: Schaduw volumes d.m.v. GLSL shader In de shader zullen we aan de hand van de positie van het licht en de binnenkomende vertex de hoek berekenen tussen de normaal van die vertex en de licht-vector. Als die hoek groter is dan 90 graden verplaatsen we die bewuste vertex over een bepaalde afstand, weg van het licht zoals in figuur 9.12 (p.114). Als de hoek kleiner is dan 90 graden laten we de vertex ongemoeid. Figuur 9.12: Vertices verplaatsen weg van het licht Het is duidelijk dat enkel die quads zullen uitgerokken worden waarvan de normalen van twee vertices gericht staan naar het licht en de twee andere niet, en dat is nu net de definitie van een silhouette edge! We hebben dus een methode die probleemloos gesloten schaduwvolumes kan genereren op de GPU. De GLSL vertex-shader kan er als volgt uitzien: //De p o s i t i e van de lichtbron in onze scene uniform vec3 lightpos ; //Hoe ver we de schaduwvolumes wensen uit te rekken uniform f l o a t extrusionfactor ;

117 void main () //Bepaal de lichtvector vec3 lightdirection = normalize ( lightpos vec3 ( gl Vertex ) ) ; //Bepaal de cosinus van de hoek tussen de normaal en l i c h t // vector f l o a t cosangle = dot ( gl Normal, lightdirection ) ; // Sla de huidige vertex op vec4 position = gl Vertex ; //Als cos ( hoek ) <= 0.0 > hoek >= 90 i f ( cosangle <= 0.0) // Verplaats de vertex weg van de lichtbron position = gl Vertex + ( gl Vertex vec4 ( lightpos, 1.0)) extrusionfactor ; position.w = 1. 0 ; // Transformeer de u i t e i n d e l i j k e p o s i t i e naar screen space. gl Position = gl ModelViewProjectionMatrix position ; Soft-shadows door Light-jittering Een nadeel van het renderen van schaduwen d.m.v. schaduw volumes is dat de schaduwen bijzonder scherp zijn. In een realistische scene komen zulke schaduwen eigenlijk niet voor (echte schaduwen zijn wat waziger rond de randen). Voor shadow-mapping kunnen we dit effect bereiken door de shadowmap te filteren maar voor shadow-volumes is dit niet mogelijk. We moeten andere oplossingen zoeken. Een oplossing hiervoor is bij het renderen van de schaduwen elke lichtbron een aantal keren lichtjes te verplaatsen en de resultaten te blenden. Dit levert mooie soft-shadows maar is moordend voor de fill-rate van je videokaart, aangezien je de schaduwvolumes moet herrenderen elke keer je de lichtbron verplaatst Implementatie in de engine In deze engine zijn zowel de Z-pass en Z-fail methoden, met support voor soft-shadowing, geïmplementeerd. In wat volgt beschrijf ik kort hoe je die een Z-fail ShadowRender moet gebruiken in je applicatie (Z-Pass is analoog): ShadowRenderer s r ; OBJLoader loader ; Light l i g ht ; SceneNode lightnode ; Entity cube ; SceneNode cubenode ;

118 l i g h t = new Light (GL11.GL LIGHT0, new Vector3 (0.5 f, 0.5 f, 0.2 f ), new Vector3 (0.5 f, 0.5 f, 0.2 f ) ) ; loader = new OBJLoader ( ) ; cube = new Entity ( loader. load ( models/cube. obj ), amaterial ) ; //!!! // Belangrijk voor Z f a i l methode! Dit vervangt de edges in het //model door Quads zodat we gebruik kunnen maken van een shader. //Dit i s niet nodig voor de ZPass methode. cube. replaceedgeswithtriangles ( ) ; //!!! // plaats het l i c h t in je scene lightnode = new SceneNode ( l i g h t, l i g ht ) ; lightnode. translate (new Vector3 (0, 10, 0 ) ) ; getrootscenenode ( ). addchildnode ( lightnode ) ; // plaats een object cubenode = new SceneNode ( cube, cube ) ; cubenode. translate (new Vector3 (0, 230, 48)); getrootscenenode ( ). addchildnode ( cubenode ) ; // Creer je ShadowRenderer, s r = new ShadowVolumeZFailRenderer ( ) ; sr. addlight ( lightnode ) ; space. setshadowrenderer ( sr ) ;

119 Hoofdstuk 10 Reflection / Refraction 10.1 Reflection plane Een onmisbaar effect in een moderne engine is het kunnen renderen van spiegelende oppervlakken. In het onderdeel over portals vermelde ik reeds dat een portal zich kan gedragen zoals een spiegel, door de SectorManager aan de andere kant, gespiegeld, te renderen. Niettegenstaande dit een interessante oplossing is (dit maakt het mogelijk om bijvoorbeeld door een spiegel te stappen en rond te lopen in het spiegelbeeld ), is de methode niet praktisch in sommige situaties. Het renderen van spiegels d.m.v. een portal vereist het gebruik van de Stencil buffer en kan dikwijls niet zomaar gebruikt worden (omdat die bijvoorbeeld reeds in gebruik is). Ook vereist het maken van niet-rechthoekige spiegels een aantal kunstgrepen. Een oplossing voor deze problemen is het renderen van de gespiegelde scene naar een textuur en die textuur te gebruiken op een vorm die de spiegel zal voorstellen. Deze methode heeft het grote voordeel dat we een off-screen render-target kunnen gebruiken (en dus de colordepth en stencil-buffer ongemoeid laten) en dat we het detail van de gespiegelde scene naar believen kunnen aanpassen door de grootte van de render-target aan te passen Werkwijze Voor het renderen van de gereflecterende scene gaan we grotendeels te werk als het renderen van een Mirror, waarbij we een Reflection-matrix zullen bepalen waarmee we de scene over een willekeurig vlak kunnen spiegelen. Het enige verschil is dat we nu niet zullen renderen naar het scherm en de inhoud kopiëren naar een RGB-textuur. Zoals reeds besproken in het hoofdstuk over Portals ziet een reflection-matrix voor een willekeurig vlak met normaal n er als volgt uit: M = (n.x) 2 + (n.y) 2 + (n.z) 2 2 (n.x) (n.y) 2 (n.x) (n.z) 0 2 (n.x) (n.y) (n.y) 2 + (n.x) 2 + (n.z) 2 2 (n.y) (n.z) 0 2 (n.x) (n.z) 2 (n.y) (n.z) (n.z) 2 + (n.x) 2 + (n.y) Deze reflection matrix is enkel geldig voor een vlak in de oorsprong, dus we mogen niet vergeten het assenstelsel te verplaatsen naar de lokatie van het vlak vooraleer we reflecteren. Uiteindelijk passen we volgende serie transformaties toe op de ModelView-matrix om de scene 117

120 te reflecteren over het vlak, waarbij we het assenstelsel plaatsen naar de lokatie van het vlak, de reflectie toepassen en het assenstelsel terugplaatsen: p.x p.x p.y REFL = p.z M p.y p.z Onthoud dat wanneer we de wereld reflecteren de oriëntatie van de polygonen ook omgedraaid wordt, we moeten dus de culling-methode aanpassen (i.p.v. de back-faces te cullen dienen dit nu de front-faces te zijn). Nu is het enkel nog een kwestie van een render-target te creren en de scene te renderen. Vooraleer we die reflectie in de textuur kunnen gebruiken op een oppervlak in onze scene moeten we een kleine maatregel nemen. Stel dat we een quad hebben in onze wereld waarop we de wereld willen spiegelen, dan mogen we niet zomaar de textuur erop plakken aangezien de reflectie van de wereld onafhankelijk is van de positie van de camera. We dienen de textuur te projecteren op de quad in screen-space, door de textuur-coördinaten voor de reflectiontextuur als volgt te bepalen in een GLSL pixel-shader: vec4 screenspacecoord = gl ModelViewProjectionMatrix gl Vertex ; vec4 projcoord = screenspacecoord / screenspacecoord. q ; projcoord = ( projcoord + 1.0) 0. 5 ; projcoord = clamp ( projcoord, 0.001, 0.999); De volledige vertex- en pixel-shaders die ik gebruik in de RenderEngine, zien er als volgt uit: Reflection.vert varying vec4 viewcoords ; void main () //Bereken screen space coordinaten viewcoords = gl ModelViewProjectionMatrix gl Vertex ; // Transformeer vertex gl Position = viewcoords ; Reflection.frag varying vec4 viewcoords ; uniform sampler2d r e f l e c t i o n ;

121 void main () //Bereken correcte textuur coordinaten zodat // d i e overeenstemmen met texturemapping i n // screen space vec4 projcoord = viewcoords / viewcoords. q ; projcoord = ( projcoord + 1.0) 0. 5 ; projcoord = clamp ( projcoord, 0.001, 0.999); //Gebruik de reflection textuur met de berekende // coordinaten vec4 reflectioncolor = texture2d ( reflection, projcoord. xy ) ; //Geef een kleurtje vec4 color = vec4 (0.6, 0.5, 0.5, 1. 0 ) ; //Bepaal het u i t e i n d e l i j k fragment kleur. gl FragColor = reflectioncolor color ; Als de videokaart het ondersteunt is het beter te renderen naar een FBO (Frame Buffer Object) omdat je dan rechtstreeks rendert naar een stuk geheugen in de videokaart. Bovendien hoeven we met een FBO niet te renderen naar de color-buffer, en ze achteraf ook niet te clearen, wat de renderinstructie eens stukje sneller maakt Implementatie in de engine Figuur 10.1: Een ReflectionPlane in een scene In deze RenderEngine is het opnieuw erg eenvoudig gemaakt een reflecterend vlak toe te voegen aan een scene. De klasse ReflectionPlane zal zo een vlak aanmaken, de correcte reflection-textuur berekenen en de textuur-coordinaten correct aanpassen. Een reflection- Plane toevoegen aan een scene gebeurt als volgt: SectorManager space ; SkyBox skybox ;

122 OBJLoader loader ; Entity cube ; SceneNode cubenode ; ReflectionPlane plane ; public void setup () loader = new OBJLoader ( ) ; cube = new Entity ( loader. load ( models/cubenormals. obj ), amaterial ) ; skybox = new SkyBox (5000); plane = new ReflectionPlane ( engine. camera, //camera new Vector3 (0, 0, 1), //normaal van het vlak 0, // p o s i t i e 512, // grootte reflection textuur data/glass1.bmp ) ; // optionele textuur space = new Galaxy SectorManager ( Galaxy ) ; engine. positioncamera ( 100, 50, 100, 20, 50, 0, 0, 1, 0); engine. setroot ( space ) ; cubenode = new SceneNode ( cube, cube ) ; cubenode. translate (new Vector3 (0, 0, 100)); space. getrootscenenode ( ). addchildnode ( cubenode ) ; //Add a SectorManager to the plane. plane. sector = space ; public void render () engine. clear ( ) ; engine. positioncamera ( ) ; // Creeer de r e f l e c t i o n textuur. plane. createreflectiontexture (skybox ) ; engine. render ( ) ; //Render het spiegelend oppervlak. plane. render (skybox ) ; 10.2 Cubic environmental mapping De bovenstaande methode is enkel bruikbaar voor vlakke spiegelende oppervlakken, wanneer we ruimtelijke figuren willen gebruiken die de omgeving reflecteren moeten we anders te

123 Figuur 10.2: Real-time cubic environmental mapping in een BSP-scene werk gaan, door gebruik te maken van Cubic Environmental Mapping. Hierbij gaan we gelijkaardig te werk zoals bij Cubic Shadowmapping waarbij we de scene zullen renderen in 6 richtingen, parallel met de positieve en negatieve assen in het assenstelsel. Hierbij renderen we gewoon de kleurwaarden naar de texturen i.p.v. de depth-values. We definiëren dus opnieuw 6 Frustums (1 voor elke richting), zie figuur 10.3 (p.121) renderen naar 6 render-targets en slaan het resultaat in een CubeMap. Hierna verwerken we dit in een shader en mappen de CubeMap op een willekeurig object. Figuur 10.3: Zes View-Frustums gedefinieerd rond een object voor environmental cube mapping Het is mogelijk de CubeMap af en toe (bijvoorbeeld elke 5 frames) te updaten zodat de omgeving in real-time zal gereflecteerd worden in het object. De GLSL-shader die gebruikt wordt in deze RenderEngine, ziet er als volgt uit:

124 CubeMapping.vert uniform vec3 camerapos ; void main () gl TexCoord [ 0 ] = gl MultiTexCoord0 ; vec4 pos = gl Vertex ; vec3 normal = gl Normal ; //Bereken de view vector vec3 view = normalize ( camerapos pos. xyz ) ; // Reflecteer de viewvector over de normaal //van de huidige vertex vec3 r e f l e x = r e f l e c t ( view, normal ) ; // Wijzig de textuur coordinaten gl TexCoord [ 1 ] = gl TextureMatrix [ 0 ] vec4 ( reflex, 1. 0 ) ; // Transformeer de vertex naar screen space. gl Position = ftransform ( ) ; CubeMapping.frag // Onze CubeMap uniform samplercube TexCube ; //Een optionele textuur uniform sampler2d tex ; void main () // Indexeer de textuur vec4 texel = texture2d ( tex, vec2 ( gl TexCoord [ 0 ]. x, gl TexCoord [ 0 ]. y ) ) ; // Indexeer de cubemap vec4 color = texturecube ( TexCube, vec3( gl TexCoord [ 1 ]. x, gl TexCoord [ 1 ]. y, gl TexCoord [ 1 ]. z ) ) ; //Bepaal de u i t e i n d e l i j k e fragment kleur gl FragColor = color + texel ; 10.3 Refraction plane Doorschijnende oppervlakken renderen, is erg eenvoudig door gebruik te maken van Alphablending maar sinds de komst van videokaarten die pixel-shaders ondersteunen is het mogelijk interessantere en realistischere resultaten te bekomen. In realiteit is een venster meestal niet

125 volledig doorzichtig maar zitten er oneffenheden in of is het glas wazig. Wanneer we door een gebogen glas kijken (zoals een glazen bol) is de achterliggende scene dikwijls vervormd. Zulke resultaten kunnen we bekomen door gebruik te maken van een refraction-shader die lichtbreking zal simuleren. Refraction kunnen we definiëren als de verandering in richting die licht ondervindt wanneer het zich verplaatst van een bepaald medium, naar een ander, zoals een overgang van lucht naar glas. Die lichtbreking is een gevolg van verschil in lichtsnelheid in de verschillende materialen. Figuur 10.4: Refraction Om de lichtbreking te berekenen op een oppervlak zullen we gebruik maken van een DUDV-map. In een normal-map worden de normalen van een object opgeslaan, in een DUDVmap worden simpeleweg de afgeleiden van die normalen bijgehouden. Een DUDV-map zal dienen als lookup-texture waarmee we in real-time kunnen berekenen hoe het licht zal breken op een oneffen oppervlak. Opmerking: er bestaat een bijzonder handige tool van ATI die toelaat een TGA-afbeelding te transformeren naar normal-map en DUDV-map Werkwijze De werkwijze is gelijkaardig als bij de Reflection Plane waarbij we een scene renderen naar een render-target en die verwerken in een pixel-shader. Hier renderen we niet het spiegelbeeld van een scene maar renderen we doodgewoon wat achter het refraction-vlak ligt en mappen die opnieuw op het vlak in screen-space. De gebruikte GLSL-refraction-shader in de RenderEngine ziet er als volgt uit: Refraction.vert varying vec4 refrcoords ; varying vec4 normcoords ;

126 (a) Normal map (b) DUDV map Figuur 10.5: Voorbeeld Normal- en DUDV-map varying vec4 viewcoords ; void main () //Gebruik texture layer1 voor de refraction // textuur refrcoords = gl MultiTexCoord1 ; //Gebruik texture layer2 voor de dudv map normcoords = gl MultiTexCoord2 ; // Transformeer vertex naar screen space. viewcoords = gl ModelViewProjectionMatrix gl Vertex ; gl Position = viewcoords ; Refraction.frag varying vec4 refrcoords ; varying vec4 normcoords ; varying vec4 viewcoords ; //Onze r e f r e c t i o n texture uniform sampler2d refraction ; // Onze DUDV map uniform sampler2d dudvmap; //Een optionele textuur uniform sampler2d texture ; void main () //Een aantal constanten die toelaten de refraction

127 //aan te passen. Wijzig deze naar believen tot je een //goed resultaat hebt. const f l o a t kdistortion = 0.015; const f l o a t krefraction = 0.05; //Bepaal de textuurcoordinaat voor de dudv map per pixel en pas //een d i s t o r t i e toe vec4 distoffset = texture2d (dudvmap, normcoords. xy) kdistortion ; vec4 dudvcolor = texture2d (dudvmap, vec2 ( refrcoords + distoffset ) ) ; dudvcolor = normalize ( dudvcolor ) krefraction ; //Bepaal de textuur coordinaten van de refraction textuur in // screen space vec4 projcoord = viewcoords / viewcoords. q ; projcoord = ( projcoord + 1.0) 0. 5 ; // Wijzig de coordinaat l i c h t j e s naargelang de waarde in de //dudv map projcoord += dudvcolor ; projcoord = clamp ( projcoord, 0.001, 0.999); //Pas de gegenereerde textuur coordinaat toe vec4 refractioncolor = texture2d ( refraction, projcoord. xy ) ; //Voeg een kleurtje toe, indien gewenst. vec4 color = vec4 (0.9, 0.9, 0.9, 1. 0 ) ; //Bepaal de u i t e i n d e l i j k fragment kleur. gl FragColor = refractioncolor color ; Ook hier is het aan te raden een FBO te gebruiken als je hardware die ondersteunt Implementatie in de engine Figuur 10.6: Een refraction plane in een scene Een refraction-plane in je applicatie gebruiken, gebeurt gelijkaardig aan het gebruiken van een reflection-plane:

128 SectorManager space ; SkyBox skybox ; OBJLoader loader ; Entity cube ; SceneNode cubenode ; RefractionPlane plane ; public void setup () loader = new OBJLoader ( ) ; cube = new Entity ( loader. load ( models/cubenormals. obj ), amaterial ) ; skybox = new SkyBox (5000); plane = new RefractionPlane ( engine. camera, //camera new Vector3 (0, 0, 1), //normaal van het vlak 0, // p o s i t i e 512, // grootte reflection textuur data/mydudv. bmp, //De DUDV map data/glass1.bmp ) ; // optionele textuur space = new Galaxy SectorManager ( Galaxy ) ; engine. positioncamera ( 100, 50, 100, 20, 50, 0, 0, 1, 0); engine. setroot ( space ) ; cubenode = new SceneNode ( cube, cube ) ; cubenode. translate (new Vector3 (0, 0, 100)); space. getrootscenenode ( ). addchildnode ( cubenode ) ; //Voeg een SectorManager toe aan de RefractionPlane. plane. sector = space ; public void render () engine. clear ( ) ; engine. positioncamera ( ) ; // Creeer de r e f l e c t i o n textuur. plane. createrefractiontexture (skybox ) ; engine. render ( ) ; //Render het refraction oppervlak plane. render (skybox ) ;

129 10.4 Water-rendering Nu we werkende implementaties hebben van reflection- en refraction-oppervlakken kunnen we dit alles perfect combineren tot iets waar tegenwoordig bijzonder veel aandacht aan besteed wordt in moderne render-engines: het renderen van realistische wateroppervlakken. Algemeen kunnen we het renderen van water beschrijven door volgende drie render-passes: 1. Render alles wat boven het wateroppervlak ligt in een reflection-plane. 2. Render alles wat onder het wateroppervlak ligt in een refraction-plane. 3. Render alles wat onder het wateroppervlak ligt in een depth-texture. Dit alles combineren we in een GLSL-shader. Vooraleer we hier dieper op ingaan, ben ik genoodzaakt een aantal zaken te verduidelijken. Fresnel-term Wanneer we aandachtig naar een wateroppervlak kijken zal je merken dat hoe verder we kijken, op het wateroppervlak hoe minder we zien wat er onder ligt en hoe meer we zien van de gereflecteerde omgeving. De hoeveelheid licht dat zo weerkaatst wordt, wordt beschreven door de Fresnel-term, en zullen we in de RenderEngine benaderen door Fresnel = (N L) waarbij N de normaal van de pixel is en L de genormaliseerde vector naar de positie van de camera. Merk op dat deze berekening de inverse oplevert van de Fresnel-term, we moeten die dus nog inverteren om zinvol te zijn: Fresnel = 1.0 (N L) Figuur 10.7: De fresnel-term in de praktijk De basis-idee is dus: hoe kleiner de hoek tussen de view-vector en de normaal van een pixel van het wateroppervlak, of hoe dichter de pixel bij de camera ligt, hoe meer we zullen tonen van de refraction-textuur (en dus wat onder het water ligt). Hoe groter de hoek, hoe verder de pixel van de camera ligt, hoe meer we zullen renderen van de reflection-textuur.

130 Fog Door vuildeeltjes die rondzweven in een realistisch water-lichaam zal water er mistig uitzien en zal hoe dieper we willen kijken, blijken dat we minder en minder zullen kunnen zien wat onder het oppervlak ligt. Dit is een effect waar we rekening mee moeten houden om water realistisch te kunnen renderen, en zullen we simuleren door alles wat onder het wateroppervlak ligt te renderen naar een depth-texture en naargelang de waarden die daar opgeslaan liggen, zullen we fog toevoegen d.m.v. een shader Werkwijze Vooraleer we verder gaan lijkt het me nuttig een diagram te geven dat schematisch toont welke stappen er juist gedaan worden om te komen tot een realistisch water-oppervlak. Hoe de reflection- en refraction texturen gemaakt worden, werd uitgelegd in hun respectieve hoofdstukken. Daar wordt hier niet meer in detail op teruggekomen, maar ik zal tonen wat er gebeurt met behulp van de GLSL-shader die gebruikt wordt in de RenderEngine: Water.vert // Textuurcoordinaten van refraction textuur varying vec4 refrcoords ; // Textuurcoordinaten van normal map varying vec4 normcoords ; //The vertex in screen space varying vec4 viewcoords ; // Camerapositie in Tangent space varying vec4 viewtangetspace ; //Uniforme variabelen die camera p o s i t i e bevat. uniform vec4 camerapos ; void main () // Water i s een vlak in het XZ vlak, we kennen dus de //tangent, normal en bitangent vectoren. vec4 tangent = vec4 (1.0, 0.0, 0.0, 0. 0 ) ; vec4 normal = vec4 (0.0, 1.0, 0.0, 0. 0 ) ; vec4 bitangent = vec4 (0.0, 0.0, 1.0, 0. 0 ) ; //De vector van de vertex naar de camera vec4 viewdir = camerapos gl Vertex ; //Bereken camera in Tangent space viewtangetspace. x = dot ( viewdir, tangent ) ; viewtangetspace. y = dot ( viewdir, bitangent ) ; viewtangetspace. z = dot ( viewdir, normal ) ; viewtangetspace.w = 1. 0 ; //Gebruik de multi texture coordinaten op laag 1 en 2

131 Figuur 10.8: Water-rendering schematisch voorgesteld

132 refrcoords = gl MultiTexCoord1 ; normcoords = gl MultiTexCoord2 ; // Transformeer vertex in screen space. viewcoords = gl ModelViewProjectionMatrix gl Vertex ; // Transformeer vertex gl Position = viewcoords ; Water.frag varying vec4 refrcoords ; varying vec4 normcoords ; varying vec4 viewcoords ; varying vec4 viewtangetspace ; //Textuur met r e f l e c i e uniform sampler2d r e f l e c t i o n ; //Textuur met refraction uniform sampler2d refraction ; //Normalmap textuur uniform sampler2d normalmap ; //DUDV map uniform sampler2d dudvmap; //Textuur met depth values uniform sampler2d depthmap ; //Kleur van het water uniform vec4 watercolor ; void main () //Constanten voor refraction const f l o a t kdistortion = 0.015; const f l o a t krefraction = 0.009; //Lees waarde uit DUDV map vec4 distoffset = texture2d (dudvmap, normcoords. xy) kdistortion ; vec4 dudvcolor = texture2d (dudvmap, vec2 ( refrcoords + distoffset ) ) ; dudvcolor = normalize ( dudvcolor ) krefraction ; // Lees waarde u i t Normal map vec4 normalvector = texture2d (normalmap, vec2 ( refrcoords + distoffset ) ) ; normalvector = normalvector ; normalvector. a = 0. 0 ; // Normaliseer view vector in Tangent space vec4 viewreflection = normalize ( viewtangetspace ) ; //Bereken geinverteerde Fresnel term door het DOT product // te nemen van

133 vec4 invertedfresnel = vec4 ( dot ( normalvector, viewreflection ) ) ; //Bereken Fresnel term vec4 fresnelterm = 1.0 invertedfresnel ; //Bereken textuurcoordinaten voor refraction vec4 projcoord = viewcoords / viewcoords. q ; projcoord = ( projcoord + 1.0) 0. 5 ; projcoord += dudvcolor ; projcoord = clamp ( projcoord, 0.001, 0.999); //Lees r e f l e c t i e textuur vec4 reflectioncolor = texture2d ( reflection, projcoord. xy ) ; //Lees refraction textuur vec4 refractioncolor = texture2d ( refraction, projcoord. xy ) ; //Lees depth textuur vec4 depthvalue = texture2d ( depthmap, projcoord. xy ) ; depthvalue = vec4 (pow( depthvalue. x, 256.0)); //Bereken geinverteerde diepte waarde. vec4 invdepth = 1.0 depthvalue ; //Bereken u i t e i n d e l i j k e refraction kleur ( zie schema! ) refractioncolor = invertedfresnel invdepth ; refractioncolor += watercolor depthvalue invertedfresnel ; //Bereken u i t e i n d e l i j k e r e f l e c t i e kleur reflectioncolor = fresnelterm ; //Tel u i t e i n d e l i j k e resultaten op gl FragColor = refractioncolor + reflectioncolor ; Water-rendering en FBO (Frame Buffer Object) Uiteraard kan je de reflectie-, refraction- en depth-texturen, nodig voor het renderen van water, renderen d.m.v. een FBO. De setup van de FBO is volledig gelijkaardig aan het voorbeeld besproken bij ShadowMapping maar voor de Reflectie-textuur moeten we een extra maatregel nemen. Wanneer we renderen naar een FBO worden de color-, depth- en stencil-buffer buiten beschouwing gelaten en renderen we rechtstreeks naar het geheugen van je videokaart. Uiteraard betekent dit dat we geen depth-tests kunnen uitvoeren en indien we wensen dat de gereflecteerde scene correct gerenderd wordt, moeten we een depth-rendertarget linken aan onze FBO: glbindframebufferext (GL FRAMEBUFFER EXT, fbid ) ; glframebuffertexture2dext (GL FRAMEBUFFER EXT, GL COLOR ATTACHMENT0 EXT, GL11.GL TEXTURE 2D, refractionid, 0); //Voeg een depth buffer toe depthbufferhandle = BufferUtils. createintbuffer ( 1 ) ;

134 glgenrenderbuffersext ( depthbufferhandle ) ; glbindrenderbufferext (GL RENDERBUFFER EXT, depthbufferhandle. get ( 0 ) ) ; glr ender buffer Stor ageext (GL RENDERBUFFER EXT, GL11.GL DEPTH COMPONENT, texturesize, texturesize ) ; glframebufferrenderbufferext (GL FRAMEBUFFER EXT, GL DEPTH ATTACHMENT EXT, GL RENDERBUFFER EXT, depthbufferhandle. get ( 0 ) ) ; GL DEPTH ATTACHMENT EXT, GL RENDERBUFFER EXT, 0); glbindframebufferext (GL FRAMEBUFFER EXT, 0); Onze FBO kunnen we op net dezelfde manier gebruiken als een normale textuur Implementatie in de engine (a) Water-plane in Galaxy-sector (b) Water-plane in Octree-sector Figuur 10.9: Waterplanes in verschillende scenes Water werd in de engine geïmplementeerd als een WaterPlane en zal bovenstaande zaken autonoom verwerken zodat je als gebruiker hier geen rekening mee hoeft te houden. Een WaterPlane toevoegen is bijzonder eenvoudig gehouden: SectorManager space ; SkyBox skybox ; WaterPlane water ; public void initdata () skybox = new SkyBox (5000); water = new WaterPlane ( engine. camera, //De camera in de scene new Vector3 (0, 1, 0), //de normaal 0, // Translatie 165, // breedte 100, // hoogte 512, // r e s o l u t i e van gebruikte render targets

Vakgroep CW KAHO Sint-Lieven

Vakgroep CW KAHO Sint-Lieven Vakgroep CW KAHO Sint-Lieven Objecten Programmeren voor de Sport: Een inleiding tot JAVA objecten Wetenschapsweek 20 November 2012 Tony Wauters en Tim Vermeulen tony.wauters@kahosl.be en tim.vermeulen@kahosl.be

Nadere informatie

Vakinhoudelijke uitwerking Keuzevak Applicatieontwikkeling van het profiel MVI vmbo beroepsgericht

Vakinhoudelijke uitwerking Keuzevak Applicatieontwikkeling van het profiel MVI vmbo beroepsgericht Vakinhoudelijke uitwerking Keuzevak Applicatieontwikkeling van het profiel MVI vmbo beroepsgericht Deze vakinhoudelijke uitwerking is ontwikkeld door het Redactieteam van de Schooleamenbank vmbo voor dit

Nadere informatie

Uitgebreid eindwerkvoorstel Lokaliseren van personen en objecten met behulp van camera s

Uitgebreid eindwerkvoorstel Lokaliseren van personen en objecten met behulp van camera s Uitgebreid eindwerkvoorstel Lokaliseren van personen en objecten met behulp van camera s Sofie De Cooman 21 December 2006 Stagebedrijf: Interne begeleider: Externe begeleider: BarcoView Koen Van De Wiele

Nadere informatie

IN2905-I Computer Graphics 18 juni 2008, uur.

IN2905-I Computer Graphics 18 juni 2008, uur. TECHNISCHE UNIVERSITEIT DELFT Faculteit Elektrotechniek, Wiskunde en Informatica Afdeling Mediamatica IN95-I Computer Graphics 8 juni 8, 4. - 7. uur. Dit is een tentamen met 3 meerkeuzevragen Het aantal

Nadere informatie

Open Dag Informatica (28 nov 2003) 3D Graphics Workshop

Open Dag Informatica (28 nov 2003) 3D Graphics Workshop Open Dag Informatica (28 nov 2003) 3D Graphics Workshop Dr. Erwin M. Bakker Ing. Ernst Lindoorn Leiden Institute of Advanced Computer Science Leiden University E-mail: erwin@liacs.nl 3D Graphics Toepassingen

Nadere informatie

FACULTEIT ECONOMIE EN BEDRIJFSKUNDE Afdeling Kwantitatieve Economie

FACULTEIT ECONOMIE EN BEDRIJFSKUNDE Afdeling Kwantitatieve Economie FACULTEIT ECONOMIE EN BEDRIJFSKUNDE Afdeling Kwantitatieve Economie Lineaire Algebra, tentamen Uitwerkingen vrijdag 4 januari 0, 9 uur Gebruik van een formuleblad of rekenmachine is niet toegestaan. De

Nadere informatie

Labo 2 Programmeren II

Labo 2 Programmeren II Labo 2 Programmeren II L. Schoofs K. van Assche Gebruik Visual Studio 2005 om een programma te ontwikkelen dat eenvoudige grafieken tekent. Deze opgave heb je vorig academiejaar reeds in Java geïmplementeerd.

Nadere informatie

UML. From weblog http://dsnippert.wordpress.com. Dennis Snippert

UML. From weblog http://dsnippert.wordpress.com. Dennis Snippert UML From weblog http://dsnippert.wordpress.com Naam: Dennis Snippert Inhoudsopgave 1. Wat is Uml?... 3 2. UML diagrammen... 4 3. Uitleg diagrammen... 5 3.1. Usecase diagram:... 5 3.2. Class diagram:...

Nadere informatie

www.ontmoeting.nl ISBN: 978-90-8814-040-2 NUR-code: 648 Bouwkunst, Architectuur 1 ed. Uitgeverij Ontmoeting, Huizen, NH * Materialen *Texturen

www.ontmoeting.nl ISBN: 978-90-8814-040-2 NUR-code: 648 Bouwkunst, Architectuur 1 ed. Uitgeverij Ontmoeting, Huizen, NH * Materialen *Texturen Gebaseerd op ARTLANTIS v. 5.x TIPS & TRUCS - I Render programma Uitgeverij Ontmoeting, Huizen, NH www.ontmoeting.nl * Materialen *Texturen *Zelf Shaders maken * Bump maps * Normal maps * 3D effecten *

Nadere informatie

Next-gen level in Unity

Next-gen level in Unity Tijdens de les behandeld (3D periode 4 jaar 2 2011/2012) Next-gen level in Unity Naamgeving en mappenstructuur De mappenstructuur en benaming van de verschillende assets bij het maken van een van een next-gen

Nadere informatie

Zelftest Informatica-terminologie

Zelftest Informatica-terminologie Zelftest Informatica-terminologie Document: n0947test.fm 01/07/2015 ABIS Training & Consulting P.O. Box 220 B-3000 Leuven Belgium TRAINING & CONSULTING INTRODUCTIE Deze test is een zelf-test, waarmee u

Nadere informatie

Kleine cursus PHP5. Auteur: Raymond Moesker

Kleine cursus PHP5. Auteur: Raymond Moesker Kleine cursus PHP5 Auteur: Raymond Moesker Kleine cursus PHP PHP is platform en CPU onafhankelijk, open source, snel, heeft een grote userbase, het is object georiënteerd, het wordt omarmd door grote bedrijven

Nadere informatie

Louis van Amerongen - Witteveen+Bos

Louis van Amerongen - Witteveen+Bos Christiaan Post Mark Stals - Sweco - Gemeente Eindhoven Louis van Amerongen - Witteveen+Bos Algemeen: Tijdens deze workshop zullen we twee manieren behandelen om vanuit MicroStation een 3D model om te

Nadere informatie

Wat is Computer Graphics?

Wat is Computer Graphics? Inleiding Wat is Computer Graphics? Wat is Computer Graphics? Computer user output vrijwel alles wat geen tekst of geluid is, incl. user interface Beperkt: het creëren en manipuleren van grafische informatie

Nadere informatie

BK Licht en Renderen Workshop 3 Technisch Ontwerp en Informatica

BK Licht en Renderen Workshop 3 Technisch Ontwerp en Informatica BK3070 - Licht en Renderen Workshop 3 Wat is Renderen? To render: give an interpretation or rendition of... In Computer Graphics: To transform digital information in the form received from a repository

Nadere informatie

De fotogrammetrie bij het NGI

De fotogrammetrie bij het NGI De fotogrammetrie bij het NGI 1. Inleiding De fotogrammetrie is de techniek die toelaat metingen te verrichten vanaf foto s (of volgens de ontwikkelingen gedurende de laatste jaren metingen te verrichten

Nadere informatie

Informatica: C# WPO 12

Informatica: C# WPO 12 Informatica: C# WPO 12 1. Inhoud Datacontainers, bestanden uitlezen, bestanden schrijven en data toevoegen aan en bestand, csv-bestanden 2. Oefeningen Demo 1: Point2D Demo 2: Notepad Demo 3: Read CSV-file

Nadere informatie

Software Test Documentation

Software Test Documentation FACULTEIT INGENIEURSWETENSCHAPPEN & WE- TENSCHAPPEN DEPARTMENT OF COMPUTER SCIENCE AND APPLIED COMPUTER SCIENCE Software Test Documentation Software Engineering Nicolas Carraggi, Youri Coppens, Christophe

Nadere informatie

voor Blender v2.42a Software Box Bas van Dijk v1.1 februari 2007

voor Blender v2.42a Software Box Bas van Dijk v1.1 februari 2007 voor Blender v2.42a Software Box Bas van Dijk v1.1 februari 2007 Copyright (c) 2007 - Bas van Dijk Toestemming tot het kopiëren en verspreiden van dit document wordt verleend mits het document ongewijzigd

Nadere informatie

Tips en Trucs Artlantis Shaders, Bump-, Normal Map, 3D effect Objects maken

Tips en Trucs Artlantis Shaders, Bump-, Normal Map, 3D effect Objects maken Tips en Trucs Artlantis Shaders, Bump-, Normal Map, 3D effect Objects maken Als startende architect wil je graag zo goed mogelijk voor de dag komen Gebaseerd op ARTLANTIS v. 5.x TIPS & TRUCS - I Render

Nadere informatie

Tentamen 2IV10 Computergrafiek

Tentamen 2IV10 Computergrafiek Tentamen IV Computergrafiek 8 augustus 8, 4:-7: uur Dit tentamen bestaat uit vier vragen met in totaal 5 deelvragen. Elke deelvraag weegt even zwaar. In alle gevallen geldt: LICHT UW ANTWOORD TOE. Gebruik

Nadere informatie

HTML Graphics. Hans Roeyen V 3.0

HTML Graphics. Hans Roeyen V 3.0 HTML Graphics Hans Roeyen V 3.0 19 maart 2015 Inhoud 1. HTML5 Canvas... 3 1.1. Het Canvas element... 3 2. SVG Element... 9 2.1. SVG vergeleken met Canvas... 9 2.2. Een cirkel tekenen met SVG... 10 2.2.1.

Nadere informatie

Python (gem=1,86) Java (gem=1,57) Enquete cursus informatica 1e bachelors oefeningen beter aansluiten bij project?

Python (gem=1,86) Java (gem=1,57) Enquete cursus informatica 1e bachelors oefeningen beter aansluiten bij project? Enquete cursus informatica 1e bachelors 216-217 Python (gem=1,86) Java (gem=1,7) 3 3 2 2 1 1 3 2 1-1 -2-3 3 2 1-1 -2-3 2 Combinatie python va (gem=1,6) 1 Hoe is de overgang python2va 1 1 3 2 1-1 -2-3 3

Nadere informatie

Technisch Ontwerp W e b s i t e W O S I

Technisch Ontwerp W e b s i t e W O S I Technisch Ontwerp W e b s i t e W O S I WOSI Ruud Jungbacker en Michael de Vries - Technisch ontwerp Website Document historie Versie(s) Versie Datum Status Omschrijving / wijzigingen 0.1 20 nov 2008 Concept

Nadere informatie

BK3070 Rendering en Licht. BK Renderen en licht

BK3070 Rendering en Licht. BK Renderen en licht BK3070 Rendering en Licht 1 Wat is Renderen? To render: give an interpretation or rendition of... In Computer Graphics: To transform digital information in the form received from a repository into a display

Nadere informatie

Non Diffuse Point Based Global Illumination

Non Diffuse Point Based Global Illumination Non Diffuse Point Based Global Illumination Karsten Daemen Thesis voorgedragen tot het behalen van de graad van Master of Science in de ingenieurswetenschappen: computerwetenschappen Promotor: Prof. dr.

Nadere informatie

Een gelinkte lijst in C#

Een gelinkte lijst in C# Een gelinkte lijst in C# In deze tutorial ga demonstreren hoe je een gelinkte lijst kan opstellen in C#. We gaan een klasse schrijven, die een gelijkaardige functionaliteit heeft als een ArrayList, namelijk

Nadere informatie

Informatica: C# WPO 13

Informatica: C# WPO 13 Informatica: C# WPO 13 1. Inhoud Bestanden uitlezen, bestanden schrijven en data toevoegen aan een bestand, csv-bestanden 2. Oefeningen Demo 1: Notepad Demo 2: Read CSV-file Demo 3: Write CSV-file A: Plot

Nadere informatie

Lineaire afbeeldingen

Lineaire afbeeldingen Les 2 Lineaire afbeeldingen Als een robot bij de robocup (het voetbaltoernooi voor robots een doelpunt wil maken moet hij eerst in de goede positie komen, d.w.z. geschikt achter de bal staan. Hiervoor

Nadere informatie

Software Test Plan. Yannick Verschueren

Software Test Plan. Yannick Verschueren Software Test Plan Yannick Verschueren November 2014 Document geschiedenis Versie Datum Auteur/co-auteur Beschrijving 1 November 2014 Yannick Verschueren Eerste versie 1 Inhoudstafel 1 Introductie 3 1.1

Nadere informatie

Inhoudstabel. Habils Kenny 2

Inhoudstabel. Habils Kenny   2 Inhoudstabel Inhoudstabel... 2 1. Inleiding... 3 2. Arceerpatroon definiëren... 3 Hoe is een arceerpatroon samen gesteld.... 4 Opmerkingen... 5 Hoe laad je nu uw aangemaakte patronen... 5 Kan je uw arceerpatronen

Nadere informatie

Tentamen Programmeren in C (EE1400)

Tentamen Programmeren in C (EE1400) TU Delft Tentamen Programmeren in C (EE1400) 5 april 2012, 9.00 12.00 Faculteit EWI - Zet op elk antwoordblad je naam en studienummer. - Beantwoord alle vragen zo nauwkeurig mogelijk. - Wanneer C code

Nadere informatie

DATAMODELLERING DATA MAPPING MODEL

DATAMODELLERING DATA MAPPING MODEL DATAMODELLERING DATA MAPPING MODEL Inleiding In dit whitepaper wordt de datamodelleervorm data mapping model beschreven. Deze modelleervorm staat in verhouding tot een aantal andere modelleervormen. Wil

Nadere informatie

Functionele beschrijving: Scannen naar AFAS Profit.

Functionele beschrijving: Scannen naar AFAS Profit. Functionele beschrijving: Scannen naar AFAS Profit. Algemeen Met de Kyocera Scannen naar AFAS Profit beschikt u over een efficiënte oplossing om uw documenten te scannen naar AFAS Profit. Met deze oplossing

Nadere informatie

Programmeren. Inleiding

Programmeren. Inleiding Programmeren Inleiding STAPPEN IN DE ONTWIKKELING VAN EEN PROGRAMMA 1. Probleem 1. Probleem Ideaal gewicht berekenen Wortel van een vierkantsvergelijking berekenen Schaakspel spelen Boekhouding doen 2.

Nadere informatie

Functionele beschrijving: scannen naar UNIT4 DocumentManager

Functionele beschrijving: scannen naar UNIT4 DocumentManager Functionele beschrijving: scannen naar UNIT4 DocumentManager Algemeen Met de KYOCERA Scannen naar UNIT4 DocumentManager beschikt u over een efficiënte oplossing om uw documenten te scannen naar UNIT4 DocumentManager

Nadere informatie

Functionele beschrijving: scannen naar UNIT4 Cura Documentmanagement.

Functionele beschrijving: scannen naar UNIT4 Cura Documentmanagement. Functionele beschrijving: scannen naar UNIT4 Cura Documentmanagement. Algemeen Met KYOCERA scannen naar UNIT4 Cura Documentmanagement beschikt u over een efficiënte oplossing om uw documenten te scannen

Nadere informatie

Computer Graphics (in2770) 16 augustus 2004, uur. Dit tentamen bestaat uit 30 opgaven Totaal aantal bladzijden: 10

Computer Graphics (in2770) 16 augustus 2004, uur. Dit tentamen bestaat uit 30 opgaven Totaal aantal bladzijden: 10 TECHNISCHE UNIVERSITEIT DELFT Faculteit Elektrotechniek, Wiskunde en Informatica Afdeling Mediamatica Computer Graphics (in77) 6 augustus 4, 4. - 6. uur. N.B.: Dit tentamen bestaat uit 3 opgaven Totaal

Nadere informatie

Data Vision. Your partner in Vision Solutions

Data Vision. Your partner in Vision Solutions Data Vision Your partner in Vision Solutions Wie ben ik? Gaspar van Elmbt Account Manager - Data Vision Zuid Nederland + Belgisch Limburg Historie: - Bachelor Electrical Engineering - Hard & Software engineer

Nadere informatie

DNAQL Simulator. Presentatie Bachelorproef. Tom Desair. Universiteit Hasselt. Academiejaar 2010-2011

DNAQL Simulator. Presentatie Bachelorproef. Tom Desair. Universiteit Hasselt. Academiejaar 2010-2011 DNAQL Simulator Presentatie Bachelorproef Tom Desair Universiteit Hasselt Academiejaar 2010-2011 Tom Desair (Universiteit Hasselt) DNAQL Simulator Academiejaar 2010-2011 1 / 13 Inhoud Inleiding Inhoud

Nadere informatie

Functionele beschrijving: scannen naar van Brug software.

Functionele beschrijving: scannen naar van Brug software. Functionele beschrijving: scannen naar van Brug software. Algemeen Met de KYOCERA scannen naar van Brug Software beschikt u over een efficiënte oplossing om uw documenten te scannen naar het Notarieel

Nadere informatie

Functionele beschrijving: scannen naar Exact Globe.

Functionele beschrijving: scannen naar Exact Globe. Functionele beschrijving: scannen naar Exact Globe. Algemeen Met de KYOCERA scannen naar Exact Globe beschikt u over een efficiënte oplossing om uw documenten te scannen naar Exact Globe. Met deze oplossing

Nadere informatie

Computer Graphics (in2770) 3 november 2005, uur. Dit tentamen bestaat uit 30 opgaven Totaal aantal bladzijden: 11

Computer Graphics (in2770) 3 november 2005, uur. Dit tentamen bestaat uit 30 opgaven Totaal aantal bladzijden: 11 TECHNISCHE UNIVERSITEIT DELFT Faculteit Elektrotechniek, Wiskunde en Informatica Afdeling Mediamatica Computer Graphics (in77) 3 november 5, 9. -. uur N.B.: Dit tentamen bestaat uit 3 opgaven Totaal aantal

Nadere informatie

Programmeren in C++ Efficiënte zoekfunctie in een boek

Programmeren in C++ Efficiënte zoekfunctie in een boek Examen Software Ontwikkeling I 2e Bachelor Informatica Faculteit Wetenschappen Academiejaar 2010-2011 21 januari, 2011 **BELANGRIJK** 1. Lees eerst de volledige opgave (inclusief de hints/opmerkingen)!

Nadere informatie

(2) Stel een parametervoorstelling op van de doorsnijdingskromme van sfeer en cilinder in de voorkeurpositie.

(2) Stel een parametervoorstelling op van de doorsnijdingskromme van sfeer en cilinder in de voorkeurpositie. Vraag op 5 punten de sfeer met middelpunt in,, 4 en straal 6; de omwentelingscilinder met straal 6 en als as de rechte door,, met richtingsvector,, Bepaal een affiene transformatie of een coördinatentransformatie,

Nadere informatie

Kennis na het volgen van de training. Na het volgen van deze training bent u in staat:

Kennis na het volgen van de training. Na het volgen van deze training bent u in staat: Training Trainingscode Duur Gepubliceerd Taal Type Leermethode Kosten SF2015V8 4 dagen 02/02/2015 Nederlands & Engels Developer, basis Invidueel & klassikaal Op aanvraag Deze training richt zich op het

Nadere informatie

Zo gaat jouw kunstwerk er straks uitzien. Of misschien wel heel anders.

Zo gaat jouw kunstwerk er straks uitzien. Of misschien wel heel anders. Spirograaf in Python Een kunstwerk maken Met programmeren kun je alles maken! Ook een kunstwerk! In deze les maken we zelf een kunstwerk met Python. Hiervoor zal je werken met herhalingen en variabelen.

Nadere informatie

Tentamen Objectgeorienteerd Programmeren TI februari Afdeling ST Faculteit EWI TU Delft

Tentamen Objectgeorienteerd Programmeren TI februari Afdeling ST Faculteit EWI TU Delft I ' Tentamen Objectgeorienteerd Programmeren TI 1200 1 februari 2012 9.00-12.00 Afdeling ST Faculteit EWI TU Delft Bij dit tentamen mag je geen gebruik maken van hulpmiddelen zoals boek of slides. Dit

Nadere informatie

Flex_Rooster WERKBOEK. INTRODUCTIE iseries. Dit werkboek is eigendom van ICS opleidingen en mag niet worden meegenomen.

Flex_Rooster WERKBOEK. INTRODUCTIE iseries. Dit werkboek is eigendom van ICS opleidingen en mag niet worden meegenomen. Flex_Rooster WERKBOEK INTRODUCTIE iseries Dit werkboek is eigendom van ICS opleidingen en mag niet worden meegenomen. ICS Opleidingen Niets uit deze uitgave mag worden verveelvoudigd en/of openbaar gemaakt

Nadere informatie

Ijkingstoets industrieel ingenieur UGent/VUB, september 2015

Ijkingstoets industrieel ingenieur UGent/VUB, september 2015 IJkingstoets 4 september 05 - reeks - p. /0 Ijkingstoets industrieel ingenieur UGent/VUB, september 05 Oefening De evolutie van een bepaalde radioactieve stof in de tijd volgt het wiskundig model N (t)

Nadere informatie

Software Test Plan. Yannick Verschueren

Software Test Plan. Yannick Verschueren Software Test Plan Yannick Verschueren Maart 2015 Document geschiedenis Versie Datum Auteur/co-auteur Beschrijving 1 November 2014 Yannick Verschueren Eerste versie 2 December 2014 Yannick Verschueren

Nadere informatie

Modelleren en Programmeren

Modelleren en Programmeren Modelleren en Programmeren Jeroen Bransen 11 december 2015 Ingebouwde datastructuren Meer boomstructuren Access specifiers Gebruikersinvoer Codestijl Packages SAT-solver Ingebouwde datastructuren Ingebouwde

Nadere informatie

Microsoft Office voor het onderwijs. Introductie tot het gebruik van Microsoft Office 2007 in de klas

Microsoft Office voor het onderwijs. Introductie tot het gebruik van Microsoft Office 2007 in de klas Microsoft Office voor het onderwijs Introductie tot het gebruik van Microsoft Office 2007 in de klas Dit document wordt enkel ter informatie gepubliceerd. MICROSOFT GEEFT GEEN IMPLICIETE OF EXPLICIETE

Nadere informatie

Inhoud. Inleiding computer graphics. Introductie 11. Leerkern 14. Terugkoppeling 57. Uitwerking van de opgaven 57

Inhoud. Inleiding computer graphics. Introductie 11. Leerkern 14. Terugkoppeling 57. Uitwerking van de opgaven 57 Inhoud Inleiding computer graphics Introductie 11 Leerkern 14 1 Invoer en opslag van 3D objecten 14 2 Ray tracing 18 2.1 Basisprincipe van ray tracing 18 2.2 Secundaire stralen 20 2.3 Intersectieberekeningen

Nadere informatie

Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar juni 2011

Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar juni 2011 Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar 2010-2011 21 juni 2011 **BELANGRIJK** 1. Lees eerst de volledige opgave (inclusief

Nadere informatie

Informatica. Objectgeörienteerd leren programmeren. Van de theorie met BlueJ tot een spelletje met Greenfoot... Bert Van den Abbeele

Informatica. Objectgeörienteerd leren programmeren. Van de theorie met BlueJ tot een spelletje met Greenfoot... Bert Van den Abbeele Informatica Objectgeörienteerd leren programmeren Van de theorie met BlueJ tot een spelletje met Greenfoot... Bert Van den Abbeele http://creativecommons.org/licenses/by-nc-nd/3.0/legalcode Objectgeörienteerd

Nadere informatie

Vectoren bij reflectie van licht. Belichting. Materiaaleigenschappen. Diffuse reflection

Vectoren bij reflectie van licht. Belichting. Materiaaleigenschappen. Diffuse reflection Belichting Kleuren van oppervlakken: resultaat van een complexe interactie tussen licht en materie. Belichtingsmodellen: Licht: empirisch: berekeningen in overeenstemming met de waarneming fysica wetten:

Nadere informatie

CTB1002 deel 1 - Lineaire algebra 1

CTB1002 deel 1 - Lineaire algebra 1 CTB100 deel 1 - Lineaire algebra 1 College 5 5 februari 014 1 Opbouw college Vandaag behandelen we hoofdstuk 1.7 en deel van 1.8 Voor de pauze: hoofdstuk 1.7 Na de pauze: hoofdstuk 1.8 Verschillende notaties

Nadere informatie

simplebim KUBUS templates

simplebim KUBUS templates Om het gebruik van simplebim voor Nederlandse en Belgische bedrijven te vergemakkelijken heeft KUBUS een aantal templates samengesteld. Door gebruik van deze templates is een efficiëntere inzet van simplebim

Nadere informatie

Waarmaken van Leibniz s droom

Waarmaken van Leibniz s droom Waarmaken van Leibniz s droom Artificiële intelligentie Communicatie & internet Operating system Economie Computatietheorie & Software Efficiënt productieproces Hardware architectuur Electronica: relais

Nadere informatie

Les 11: systeemarchitectuur virtuele machines

Les 11: systeemarchitectuur virtuele machines Les 11: systeemarchitectuur virtuele machines Geavanceerde computerarchitectuur Lieven Eeckhout Academiejaar 2008-2009 Universiteit Gent Virtuele machines Motivatie Interfaces Virtualisatie: inleiding

Nadere informatie

Stap 1 Eenvoudig tekenprogramma maken In eerste instantie alleen 'freehand' curves Demo in de les

Stap 1 Eenvoudig tekenprogramma maken In eerste instantie alleen 'freehand' curves Demo in de les Tekenen in Java Doel Stap 1 Eenvoudig tekenprogramma maken In eerste instantie alleen 'freehand' curves Demo in de les Stap 2 Tekening opslaan op file en weer teruglezen Demo in de les Stap 3 Rechthoeken,

Nadere informatie

Werkblad Cabri Jr. Rotaties

Werkblad Cabri Jr. Rotaties Werkblad Cabri Jr. Rotaties Doel Het onderzoeken van de eigenschappen van een rotatie in het platte vlak, in het bijzonder de relatie tussen origineel en beeld. Inleiding Een rotatie is één van de vier

Nadere informatie

Handleiding ISaGRAF. Wil men het programma bewaren, dan is het verstandig een back-up te maken: C9 Back-up / Restore

Handleiding ISaGRAF. Wil men het programma bewaren, dan is het verstandig een back-up te maken: C9 Back-up / Restore Handleiding ISaGRAF C Handleiding ISaGRAF Deze handleiding beoogt een korte samenvatting te geven van handelingen die verricht moeten worden om met behulp van ISaGRAF een PLC-programma te schrijven en

Nadere informatie

Vraag 1. Vraag 1a TERUGKOPPELING PROEFTENTAMEN. Software architecture

Vraag 1. Vraag 1a TERUGKOPPELING PROEFTENTAMEN. Software architecture Software architecture IM0203 TERUGKOPPELING PROEFTENTAMEN Vraag 1 Vraag 1a Veel van de in het werkboek besproken patterns kunnen ingezet worden voor het referentiesysteem. We lopen de patterns hier stuk

Nadere informatie

React en React Native voor websites en apps

React en React Native voor websites en apps React en React Native voor websites en apps H A N S-PE T E R H ARMSEN HEEFT DI T GE SCH R E V EN IN APRI L 2017 Deze whitepaper is bedoeld voor product owners en beslissers. Hij gaat over React, een JavaScript

Nadere informatie

Tentamen Object Georiënteerd Programmeren TI1200 30 januari 2013, 9.00-12.00 Afdeling SCT, Faculteit EWI, TU Delft

Tentamen Object Georiënteerd Programmeren TI1200 30 januari 2013, 9.00-12.00 Afdeling SCT, Faculteit EWI, TU Delft Tentamen Object Georiënteerd Programmeren TI1200 30 januari 2013, 9.00-12.00 Afdeling SCT, Faculteit EWI, TU Delft Bij dit tentamen mag je geen gebruik maken van hulpmiddelen zoals boek of slides. Dit

Nadere informatie

Beknopt overzicht Novell imanger

Beknopt overzicht Novell imanger Beknopt overzicht Novell imanger Dirk Vanderbist (DIBIS LK 14) 26-IV-2004 0.0.1 Inhoud 1 Novell imanager... 3 1.1 Situering Novell imanager... 3 1.2 Managementhiërarchie... 3 1.3 Werkwijze... 4 1.4 Architectuur...

Nadere informatie

Erik Poll Martijn Warnier. http://www.cs.kun.nl/~erikpoll/linux

Erik Poll Martijn Warnier. http://www.cs.kun.nl/~erikpoll/linux Introductie Linux/UNIX Erik Poll Martijn Warnier http://www.cs.kun.nl/~erikpoll/linux Concrete doel van vandaag Basisvaardigheden UNIX/Linux werken met de command line shell file beheer proces beheer Betere

Nadere informatie

Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar juni, 2010

Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar juni, 2010 Examen Programmeren 2e Bachelor Elektrotechniek en Computerwetenschappen Faculteit Ingenieurswetenschappen Academiejaar 2009-2010 16 juni, 2010 **BELANGRIJK** 1. Lees eerst de volledige opgave (inclusief

Nadere informatie

Inhoud Inhoud. Over dit boek 7. 1 Eclipse IDE (Integrated Development Environment) 9. 2 Functionele specificatie 13

Inhoud Inhoud. Over dit boek 7. 1 Eclipse IDE (Integrated Development Environment) 9. 2 Functionele specificatie 13 5 Inhoud Inhoud Over dit boek 7 1 Eclipse IDE (Integrated Development Environment) 9 2 Functionele specificatie 13 3 Implementatie grafische gebruikersinterface 31 4 De klassen en methoden 57 5 Technische

Nadere informatie

Modulewijzer InfPbs00DT

Modulewijzer InfPbs00DT Modulewijzer InfPbs00DT W. Oele 0 juli 008 Inhoudsopgave Inleiding 3 Waarom wiskunde? 3. Efficiëntie van computerprogramma s............... 3. 3D-engines en vectoranalyse................... 3.3 Bewijsvoering

Nadere informatie

Installatie SQL: Server 2008R2

Installatie SQL: Server 2008R2 Installatie SQL: Server 2008R2 Download de SQL Server 2008.exe van onze site: www.2work.nl Ga naar het tabblad: Downloads en meld aan met: klant2work en als wachtwoord: xs4customer Let op! Indien u een

Nadere informatie

mailgroep photoshop Copyright Lesje: Stel je eigen kamer samen -

mailgroep photoshop Copyright Lesje: Stel je eigen kamer samen - Lesje: Stel je eigen kamer samen - http://www2.hku.nl/~fotoshop/img-tutorial5 In deze les gaan we een drie-dimensionale ruimte bouwen, in dit geval een gezellige woonkamer. Uiteraard mag je deze zelf in

Nadere informatie

Combinatorische Algoritmen: Binary Decision Diagrams, Deel III

Combinatorische Algoritmen: Binary Decision Diagrams, Deel III Combinatorische Algoritmen: Binary Decision Diagrams, Deel III Sjoerd van Egmond LIACS, Leiden University, The Netherlands svegmond@liacs.nl 2 juni 2010 Samenvatting Deze notitie beschrijft een nederlandse

Nadere informatie

te vermenigvuldigen, waarbij N het aantal geslagen Nederlandse munten en B het aantal geslagen buitenlandse munten zijn. Het resultaat is de vector

te vermenigvuldigen, waarbij N het aantal geslagen Nederlandse munten en B het aantal geslagen buitenlandse munten zijn. Het resultaat is de vector Les 3 Matrix product We hebben gezien hoe we matrices kunnen gebruiken om lineaire afbeeldingen te beschrijven. Om het beeld van een vector onder een afbeelding te bepalen hebben we al een soort product

Nadere informatie

Excel reader. Beginner Gemiddeld. bas@excel-programmeur.nl

Excel reader. Beginner Gemiddeld. bas@excel-programmeur.nl Excel reader Beginner Gemiddeld Auteur Bas Meijerink E-mail bas@excel-programmeur.nl Versie 01D00 Datum 01-03-2014 Inhoudsopgave Introductie... - 3 - Hoofdstuk 1 - Databewerking - 4-1. Inleiding... - 5-2.

Nadere informatie

Handleiding JCreator. Inhoud. Een Workspace en een eerste project maken

Handleiding JCreator. Inhoud. Een Workspace en een eerste project maken Handleiding JCreator Inhoud Een Workspace en een eerste project maken Een tweede project maken De editor van JCreator Aanpassen van de basis-directory Documentatie over klassen en methoden van de JDK Bestand

Nadere informatie

Geheugenbeheer. ICT Infrastructuren 2 december 2013

Geheugenbeheer. ICT Infrastructuren 2 december 2013 Geheugenbeheer ICT Infrastructuren 2 december 2013 Doelen van geheugenbeheer Reloca>e (flexibel gebruik van geheugen) Bescherming Gedeeld/gemeenschappelijk geheugen Logische indeling van procesonderdelen

Nadere informatie

Functionele beschrijving: scannen naar Trivium FORTUNA.

Functionele beschrijving: scannen naar Trivium FORTUNA. Functionele beschrijving: scannen naar Trivium FORTUNA. Algemeen Met KYOCERA scannen naar Trivium FORTUNA beschikt u over een efficiënte oplossing om uw documenten te scannen naar Trivium FORTUNA. Met

Nadere informatie

Applicaties op afstand draaien met X11

Applicaties op afstand draaien met X11 LinuxFocus article number 222 http://linuxfocus.org Applicaties op afstand draaien met X11 door Guido Socher (homepage) Over de auteur: Guido houdt van Linux en niet alleen omdat het interessant is te

Nadere informatie

Programmeren: Visual Basic

Programmeren: Visual Basic PETERSTUYVESANT COLLEGE INFORMATICA 2009-2010 Programmeren: Visual Basic Algemene Kennis: 01. Programmeren Programmeren is het schrijven van een computerprogramma, een concrete verzameling instructies

Nadere informatie

Unicomedia biedt een totaal oplossing voor narrowcasting en adviseert over hardware, implementatie en beheer van deze netwerken.

Unicomedia biedt een totaal oplossing voor narrowcasting en adviseert over hardware, implementatie en beheer van deze netwerken. Retail Digital Signage heeft zich in verschillende vormen effectief bewezen om doelgericht met consumenten te communiceren. Digitale media wordt ingezet om merkenherkenning te ondersteunen, winkel promoties

Nadere informatie

Tips en tricks nr. 50: teken een hoofd van een dier (monster).

Tips en tricks nr. 50: teken een hoofd van een dier (monster). Tips en tricks nr. 50: teken een hoofd van een dier (monster). Deze maand gaan we plezier maken als tegenpool voor de serieuze tekeningen die we normaal maken. We vergeten gemakkelijk dat we in KeyCreator

Nadere informatie

Oefeningen Jaarproject I

Oefeningen Jaarproject I Oefeningen Jaarproject I Deze oefeningenreeks behandelt de grafische Scheme bibliotheek die jullie mogen gebruiken voor de implementatie van het Pacman spel. De bibliotheek i is een evaluator voor Scheme

Nadere informatie

Examen Datastructuren en Algoritmen II

Examen Datastructuren en Algoritmen II Tweede bachelor Informatica Academiejaar 2012 2013, tweede zittijd Examen Datastructuren en Algoritmen II Naam :.............................................................................. Lees de hele

Nadere informatie

Programmeren (1) Examen NAAM:

Programmeren (1) Examen NAAM: Schrijf al je antwoorden op deze vragenbladen (op de plaats die daarvoor is voorzien) en geef zowel klad als net af. Bij heel wat vragen moet je zelf Java-code schrijven. Hou dit kort en bondig. Je hoeft

Nadere informatie

Appendix Inversie bekeken vanuit een complex standpunt

Appendix Inversie bekeken vanuit een complex standpunt Bijlage bij Inversie Appendix Inversie bekeken vanuit een complex standpunt In dee paragraaf gaan we op een andere manier kijken naar inversie. We doen dat met behulp van de complexe getallen. We veronderstellen

Nadere informatie

WETENSCHAPPEN oefeningen perspectief OEFENING 5. Arnout Van Vaerenbergh

WETENSCHAPPEN oefeningen perspectief OEFENING 5. Arnout Van Vaerenbergh WETENSCHAPPEN oefeningen perspectief OEFENING 5 Arnout Van Vaerenbergh vorige oefening: 1/ contextsimulatie - Muziekles van Vermeer 2/ exacte input - objecten tekenen in perspectief 3/ exacte output -

Nadere informatie

2IV10 Oefentoets uitwerking

2IV10 Oefentoets uitwerking 2IV10 Oefentoets uitwerking Deze oefentoets bestaat uit drie opgaven, waarvoor twee uur beschikbaar is. Bij voldoende resultaat wordt een bonuspunt toegekend voor het tentamen. De opgaven betreffen een

Nadere informatie

Software Factories. Toepassing van Domain Specific Languages. achtergrond

Software Factories. Toepassing van Domain Specific Languages. achtergrond In de software-industrie zijn budget- en deadline-overschrijdingen aan de orde van de dag, er wordt vaak niet aan de gestelde verwachtingen voldaan. Dit kan worden voorkomen door software-ontwikkeling

Nadere informatie

Je gaat leren programmeren en een spel bouwen met de programmeertaal Python. Websites zoals YouTube en Instagram zijn gebouwd met Python.

Je gaat leren programmeren en een spel bouwen met de programmeertaal Python. Websites zoals YouTube en Instagram zijn gebouwd met Python. 1 Je gaat leren programmeren en een spel bouwen met de programmeertaal Python. Websites zoals YouTube en Instagram zijn gebouwd met Python. Voordat je leert programmeren, moet je jouw pc zo instellen dat

Nadere informatie

Datastructuren Uitwerking jan

Datastructuren Uitwerking jan Datastructuren Uitwerking jan 2015 1 1a. Een abstracte datastructuur is een beschrijving van een datastructuur, met de specificatie van wat er opgeslagen wordt (de data en hun structuur) en welke operaties

Nadere informatie

Tentamen Object Georiënteerd Programmeren TI1206 29 oktober 2014, 9.00-11.00 Afdeling SCT, Faculteit EWI, TU Delft

Tentamen Object Georiënteerd Programmeren TI1206 29 oktober 2014, 9.00-11.00 Afdeling SCT, Faculteit EWI, TU Delft Tentamen Object Georiënteerd Programmeren TI1206 29 oktober 2014, 9.00-11.00 Afdeling SCT, Faculteit EWI, TU Delft Bij dit tentamen mag je geen gebruik maken van hulpmiddelen zoals boek of slides. Digitale

Nadere informatie

Correspondentie inzake overnemen of reproductie kunt u richten aan:

Correspondentie inzake overnemen of reproductie kunt u richten aan: Vrijwel alle namen van software- en hardwareproducten die in deze cursus worden genoemd, zijn tegelijkertijd ook handelsmerken en dienen dienovereenkomstig te worden behandeld. Alle rechten voorbehouden.

Nadere informatie

Projectdocument Minecraft Mod Builder

Projectdocument Minecraft Mod Builder Projectdocument Minecraft Mod Builder Projectgroep Twintro 11 december 2015 Inhoudsopgave 1 Probleemstelling 2 2 Productbeschrijving 2 3 Requirements analyse 3 3.1 Functional requirements................................

Nadere informatie

Introductie testtooling Wink

Introductie testtooling Wink Introductie testtooling Wink SYSQA B.V. Almere Datum : 10-04-2013 Status : 1.0 Opgesteld door : Organisatie SYSQA B.V. Pagina 2 van 16 Inhoudsopgave 1 Inleiding... 3 1.1 Opbouw... 3 2 Wink... 4 2.1 Wat

Nadere informatie

Uitgebreid voorstel Masterproef Informatica

Uitgebreid voorstel Masterproef Informatica HoGent Uitgebreid voorstel Masterproef Informatica Titel van het project: Optimalisatie & ontwikkeling van een gegevenstransfertool voor Business Intelligence-gebruikers Datum : 01/11/2012 Naam student

Nadere informatie

INFORMATICA 1STE BACHELOR IN DE INGENIEURSWETENSCAPPEN

INFORMATICA 1STE BACHELOR IN DE INGENIEURSWETENSCAPPEN INFORMATICA 1STE BACHELOR IN DE INGENIEURSWETENSCAPPEN voorbeeldexamen NAAM :... OPMERKINGEN VOORAF Je krijgt 3 uur de tijd om de opdrachten voor dit examen uit te voeren. Verder werken aan je oplossing

Nadere informatie

Programmeren in C ++ met wxwidgets les 5

Programmeren in C ++ met wxwidgets les 5 Elektrotechniek/Embedded Systems engineering inf2d Programmeren in C ++ met wxwidgets les 5 cursus 2009-2010 ir drs E.J Boks Les 5 Grafische toolkits Basisbeginselen gebruik grafische toolkit WxWidgets

Nadere informatie