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

146
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 2005–2006

Transcript of Implementatie in Java van een Architectuur voor Real-time 3D...

Page 1: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Faculteit Ingenieurs Wetenschappen

Vakgroep Informatietechnologie

Voorzitter: Prof. Dr. Ir. Paul Lagasse

Implementatie in Java van een Architectuur voorReal-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 2005–2006

Page 2: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Faculteit Ingenieurs Wetenschappen

Vakgroep Informatietechnologie

Voorzitter: Prof. Dr. Ir. Paul Lagasse

Implementatie in Java van een Architectuur voorReal-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 2005–2006

Page 3: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Toelating tot bruikleen

“De auteur geeft de toelating deze scriptie voor consultatie beschikbaar te stellen en delenvan de scriptie te kopieren voor persoonlijk gebruik.Elk ander gebruik valt onder de beperkingen van het auteursrecht, in het bijzonder met be-trekking tot de verplichting de bron uitdrukkelijk te vermelden bij het aanhalen van resultatenuit deze scriptie.”

Bert Van Semmertier, mei 2006

Page 4: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Implementatie in Java van eenArchitectuur voor Real-time 3D applicaties

door

Bert Van Semmertier

Scriptie ingediend tot het behalen van de academische graad van

Licentiaat in de Informatica

Academiejaar 2005–2006

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 render-

technieken zijn al veel studies gepubliceerd. Maar wat veelal ontbreekt, is hoe deze verschil-

lende technieken kunnen gecombineerd worden in een applicatie. De meeste render-engines

specialiseren zich bijvoorbeeld in het renderen van binnenlokaties en leggen zichzelf de beperk-

ing op dat er geen complexe buitenlokaties kunnen gerenderd worden. Dit legt uiteraard een

serieuze beperking op de variatie aan omgevingen die gecreeerd 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 een scene en waartussen

de overgang volstrekt seamless is.

In de vooropgestelde architectuur wordt een scene opgebouwd uit onafhankelijk gedefinieerde

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 efficient mogelijk zal

renderen.

Page 5: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

In de tekst wordt een volledige beschrijving gegeven van de architectuur. Evenals hoe zicht-

baarheid 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, ver-

schillende 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, material-

framework, sector, sectorManager, real-time shadows, water-rendering, SceneGraph.

Page 6: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Inhoudsopgave

I Introductie 9

1 Inleiding 10

2 Basisprincipes van een OpenGL applicatie 132.1 Wat is OpenGL? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132.2 Verduidelijking van enkele gebruikte termen in de tekst . . . . . . . . . . . . 142.3 Assenstelsels en spaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

2.3.1 Inleiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142.3.2 De Modelview- Projection- en Texture-matrix . . . . . . . . . . . . . . 152.3.3 Transformatie tussen assenstelsels . . . . . . . . . . . . . . . . . . . . 17

2.4 View Frustum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.4.1 Introductie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182.4.2 Bepaling van de View Frustum . . . . . . . . . . . . . . . . . . . . . . 18

2.5 Color-, Depth- en Stencil-buffer . . . . . . . . . . . . . . . . . . . . . . . . . . 202.5.1 Color-buffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202.5.2 Depth-buffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212.5.3 Stencil-buffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

II De Libera Engine 22

3 De filosofie 23

4 Beschrijving architectuur 254.1 SectorManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

4.1.1 BSP SectorManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274.1.2 Octree SectorManager . . . . . . . . . . . . . . . . . . . . . . . . . . . 284.1.3 Galaxy SectorManager . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

4.2 Sector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294.2.1 BSP Sector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294.2.2 Octree Sector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294.2.3 Galaxy Sector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294.2.4 Hoe een nieuwe Sector(Manager) schrijven? . . . . . . . . . . . . . . . 29

4.3 Portal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324.3.1 Spiegels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324.3.2 Monitoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

4.4 RenderEngine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

4

Page 7: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

4.4.1 Bepalen root-sector . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344.4.2 Zichtbaarheidsbepaling . . . . . . . . . . . . . . . . . . . . . . . . . . . 354.4.3 Berekenen bounding-box en view-Frustum . . . . . . . . . . . . . . . . 36

4.5 ShadowRenderer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384.6 SceneNode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

4.6.1 MovableObject . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394.6.2 Renderen van de SceneGraph . . . . . . . . . . . . . . . . . . . . . . . 404.6.3 Culling van SceneNodes . . . . . . . . . . . . . . . . . . . . . . . . . . 42

4.7 Collision detection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444.8 Code-voorbeelden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

5 Resource-framework 505.1 Inleiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505.2 Texture-manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50

5.2.1 De klasse Texture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505.2.2 De klasse TextureManager . . . . . . . . . . . . . . . . . . . . . . . . . 50

5.3 Materials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515.3.1 Material . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515.3.2 MaterialManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

5.4 Material-scripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525.4.1 Inleiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525.4.2 Voorbeelden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

5.5 Shaders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555.5.1 Introductie tot shaders . . . . . . . . . . . . . . . . . . . . . . . . . . . 575.5.2 Per-pixel lighting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615.5.3 Per-pixel lighting met specular component . . . . . . . . . . . . . . . . 635.5.4 Cel-shading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 665.5.5 Normal-mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

5.6 MeshLoader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715.6.1 (Sub)Mesh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715.6.2 OBJLoader . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715.6.3 Een nieuwe MeshLoader schrijven . . . . . . . . . . . . . . . . . . . . 71

5.7 FontManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 725.7.1 Implementatie in de engine . . . . . . . . . . . . . . . . . . . . . . . . 72

5.8 Logmanager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 735.9 Config-bestanden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

III Uitbreidingen 76

6 Particle engine 786.1 Inleiding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 786.2 Billboarded particles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796.3 Particles met point sprites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 816.4 Implementatie in de engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82

Page 8: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

7 Dynamic clothing 867.1 Simulatie van een koord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 867.2 Simulatie van clothing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 877.3 Implementatie in de engine . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

8 Projective texturing 92

9 Real-time shadow rendering 969.1 Shadow Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

9.1.1 Gebruikte GLSL-shader . . . . . . . . . . . . . . . . . . . . . . . . . . 989.1.2 Shadow mapping via een FBO (Frame Buffer Object) . . . . . . . . . 1009.1.3 Spotlights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1029.1.4 Directional lights . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1029.1.5 Point-lights via Cubic shadowmapping . . . . . . . . . . . . . . . . . . 1029.1.6 Soft-shadows via PCF-filter . . . . . . . . . . . . . . . . . . . . . . . . 102

9.2 Shadow Volumes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1049.2.1 Bepalen silhouette edges . . . . . . . . . . . . . . . . . . . . . . . . . . 1059.2.2 Bepalen shadow-volumes . . . . . . . . . . . . . . . . . . . . . . . . . . 1089.2.3 Renderen van de schaduwen . . . . . . . . . . . . . . . . . . . . . . . . 1099.2.4 Shadow volumes met GLSL . . . . . . . . . . . . . . . . . . . . . . . . 1139.2.5 Soft-shadows door Light-jittering . . . . . . . . . . . . . . . . . . . . . 1159.2.6 Implementatie in de engine . . . . . . . . . . . . . . . . . . . . . . . . 115

10 Reflection / Refraction 11710.1 Reflection plane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

10.1.1 Werkwijze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11710.1.2 Implementatie in de engine . . . . . . . . . . . . . . . . . . . . . . . . 119

10.2 Cubic environmental mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . 12010.3 Refraction plane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

10.3.1 Werkwijze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12310.3.2 Implementatie in de engine . . . . . . . . . . . . . . . . . . . . . . . . 125

10.4 Water-rendering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12710.4.1 Werkwijze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12810.4.2 Water-rendering en FBO (Frame Buffer Object) . . . . . . . . . . . . 13110.4.3 Implementatie in de engine . . . . . . . . . . . . . . . . . . . . . . . . 132

IV Besluit 13410.5 Nabeschouwing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135

V Bijlage: UML-diagrammen 138

Page 9: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Lijst van figuren

2.1 De vlakken van een Frustum . . . . . . . . . . . . . . . . . . . . . . . . . . . 172.2 OpenGL Transformatie pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . 182.3 View Frustum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

4.1 Alignering van twee portals . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264.2 SectorManager voorbeelden . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284.3 SectorManager voorbeelden . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304.4 Een portal, met textuur, dat dienst doet als een spiegel . . . . . . . . . . . . 324.5 Frustums voor portals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364.6 Boundingbox van een portal . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374.7 Een voorbeeld van een SceneGraph . . . . . . . . . . . . . . . . . . . . . . . . 404.8 SceneGraph met en zonder pivot-node . . . . . . . . . . . . . . . . . . . . . . 424.9 Boundingboxen en BoundingSphere . . . . . . . . . . . . . . . . . . . . . . . . 444.10 Een voorbeeld scene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

5.1 Eenzelfde model gerenderd d.m.v. verschillende material-scripts in de Ren-derEngine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

5.2 Schema van de OpenGL pipeline . . . . . . . . . . . . . . . . . . . . . . . . . 575.3 Een gekleurde driehoek . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 585.4 OpenGL pipeline, grafisch voorgesteld . . . . . . . . . . . . . . . . . . . . . . 595.5 Diffuse lighting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 615.6 Phong specular lighting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 635.7 Blinn specular lighting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 645.8 Normal mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685.9 Normal map . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 685.10 Bitmapped Font . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 735.11 Een voorbeeld log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

6.1 Particles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 796.2 Billboarding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 806.3 De OpenGL Modelview matrix . . . . . . . . . . . . . . . . . . . . . . . . . . 806.4 Modelview matrix voor Billboarding . . . . . . . . . . . . . . . . . . . . . . . 816.5 Modelview matrix voor Billboarding met schaling . . . . . . . . . . . . . . . . 816.6 Enkele particle-renderers in een scene waaronder fakkels en regen. . . . . . . . 82

7.1 Een koord, samengesteld uit punten verbonden met veren . . . . . . . . . . . 867.2 Een eenvoudig model voor textiel . . . . . . . . . . . . . . . . . . . . . . . . . 877.3 Een robuuster model voor textiel . . . . . . . . . . . . . . . . . . . . . . . . . 88

7

Page 10: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

7.4 Dynamic clothing in een BSP-scene . . . . . . . . . . . . . . . . . . . . . . . . 90

8.1 Projective texturing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 928.2 Matrices en Projective Texturing . . . . . . . . . . . . . . . . . . . . . . . . . 938.3 Projective texturing en real-time shadows in een BSP-scene . . . . . . . . . . 95

9.1 Verschillende ShadowRenderers in de engine . . . . . . . . . . . . . . . . . . . 979.2 Shadow mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 999.3 Shadow mapping voor een punt-licht en een aantal objecten in een scene . . . 1039.4 Object en lichtbron in een scene . . . . . . . . . . . . . . . . . . . . . . . . . 1069.5 Silhouette edges van het object . . . . . . . . . . . . . . . . . . . . . . . . . . 1069.6 Belichte polygoon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1079.7 Schaduw-volume van het object . . . . . . . . . . . . . . . . . . . . . . . . . . 1089.8 Stencil buffer voor Shadow volumes . . . . . . . . . . . . . . . . . . . . . . . . 1109.9 Gesloten schaduw-volume van het object . . . . . . . . . . . . . . . . . . . . . 1129.10 Schaduw volumes d.m.v. GLSL shader . . . . . . . . . . . . . . . . . . . . . . 1139.11 Schaduw volumes d.m.v. GLSL shader . . . . . . . . . . . . . . . . . . . . . . 1149.12 Vertices verplaatsen weg van het licht . . . . . . . . . . . . . . . . . . . . . . 114

10.1 Een ReflectionPlane in een scene . . . . . . . . . . . . . . . . . . . . . . . . . 11910.2 Real-time cubic environmental mapping in een BSP-scene . . . . . . . . . . . 12110.3 Zes View-Frustums gedefinieerd rond een object voor environmental cube map-

ping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12110.4 Refraction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12310.5 Voorbeeld Normal- en DUDV-map . . . . . . . . . . . . . . . . . . . . . . . . 12410.6 Een refraction plane in een scene . . . . . . . . . . . . . . . . . . . . . . . . . 12510.7 De fresnel-term in de praktijk . . . . . . . . . . . . . . . . . . . . . . . . . . . 12710.8 Water-rendering schematisch voorgesteld . . . . . . . . . . . . . . . . . . . . . 12910.9 Waterplanes in verschillende scenes . . . . . . . . . . . . . . . . . . . . . . . . 13210.10Main UML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13910.11Sector(Manager) UML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14010.12ShadowRenderer en Portal UML . . . . . . . . . . . . . . . . . . . . . . . . . 14110.13MovableObject UML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14210.14Sequentie diagram van de render-pass . . . . . . . . . . . . . . . . . . . . . . 14310.15Sequentie diagram voor culling van SceneNodes . . . . . . . . . . . . . . . . . 144

Page 11: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Deel I

Introductie

9

Page 12: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoofdstuk 1

Inleiding

Mijn afstudeerwerk als Licentiaat in de Informatica, gaat over de implementatie van een ar-chitectuur voor real-time 3D applicaties in Java.

Een algemeen gekende moeilijkheid bij de ontwikkeling van een volwaardige render-engineis scene-management. Hoe snel videokaarten vandaag de dag ook mogen zijn, het is en blijftgewenst zo weinig mogelijk data naar de videokaart te sturen.Geen twee 3D-scenes zijn bovendien hetzelfde, wat het probleem nog een stuk complexermaakt. Binnenlokaties vereisen doorgaans andere render-technieken dan buitenlokaties enscene-management dient op verschillende manieren te gebeuren. Over elk van die render-technieken zijn al veel studies gepubliceerd. Maar wat veelal ontbreekt, is hoe deze verschil-lende technieken kunnen gecombineerd worden in een applicatie. De meeste render-enginesspecialiseren zich bijvoorbeeld in het renderen van binnenlokaties en leggen zichzelf de beperk-ing op dat er geen complexe buitenlokaties kunnen gerenderd worden. Dit legt uiteraard eenserieuze beperking op de variatie aan omgevingen die gecreeerd kunnen worden.

In dit werk stel ik een architectuur voorop die het op een robuuste manier mogelijk maaktvolstrekt willekeurige renderers tegelijk te combineren met elkaar in een scene en waartussende overgang volstrekt seamless is.

In de vooropgestelde architectuur wordt een scene opgebouwd uit onafhankelijk gedefinieerdegebieden, die ik ”sectoren”noem, die aan elkaar gelinkt worden via portals. Het is de bedoelinghierbij dat elke sector gebruik maakt van een eigen, unieke renderer, BSP- en Octree-rendererszijn momenteel geımplementeerd, die de data in de specifieke sector zo efficient mogelijk zalrenderen.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 omgev-ing.We houden ook voor ogen dat het erg eenvoudig moet zijn nieuwe renderers toe te voegen aande engine, zodat in de toekomst meer soorten sectoren en renderers kunnen gebruikt wordenin een scene.

De correcte werking van de architectuur tracht ik aan te tonen door hier een implementatievan te maken in een volwaardige render-engine met ondersteuning voor real-time shadows,

10

Page 13: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

scene-graph-based rendering en een robuuste material-framework. De vrijheid die deze archi-tectuur biedt aan de gebruiker om complexe scenes op te bouwen, gaf aanleiding tot de naamvan de engine: Libera.

In wat volgt, geef ik een gedetailleerde beschrijving van de architectuur van Libera en hoe diegeımplementeerd wordt. Dit alles wordt voorzien van Java source-code, implementaties vangebruikte GLSL-shaders, duidelijke tekeningen, screenshots en voorbeelden. De screenshotskomen rechtstreeks uit Libera en de tekeningen zijn allen van mijn hand, ter ondersteuningvan de verschillende processen die aan bod komen.

Java was een vereiste en de implementatie van de engine was een goede test in hoeverreJava geschikt is voor de ontwikkeling van geavenceerde real-time 3D-applicaties. Libera werdontwikkeld met de OpenGL API, d.m.v de Java-OpenGL binding LWJGL (LightWeight JavaGame Library, http://www.lwjgl.org/).

Om source-code en scripts duidelijk te onderscheiden van de eigenlijke tekst werd gekozenvoor een volgend formaat:

• Voorbeeld-code in Java en/of pseudocode wordt geschreven in een enkele kader metrechte hoeken:

pub l i c void foo ( S t r ing text ){

//Commentaar z i e t er a l s vo l g t u i t .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 a l s vo l g t u i t .g l P o s i t i o n = f t rans fo rm ( ) ;

}

• Material-scipts worden geschreven in een enkele kader met schaduw-rand:

Wood{

//Commentaar z i e t er a l s vo l g t u i t .tex data/MyTexture .bmps c r o l l 0 . 1 0 . 2

}

Page 14: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

De volledige source-code en alle demo’s worden gereleased als OpenSource project ondereen BSD-licentie.

De scriptie is als volgt ingedeeld:

Deel IEen korte beschrijving van enkele basis-principes van een OpenGL applicatie: assenstelsels,ruimtes, matrices, View-frustums en buffers.

Deel IIDe volledige beschrijving van de architectuur van Libera en informatie over hoe een sceneopgebouwd en gerenderd wordt d.m.v. de verschillende sectoren en portals wordt in dit deelbeschreven? Evenals hoe zichtbaarheid van elke Sector bepaald wordt, het renderen vanschaduwen en beschrijving van scene-beheer d.m.v. de SceneGraph-structuur.De resource-framework, die het beheer doet van gebruikte texturen, modellen en shaders ineen applicatie, wordt gedetailleerd besproken.Ter afsluiting van dit deel wordt een voorbeeld uitgewerkt dat beschrijft hoe je zelf een scenekan opbouwen in een eigen applicatie d.m.v. deze engine.

Deel IIIDit deel behandelt de uitbreidingen die gemaakt zijn op de architectuur. Hier komen zakenaan bod als: particle renderers, dynamic clothing, projective texturing, verschillende real-time shadow rendering technieken, refraction/reflection en water-rendering. Dit alles met eenvolledig beschrijving van de implementatie, vergezeld van eventueel gebruikte GLSL shadersen 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 vector-en matrix-rekenen onlosmakelijk verbonden aan 3D-programming. Kennis van een aantal el-ementaire 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 aanscene-beheer en rendering te doen zijn aanwezig, maar er wordt geen aandacht gegeven aansound, collision detection, physics en networking. Dit zijn zaken die volledig los staan van derender-engine (maar maken wel allen onderdeel uit van de game-engine).

Page 15: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoofdstuk 2

Basisprincipes van een OpenGLapplicatie

2.1 Wat is OpenGL?

OpenGL (Open Graphics Library) maakt onderdeel uit van de grafische hardware in je PCen is een grafisch systeem dat speciaal gemaakt is voor het maken van interactieve driedimen-sionale toepassingen. Merk op dat het niet meer is dan een bibliotheek met instructies om jevideokaart aan te spreken: OpenGL biedt geen support voor input-verwerking, GUI, collision-detection, schaduw-rendering, model-rendering, level-rendering, culling, scene -management,etc... dit zijn zaken die je als programmeur zelf moet implementeren d.m.v. een program-meertaal naar keuze en de OpenGL API.OpenGL is een interface die hardware- en software-onafhankelijk is, dit betekent dat eenOpenGL applicatie theoretisch kan draaien op gelijk welke software/hardware die er te vin-den is, als er maar een OpenGL-implementatie bestaat voor dat platform. Een OpenGLapplicatie kan draaien op een ATI-, nVidia-kaart, op een Windows-, Mac-, Linux- of Solaris-systeem. Er bestaan ondertussen zelfs OpenGL-implementaties voor mobiele-telefoons enPDA’s.

Sinds de eerste release van OpenGL in 1992 worden regelmatig revisies en uitbreiding gere-least, versies 1.0, 1.1, 1.2, 1.3, 1.4, 1.5 zijn reeds de revue gepasseerd. In september 2004werd de huidige versie, 2.0, beschikbaar gesteld wat onder andere GLSL (OpenGL ShadingLanguage) introduceerde. GLSL maakt het mogelijk voor programmeurs de OpenGL pipelinete programmeren in een elegante taal (lijkt erg goed op C), dit met volledige ondersteuningdoor OpenGL.

Naast de standaardbibliotheken bestaat de mogelijkheid OpenGL uit te breiden d.m.v.”ex-

tensions”. Die worden geımplementeerd door hardware-fabrikanten die instructies toevoegenaan 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 toegevoegdworden aan de standaard OpenGL specificatie.Een greep uit reeds gereleaste extenties: blending, texture mapping, lighting, fog, occlusionculling, fragment- en vertex-programs.

13

Page 16: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Opmerkelijk is dat OpenGL gebruik maakt van een Client/Server-architectuur. Dit betekentdat de computer die de grafische data verwerkt niet dezelfde moet zijn als die computer diede data zal weergeven. Een OpenGL programma kan je m.a.w zonder probleem over eennetwerk 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

VertexEen punt in de ruimte dat wordt voorgesteld door een tripel (x, y, z) die de plaatsing bepaaltop respectievelijk de X-, Y- en Z-as. Onthoud dat OpenGL intern vertices zal behandelen alshomogene coordinaten, van de vorm (x, y, z, 1).

PixelEen beeldpunt op het scherm dat wordt voorgesteld door een getal die de kleur-informatiebevat. Indien we 24-bits kleur gebruiken zal 8-bit gebruikt worden om respectievelijk de ho-eveelheid rood, groen en blauw (8 x 3 = 24) te beschrijven. De hoeveelheid pixels op hetscherm hangt af van de gebruikte resolutie. OpenGL zal elke frame in je 3D-wereld omzettennaar pixels, dit gebeurt door het toepassen van een transformatie d.m.v. een serie matrices,zie ‘Transformatie tussen assenstelsels’ (p.17).

FragmentZoals vermeld, zal OpenGL je 3D-scene omzetten naar pixels op je scherm. Vooraleer eenbeeldpunt een pixel wordt, zullen we spreken over een fragment. Denk aan een fragmentalsof het een tijdelijke pixel is. Het is namelijk goed mogelijk dat, voordat alle data in descene verwerkt is, een fragment overschreven wordt door een ander fragment. Of: meerderefragmenten kunnen kandidaat zijn om een pixel te worden op een bepaalde plaats. Welkfragment gekozen wordt om een volwaardige pixel te worden, hangt af van de huidge toestandvan de OpenGL-pipeline.

2.3 Assenstelsels en spaces

2.3.1 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 ver-tex 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 vertexdeel 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 hetcross-product te nemen tussen de Normal- en Tangent-vector:

B = (N × T )

Page 17: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Object-space Elk object in een 3D wereld heeft een eigen lokaal assenstelsel dat bepaaldwordt tijdens het modelleren van het object. Een rotatie van een object gebeurt altijdrond de oorsprong van het assenstelsel in zijn Object-space.

World-space World-space an sich bestaat niet in OpenGL maar veel programmeurs houdenervan dit concept voor ogen te houden in hun applicatie omdat dit het meest intuıtiefis. In world-space is een (uniek) assenstelsel bepaald dat het ”centrum”van de virtuelewereld voorstelt en toelaat om objecten relatief ten opzichte van elkaar te positionerent.o.v een centraal punt, de oorsprong. Objecten in World-space plaatsen doe je doordie te manipuleren met behulp van glTransform(), glRotate() en glScale().

View-space Het assenstelsel bepaalt hier de positie van de camera in de scene op zo eenmanier dat de camera te allen tijde langs de negatieve Z-as kijkt. Alle objecten staanhier relatief t.o.v. van de camera, die hier steeds in het nulpunt (0, 0, 0) staat. Dezeruimte staat ook bekend als de Camera-space.

Clip-space In deze ruimte beschrijft het assenstelsel nog steeds de positie van de cameramaar de x, y en z coordinaten van de punten worden genormaliseerd over het [-1, 1]interval. Dit is nodig om de z-buffer (zie onder) een zo hoog mogelijke nauwkeurigheidte garanderen.

Screen-space Dit is de twee-dimensionale ruimte die men verkrijgt door de punten in de3D-scene te projecteren op het computer scherm. Deze ruimte beschrijft de uiteindelijkepositie van de vertices op het scherm, relatief t.o.v. de linker onderhoek van de viewport.

2.3.2 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 vande individuele objecten in de scene beschrijft. Standaard is dit een identiteitsmatrix:

M =

1 0 0 00 1 0 00 0 1 00 0 0 1

Via deze matrix kan je vertices in world-space transformeren naar view-space (en ze dusrelatief plaatsen t.o.v. de positie van de camera).

(viewx, viewy, viewz, 1) = (worldx, worldy, worldz, 1) ∗ M

Objecten manipuleren in je scene doe je door de Modelview matrix aan te passen, beschouwbijvoorbeeld een ModelView-matrix van de volgende vorm:

M =

1 0 0 00 1 0 00 0 1 1500 0 0 1

Page 18: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hierin stelt de vector in de laatste kolom (0, 0, 150, 1) de positie van de oorsprong (endus de plaatsing van de camera in World-space) voor. Stel dat we een vertex plaatsen in deruimte 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) ∗

1 0 0 00 1 0 00 0 1 1500 0 0 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) ∗

1 0 0 00 1 0 00 0 1 1000 0 0 1

Merk op dat het dus geen verschil maakt om de camera 50 units naar voor te plaatsenof het punt 50 units naar achter om een illusie van beweging op te wekken. Vandaar datin OpenGL de ModelView-matrix de camera of de positie van je elementen in je scene zalaanpassen, 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 alzorgt voor veel kopzorgen bij de meeste beginnende OpenGL-programmeurs. Op een dagwordt het allemaal duidelijk.

Hiermee is meteen duidelijk dat het concept camera eigenlijk niet bestaat in OpenGL: decamera verplaats je door de ModelView-matrix aan te passen, en dus onrechtstreeks ver-plaats je de objecten in View-space.Onthoud hier echter dat het verplaatsen van het assenstelsel niet de eigenlijke waarden vande vertices zal veranderen! Voor collision-detection of Frustum culling bijvoorbeeld zal jemaatregelen moeten nemen om dit op te vangen...

Wens je punten te transformeren van View- naar World-space (bijvoorbeeld als je de positievan 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 demodelview matrix:

(worldx, worldy, worldz, 1) = (viewx, viewy, viewz, 1) ∗ M−1

Projection-matrix

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 onderscheidentwee soorten projection-matrices: perspectief en orthogonaal:

- Perspectief projectie Deze projectie benadert de realiteit het best. Objecten die verdervan de camera staan zullen kleiner gerenderd worden zodat je als gebruiker een correctdieptezicht ervaart.

Page 19: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

- Orthogonale projectie Deze projectie kent geen perspectief-correctie. Orthogonale pro-jectie wordt vooral gebruikt in grafische ontwerppakketten (zoals AutoCAD) of om in2D te renderen in OpenGL.

De projectiematrix zal een View-Frustum (‘ViewFrustum’ (p.27)) definieren, zoals afge-beeld in figuur 2.1 (p.17).

De projectie-matrix heeft steeds de volgende vorm:

P =

2nr−l

0 r+lr−l

0

0 2nt−b

t+bt−b

0

0 0 −(f+n)f−n

−2fnf−n

0 0 −1 0

Figuur 2.1: De vlakken vaneen Frustum

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 ongewi-jzigd blijven doorheen het programmaverloop terwijl deModelView-matrix vele keren per frame zal gewijzigd wor-den.

Naast de ModelView- en Projection-matrix bestaat ook eenTexture-matrix die bepaalt hoe een textuur zal gerenderdworden en die toelaat de textuur-coordinaten van een ob-ject eenvoudig aan te passen. Om een textuur te latenscrollen in X-richting volstaat het de Texture-matrix te ver-menigvuldigen met een richtingsvector en het object te renderen.

2.3.3 Transformatie tussen assenstelsels

Het is belangrijk in te zien dat elke vertex steeds een vaste sequentie van transformaties zaldoorlopen vooraleer het een zichtbare pixel wordt op het scherm.

Als programmeur mag je dus niet vergeten rekening te houden in welke ruimte je de datawenst te interpreteren en waar die eigenlijk gedefinieerd staat. Belichting in OpenGL gebeurtbijvoorbeeld steeds in view-space, omdat belichting soms afhankelijk is van de positie enorientatie van de camera (bijvoorbeeld voor Specular Lighting).

Page 20: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 2.2: OpenGL Transformatie pipeline

Dit wordt vooral belangrijk wanneer je begint aan shader-programming, waar je als program-meur zelf die transformaties zal moeten doorvoeren.

Een andere veel gemaakte beginners-fout is de ModelView- en Projectie-matrices verkeerdgebruiken. 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 doorte voeren, met totaal onverwachte resultaten als gevolg.

2.4 View Frustum

2.4.1 Introductie

Hoe snel videokaarten vandaag de dag ook mogen zijn, het is onnodig elementen naar jevideokaart te sturen die uiteindelijk niet op het scherm zullen komen (omdat ze bijvoorbeeldachter de camera liggen). Vaak zijn 3D-werelden zodanig groot dat we er steeds maar eenfractie van zien op elk bepaald moment, methodes om stukken van je scene te ”cullen”(”weg telaten”) dringen zich op. Het behoeft geen uitleg dat het voor complexe 3D-scenes noodzakelijkis enkel maar het strikt noodzakelijke te renderen.Het is dus zeer interessant om te kunnen bepalen wat de gebruiker kan zien in de 3D-wereldop een bepaald moment, om te beslissen om delen van je 3D-omgeving niet in aanmerking telaten komen ze door de render-pipeline te sturen.Hiervoor moeten we een idee kunnen vormen van het gebied waarin vertices moeten liggenzodat 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 wordenop het scherm; die matrix lijkt dus uitstekend geschikt om de Frustum te bepalen.

2.4.2 Bepaling van de View Frustum

Door handig gebruik te maken van matrix-rekenen en de figuur in ‘Transformatie tussenassenstelsels’ (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 liggenin het interval [-1, 1], in Clip-space wordt de View-frustum dus doodgewoon bepaald dooreen kubus met zijden [-1, 1] rond de oorsprong.

Echt nuttig is dit nog niet aangezien we de World-space coordinaten van de Frustummoeten kennen vooraleer we kunnen testen indien een object al dan niet in de ViewFrustumligt. De hoekpunten van de kubus transformeren we naar de World-space via een aantalinverse transformaties:

Page 21: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

(a) View Frustum in Clip-space

(b) View Frustum in World-space

Figuur 2.3: View Frustum

(worldx, worldy, worldz, 1) = (clipx, clipy, clipz, 1) ∗ P−1 ∗ M−1

Gebruikmakend van die informatie kunnen we de vergelijking van de 6 vlakken, die deFrustum 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 deklasse Plane van de RenderEngine):

pub l i c 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 ) ;

p o s i t i o n = v1 ;//Afstand d van het vlakconstant = ( f l o a t ) Vector3 . Inner ( normal , p o s i t i o n ) ;

}

Wanneer de zes vlakken van de frustum gekend zijn, kunnen we testen of bepaalde voor-werpen 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 ){

Page 22: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

// Bereken de a f s tand van het punt (met coord inaten (x , y , z ) )// to t het vlak .// De vec to r ( a , b , c ) i s de normaal van het hu id ige vlak .a f s tand = a∗x + b∗y + c∗z + d ;

i f ( a f s tand < 0)//Punt l i g t achter het vlak ( en dus n i e t in de Frustum ) .//Stop a lgor i tmebreak ;

}

Testen indien in een balk zich in de Frustum bevindt, kan als volgt (pseudocode):

f o r ( AllPlanesOfFrustum ){

// Contro l ee r a l l e punten van de balk d .m. v . bovenstaand a lgor i tme .PointInFrustum ( punt1 ) ;PointInFrustum ( punt2 ) ;PointInFrustum ( punt3 ) ;PointInFrustum ( punt4 ) ;PointInFrustum ( punt5 ) ;PointInFrustum ( punt6 ) ;PointInFrustum ( punt7 ) ;PointInFrustum ( punt8 ) ;

//Als we h i e r geraken l i g t de balk n i e t in de frustum !re turn f a l s e ;

}re turn true ;

Testen voor een bol met straal r gebeurt zo (pseudocode):

f o r ( AllPlanesOfTheFrustum ){

// Bereken de a f s tand van het middelpunt (met coord inaten (x , y , z ) )// to t het vlak .a f s tand = a∗x + b∗y + c∗z + d ;

i f ( a f s tand < r )//Stop a lgor i tmebreak ;

}

2.5 Color-, Depth- en Stencil-buffer

OpenGL kent drie buffers die ter beschikking staan tot de gebruiker om invloed te hebbenop wat er uiteindelijk gerenderd zal worden.

2.5.1 Color-buffer

De color-buffer bevat de kleur-informatie van de pixels op het scherm. In OpenGL kan je ver-hinderen dat er geschreven wordt naar de color-buffer, terwijl de depth-buffer wel geevalueerd

Page 23: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

wordt. Dit kan nuttig zijn in een aantal specifieke situaties, bijvoorbeeld bij Shadowmapping,zie ‘Shadow Mapping’ (p.96).

2.5.2 Depth-buffer

De depth-buffer bevat de diepte informatie van de pixels op het scherm. Vooraleer een frag-ment op het scherm verschijnt, zal zijn depth-waarde vergeleken worden met de waarde opdie plaats in de depth-buffer. Als blijkt dat de depth-value groter is (en dus achter een anderepixel 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 vandiepte 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-bitsper 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- enfar-planes van de View-Frustum. Je dient dit in beschouwing te nemen bij het renderen vanje scene en een afweging maken tussen je view-distance en depth-precision is noodzakelijk.

2.5.3 Stencil-buffer

De Stencil-buffer is een 8-bit buffer die toelaat op een per-pixel basis te beslissen of een pixelop het scherm mag getekend worden. Aan elke pixel wordt een gehele waarde gehecht waaropje later tests kan uitvoeren.Indien je bijvoorbeeld wenst dat enkel op de linker-helft van het scherm gerenderd wordt, kanje de linker-helft van de Stencil-buffer opvullen met 1-en en enkel renderen naar die pixelsmet een Stencil-waarde 1.Geavanceerdere toepassingen van de Stencil-buffer zijn het renderen van reflecterende opper-vlakken (zie ‘Spiegels’ (p.32)) en Shadow-volume rendering (zie ‘Shadow Volumes’ (p.104)).

Page 24: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Deel II

De Libera Engine

22

Page 25: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoofdstuk 3

De filosofie

Een belangrijk probleem bij elke 3D-engine is ervoor zorgen dat enkel het strikt noodzakelijkegerendert wordt naar het scherm. Niettegenstaande dat de videokaarten van vandaag miljoe-nen polygonen per seconde kunnen verwerken, is en blijft het overbodig polygonen door depipeline te sturen die uiteindelijk toch niet op het scherm zullen komen omdat ze buiten deView-Frustum liggen of, belangrijker, omdat ze afgeschermd worden door een ander object inde scene .

De grote moeilijkheid hierbij is dat geen twee 3D-scenes hetzelfde zijn. Het spreekt voorzich dat voor binnenlokaties, waar meestal veel muren zijn die andere objecten onzichtbaarkunnen maken, andere culling-technieken zullen gebruikt moeten worden dan in buiten-lokaties, waar je meestal tot aan de horizon kan kijken. Een bekende techniek die kan helpenbij 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 portalzichtbaar is. Omdat het over het algemeen erg eenvoudig en goedkoop is om te testen of eenportal 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 implementatievan een robuuste en flexibele Portal-based render-engine in Java. Niet alleen werd verwachteen portal-engine te implementeren, het moest mogelijk zijn Sectoren te definieren die elkgebruik (kunnen) maken van een andere SectorManager en renderer, bijvoorbeeld een BSP-renderer. Bovendien moeten nieuwe SectorManagers eenvoudig toe te voegen zijn, zonder datdit invloed heeft op je werking van de RenderEngine en elke Sector moet kunnen bestaanin zijn eigen, lokaal assenstelsel. Concreet betekent dit laatste dat je als level-designer geenrekening hoeft te houden waar het gebied uiteindelijk zal komen in de scene , dit wordtvolledig afgehandeld door de Portal-engine.

Kort gezegd: een wereld in Libera wordt opgebouwd door Sectoren (en hun bijhorende Sec-torManagers) die gelinkt worden aan elkaar via portals. De RenderEngine zorgt ervoor datdit alles correct en zo efficient mogelijk gerenderd wordt. Binnen zo een SectorManager kanje je verbeelding volledig de vrije loop laten en een unieke renderer schrijven, vermist zij zichtoch gedragen als volledig op zichzelf staande blokken.

Meer informatie hierover wordt beschreven in de hierop volgende hoofdstukken.

23

Page 26: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Het resultaat is alvast erg bemoedigend: buitenlokaties kunnen seamless gelinkt worden aanbinnenlokaties terwijl elke sector toch zo efficient mogelijk gerenderd wordt door zijn specifiekeSectorManager. Bovendien worden sectoren gedefinieerd in een lokaal assenstelsel, waardoorinstanciering zonder probleem mogelijk is (een bepaalde sector kan op verschillende plaatsengerenderd worden, dit terwijl er maar 1 instantie van in het geheugen opgeslaan zit), dit isvooral nuttig voor het renderen van spiegels en monitors door middel van portals of als je eenzelfde Sector meerdere malen wilt renderen in een scene .

Ook kunnen aan verschillende sectoren eenvoudig unieke eigenschappen gelinked worden zoalseen ShadowRenderer of Fog, zonder dat die weerslag hebben op andere sectoren in de scene.Daarbovenop gebeurt culling en collision detection specifiek voor een SectorManager, zodatwe die optimaal kunnen maken voor die bepaalde scene . Elke SectorManager handelt dieopdracht 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.

Page 27: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoofdstuk 4

Beschrijving architectuur

Hierna volgt een gedetailleerde beschrijving van de architectuur, de geımplementeerdeklassen en hun gebruik. Er wordt besproken hoe je eenvoudig een scene kan opbouwen doormiddel van de engine en hoe je zelf een SectorManager kan schrijven. Voor het UML-diagramdat 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 goedmogelijke manier af te handelen. Het beschikt over volgende attributen om het renderen toteen goed einde te kunnen brengen:

Portals

Een List van de portals in de scene . Door middel van die portals is het mogelijk andereSectorManagers te linken aan deze SectorManager en kan zichtbaarheid naar andere Sectorenbepaald worden.

Sector

De Sector die door de SectorManager zal gerenderd worden. De SectorManager zal de rauwevertex-data, die opgeslaan zit in een Sector, renderen volgens zijn eigen renderer.

TransformMatrix

Zoals vermeld worden Sectoren gedefinieerd in een eigen assenstelsel, maar het moet bekendzijn hoe deze SectorManager geplaatst staat t.o.v van de andere Managers in de scene . Dieinformatie wordt opgeslaan in de TransformMatrix.TransformMatrix is een 4x4 matrix die met de ModelView-matrix zal vermenigvuldigd wordenvooraleer de Sector gerenderd wordt. Door middel van die gemodificeerde ModelView-matrixzal het mogelijk zijn de Sector, die gedefinieerd staat in een lokaal-assenstelsel, op de correcteplaats te renderen in Camera-space.De waarde in de TransformMatrix is afhankelijk van de positie en orientatie van de por-tal, waaraan de SectorManager gelinkt staat, en de TransformMatrix van de SectorManagerwaaraan hij gelinkt is.

25

Page 28: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Wanneer je een SectorManager linkt aan een andere moet die matrix dus aangepast wor-den. De berekening hiervan gebeurt volledig automatisch zonder dat je hier als programmeurrekening mee moet houden.

De RenderEngine zal als volgt de TransformMatrix updaten indien nodig:

//We berekenen de hoek tussen de twee po r t a l s .//De hoek tussen twee vectoren wordt gegeven door de arcCos inus van// het Inner−product van d i e vectoren .ang le = (Math . acos ( Vector3 . Inner ( po r ta l 1 . normal ,

Vector3 . Inve r s e ( po r ta l 2 . normal ) ) ) ) ;

//We t r an s l a t e r e n de oorsprong van de ANDERE se c t o r naar de l o c a t i e//van de po r t a l .t r an s l a t i onVec t o r=Vector3 .Add( t rans l a t i onVec to r , po r ta l 1 . p o s i t i o n ) ;//We t r an s l a t e r e n de po r t a l in de ANDERE se c t o r naar de l o c a t i e van//de po r t a l in de hu id ige s e c t o r .t r an s l a t i onVec t o r=Vector3 .Add( t rans l a t i onVec to r , po r ta l 2 . p o s i t i o n ) ;

//We s laan de r o t a t i e en t r a n s l a t i e −vec to r op in de transformMatrix// voor l a t e r gebru ik .transformMatrix = new Matrix4 ( ) ;

transformMatrix=Ut i l s . t r an s l a t i onMat r i x (Vector3 . Inve r s e (p . g e tPo s i t i on ( ) ) ) ;

transformMatrix=Matrix4 . mult (mat , U t i l s . rotat ionMatr ixY ( ro t ) ) ;

transformMatrix=Matrix4 . mult (mat , U t i l s . t r an s l a t i onMat r i x (p . g e tPo s i t i on ( ) ) ) ;

transformMatrix=Matrix4 . mult (mat , U t i l s . t r an s l a t i onMat r i x ( t rans ) ) ;

transformMatrix=Matrix4 . mult (mat , new Matrix4 ( other . transformMatrix ) . t ranspose ( ) ) ;

Figuur 4.1: Alignering van twee portals

Page 29: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

ViewFrustum

Dit is een frustum, zie ‘ViewFrustum’ (p.27), uniek voor deze SectorManager. Aangezien elkeSectorManager een Sector zal renderen dat gedefinieerd staat in een lokaal assenstelsel, is hetnoodzakelijk dat hij beschikt over een eigen Frustum. Wanneer een SectorManager gevraagdwordt zich te renderen zal een Frustum bepaald worden in dat lokale-assenstelsel en zal zoaan Frustum-culling gedaan worden. Zie ‘RenderEngine’ (p.34) voor een gedetailleerde uitleghierover.

SceneNode

Dit is de root van de SceneGraph behorende bij de SectorManager. Deze wordt gebruikt omobjecten te linken aan een SectorManager die op hun beurt bijgehouden in een SceneGraph-structuur, zie ‘SceneNode’ (p.39) voor meer uitleg hierover.

ShadowRenderer

Aan een SectorManager kan een (optionele) ShadowRenderer gelinkt worden. Deze rendererzal schaduwen renderen voor je scene en eventuele objecten die in de SceneGraph staan. Shad-owMapping en ShadowVolumes (zowel Z-pass en Z-fail methoden) worden ondersteund. Vooreen 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. Inwat volgt zal ik deze kort bespreken.

4.1.1 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 verdeeldin sub-scenes, volgens een aantal goedgekozen vlakken, en die structureert in een boom-structuur. Aan de hand van die boom kan erg eenvoudig bepaald worden welke vlakken wekunnen zien vanaf de positie van de camera.BSP-rendering was bijzonder nuttig een aantal jaren geleden, wanneer rendering uitsluitendop 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 verw-erken, heeft het een beetje zijn nut verloren, maar het formaat wordt niettemin nog veelgebruikt.

Wanneer een PVS (Possible Visibility Set) gebruikt wordt, kan op een nog betere manierzichtbaarheid van grote delen van een level bepaald worden.Een BSP-renderer is over het algemeen de optimale manier voor het renderen van binnen-lokaties 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 toelaatBSP-levels te maken, is GTKRadiant.

Libera biedt ondersteuning voor het laden en renderen van Quake3 BSP-levels en maakt

Page 30: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

gebruik van Frustum-culling, PVS en een face-cache om de scene zo optimaal mogelijk terenderen.

4.1.2 Octree SectorManager

De Octree-sectormanager zal een heightmap inlezen (zowel 8- en 16-bit versies worden onder-steund) en de data van het level opslaan in een Octree-Structuur. Het bouwen van een octreegebeurt door de geometrie van de scene te omringen door een zo klein mogelijke kubus en dierecursief te verdelen in 8 kleinere kubussen tot je op een punt komt dat in elke sub-kubus eenvooraf bepaald aantal polygonen zit.Na het uitvoeren van het algoritme zal in de Octree alle level-data verdeeld staan in een boom-structuur. D.m.v. Frustum culling kan nu zeer eenvoudig voor alle sub-kubusjes bepaaldworden of die zichtbaar zijn of niet. Als een kubus niet zichtbaar is, wordt de level-data dieerin opgeslaan staat ook doodgewoon niet gerenderd.

Octree-based frustum culling is een eenvoudige maar erg efficiente techniek voor het renderenvan groot uitgestrekte scenes, zoals landschappen. Het grote voordeel aan Octree-rendering isdat dit gebruikt kan worden voor virtueel elk mogelijk scene : verdeel vooraf gewoon alle ver-tices op in sub-gebieden, naargelang hun positie in de wereld, en pas er tijdens de render-faseFrustum-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 eenbrute-force methode, maar door OctreeSectorManager uit te breiden kan een eigen renderergeschreven worden die het terrein bijvoorbeeld rendered d.m.v. Geometrical Mipmapping ofR.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 ikhoe je dit het best kan doen.

Page 31: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

4.1.3 Galaxy SectorManager

Een GalaxySectorManager is dummy-manager die een lege sector rendert met enkel een Sky-box 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 devertices, indices, faces, normalen, textuurcoordinaten van alle statische data in de scene . Ineen Sector wordt de data opgeslaan in een standaard formaat. Een BSP-Sector zal bij inlezenvan een BSP-bestand de data vertalen naar dit formaat, die dan op zijn beurt geınterpreteerdkan worden door een SectorManager naar keuze.Een sector zal zichzelf niet renderen, daarvoor moet je een SectorManager selecteren die dedata in de sector zal verwerken en dan zo efficient mogelijk renderen.

In wat volgt, beschrijf ik kort de Sectoren die reeds geımplementeerd zijn in Libera .

4.2.1 BSP Sector

Een BSPSector zal een .bsp file inlezen, parsen, opslaan en klaarmaken voor het renderen doormiddel van een BSPSectorManager. Enkel Quake3 BSP files worden ondersteund. Bezier-curven en Quake3shaders worden niet gerenderd, maar support kan in principe toegevoegdworden door de BSPSector uit te breiden met de gewenste functionaliteit.

4.2.2 Octree Sector

Een Octree Sector zal een heightmap inlezen en zo opslaan dat een Octree SectorManagerde data kan opslaan in een Octree-datastructuur om die dan efficient te kunnen renderen.Heightmaps in .raw formaat worden ondersteund, zowel in 8bit en 16bit mode, opgeslaan inBIG-ENDIAN of LITTLE-ENDIAN formaat.

4.2.3 Galaxy Sector

Een GalaxySector is een dummy-sector behorende bij een GalaxySectorManager, die geendata inleest. Het maakt gewoon een lege scene waar, via de SectorManager, objecten ingeplaatst kunnen worden aan de SceneGraph.

4.2.4 Hoe een nieuwe Sector(Manager) schrijven?

De flexibiliteit van de RenderEngine zit grotendeels in het feit dat het geen probleem is naarbelieven nieuwe SectorManagers te schrijven en die zonder moeite kunnen gebruikt wordenin 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 schri-jven.

Page 32: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

(a) Octree sector (b) BSP sector (c) Galaxy sector metNormalmapping-test

Figuur 4.3: SectorManager voorbeelden

Stel, we zijn ambitieus, en we willen proceduraal terrein kunnen renderen d.m.v. bijvoorbeeldNURBS zodat we niet meer moeten leven met de beperkingen van heightmaps (Geen

”over-

hangs”, grote memory-footprint, beperkt detail). We zouden dan als volgt te werk kunnengaan (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

pub l i c c l a s s NURBS Sector extends Sector{

pub l i c NURBS Sector ( ){

//Doe i n i t i a l i s a t i e h i e r .}

pub l i c loadData ( ){

/∗Hier z a l j e de NURBS−data moeten trans formeren naar heti n t e rn e formaat van de engine , d i e enke l werkt met v e r t i c e sen i n d i c e s .Voor een NURBS−r endere r betekent d i t wa a r s c h i j n l i j k dat j e decont ro l e−punten za l ops laan a l s v e r t i c e s en d i e naar wensverwerken in j e SectorManager∗/

}}

Een nieuwe SectorManager dient de abstracte klasse SectorManager uit te brei-den

pub l i c c l a s s NURBS SectorManager extends SectorManager{

//De Sector d i e de SectorManager z a l renderen .NURBS Sector s e c t o r ;

pub l i c NURBS SectorManager ( S t r ing name)

Page 33: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

{super (name ) ;

s e c t o r = new NURBS Sector ( ) ;

g ene ra t ePor ta l s ( ) ;}

pub l i c void gene ra t ePor ta l s ( ){

//Voeg h i e r een manier toe om po r t a l s toe te voegen//aan j e scene . Dit kunnen eventuee l hand−placed po r t a l s// z i j n . . .

}

/∗De volgende twee f u n c t i e s z i j n de meest b e l a n g r i j k e en bevattende code d i e gebru ik t wordt om te renderen .Ik heb een s t r i k t ondersche id gemaakt tussen een Root− en n ie t−Root−Sector . Soms i s het immers wen s e l i j k een n ie t−root−s e c t o r terenderen met minder f e a t u r e s ( b i j v oo rb e e l d zonder schaduwen o f shaders )∗///Deze methode wordt opgeroepen a l s de SectorManager de root i s .pub l i c void renderSectorAsRoot (Camera c , Frustum f , boolean debugMode ){

renderMyNurbs ( ) ;

//Render de elementen in de SceneGraph . Via de Frustum zu l l e n de// elementen g e cu l l e d worden .getRootSceneNode ( ) . render ( frustum ) ;

}

//Deze methode wordt opgeroepen a l s de SectorManager n i e t de root i s .pub l i c void renderSectorAsChi ld (Camera c , Frustum f , boolean debugMode ){

renderMyNurbs ( ) ;

getRootSceneNode ( ) . render ( frustum ) ;}

pr i va t e void renderMyNurbs ( ){

// S c h r i j f h i e r j e code d i e het NURBS−t e r r e i n z a l renderen .}

}

Als alles correct geımplementeerd werd, kan je deze nieuwe SectorManager op net dezelfdemanier gebruiken in een scene zoals de reeds geımplementeerde managers. SectorManagerslinken, gelinkte SectorManagers cullen, de SceneGraph managen, de Sector correct trans-formeren in de 3D-wereld, ... wordt allemaal correct afgehandeld door de RenderEngine ende gebruiker moet daar geen rekening mee houden.

Page 34: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

4.3 Portal

Een portal wordt voorgesteld door een onzichtbare rechthoek (in de Engine kan wel eentextuur aan een Portal gehangen worden) waaraan een SectorManager gelinkt wordt. DieSectorManager zal dan enkel gerenderd worden wanneer de Portal in kwestie zichtbaar isdoor de camera.Het grote voordeel aan Portals is dat ze eenvoudig te implementeren zijn, flexibel zijn en heterg goedkoop is te testen of een Portal al dan niet in de ViewFrustum ligt. In de RenderEnginezal voor elke Portal een BoundingSphere (een bol die de Portal omvat) gedefinieerd wordenom 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 deportal, maar meestal is de BoundingSphere-test meer dan voldoende.

Figuur 4.4: Een portal, mettextuur, dat dienst doet alseen spiegel

In de Engine worden spiegels en monitoren gedefinieerdals een extentie van de klasse Portal, met dit ver-schil dat daarbij de Stencil-buffer zal gebruikt wor-den om de scene achter de spiegel of monitor cor-rect te renderen. In het geval van een spiegelwijst de portal gewoon naar zijn eigen SectorMan-ager.

Portals zijn steeds rechthoeken maar met een minimum aancreativiteit hoeft dat geen beperking te zijn. Je kan immerszonder probleem geometrie toevoegen aan de scene die eendeel van de portal overlapt om een niet rechthoekige door-gang te simuleren. De Stencil-buffer gebruiken kan ook bij-zonder nuttig zijn om te renderen naar een willekeurige vorm(bijvoorbeeld voor een ronde spiegel).

4.3.1 Spiegels

Zoals vermeld, zijn spiegels portals die wijzen naar eenzelfde SectorManager, maar hoe slagenwe erin de Sector aan de andere kant van de spiegel correct te renderen? De sector moetimmers gespiegeld worden naargelang de orientatie en plaatsing van de spiegel, hiervoor moetde ModelView-matrix aangepast worden d.m.v. een reflectie matrix.

Een spiegel toevoegen aan een scene gebeurt als volgt:

Porta l 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 . se tOtherS ide ( mySector ) ;

Page 35: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 isbijzonder eenvoudig, we hoeven hiervoor enkel maar de assen te inverteren. Een reflectie overhet XZ-vlak wordt bijvoorbeeld gegeven door de ModelView-matrix te vermenigvuldigen metvolgende matrix (we inverteren gewoon de Y-coordinaten):

M =

1 0 0 00 −1 0 00 0 1 00 0 0 1

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:

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)2 0

0 0 0 1

Deze methode verwacht wel dat de spiegel in de oorsprong van het assenstelsel staat, wemoeten dus het assenstelsel verplaatsen naar de positie p van de spiegel, daar reflecteren endan terug translateren naar de oorspronkelijke oorsprong. De volledige reflectie matrix wordtm.a.w. gegeven door:

REFL =

1 0 0 −p.x0 1 0 −p.y0 0 1 −p.z0 0 0 1

∗ M ∗

1 0 0 p.x0 1 0 p.y0 0 1 p.z0 0 0 1

Wanneer we de SectorManager achter de spiegel willen renderen, volstaat het de ModelView-matrix te vermenigvuldigen met de matrix REFL.

De methode

pub l i c void re f l ec tCamera ( Plane plane )

van de klasse Mirror, maakt die implementatie reeds.

Dynamische spiegels

Roterende spiegels worden ondersteund in Libera , hiervoor moet elke frame de reflection-matrix opnieuw berekend worden.

De methode

pub l i c void ro ta t eMi r ro r ( f l o a t ang le ) ;

van de klasse Mirror, maakt dit mogelijk.

Page 36: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

4.3.2 Monitoren

Een monitor wordt op net dezelfde manier gerenderd als een normale portal maar de stencil-buffer 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:

Porta l 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 , 2 8 0 ) ) ;

monitor . se tOtherS ide ( mySector ) ;

4.4 RenderEngine

De klasse RenderEngine is het hart van Libera . Het staat in voor de zichtbaarheidsbepal-ing van SectorManagers, het efficient renderen van Sectoren maar ook camera-managementgebeurt hier en verschillende parameters (fullscreen, wireframe, viewportSize, etc...) om derender-pipeline aan te passen staan hier gedefinieerd.RenderEngine beschikt over een lijst waar de SectorManagers in de scene opgeslaan staan, aande hand daarvan (en de positie van de camera in de scene ) zullen gedurende de render-fasede 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 zalberekend worden welke SectorManagers zichtbaar zijn vanuit de root-sector. Ook zalvoor elke sector een unieke Frustum bepaald worden om zoveel mogelijk te kunnencullen.

Render fase De zichtbare SectorManagers worden gerenderd, evenals de SceneGraphs, ge-bruikmakende van de Frustums die bepaald werden in de pre-render fase. De Sec-torManagers worden front-to-back gerenderd (startende vanuit de root-sector) om zoveel mogelijk te vermijden dat gerenderde sectoren later toch overtekend worden doorandere.

Post-render fase Dit is een post-processing fase en een ideale plaats om post-process ef-fecten zoals Bloom, HDR-lighting of Motionblur te implementeren.

4.4.1 Bepalen root-sector

Als gebruiker moet je steeds een initiele root-sector opgeven, aan de hand van die Sector zalgedurende het verloop van het programma de root-sector eventueel geupdatet worden.De start-sector vastleggen gebeurt d.m.v. volgende methode van RenderEngine:

pub l i c void setRoot ( SectorManager s e c t o r ) ;

Page 37: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Gedurende de pre-render fase zal steeds getest worden of de camera zich in een anderesector bevindt, dit gebeurt als volgt (pseudo-code):

f o r ( a l lPor ta l s InTheRootSec tor ){

// Transform camera in to Local−space o f the s e c t o r .Vector3 pos = Matrix4 . mult ( inverseTransformMatrix , camerapos ) ;

// I f the camera i s with in the boundar ies o f a Porta li f ( cu r r en tPor ta l . pointInBox ( pos ) ){

// Set the r o o t s e c t o r to the SectorManager on the o th e r s i d e// o f the Porta l .root = tempPortal . o the rS ide ;

}}

Onthoud dat SectorManagers allemaal gedefinieerd staan in een eigen assenstelsel, dus het isbelangrijk in te zien dat hier de camerapositie (die in World-space gedefinieerd staat) steedsgetransformeerd moet worden naar de Local-space van de RootSector, anders heeft die vergeli-jking, wiskundig gezien, geen zin.Zoals vermeld in ‘SectorManager’ (p.25) beschikt elke SectorManager over een Transfor-matieMatrix T, die bepaalt waar die sector uiteindelijk zal komen in World-space. Om decamera te transformeren naar de Local-space van de sector volstaat het de camera-positie tevermenigvuldigen met de inverse van de tranformatie-matrix van die specifieke SectorMan-ager:

(localx, localy, localz, 1) = (worldx, worldy, worldz, 1) ∗ T−1

4.4.2 Zichtbaarheidsbepaling

Vanuit de root-sector wordt elke frame bepaald welke Sectoren zichtbaar zijn, op een manierdie het midden houdt tussen een iteratief en recursief algoritme (pseudocode):

//Een b e l a n g r i j k e counter d i e ervoor z a l zorgen dat// z e l f d e sectormanagers n i e t meer dan eenmaal geeva luee rd//wordeni n t counter = 0 ;// Verwi jder a l l e Sectoren u i t de render− l i j s t .renderConta iner . c l e a r ( ) ;//Voeg de root−s e c t o r toe ( daar beginnen we immers te renderen ) .renderConta iner . add ( root ) ;

whi l e ( counter < renderConta iner . s i z e ( ) ){//Neem de ee r s tvo l g ende n ie t−geeva luee rde SectorManager .cur r ent = renderConta iner . get ( counter ) ;

f o r ( a l lPor ta l s InTheCurrentSec to r ){

//Test de z i ch tbaa rhe id

Page 38: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

i f ( Porta l v i s i b l e ){

//Als de SectorManager g e l i n k t aan de po r t a l z i ch tbaa r// i s , bereken dan z i j n frustum .CalculateFrustum ( ) ;renderConta iner . add ( cur rent )

}}counter++;

}

4.4.3 Berekenen bounding-box en view-Frustum

Aangezien je door een portal maar zelden de sector, aan de andere zijde, volledig kan zien, ishet nuttig moeite te doen de scene achter een portal zoveel mogelijk te cullen. AchterliggendeSectorManagers zullen we dus cullen d.m.v steeds kleiner wordende Frustums, naargelang degrootte 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 deportal in kwestie zo goed mogelijk zal omsluiten. In wat volgt zal ik beschrijven hoe je een-voudig zo een frustum kan vinden.

In grote lijnen gaat het algoritme als volgt:

1. Zoek de coordinaten van de portal in ScreenSpace.

2. Bepaal de boundingbox van de portal in ScreenSpace.

Page 39: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

3. ”Un-project”de hoekpunten van de boundingbox zodat we een Frustum verkrijgen inWorld-space die door de boundingbox wordt bepaald.

4. Gebruik de verkregen Frustum om de SectorManager te renderen.

Coordinaten in Screen-space

De hoekpunten van een portal zijn gekend, we transformeren die sequentieel van World-spacenaar Camera-space en Clip-space (P is de ProjectionMatrix, MV is de ModelviewMatrix):

(clipx, clipy, clipz, clipw) = (worldx, worldy, worldz, clipw) ∗ MV ∗ P

Aan de hand van de Clipspace-coordinaten kunnen we de Screen-Space coordinaten alsvolgt verkrijgen:Sla de kenmerken van de viewport op:

glGetFloat (GL11 .GL VIEWPORT, bu f f e r ) ;

Normaliseer de coordinaten:

ndc = (clipx/clipw, clipy/clipw, clipz/clipw)

Bereken Screen-space coordinaten:

f l o a t w = bu f f e r . get (2 ) ∗ 0 .5 f ;f l o a t h = bu f f e r . get (3 ) ∗ 0 .5 f ;f l o a t x = ( ndc . x + 1 .0F) ∗ w + bu f f e r . get ( 0 ) ;f l o a t y = ( ndc . y + 1 .0F) ∗ h + bu f f e r . 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 onsenkel nog de BoundingBox van de Portal in Screen-space te zoeken (zie figuur 4.8(b) (p.42)):

Page 40: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 coordinaten kunnen we de Frustum als volgt bepalen (we makenhierbij gebruik van GLU):

GL11 . glMatrixMode (GL11 .GL PROJECTION) ;GL11 . g lLoadIdent i ty ( ) ;GLU. gluPickMatr ix (x , y , width , height , view ) ;GLU. g luPe r spe c t i v e ( 60 . 0 f , viewportW/viewportH , nearPlane , f a rP lane ) ;

4.5 ShadowRenderer

Het is mogelijk met elke SectorManager een unieke (optionele) ShadowRenderer te ge-bruiken, dit omdat bepaalde schaduw-technieken beter geschikt zijn voor bepaalde scenesdan andere.Een ShadowRenderer toevoegen aan een scene is bijzonder eenvoudig gehouden en gaat door-gaans als volgt:

ShadowRenderer s r = new ShadowVolumeZFailRenderer ( ) ;s r . addLight ( l ightNode ) ;s r . addLight ( l ightNode2 ) ;s e c t o r . setShadowRenderer ( s r ) ;

De ShadowRenderer zal aan de hand van de MovableObjects in zijn SceneGraph en demeegegeven

”Lights” schaduwen renderen in de scene . Standaard zullen de meeste objecten

schaduwen casten (behalve Particles) maar je kan, voor performantie redenen, een objecteenvoudig uitsluiten door de volgende methode van MovableObject:

pub l i c 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 onders-teund door de ShadoRenderers die gebruik maken van Shadow Mapping. Zie onder voor eenopsomming van de beschikbare ShadowRenderers in Libera .

ShadowRenderer abstraheert het renderen van schaduwen, wat het erg eenvoudig maakt an-dere 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)

Page 41: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

• 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 ‘Real-time shadow rendering’ (p.96).

4.6 SceneNode

Alle elementen in een scene die geen deel uitmaken van de statische level-data worden inde RenderEngine geordend in een Scene-Graph. Het gebruik van een scene-graph heeft grotevoordelen voor het renderen van complexe, dynamische omgevingen. Zo is het mogelijk eenObject te linken aan een ander Object, zoals een wapen of licht aan een personage, die meezullen bewegen wanneer dat personage verplaatst wordt.Een Scene-graph is een boomstructuur waarin SceneNodes opgeslaan staan die in moeder-dochter relatie staan met elkaar, aan elke SceneNode wordt een MovableObject gelinkt diegerenderd wordt wanneer de SceneNode zichtbaar is. Wanneer op een SceneNode een trans-formatie wordt toegepast (translatie, rotatie, schaling) zal die ook toegepast worden op allekinderen van die SceneNode.Elke SectorManager beschikt standaard over een root-SceneNode waaraan andere SceneNodeskunnen gelinkt worden.Elke SceneNode houdt zijn translatie, rotatie, schaling bij en zal bij het renderen van deSceneNode 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 figuur4.7 (p.40)

Merk op dat het in de RenderEngine niet mogelijk is items te renderen zonder die aan deSceneGraph van een SectorManager toe te voegen.

4.6.1 MovableObject

Zoals vermeld, kunnen enkel MovableObjects in SceneNodes opgeslaan worden, misschienbehoeft dat concept wat verduidelijking vooraleer verder te gaan.Een MovableObject is een abstrahering van wat een voorwerp in een 3D-scene kan zijn enwordt gedefinieerd door de abstracte klasse MovableObject. Door die abstrahering is hetmogelijk willekeurig wat in de SceneGraph op te slaan en die toch correct te cullen tegen deview-Frustum.Momenteel zijn volgende MovableObjects geımplementeerd (meer uitleg voor elk van hen iste vinden in de respectievelijke hoofdstukken), maar niets houd je tegen MovableObject naarbelieven uit te breiden met extra implementaties (bijvoorbeeld een Spline- of Bezier-curverenderer).

Billboard Een billboard is een rechthoek met een textuur die steeds gericht zal staan naarde camera.

Page 42: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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

4.6.2 Renderen van de SceneGraph

Bij het renderen van deze SceneGraph zal de boom diepte-eerst, recursief doorlopen wordenen bij elke SceneNode een correcte assenstelsel-transformatie doorvoeren vooraleer de Mov-ableObject (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);

Page 43: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 een belangrijke implicatie: voorwerpen kunnen enkel rond hun lokale oorsponggeroteerd worden, niet rond een willekeurig punt! Uiteraard is dit niet gewenst en een goedeoplossing 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 eentransformatie 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-vlakmet een straal van 10 eenheden, dan kan je als volgt tewerkgaan:

SceneNode p ivot ;SceneNode cubeNode ;SceneNode sphereNode ;MovableObject cube , sphere ;

pub l i c void setupScene ( ){

cube = new Entity ( cubeMesh , cubeMater ia l ) ;sphere = new Entity ( sphereMesh , sphereMater ia l ) ;

//Geef een nu l l ob j e c t om een l e g e SceneNode te// c r e e r en .

Page 44: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

sphereNode = new SceneNode ( ” sphere ” , sphere ) ;sphereNode . t r a n s l a t e (new Vector3 (50 , 10 , 0 ) ) ;p ivot = new SceneNode ( ” p ivot ” , nu l l ) ;sphereNode . getRootSceneNode ( ) . addChildNode ( p ivot ) ;

cubeNode = new SceneNode ( ”cube” , cube ) ;cubeNode . t r a n s l a t e (new Vector3 (0 , 0 , 1 0 ) ;p ivot . getRootSceneNode ( ) . addChildNode ( cubeNode ) ;

}

pub l i c void update ( ){

// Roteer het punt e l k e frame .p ivot . rotateY (0 . 01 f ) ;

}

(a) Zonder pivot-node (b) Met pivot-node

Figuur 4.8: SceneGraph met en zonder pivot-node

4.6.3 Culling van SceneNodes

Om de SceneNodes correct te cullen moeten extra maatregelen genomen worden: een trans-formatie van het assenstelsel verplaatst immers enkel de oorsprong maar verandert niet deeigenlijk waarden van de vertices van het object in kwestie. Om een SceneNode correct tecullen tegen de View-Frustum moeten dus de coordinaten van de vertices van dat objectgekend zijn in de ruimte waar die Frustum gedefinieerd is, in local-space van de bijhorendeSectorManager.Omdat voor objecten met veel vertices het erg duur zou zijn bij elke transformatie van hetassenstelsel de waarden van de vertices aan te passen en elke vertex tegen de Frustum te testenkunnen we, wanneer we een (meestal minimale) graad van onnauwkeurigheid toelaten, voorelk object een BoundingBox definieren en enkel de (acht) hoekpunten van de BoundingBoxte transformeren en die testen tegen de Frustum.

Een BoundingBox voor een 3D-object is een balk die het object in kwestie zo goed mo-gelijk omvat, in Libera wordt de BoundingBox voor een Entity als volgt bepaald (bepalingvan de BoundingBox kan aangepast worden voor elk soort MovableObject):

pub l i c BoundingBox getBoundingBox ( ){

i n t numberOfVerts = getSubMesh ( ) . v e r t i c e s . capac i ty ( ) / 3 ;

Page 45: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 l z = −15000;Vector3 cente r = new Vector3 ( ) ;

//Bereken het centrum van het ob j e c t .f o r ( i n t i = 0 ; i < numberOfVerts ; i++){

// Add the cur rent ver tex to the cente r v a r i ab l eVector3 temp = new Vector3 ( subMesh . v e r t i c e s . get ( i ∗3) ,

subMesh . v e r t i c e s . get ( ( i ∗3)+1) ,subMesh . v e r t i c e s . 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 > l x )l x = temp . x ;

i f ( temp . y > l y )l y = temp . y ;

i f ( temp . z > l z )l z = temp . z ;

}

Vector3 v1 = new Vector3 ( sx , sy , sz ) ;Vector3 v2 = new Vector3 ( lx , ly , l z ) ;

Vector3 d i f f = Vector3 . Minus ( v2 , v1 ) ;c en te r = Vector3 .Add(v1 , Vector3 . DevideByScalar ( d i f f , 2 ) ) ;

f l o a t w = Math . abs ( c ente r . x − l x ) ; // breedtef l o a t h = Math . abs ( c ente r . y − l y ) ; // hoogtef l o a t d = Math . abs ( c ente r . z − l z ) ; // d i ep t e

f l o a t rad iu s = w; // S t r aa l van BoundingSpherei f (h > rad iu s )

rad iu s = h ;i f (d > rad iu s )

rad iu s = d ;

//Pythagorasrad iu s = ( f l o a t )Math . s q r t ( rad iu s ∗ rad iu s + rad iu s ∗ rad iu s ) ;

r e turn new BoundingBox (new Vector3 ( c ente r . x − w, cente r . y + h , c ente r . z + d ) ,new Vector3 ( c ente r . x + w, cente r . y − h , c ente r . z − d ) ,center ,r ad iu s ) ;

}

Page 46: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Om de Frustum-test nog wat te versnellen, kunnen we een Bounding-sphere (een boldie het object zo goed mogelijk omvat) definieren voor elk object en eerst testen tegen deBoundingSphere 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, smalleobjecten), dus we kunnen het Frustum-culling algoritme als volgt aanpassen (pseudocode):

// F i r s t t e s t the boundingSpherei f ( BoundingSphereInFrustum ( ) == true ){

// I f sphere v i s i b l e , t e s t the boxi f ( BoundingBoxInFrustum ( ) == true )

renderObject ( ) ;}e l s e{

// I f the BoundingSphere i s n ’ t v i s i b l e , the boundingbox w i l l c e r t a i n l y//not be v i s i b l e , so stop t e s t i n g the ob j e c t .

}

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 Frustumculling 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, kunnenobjecten achter een portal erg goed geculled worden, zodat opnieuw enkel objecten gerenderden geupdatet worden (erg nuttig bijvoorbeeld relatief dure update-operaties voor Particleengines 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 mogelijkheiddie toe te voegen is er en het lijkt me nuttig hier even bij stil te staan. Reeds bij het on-twerp van de architectuur werd immers in het achterhoofd gehouden hoe we Frustum-culling

Page 47: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

en collision-detection zouden kunnen afhandelen. SectorManagers staan allen gedefinieerdin een eigen, lokaal, assenstelsel en collision-detection doen op de world-coordinaten van devertices van het level zal dus niet lukken (SectorManagers worden immers gerenderd relatieft.o.v zijn portals en eventuele andere SectorManagers).Om Frustum culling tot een goed einde te brengen zal, zoals ik reeds heb uitgelegd, de frustumsteeds getransformeerd worden naar het lokaal-assenstelsel van de SectorManager in kwesteen daar bepalen welke zaken al dan niet zichtbaar zijn. Voor collision detection zullen wegelijkaardig te werk gaan.Om collision detection voor de camera te doen, bijvoorbeeld om te vermijden dat door murenkan gelopen worden, volstaat het dus de camera-positie te translateren naar het lokaal-assenstelsel 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 Ren-derEngine laat dus toe om grote delen van een level zowiezo al te schrappen voor het testenvan 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 eenOctree-sector. Zo kan je niet alleen collision-detection beperken tot een bepaalde SectorMan-ager (zoals je root-sector) maar ook steeds de meest efficiente collision-methode gebruikenvoor 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 eencollision zal optreden.

• De CollisionManager geeft een nieuwe camera-positie terug (bij collision kan dit het-zelfde zijn als de input).

• Transformeer camera-positie terug naar world-space en plaats de camera in je scenedoor 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 klasseAbstractApp. In dit voorbeeld zal een eenvoudige BSPSectorManager gebruikt worden, meteen kubus, een Skybox en een ShadowRenderer, die via een portal gelinkt wordt aan een Oc-treeSectorManager. Dit is alles om een scene te renderen, de RenderEngine zal alles correctafhandelen achter de schermen!

pub l i c c l a s s MyFirstApp extends AbstractApp{

//De BSP SectorManagerp r i va t e BSP SectorManager f i r s t ;

Page 48: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

//De Octree SectorManagerp r i va t e Octree SectorManager second ;//De skyboxSkyBox skyBox ;//Een .OBJ− f i l e l ade r .OBJLoader l oade r ;//De Entity dat onze kubus z a l v o o r s t e l l e n .p r i va t e Entity cube ;//De SceneNode voor onze kubus .SceneNode cubeNode ;

//Een l i c h tLight l i g h t ;//Een SceneNode voor het l i c h t .SceneNode l ightNode ;//De ShadowRendererShadowRenderer s r ;

pub l i c MyFirstApp ( St r ing s t r ){

super ( s t r ) ;}

/∗−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−In de engine wordt een v e r s c h i l gemaakt tussen het i n i t i a l i s e r e n van dedata en het opbouwen van de scene . Dit omdat het dan moge l i jk i s dedata van een s e c t o r op voorhand te laden , maar de opbouw van de s e c t o ren het p laa t s en van de ob jec t en in een l a t e r e f a s e te doen .−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−∗/

pub l i c void in i tData ( ){

//Onze mater ia l en .MaterialManager . g e tS ing l e t on ( ) .

p a r s eMat e r i a lF i l e s ( ” mat e r i a l s /shadowVolume . mate r i a l ” ) ;

//Maak een skyBoxskyBox = 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 ) ) ;

}

pub l i c void setupScene ( ){

//Maak een nieuwe BSP SectorManager , en laad de map met naam ” ‘map2” ’// in het . con f i g−bestand . Zie het onderdee l over Config−bestanden ,// voor meer u i t l e g h i e r ov e r .f i r s t = new BSP SectorManager ( ”map2” ) ;//Maak een nieuwe Octree SectorManager .second = new Octree SectorManager ( ” sa2 ” ) ;

Page 49: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

l oade r = new OBJLoader ( ) ;//Laad de kubus .cube = new Entity ( l oade r . load ( ”models /CubeNormals . obj ” ) ,

MaterialManager . g e tS ing l e t on ( ) . g e tMate r i a l ( ”Brick ” ) ) ;

/////// Be l ang r i j k : d i t i s noodzake l i j k voor Z−Fa i l S t e n c i l Shadows !// Zie h i e rvoo r het onderdee l over Shadow Volumes ./////cube . rep laceEdgesWithTr iang les ( ) ;//Maak een SceneNode voor de kubus .cubeNode = new SceneNode ( ”cube” , cube ) ;// Plaats hem in te scenecubeNode . t r a n s l a t e (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 tl ightNode = new SceneNode ( ” l i g h t ” , l i g h t ) ;// Plaats hem in de scenel ightNode . t r a n s l a t e (new Vector3 (100 , 50 , 0 ) ) ;//Update ed wor ldPos i t i on ( b e l a n g r i j k voor l i c h t e n )l ightNode . updateWorldPosit ion ( ) ;

//Hang de po r t a l met index 0 in ” f i r s t ” aan de po r t a l//met index 0 in ” second ” .f i r s t . l i n k ( second , 0 , 0 ) ;

// Plaats de camera in de sceneengine . posit ionCamera (100 , 50 , −100, 20 , 50 , 0 , 0 , 1 , 0 ) ;//Bepaal de root−s e c t o r .eng ine . setRoot ( f i r s t ) ;

//Maak een Z−Fa i l schaduw−r endere r .s r = new ShadowVolumeZFailRenderer ( ) ;//Voeg l i c h t e n toe .s r . addLight ( l ightNode ) ;//Hang aan ” f i r s t ” .f i r s t . setShadowRenderer ( s r ) ;

}

pub l i c void render ( ){

eng ine . c l e a r ( ) ;eng ine . posit ionCamera ( ) ;

skyBox . render ( eng ine . camera ) ;eng ine . render ( ) ;

}}

Page 50: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 SectorManageruit te breiden en die te gebruiken in je applicatie, als volgt:

Create eigen BSPSectorManager:

pub l i c c l a s s MyBspSectorManager extends BSP SectorManager{

SkyBox skyBox ;

Entity cube ;SceneNode cubeNode ;OBJLoader l oade r ;

pub l i c MyBspSectorManager ( S t r ing name){

super (name ) ;

//Maak een cube en hang aan de sceneGraph .cube = new Entity ( l oade r . load ( ”models /CubeNormals . obj ” ) ,

MaterialManager . g e tS ing l e t on ( ) . g e tMate r i a l ( ”Brick ” ) ) ;cubeNode = new SceneNode ( ”cube” , cube ) ;

getRootSceneNode ( ) . addChildNode ( cubeNode ) ;}

}

Page 51: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Gebruik de SectorManager in je applicatie:

pub l i c c l a s s DemoApp extends AbstractApp{

pub l i c SectorManager s e c t o r ;SkyBox skyBox ;

pub l i c DemoApp( St r ing s t r ){

super ( s t r ) ;

}

pub l i c void in i tData ( ){

FontManager . bui ldFont ( ”data/ font tahoma . png” , 7 ) ;

skyBox = new SkyBox (5000 ) ;ang le = 0 .05 f ;

}

pub l i c void setupScene ( ){

//Gebruik j e g ew i j z i gde BSP SectorManager z o a l s voorheen .s e c t o r = new MyBspSectorManager ( ” Hal l ” ) ;

eng ine . posit ionCamera (0 , 100 , −450, 0 , 100 , 0 , 0 , 1 , 0 ) ;eng ine . setRoot ( s e c t o r ) ;

}

pub l i c void render ( ){

eng ine . c l e a r ( ) ;eng ine . posit ionCamera ( ) ;eng ine . render ( ) ;

}}

Page 52: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoofdstuk 5

Resource-framework

5.1 Inleiding

Een absolute noodzaak in een volwassen RenderEngine is een manier om efficient je re-sources (Texturen, Shaders, 3D-meshes, ...) te beheren. Texturen en meshes worden meestalvele keren opnieuw gebruikt in een scene , het is uiteraard wenselijk dat we de data maareenmaal in het geheugen lezen en werken met instanties van die data in de applicatie. Deenige manier om dit goed te doen is door de data centraal te beheren; in Libera is dat de taakvan de Texture-manager en Material-manager.

5.2 Texture-manager

We onderscheiden twee belangrijke klassen: Texture en TextureManager. Ze verhoudenzich tegenover elkaar zoals een Sector en SectorManager dat doen: Texture bevat de ken-merken van een Textuur (breedte, hoogte, bits-per-pixel, ...), TextureManager doet het beheervan texturen zoals laden of binden van een textuur.

5.2.1 De klasse Texture

Texture is een eenvoudige klasse die voor elke ingeladen textuur een aantal kenmerken bi-jhoudt, zoals de dimensies van de afbeelding en het formaat waarin die werd opgeslaan. Deopen-source image-bibliotheek DevIL wordt gebruikt om een Texture in te laden, dus een grotevariatie van formaten wordt ondersteund. Voor een lijst van de ondersteunde formaten verwijsik je graag naar de officiele site van DeVIL (http://openil.sourceforge.net/features.php)

5.2.2 De klasse TextureManager

De texture-manager beheert de texturen in een HashMap datastructuur. Een Hashmap isperformant, het voert in constante tijd zijn get() en put() operaties uit, maar is vooral handigomdat aan een Textuur een naam gelinkt kan worden waarmee die later kan geıdentificeerdworden. Bij het inladen van een Textuur dient die identificatie opgegeven te worden, dikwijlsis dit het file-path naar het bestand. Aan de hand van die naam zal gekeken worden of hetbestand reeds in de HashMap voorkomt, als dat het geval is, wordt de aanvraag de textuurin te laden verworpen, zonder dat de gebruiker hier iets van merkt.

50

Page 53: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

De texture-manager maakt gebruik van de Lightweight-pattern en er is te allen tijde maar eeninstantie beschikbaar in je programma-scope en die opvragen gebeurt op volgende manier:

TextureManager . g e tS ing l e t on ( ) ;

Intern gebeurt het volgende:

TextureManager in s t ance ;

pub l i c s t a t i c TextureManager g e tS ing l e t on ( ){

i f ( i n s t ance==nu l l ){

i n s t ance = new TextureManager ( ) ;}

re turn in s t ance ;}

Handmatig een textuur inladen gebeurt d.m.v. volgende methode:

TextureManager . g e tS ing l e t on ( ) . load ( St r ing ID , S t r ing Path , boolean clamp ) ;

Merk op dat dit volledig geautomatiseerd kan gebeuren door gebruik te maken van Material-scipts, ‘Material-scripts’ (p.52).

Een textuur opvragen, gebeurt op volgende manier:

TextureManager . g e tS ing l e t on ( ) . getTexture ( S t r ing ID ) ;

Een textuur binden om het te gebruiken als texturemap voor een object, doe je als volgt:

TextureManager . g e tS ing l e t on ( ) . getTexture ( S t r ing ID ) . bind ( ) ;

5.3 Materials

Een Material staat een trapje hoger dan een Texture en laat toe met groter detail en flex-ibiliteit te beschrijven hoe een object zal gerenderd worden. Een Material kan zich beperkentot 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 inMaterial-scipts en worden ingeladen bij het laden van je applicatie. Voor meer informatiehierover verwijs ik je naar ‘Material-scripts’ (p.52).Ook hier wordt weer een onderscheid gemaakt tussen een beschrijvende klasse Material eneen beherende klasse MaterialManager.

5.3.1 Material

Een Material houdt de kenmerken van een material bij, dit gaat van rotatiesnelheid en scroll-snelheid (in het geval van geanimeerde texturen) over het al dan niet transparent zijn tot devertex- en fragment-shader code die moet gebruikt worden bij het renderen van dat specifiekobject.Om een Material te kunnen gebruiken, moet die eerst

”gebonden” worden, als volgt:

Page 54: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

myMaterial . s e t ( ) ;

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 . r e s e t ( ) ;

5.3.2 MaterialManager

Ook de MaterialManager maakt gebruik van de LightWeight-pattern en het opvragen van deinstantie gebeurt gelijkaardig aan de TextureManager:

MaterialManager . g e tS ing l e t on ( ) ;

Net als de TextureManager zal de MaterialManager de gekende Materials opslaan in eenHashMap datastructuur zodat een material nooit meer dan eenmaal wordt geinitialiseerd eneenvoudig kan opgevraagd worden door middel van zijn ID:

MaterialManager . g e tS ing l e t on ( ) . g e tMate r i a l ( S t r ing ID ) ;

5.4 Material-scripts

5.4.1 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 hetprincipe van Material scripts toegevoegd aan de engine. Een material-script is een simpeltekst-bestand, dat geparsed en geladen wordt tijdens het opstarten van je applicatie, waarinkenmerken van een material kunnen beschreven worden. Zo een material kan op de klassiekemanier gebruikt worden in je applicatie.

Materialscripts worden in een bestand geplaatst met extentie .material, dat als volgt wordtgeladen:

MaterialManager . g e tS ing l e t on ( ) . p a r s eMat e r i a lF i l e s ( ” d i r /mat . mate r i a l ” ) ;

Na uitvoeren van deze instructie zal de volledige material file geparset worden en zullen degedefinieerde Materials, indien deze correct worden geformuleerd, ingeladen worden in deMaterialManager voor verder gebruik.

5.4.2 Voorbeelden

Als eerste voorbeeld toon ik een script dat een Material zal aanmaken dat enkel een textuurzal mappen op het voorwerp waar de Material aan gelinkt wordt.Een script bestaat uit twee componenten, de header en body :

Page 55: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Header Dit is de naam van je material die zal gebruikt worden door de MaterialManagerom 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 Mater ia l met de naam ”texWood”texWood{ // Star t body

// De f i n i e e r 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: Inhet volgend code-fragment staat een nieuwe klasse OBJLoader, zie ‘OBJLoader’ (p.71) voormeer informatie hierover):

Entity cube ;OBJLoader l oade r ;

MaterialManager . g e tS ing l e t on ( ) . p a r s eMat e r i a lF i l e s ( ”mat . mate r i a l ” ) ;

l oade r = new OBJLoader ( ) ;cube = new Entity (

l oade r . load ( ”models /CubeNormals . obj ” ) ,MaterialManager . g e tS ing l e t on ( ) . g e tMate r i a l ( ”texWood” ) ) ;

//Wanneer j e deze Entity cube hangt aan een SceneNode za l het model// gerenderd worden met de gewenste mate r i a l .

Merk op dat indien we later zouden beslissen dat de kubus in onze scene geen Hout-textuurmag krijgen maar bijvoorbeeld een Steen-textuur, dan volstaat het de Materialscript aan tepassen 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 Mater ia l met de naam ” texGlass ”texGlass{ // Star t body

// De f i n i e e r een textuurtex data/Glass .bmp//Blend de textuur door het keyword ”blend”// toe te voegen met twee argumenten ’ a ’ en ’b ’ .// Inte rn za l de i n s t r u c t i e glBlendFunc (a , b) gebru ik t worden .blend GL ONE GL ONE

} //Einde body

Page 56: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Het is mogelijk texturen te animeren d.m.v. een script:

Mat3.material

Sc ro l l i ngC louds{

//Voeg textuur toetex data/ fog .bmp// Blendingblend GL ONE GL ONE//Animeer de textuur : s c r o l l de textuur// 0 .2 un i t s in r i c h t i n g en 0 .1 un i t s// in Y−r i c h t i n g . Elke frame za l de p o s i t i e//van de textuur aangepast worden door de//OpenGL TextureMatrix aan te passen naarge lang// d i e parameters .s c r o l l 0 . 2 0 . 1

}

Mat4.material

Rotat ingMagicStuf f{

//Voeg textuur toetex data/magic .bmp// Roteer de textuur 0 .1 graden . Elke frame za l de textuur// ro t e r en vo lgens de gegeven hoek .r o t a t e 0 .1//Maak de textuur dubbel zo groots c a l e 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 material-scripts. Een model renderen d.m.v. bijvoorbeeld een Cel-shader, gebeurt met volgende ma-terial:

Mat5.material

CelShader{

//Voeg textuur toetex data/myTexture .bmp// De f i n i e e r de shader door het keyword ” shader ” ,// gevolgd door het path naar j e vertex− en fragment−shaders .shader c e l . ve r t c e l . f r a g

}

Page 57: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Merk op, in eenzelfde .material file kunnen zonder probleem meerdere scripts gedefinieerdworden, als volgt:

AllMyMaterials.material

texWood{

tex data/Wood .bmp}

texGlass{

tex data/Glass .bmpblend GL ONE GL ONE

}

Sc ro l l i ngC louds{

tex data/ fog .bmpblend GL ONE GL ONEs c r o l l 0 . 2 0 . 1

}

Rotat ingMagicStuf f{

tex data/magic .bmpro ta t e 0 .1

}

CelShader{

tex data/myTexture .bmpshader c e l l . v e r t c e l l . f r a g

}

Voor een aantal voorbeelden van hoe dit eruit kan zien in je applicatie verwijs ik je graagnaar figuur 5.1 (p.56).

5.5 Shaders

GLSL shaders worden volledig ondersteund door de Material-Manager en omdat er relatiefweinig geımplementeerde GLSL-shaders te vinden zijn heb ik me voorgenomen een onderdeelvan de tekst te wijden aan een gedetailleerde beschrijving van een aantal geımplementeerdeshaders die meegeleverd worden met de RenderEngine, gaande van (textured) per-pixel light-ing over Cel-shading tot Normalmapping.In Libera zijn meer shaders meegeleverd, maar die allemaal beschrijven zou buiten het bestekvan deze tekst vallen. Ik verwijs je graag naar de source-code indien je geınteresseerd benthoe een aantal effecten, zoals Gooch-shader en Parallax mapping, geımplementeerd wordenin GLSL.

Page 58: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

(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 Ren-derEngine

Page 59: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

5.5.1 Introductie tot shaders

Om shaders te begrijpen moet je weten hoe de OpenGL pipeline werkt en welke stadia eenvertex 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, zijnnormaal, textuurcoordinaten, enz...Per vertex worden die attributen als input voor de pipeline gegeven. In deze stage van defixed-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, ditlaatste 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

Page 60: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

bepalen, elk fragment beschikt over attributen die de kleur, normaal en textuurcoordinatenzullen 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 textuurcoordinaten van het frag-ment, 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 v e r t i c e s d i e h i e rop volgen maken dee l u i t van een// Tr iang l e ( c o n n e c t i v i t e i t s i n f o rmat i e ! )g l S t a r t (GL TRIANGLE) ;

//Vertex1g lCo l o r 3 f (0 , 255 , 0 ) ;g lVe r t ex3 f (−1 , 0 , 1 ) ;

//Vertex2g lCo l o r 3 f (255 , 0 , 0 ) ;g lVe r t ex3 f (0 , 1 , 1 ) ;

//Vertex3g lCo l o r 3 f (0 , 0 , 255 ) ;g lVe r t ex3 f (1 , 0 , 1 ) ;

glEnd ( ) ;

Elke vertex heeft een andere kleur, het uiteindeljke resultaat na doorlopen van de OpenGL-pipeline ziet er uit als in figuur 5.3 (p.58), waarbij de kleurwaarden geınterpoleerd werdenover 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 kleurwaardenvan de fragmenten kunnen hier gecombineerd worden met een textuur. Ook fog wordt in deze

Page 61: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

stage toegepast op een fragment.Als eindresultaat krijgen we voor elk fragment een kleurwaarde (Color value) en een diepte-waarde (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 waar-voor 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 meerinvloed kunnen hebben op hoe vertices en fragmenten zullen gegenereerd worden. Momenteelbestaan twee soorten shaders:

Page 62: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 ver-

vangen 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

g l P o s i t i o n = 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 maarop 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 textuurcoordinaten per-pixel

• Texturen toepassen

• Fog berekenen

• Berekenen normalen per fragment voor per-pixel lighting.

In een fragment-shader moet steeds het codewoord

g l 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. Wan-neer je enkel een Vertex-shader gebruikt, zal de OpenGL fixed-function pipeline de taken vande fragment-shader gewoon overnemen. Onthoud wel dat het niet mogelijk is bijvoorbeeldkleur te berekenen in een vertex-shader maar per-vertex lighting te laten uitvoeren door defixed-function pipeline, bij het gebruik van een shader word je geacht alle instructies, die deshader vervangt, zelf uit te voeren.

Twee uitstekende IDE’s voor de ontwikkeling van GLSL-shaders zijn:

Page 63: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

• Shader designer, Typhoon Labs - http://www.typhoonlabs.com/

• Rendermonkey, ATI - http://www.ati.com/developer/rendermonkey/

5.5.2 Per-pixel lighting

Zoals hierboven beschreven gebeurt in de fixed OpenGL-renderpipeline de belichting op eenper-vertex manier. In het geval van flat-shading krijgt de polygoon een egale kleur naargelangzijn orientatie, in het geval van smooth-shading wordt de kleurwaarde geınterpoleerd over depolygoon.Omdat per-vertex lighting staat of valt bij de tessalation van je belichte object (uit hoeveelpolygonen die bestaat) en het niet toelaat detail te belichten op een model zonder belachelijkhoge 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 detessalation en laat toe detail te simuleren door gebruik te maken van BumpMapping, Nor-malMapping of Parallax Mapping.

We bekijken een eenvoudige GLSL shader die een model zal renderen zonder textuur enper-pixel diffuse lighting:

De diffuse component van invallend licht wordt voorgesteld als in figuur 5.5 (p.61) en stellenwe wiskundig voor als:

Diffuse = Lc ∗ Mc ∗ cos(θ)

waarbij Lc de kleur van het licht is, Mc de kleur van het oppervlak waar het licht op invalten θ the hoek tussen L (richtingsvector van het licht) en N (de normaal van het oppervlak).

Figuur 5.5: Diffuse lighting

DiffuseLighting.vert

Page 64: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

//Varying v a r i a b l e s worden gedee ld tussen// vertex− en fragment−shaders .vary ing vec3 normal ;vary ing vec3 l i g h tD i r ;

void main ( ){

// Transformeer de normaal van de binnenkomende ver tex// naar Eye−space .normal = normal ize ( gl NormalMatrix ∗ gl Normal ) ;

// Be l i c h t i ng in OpenGL gebeurt in Eye−space , t rans fo rmeer//de p o s i t i e van de binnenkomende ver tex naar Eye−space ,// door te vermenigvuld igen met de ModelView−matrix ,// zodat de v e r g e l i j k i n g wiskundig gez i en z in h e e f t .vec4 worldPos = gl ModelViewMatrix ∗ g l Ver t ex ;

//Bereken de r i c h t i n g s v e c t o r van het l i c h t naar de binnen−//komende ver tex . Vergeet n i e t te normal i s e ren !// l i g h tD i r i s een ” ‘ vary ing ” ’ v a r i a b e l e en kan ge l e z en worden//door de fragment−shader voor verdere a fhande l ing .l i g h tD i r = normal ize ( vec3 ( g l L i gh tSourc e [ 0 ] . p o s i t i o n − worldPos ) ) ;

// Transformeer de ver tex naar Screen−space .g l P o s i t i o n = f t rans fo rm ( ) ;

}

DiffuseLighting.frag

varying vec3 normal ;vary ing vec3 l i g h tD i r ;

void main ( ){

//We berekenen het dot−product tussen de normaal en// l i gh tD i r , d i t l e v e r t ons de co s inus van de hoek// tussen de twee vectoren . P r e c i e s wat we nodig//hebben voor de bereken ing van de d i f f u s e b e l i c h t i n g .f l o a t cosAngle = max( dot ( normal , l i g h tD i r ) , 0 . 0 ) ;

// Hier berekenen we de u i t e i n d e l i j k e k l eu r van het ob j e c t//We gaan er h i e r vanuit dat de k l eu r van het fragment waar// het l i c h t op i n v a l t wit i s .vec4 d i f f u s eL i g h t = cosAngle ∗ //Hoek

g l L i gh tSourc e [ 0 ] . d i f f u s e ∗ // L i ch tk l eu rvec4 ( 1 . 0 , 1 . 0 , 1 . 0 , 1 . 0 ) ; // Mater iaa l k l eu r

//Geef het binnenkomende fragment de gewenste k l eu r .g l FragColor = d i f f u s eL i g h t ;

}

Page 65: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

5.5.3 Per-pixel lighting met specular component

Wanneer licht invalt op glanzende oppervlakken zal ze zich complexer gedragen en uit eenextra component bestaan naast diffuse lighting, nl Specular Lighting. Dit effect is afhankelijkvan zowel de richtingsvector van het invallend licht en de kijkrichting van de camera; hetmodel voor de belichting moet hievoor aangepast worden. Er bestaan twee modellen voorSpecular lighting: Phong en Blinn, dat een benaderde, maar snellere versie is van het Phongmodel.

Phong

Het Phong model kan grafisch voorgesteld worden zoals in figuur 5.6 (p.63). Hier is R de vectordie verkregen wordt door de richtingsvector van het licht te reflecteren over het oppervlakwaar 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 ∗ Lc ∗ Mc

waarin Lc Mc opnieuw respectievelijk de licht- en materiaalkleur zijn, s is een factor diebepaalt 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 hetniet meer nodig de reflecterende licht-vector te berekenen maar we benaderen die door eenspeciale 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 stukeenvoudiger (en dus sneller) te berekenen dan R:

H = L + E

Page 66: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 5.7: Blinn specular lighting

De Blinn specular-component wordt nu gegeven door:

Specular = (N · H)s ∗ Lc ∗ Mc

Dit is het model dat we zullen gebruiken in onderstaande shader, wat een eenvoudige uitbrei-ding is van de DiffuseLighting shader, hierboven:

SpecularLighting.vert

varying vec3 normal ;vary ing vec3 l i g h tD i r ;vary ing vec3 ha l fVec to r ;

void main ( ){

//Bereken de normaalnormal = normal ize ( gl NormalMatrix ∗ gl Normal ) ;

//Bereken ver tex p o s i t i e in view−space .// ! ! !ONTHOUD! ! ! Be l i c h t i ng in OpenGL gebeurt in view−spacevec4 pos = gl ModelViewMatrix ∗ g l Ver t ex ;//Bereken l i c h t −r i c h t i n g naar de ver texl i g h tD i r = normal ize ( vec3 ( g l L i gh tSourc e [ 0 ] . p o s i t i o n − pos ) ) ;

//Bereken de ha l f−vec to r . GLSL berekent d i e r eeds z e l f !ha l fVec to r = normal ize ( g l L i gh tSourc e [ 0 ] . ha l fVec to r . xyz ) ;

// Transformeer de ver tex naar screen−space .g l P o s i t i o n = f t rans fo rm ( ) ;

}

SpecularLighting.frag

varying vec3 normal ;vary ing vec3 l i g h tD i r ;vary ing vec3 ha l fVec to r ;

Page 67: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

void main ( ){

// D i f f u s e component van het l i c h t .vec4 d i f f u s eL i g h t ;// Specu lar component van het l i c h t .vec4 specu l a rL igh t ;//Een constante d i e de hoevee lhe id spe cu l a r// l i c h t bepaa l t .f l o a t sh ine = 64 . 0 ;

vec3 n = normal ize ( normal ) ;

f l o a t co sang l e = max( dot (n , normal ize ( l i g h tD i r ) ) , 0 . 0 ) ;

//Bereken d i f f u s ed i f f u s eL i g h t = cosang l e ∗ g l L i gh tSourc e [ 0 ] . d i f f u s e ;

//Bereken specu l a rspecu l a rL igh t = pow(max( dot (n , normal ize ( ha l fVec to r ) ) , 0 . 0 ) , sh ine ) ∗

g l L i gh tSourc e [ 0 ] . s p e cu l a r ;

// . . . en combineer de twee waarden voor onze u i t e i n d e l i j k e// b e l i c h t i n g .g l FragColor = d i f f u s eL i g h t + specu la rL igh t ;

}

Met deze eenvoudige shaders die toelaten een model te renderen met per-pixel lightingkunnen we al iets doen. Vergeet echter niet dat een shader het stuk in de OpenGL-pipelinevervangt die onder andere zorgt voor texture-mapping; een model dat met deze shader zalgerenderd worden zal dus geen textuur hebben. We moeten hiervoor zelf support toevoegenin de shader. Dit is eenvoudig, als voorbeeld zal ik de SpecularLighting-shader uitbreidenvoor textuur-ondersteuning:

TexturedSpecularLighting.vert

varying vec3 normal ;vary ing vec3 l i g h tD i r ;vary ing vec3 ha l fVec to r ;

void main ( ){

////We wensen de textuur−coord inaten te gebruiken vanop l ay e r 0 !//gl TexCoord [ 0 ] = gl MultiTexCoord0 ;

normal = normal ize ( gl NormalMatrix ∗ gl Normal ) ;

vec4 pos = gl ModelViewMatrix ∗ g l Ver t ex ;l i g h tD i r = normal ize ( vec3 ( g l L i gh tSourc e [ 0 ] . p o s i t i o n − pos ) ) ;

ha l fVec to r = normal ize ( g l L i gh tSourc e [ 0 ] . ha l fVec to r . xyz ) ;

Page 68: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

g l P o s i t i o n = f t rans fo rm ( ) ;}

TexturedSpecularLighting.frag

varying vec3 normal ;vary ing vec3 l i g h tD i r ;vary ing vec3 ha l fVec to r ;

////Een ve rw i j z i ng naar de gebru ik t e textuur//uniform sampler2D tex ;

void main ( ){

vec4 d i f f u s eL i g h t ;vec4 specu l a rL igh t ;f l o a t sh ine = 64 . 0 ;

vec3 n = normal ize ( normal ) ;

f l o a t co sang l e = max( dot (n , normal ize ( l i g h tD i r ) ) , 0 . 0 ) ;

d i f f u s eL i g h t = cosang l e ∗ g l L i gh tSourc e [ 0 ] . d i f f u s e ;

specu l a rL igh t = pow(max( dot (n , normal ize ( ha l fVec to r ) ) , 0 . 0 ) , sh ine ) ∗g l L i gh tSourc e [ 0 ] . s p e cu l a r ;

// Indexeer de textuur met de gegeven textuur−// coord inaten en s t e ek de kleurwaarde in ” t e x e l ” .vec4 t e x e l = texture2D ( tex , gl TexCoord [ 0 ] . s t ) ;

//Combineer de berekende l i chtwaarde met de textuur−// k l eu r .g l FragColor = ( d i f f u s eL i g h t + specu la rL igh t ) ∗ t e x e l ;

}

5.5.4 Cel-shading

Cel-shading is een techniek om wat je rendert een wat cartoony uitstraling te geven. De tech-niek is erg eenvoudig, we zullen fragmenten die belicht worden in een bepaalde hoek dezelfdekleurwaarde geven:

CelShading.vert

varying vec3 Normal ;

void main ( ){

//Bereken de normaal

Page 69: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Normal = normal ize ( gl NormalMatrix ∗ gl Normal ) ;

// Transformeer de ver texg l P o s i t i o n = gl ModelViewProject ionMatr ix ∗ g l Ver t ex ;

}

CelShading.frag

varying vec3 Normal ;

void main ( ){

f l o a t co sang l e ;vec4 c o l o r ;

// Bereken co s inus van de hoek tussen de normaal en// l i c h t −r i c h t i n g .co sang l e = dot ( vec3 ( g l L i gh tSourc e [ 0 ] . p o s i t i o n ) , Normal ) ;

//Geef v i e r moge l i j ke k l euren naarge lang de hoek dat het l i c h t// i n v a l t .i f ( co sang l e > 0 . 95 )

c o l o r = vec4 ( 0 . 9 , 0 . 9 , 0 . 9 , 1 . 0 ) ;e l s e i f ( co sang l e > 0 . 5 )

c o l o r = vec4 ( 0 . 7 , 0 . 7 , 0 . 7 , 1 . 0 ) ;e l s e i f ( co sang l e > 0 . 25 )

c o l o r = vec4 ( 0 . 4 , 0 . 4 , 0 . 4 , 1 . 0 ) ;e l s e

c o l o r = 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 k l eu r .g l FragColor = co l o r ;

}

Deze shader zal opnieuw geen textuur renderen, ondersteuning hiervoor toevoegen is een-voudig en volledig gelijklopend met de TexturedSpecularLighting-shader.

5.5.5 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 zalopslaan. Gebruikmakende van die textuur zullen we, op een per-pixel basis, het licht latenweerkaatsen op denkbeeldige onneffenheden in het model zodat de indruk opgewekt wordtdat er veel detail aanwezig is.Om dit te kunnen doen, moeten we voor elke texel van het model de normaal kennen, dieinformatie 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 zoalsin figuur 5.9 (p.68). De kleur is typisch voor een Normal-map, de x-, y- en z-coordinaten vande normalen worden respectievelijk opgeslaan in de textuur als rood, groen, blauw. In onzepixel-shader zullen we voor elk binnenkomend fragment de bijhorende normal lezen en aan de

Page 70: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 tothet interval [0,1], terwijl de coordinaten van een normaal in het interval [-1,1] liggen. Wezullen dus de coordinaten 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 Nor-mal Mapping (dit geldt trouwens voor alle texture-based per-pixel render-technieken, zoalsParallax- en Relief-mapping).De normalen in een normal-map liggen opgeslaan in 1 vaste richting. Stel nu eens dat we dienormal-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 nietaangezien de waarden van de normalen vastgelegd zijn in de normal-map. We moeten ditopvangen, anders zou onze normalmap maar werken in een bepaalde richting.De oplossing hiervoor is de coordinaten van ons vlak te transformeren naar dezelfde ruimte

Page 71: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

waarin de normal-map definieerd staat: Tangent-space. In de introductie van deze tekst hebik reeds kort het concept Tangent-space aangehaald maar het lijkt me nuttig hier nog eensop terug te komen:Voor elke vertex ons vlak is een aparte ruimte gedefinieerd, de Tangent space, die bepaaldewordt door drie vectoren: Tangent-, Normal- en BiTangent-vectoren. Als we die drie vectorenin een matrix plaatsen, hebben we een TBN-Matrix (Tangent BiTangent Normal):

TBN =

Tx Bx Nx 0Ty By Ny 0Tz Bz Nz 00 0 0 1

als je een punt in Tangent-space hiermee vermenigvuldigt, zal je het transformeren naarObject-space.

Wij wensen echter het omgekeerde te doen (onze vertex transformeren van Object-spacenaar 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 lichtbronstaat nog steeds gedefinieerd in World-space. We moeten voor de positie van de lichtbroneveneens dezelfde transformatie doorvoeren.Merk op dat we dus voor elke vertex de TBN-matrix moeten kennen, gelukkig kunnen we ditberekenen in een pre-proces fase en opslaan in ons 3d-formaat. Die berekende TBN-matriceskunnen we hergebruiken zolang we het model enkel translateren in onze 3D-scene , bij eenrotatie moeten de TBN-matrices opnieuw berekend worden!Merk op dat hier een optimalisatie mogelijk is: het is eigenlijk niet nodig de TBN-matrix teberekenen voor elke vertex, maar het volstaat dit te doen voor elke polygoon (de vertices vandie 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 her-berekend 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 hetnaar 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 ;vary ing vec3 d i r e c t i o n ;

uniform vec4 l i gh tPos ;

Page 72: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

//Onze tangent en b i tangent vectorenuniform vec3 tangent ;uniform vec3 b i tangent ;

void main ( ){

// Transformeer ver tex naar c l i p−space .g l P o s i t i o n = gl ModelViewProject ionMatr ix ∗ g l Ver t ex ;

// gebru ik t ex tu r ing met textuurcoord inaten op l ay e r 0 .texCoord = gl MultiTexCoord0 . xy ;

// Bereken l i c h t −r i c h t i n gvec3 l i g h tD i r = vec3 ( l i gh tPos − g l Ver t ex ) ;

// Creeer de TBN−matrixmat3 tbnMatrix = mat3 ( tangent , b itangent , gl Normal ) ;

// Converteer l i c h t naar Tangent−space .d i r e c t i o n = tbnMatrix ∗ l i g h tD i r ;

}

NormalMapping.frag

varying vec2 texCoord ;vary ing vec3 d i r e c t i o n ;

//Onze bas i s−textuuruniform sampler2D textureMap ;//Onze normal−mapuniform sampler2D normalMap ;

void main ( ){

//Lees onze bas i s−textuurvec4 texCol = texture2D ( textureMap , texCoord ) ;

// Lees normaal u i tvec3 normal = texture2D (normalMap , texCoord ) . xyz ;

//Hermap onze normaal naar i n t e r v a l [−1 , 1 ]normal = ( normal − 0 . 5 ) ∗ 2 . 0 ;

// Normal i seer de l i c h t r i c h t i n gvec3 l ightD = normal ize ( d i r e c t i o n ) ;

//Bereken d i f f u s e −component van het l i c h t v ia de// u i t g e l e z e n normaal .vec3 d i f f u s e = vec3 ( dot ( normal , l ightD ) ) ;

//Bereken textuur−k l eu r met de l i c h t −waarde .g l FragColor = texCol ∗ vec4 ( d i f f u s e , 1 . 0 ) ;

}

Page 73: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

5.6 MeshLoader

Uiteraard is een 3D-renderer weinig interessant als het niet mogelijk is bestaande 3D-modellen in een bepaald formaat te importeren in je applicatie. De klasse MeshLoader werdgeschreven om die functionaliteit toe te voegen aan Libera . Ik heb geopteerd dit procesvolledig te abstraheren zodat het in de toekomst zonder problemen mogelijk is een nieuweLoader te schrijven voor een bepaald formaat.Momenteel is het enkel mogelijk modellen te importeren die opgeslaan werden in het Wave-front .obj formaat maar door een nieuwe implementate van de abstracte klasse MeshLoaderte maken is het mogelijk bijvoorbeeld een .3ds importer te schrijven die volledig compatibelis met het intern formaat van de RenderEngine, meer informatie hierover verder op.

5.6.1 (Sub)Mesh

Intern wordt een 3D-object opgeslaan als een Mesh die een verzameling van SubMeshes kanzijn (dit hangt uiteraard af van hoe je 3D-model is opgeslaan). Een SubMesh houdt devertices, indices, textuur-coordinaten en normalen bij in Buffers, bij het schrijven van eenMeshLoader zal je dus een manier moeten vinden je formaat te vertalen naar het formaatwaarin een SubMesh wordt opgeslaan.

5.6.2 OBJLoader

Momenteel is enkel een MeshLoader geımplementeerd voor modellen in .obj formaat. Obj-filesslaan de data gewoon op in tekst-formaat i.p.v. byte-formaat wat zorgt dat dit een intuıtiefformaat is dat erg eenvoudig te parsen is. Het laat bovendien toe zowel vertices, indices,normalen en textuurcoordinaten van modellen op te slaan wat het een uitgelezen formaatmaakt voor eenvoudige statische modellen.

5.6.3 Een nieuwe MeshLoader schrijven

Zoals vermeld is het perfect mogelijk een nieuwe MeshLoader te schrijven (zoals een Loadervoor het 3D Studio Max formaat .3ds) die volledig compatibel blijft met de RenderEngine,het volstaat hiervoor een nieuwe implementatie te maken van de methode

pub l i c ab s t r a c t Mesh load ( St r ing path ) ;

van de abstracte klasse MeshLoader, als volgt:

pub l i c c l a s s MyLoader extends MeshLoader{

Mesh mesh ;

pub l i c MyLoader ( ){

super ( ) ;

}

pub l i c Mesh load ( St r ing path ){

mesh = new Mesh ( ) ;

Page 74: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

// S c h r i j f h i e r j e code d i e het gewenste formaat z a l parsen//en ops laan in de Mesh , mesh .

re turn mesh ;}

}

De loader gebruiken in je applicatie is bijzonder eenvoudig:

MovableObject model ;MeshLoader l oade r ;

l oade r = new MyLoader ( ) ;model = new Entity (

l oade r . load ( ”models /aModel . my3DFormat” ) ,aMater ia l ) ;

//De gemaakte en t i t y kan nu doodgewoon aan een SceneNode gehangen//worden en gerenderd worden in j e 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 iserg 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, ineen gelijkmatig verdeeld rooster, zie figuur 5.10 (p.73). Voor elk karakter zullen we nu eenOpenGL displaylist genereren (in ons geval 256, voor elke letter een ), waarin in elk van heneen quad zal zitten waarop we een karakter texture-mappen. Dit alles wordt opgeslaan in eenarray die we, wanneer we tekst wensen te renderen, indexeren aan de hand van de ASCII-waarde van het karakter. We renderen de gewenste Displaylists in OpenGL Ortho-mode zodattekst 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” (http://www.lmnopc.com/bitmapfontbuilder/) en wordt volledig ondersteunddoor de RenderEngine.

5.7.1 Implementatie in de engine

In de RenderEngine wordt dit alles geımplementeerd door de klasse FontManager. Om tekstte gebruiken in je applicatie, ga je als volgt te werk:

//Laad j e font in en c r e e e r de d i s p l a yL i s t s .FontManager . bui ldFont ( ”data/ font tahoma . png” , 7 ) ;

/∗Render t ek s t ∗///Zorg dat j e in Ortho−mode werkt .eng ine . setOrthoOn ( ) ;// S c h r i j f j e t e k s t

Page 75: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 5.10: Bitmapped Font

FontManager . g lP r i n t (0 , // p o s i t i e in X−r i c h t i n g20 , // p o s i t i e 20 in Y−r i c h t i n g1 , // s cha l i n g 10 , // In een bitmap−f ont kan j e twee l e t t e r t y p e s

// opslaan , wi j gebruiken de e e r s t e .”Dit i s een t ek s t ” , //De t ek s t

1 . 0 f , 0 . 0 f , 0 . 0 f ) ; // k l eu r ( rood in d i t geva l ) .// Vergeet n i e t Orthomode terug a f te z e t t en !eng ine . setOrthoOff ( ) ;

Indien je tekst in world-space wilt renderen, zodat ze echt op een bepaalde plaats in jescene kan geplaatst worden, kan je als volgt tewerk gaan:

FontManager . glText (0 , //X−coord inaat0 , //Y−coord inaat0 , //Z−coord inaat0 , // In een bitmap−f ont kan j e twee l e t t e r t y p e s

// opslaan , wi j gebruiken de e e r s t e .1 , // s cha l i n g”Dit i s een t ek s t ” , //De t ek s t

5.8 Logmanager

In de RenderEngine is de mogelijkheid voorzien om gedetailleerde logs uit te schrijvenvan gebeurtenissen in je applicatie. Zaken zoals initialisatie van componenten, resourcesdie niet gevonden worden en errors worden daarin weggeschreven in HTML-formaat via eenduidelijke 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 hetbestand log.html) maar als gebruiker sta je vrij hier extra berichten aan toe te voegen. Ietsschrijven naar de log, is eenvoudig en gebeurt als volgt:

Page 76: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Logger . g e tS ing l e t on ( ) . wr i t e ( ” [ABSTRACTAPP] There i s no root Sector , can ’ ts t a r t r ender ing . P lease use the \” setRoot( SectorMananger mananger ) ;\” method duringScene−setup ” , //De t ek s tLogger .ERROR) ; // Soort van be r i ch t

// ( Dit bepaa l t de k l eu r in l og )

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 voorde 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.

Page 77: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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:

St r ing value = Conf igurat ion . get ( ”MyGroup” , ”MyValue” ) ;

Page 78: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Deel III

Uitbreidingen

76

Page 79: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

In wat volgt, zal ik de extenties beschrijven aan de Portal-engine. Deze features behorenniet tot de core van het thesis-onderwerp maar het zijn zaken die ik nog heb kunnen imple-menteren gedurende de periode ik aan de engine gewerkt heb. Ze voegen veel toe aan deengine en het was een goede test in hoeverre de architectuur toeliet die uitbreidingen toe tevoegen. Het bleek erg eenvoudig de bestaande architectuur van de engine uit de breiden metdie functionaliteiten, wat erg bemoedigend is voor eventuele verdere uitbreiding aan de enginein 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

Page 80: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 groepelementen op een zodanige manier onderling te laten bewegen t.o.v. elkaar zodat het gewensteeffect overtuigend overkomt tot de gebruiker.

De basiselementen in een particle engine zijn de particles die een punt in de ruimte beschri-jven met een bepaalde plaats, snelheid en/of versnelling. Naargelang het effect we willenbereiken, wensen we die parameters aan te passen tot we een goed resultaat bekomen.Omdat elke particle apart beheerd en geupdatet zal worden, moeten we verstandig omspringenmet het aantal particles: als we frame achter frame particles toevoegen aan de scene gaat diteen 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 berekenenwe een nieuw pad. Zo verkijgen we een simpele animatie met een vooraf bepaald aantal par-ticles waarin we meer invloed hebben op de performantie van het systeem. We wensen dusextra 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 parti-cles afgebeeld staan met hun parameters: de vector p is de huidige positie, de vector v stelt dehuidige snelheid voor en de vector a beschrijft de versnelling van de particle (in deze situatiezou a de zwaartekracht kunnen zijn die de particles naar beneden zal trekken). Het komt ernu simpelweg op neer om via elementair vector-rekenen de parameters naar believen aan tepassen en de particles te renderen in je scene. Meer is er niet aan een particle engine maarhet 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 efficient mogelijk?Zoals vermeld, zijn de elementen die we renderen d.m.v. een particle engine meestal simpelequads, met een, eventueel geanimeerde, textuur.Ik bespreek eerst de meest inuıtieve manier die de particles zal renderen d.m.v. billboardsen een manier die gebruik maakt van Point-sprites. Dit is een OpenGL-extentie, niet allevideokaarten ondersteunen dit, maar het zal zorgen voor een mooie snelheidswinst.

78

Page 81: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 6.1: Particles

6.2 Billboarded 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 camerain de scene. Billboarding is een techniek die bijzonder vaak gebruik wordt en bestaat inverschillende 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 maardit gebeurt rond een vast gekozen as. Meestal wordt als as de Y-as van het coordinatensysteemgenomen zodat de billboard steeds recht blijft staan maar zal meedraaien indien de cam-era zich er rond beweegt.

In dit artikel zal ik een variatie gebruiken op Spherical billboarding, die zal zorgen datde billboards steeds in de richting zullen staan van de kijkrichting i.p.v. te wijzen naar decamera-positie. Het resultaat is niet volledig hetzelfde als het echte Spherical billboardingmaar het gaat een stuk sneller om de orientatie aan te passen terwijl het resultaat meer danovertuigend genoeg is voor een particle engine.

Om te kunnen uitleggen hoe je de correcte orientatie kan vinden moet ik even teruggaannaar de inleiding van deze tekst, meer bepaald het stuk over de OpenGL ModelView-matrix.We weten dat de ModelView-matrix de positie en orientatie van zowel de camera als de ob-jecten in de scene zal bepalen, als we even de ModelView-matrix voor ogen nemen, zie figuur6.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 relatieft.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 negatieveZ-as). De groene submatrix bepaalt de rotatie en schaling van dat lokaal assenstelsel. Wanneerwe de groene submatrix vervangen door een identiteitsmatrix zullen we dus elke eventuelerotatie ongedaan maken en zullen de elementen steeds georienteerd staan langs de Z-as, precies

Page 82: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

(a) Spherical billboarding, zijaanzicht (b) Cylindrical billboarding, zijaanzicht

(c) Spherical billboarding, bove-naanzicht

(d) Cylindrical billboarding,bovenaanzicht

(e) Fake Spherical billboarding

Figuur 6.2: Billboarding

Figuur 6.3: De OpenGL Modelview matrix

Page 83: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

wat we wensen te bereiken voor onze billboards! Aan de positie wordt niets gewijzigd; dieinformatie 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 matrixzal ook de eventuele schaling verloren gaan. We moeten dus na het vervangen van de groenesub-matrix de eventuele schaling (sx, sy, sz) herstellen.Vooraleer een billboard te renderen, volstaat het dus de huidige ModelView matrix aan tepassen 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 metde 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. Hetzou mooi zijn indien we een particle zouden kunnen bepalen door 1 positie met een vasteorientatie. Dit is precies waar Pointsprites voor dienen.Point-sprites zijn gedefinieerd in een OpenGL-extentie en laten toe een Quad met een tex-tuur te definieren d.m.v. 1 vertex met die beperking dat ze steeds zal georienteerd staannaar de camera. Die betekent niet alleen dat we niet meer zelf de Modelview-matrix moetenaanpassen maar dat we dus 4 keer minder vertices door de pipeline moeten sturen voor onzeParticle-renderer!Nadeel is dat oudere videokaarten deze extentie niet ondersteunen zodat je voor die mensenzal moeten kunnen terugvallen op Billboarded-particles.

Point-sprites zijn bijzonder eenvoudig om mee te werken. De eigenschappen (zoals de grootteen 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 ) ;

Page 84: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

En we renderen onze Point Sprites:

//Bind een textuurglBindTexture (GL TEXTURE2D, myID ) ;//Render d .m. v . p o i n t s p r i t e sg lEnable ( GL POINT SPRITE ARB ) ;g lBeg in ( GL POINTS ) ;{

f o r ( i n t i = 0 ; i < MAX PARTICLES; i++ ){

g lVer t ex3 f ( p a r t i c l e s [ i ] . p o s i t i o n . x ,p a r t i c l e s [ i ] . p o s i t i o n . y ,p a r t i c l e s [ i ] . p o s i t i o n . z ) ;

}}glEnd ( ) ;g lD i s ab l e ( 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 ondersteunenen om de nadelen van PointSprites te kunnen vermijden (bijvoorbeeld indien je echt groteQuads wenst te gebruiken) wordt een abstracte particles renderer AbstractParticleSystemgedefinieerd, 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 moetenovererven en de abstracte methode

update ( )

herimplementeren.

Als voorbeeld bespreek ik de implementatie van een imaginair Billboarded particle-system:

Page 85: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

pub l i c c l a s s MyParticleSystem extends Abst rac tB i l l boardPar t i c l eSys t em{

//De cons t ruc to r met de B i l l boa rd d i e we wensen te gebruiken//om de p a r t i c l e s te renderen .pub l i c MyParticleSystem ( B i l l boa rd bb){

super (bb ) ;

MAX PARTICLES = 20 ;p a r t i c l e s = new Pa r t i c l e [MAX PARTICLES ] ;// I n i t p a r t i c l e sf o r ( i n t i = 0 ; i < MAX PARTICLES; i++ ){

Vector3 randomVector = Vector3 . mult ip lyBySca lar (new Vector3 (0 , 1 , 0 ) ,getRandomMinMax (0 . 005 f , 0 .01 f ) ) ;

p a r t i c l e s [ i ] = new Pa r t i c l e (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 ] . l i f eT ime = 1500 ;p a r t i c l e s [ i ] . age = getRandomMinMax (0 , p a r t i c l e s [ i ] . l i f eT ime ) ;

}}

// Implementatie van de ab s t r a c t e methode update ( )pub l i c void update ( ){

f o r ( i n t i = 0 ; i < MAX PARTICLES; i++ ){

// S c h r i j f h i e r j e code d i e de po s i t i e , sne lhe id , age , . . .//van j e p a r t i c l e s z a l updaten .

}}

//Een p a r t i c l e r endere r i s een MovableObject , we moeten dus//ook de methode getBoundingBox ( ) implementeren .pub l i c BoundingBox getBoundingBox ( ){

re turn new BoundingBox ( ) ;}

In de Engine zijn reeds een hele reeks particle systems geımplementeerd, klaar voor ge-bruik:

• FireParticleSystem

• FountainParticleSystem

Page 86: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

• MagicParticleSystem

• RainParticleSystem

• SmokeParticleSystem

• WaterfallParticleSystem

Een Particle System is een MovableObject en moet dus opnieuw aan de SceneGraphgelinkt worden om te gebruiken in je scene . Als voorbeeld gebruik ik de FireParticleSystemin een eenvoudige testscene:

SectorManager space ;SkyBox skyBox ;//De b i l l b o a r d dat gebru ik t wordt om het vuur te renderenB i l l boa rd f ;//De pa r t i c l e −systemAbstractPart i c l eSystem f i r e ;//De SceneNode voor de Part i c l eSystemSceneNode f i r eNode ;//Een fakke l−ob j e c tEntity ho lder ;//SceneNode voor de f akk e lSceneNode holderNode ;

pub l i c void setup ( ){

skyBox = new SkyBox (5000 ) ;

l oade r = new OBJLoader ( ) ;Mesh m = loade r . load ( ”models /Holder . obj ” ) ;ho lder = new Entity (m, aMater ia l ) ;

//Maak een nieuwe B i l l boa rd van groo t t e 4 , met mate r iaa l ” Flare ”f = new Bi l l boa rd ( ” Flare ” , 4 ) ;//Maak een nieuw pa r t i c l e −systemf i r e = new Fi r ePar t i c l eSys t em ( f ) ;

space = new BSP SectorManager ( ”mySector” ) ;eng ine . posit ionCamera (−220 , 80 , −50, 20 , 20 , 0 , 0 , 1 , 0 ) ;eng ine . setRoot ( space ) ;

//Hang a l l e s aan de ScenegraphholderNode = new SceneNode ( ” ho lder ” , ho lder ) ;holderNode . t r a n s l a t e (new Vector3 (−50 , 80 , −85));f i r eNode = new SceneNode ( ” f i r e ” , f i r e ) ;holderNode . addChildNode ( f i r eNode ) ;space . getRootSceneNode ( ) . addChildNode ( holderNode ) ;

}

pub l i c void update ( ){

//update de pa r t i c l e −systemf i r e . update ( ) ;

Page 87: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

}

pub l i c void render ( ){

eng ine . c l e a r ( ) ;eng ine . posit ionCamera ( ) ;

skyBox . render ( eng ine . camera ) ;eng ine . render ( ) ;

}

Page 88: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoofdstuk 7

Dynamic clothing

Figuur 7.1: Eenkoord, samengestelduit punten verbon-den met veren

Physics zijn niet meer weg te denken uit een moderne en-gine, vooral dynamic clothing is een goede manier om leven teblazen in een 3D-scene. Een cloth-object werd toegevoegd aande engine die een eenvoudige cloth-simulator implementeert. Clothis een MovableObject dus moet in de SceneGraph van de Sec-torManager geplaatst worden vooraleer het gerenderd zal wor-den.

Om te kunnen uitleggen hoe dynamic clothing geımplementeerdwerd, moeten we eerst kijken hoe een koord fysisch gemod-elleerd wordt, vervolgens zullen we de stap maken naar cloth-ing.

7.1 Simulatie van een koord

We kunnen een koord zien als een serie punten die met elkaar ver-bonden zijn met veren. We laten de punten bewegen evenredig met eeneventuele kracht die erop inwerkt (dit kan de zwaartekracht zijn maarook een krachtvector op een bepaald punt om dit te verplaatsen). Voorelk punt van de draad bepalen we de totale kracht die inwerkt op datpunt door simpelweg de afstand tot zijn twee buren te bepalen. Als dieafstand groter wordt dan een bepaalde grenswaarde dan zorgen we datdie afstand behouden wordt door de extentie van de veer te berekenenafhankelijk van de lengte van de veer en de normale lengte van de veer.

In pseudocode kunnen we een erg eenvoudig model voor een koord als volgt beschrijven:

ZwaarteKracht = Vector3 (0 , −9 ,87 , 0 ) ;f o r ( AllePuntenVanDeKoord ){

a f s tand1 = afstandTotEersteBuur ;e x t en t i e 1 = afs tand1 − normaleLengte ;

a f s tand2 = afstandTotTweedeBuur ;

86

Page 89: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

ex t en t i e 2 = afs tand2 − normaleLengte ;

temp = new Vector3 ( ) ;temp . x = ( EersteBuur / a f s tand1 ∗ ex t en t i e 1 ) +

(TweedeBuur / a f s tand2 ∗ ex t en t i e 2 ) +

temp . y = ( EersteBuur / a f s tand1 ∗ ex t en t i e 1 ) +(TweedeBuur / a f s tand2 ∗ ex t en t i e 2 ) +Zwaartekracht ;

temp . z = ( EersteBuur / a f s tand1 ∗ ex t en t i e 1 ) +(TweedeBuur / a f s tand2 ∗ ex t en t i e 2 ) +

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 reagerenop positie veranderingen van individuele punten. Voor een nog realistischer resultaat kan jeeen wind-compontent toevoegen aan het model die een zacht briesje kan simuleren door eenextra-kracht vector te laten inwerken op de punten van het koord.Collision-detection kan indien gewenst ook toegevoegd worden door, vooraleer de positie vaneen 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 een-voudig zetten naar dynamic clothing. Een stuk textiel kunnen we immers zien als een seriedynamische 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 berekenenmaar nu kijken we naar de 4 buren van elk punt (indien die bestaan), als volgt:

Page 90: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

ZwaarteKracht = Vector3 (0 , −9 ,87 , 0 ) ;

f o r ( AllePuntenVanDePatch ){

f o r ( AlleBurenVanHetPunt ){

Veer = Buur − huidigPunt ;Lengte = AfstandTussenBuur ( ) ;s chaa l = ( Lenghte − normaleLengte ) / normaleLengte ;Veer = Veer ∗ (1/ Length ) ;kracht = Veer ∗ s chaa l ;

kracht = kracht + ZwaarteKracht ;

//Doe een c o l l i s i o n −t e s t ind i en gewenst .DoCo l l i s i on ( ) ;

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 zalsoms 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 8buren, 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 dichtsteburen in beschouwing nemen.

Page 91: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Ook hier kunnen we de simulatie verbeteren door een wind-vector in te voeren of colli-sion detection te berekenen voor elk punt van de cloth. Een goed model voor textiel, datreageert op wind, zou deze kunnen zijn (merk op dat we hier de driehoeken, meer bepaaldhun normalen, moeten kennen waaruit het textiel is opgebouwd):

Vector3 Wind = new Vector3 (1 , 0 , 0 ) ;

f o r ( AlleDriehoekenVanDePatch ){

//Bereken de normaal van de hu id ige dr i ehoek ( d i t moet e l k e// frame opnieuw gebeuren ! )Vector3 Normaal = BerekenDeNormaalVanDeDriehoek ( ) ;//Bereken het dot−product tussen de wind−vec to r en de normaal// ( Dit h e e f t a l s r e s u l t a a t de co s inus van de hoek tussen d i e twee// vectoren )f l o a t dot = DotProduct (Normaal , Wind ) ;Vector3 kracht = Normaal ∗ dot ;

//Pas de verkregen krachtvec to r toe op de d r i e punten// d i e to t de dr i ehoek behoren .

}

In de RenderEngine is de mogelijkheid ingebouwd om een collision-test te doen tegen eenbol:

//Onze movement−vec to r verkregen door de 8 buren te eva luerenr = Vector3 .Add( c , movementVector ) ;

/∗Voora leer de update daadwerke l i jk door te voeren zu l l e n we eenc o l l i s i o n t e s t doen .∗/

//Bereken de a f s tand van het punt to t het middelpunt ( pos ) van//de bolf l o a t d i s t ance = Vector3 . Distance ( r , pos ) ;//Als d i e a f s tand > ( rad iu s van de bol + marge ) , geen c o l l i s i o n .i f ( d i s t ance > rad iu s + 5){

//update de vertex−p o s i t i e z o a l s gewoon l i jk .v e r t i c e s . put ( indexc , r . x ) ;v e r t i c e s . put ( indexc+1, r . y ) ;v e r t i c e s . put ( indexc+2, r . z ) ;

}//Er i s een c o l l i s i o n !e l s e{

//Bereken vec to r van middelpunt bol naar de ver tex .d i r e c t i o n = Vector3 . Minus ( r , pos ) ;

// Verp laats de ver tex vo lgens d i e vec to r zodat ze net buiten de

Page 92: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

// bol komt te l i g g en .d i r e c t i o n = Vector3 . mult ip lyBySca lar (

Vector3 . DevideByScalar ( d i r e c t i on , d i s t ance ) , r ad iu s + 5 ) ;r = Vector3 .Add( pos , d i r e c t i o n ) ;

//Update de ver tex met de aangepaste p o s i t i e .v e r t i c e s . put ( indexc , r . x ) ;v e r t i c e s . put ( indexc+1, r . y ) ;v e r t i c e s . 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 naarde de 8 buren. Enkel rechthoekige stukken cloth worden ondersteund en het is mogelijk aan-hechtingspunten en aanhechtingsrechten te bepalen waaraan je het textiel kan ophangen in jescene .

Dynamic clothing toevoegen in je applicaties is eenvoudig en gaat als volgt:

MovableObject c l o th ;SceneNode clothNode ;

// . . .

c l o th = new Cloth (10 , 10 , 10 ,myMaterial ,new Vector3 (0 , 0 , 0 ) ,nu l l ,new Vector3 (0 , −1, 0 ) ,new Vector3 (0 , 0 , 1 ) ) ;

//Hang het t e x t i e l op aan z i j n bovenste r i j punten

Page 93: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

c l o th . setFixRow ( 0 ) ;//Cloth i s een MovableObject , dus hang j e ook gewoon//aan een SceneNode .clothNode = new SceneNode ( ” c l o th ” , c l o th ) ;

getRootSceneNode . addChildNode ( clothNode ) ;

// . . . en j e hebt Dynamic Clothing in j e scene .

Page 94: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoofdstuk 8

Projective texturing

Projective texturing is een techniek waar we een textuur projecteren op een scene zoalseen diaprojector dit zou doen. We gebruiken hiervoor een extra texture-layer en textuur-coordinaten die we verkrijgen door een projectie uit te voeren. Projective texturing kan ge-bruikt worden om spotlichten te simuleren maar wordt ook gebruikt voor Real-time Shadow-mapping.

Figuur 8.1: Projective texturing

Wanneer we een textuur willen projecteren over een scene dienen we een ModelView- enProjection-Matrix te definieren die de positie en view-frustum van de projector zal bepalen.Gebruikmakende van die matrices zullen we de OpenGL ModelView- en Projection-matricesaanpassen zodat de camera komt te staan op de plaats van de projector en zullen we van die

92

Page 95: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

positie een textuur projecteren over het volledige scherm.Probleem is natuurlijk dat we in onze shader de textuurcoordinaten genereren in View-spacevan de camera, terwijl we de projectietextuur moeten indexeren in de clip-space van de pro-jector. We zullen dus een transformatie moeten doorvoeren op die textuurcoordinaten zodatwe 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 matricesdie we gebruiken bij Projective Texturing. Om de textuur correct te indexeren wensen wedus 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 bijpassendetransformaties toe):

TM = Pp × MVp × MV −1c

waarbij:

• Pp de Projection-matrix is van de Projector.

• MVp de ModelView-matrix is van de Projector.

• MVc de ModelView-matrix is van de Camera.

Wanneer we de textuurcoordinaten kennen in Clip-space van de projector dienen we nog1 transformatie door te voeren. De coordinaten staan nu immers in het interval [-1, 1] terwijlwe een textuur indexeren in het [0,1] interval. We gebruiken volgende matrix:

Page 96: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

TRANS =

0.5 0 0 00 0.5 0 00 0 0.5 0

0.5 0.5 0.5 1

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 descene . In de RenderEngine gebruik ik hiervoor volgende GLSL-shader:

ProjectiveTexturing.vert

varying vec4 projCoord ;

void main ( ){

// Genereer textuur coord inatengl TexCoord [ 0 ] = gl MultiTexCoord0 ;gl TexCoord [ 1 ] = gl MultiTexCoord1 ;

// Transformeer ver tex naar View−spacevec4 rea lPos = gl ModelViewMatrix ∗ g l Ver t ex ;

// Genereer t extuurcoord inaten in c l i p−space//van de p r o j e c t o r door te vermenigvuld igen met//de gew i j z i gde TextuurMatrix ( d i e aanpass ing gebeurt// buiten de shader ) .projCoord = gl TextureMatr ix [ 1 ] ∗ r ea lPos ;

// Transformeer ver tex naar Clip−space .g l P o s i t i o n = f t rans fo rm ( ) ;

}

ProjectiveTexturing.frag

uniform sampler2D baseTexture ;uniform sampler2DShadow projTex ;vary ing vec4 projCoord ;

void main ( ){

//Constante d i e bepaa l t hoe transparant de p r o j e c t i e z a l z i j n .const f l o a t transparancy = 0 . 5 ;

// Indexeer een eventue l e bas i s−textuur .vec4 t e x e l = texture2D ( baseTexture , gl TexCoord [ 0 ] . s t ) ;

Page 97: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

// Indexeer de p r o j e c t i e −textuur met de textuurcoord inaten// in c l i p−space van de p r o j e c t o r .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 n i e t buiten de ViewFrustum van de p r o j e c t o r// 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 k l eu r ba s i s t ex tuu r met k l eu r p r o j e c t i e t e x t uu rg l FragColor = t e x e l ∗ vec4 ( rValue . x , rValue . y , rValue . z , 1 . 0 ) ;

}e l s e{

//Als coord inaat buiten de view−frustum l i g t , toon dan enke l de// k l eu r van de ba s i s t ex tuu r .g l FragColor = t e x e l ;

}}

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

Page 98: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoofdstuk 9

Real-time shadow rendering

Het renderen van schaduwen is een vak op zich, er bestaan reeds vele technieken met elkhun specifieke voor- en nadelen. Het is moeilijk de beste techniek te bepalen want dit isonlosmakelijk verbonden met de scene waarvan je shaduwen wilt casten. De twee bekend-ste technieken zijn ongetwijfeld Shadow Mapping en Shadow Volumes, waarvan er opnieuwverschillende variaties bestaan. In deze engine werden Shadow Mapping, Cubic Shadow map-ping 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ımplementeerdwerden 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 dureopdracht is. De scene waarvoor je schaduwen wilt renderen moet veelal tweemaal gerenderdworden (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 camerastaan.

• Update de schaduwen niet bij elke frame.

• ...

9.1 Shadow Mapping

Shadow mapping is een image-based techniek die voor het eerst beschreven werd in hetartikel

”Casting curved shadows on curved surfaces” van Lance Williams en dateert uit 1978.

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

Page 99: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

(a) ShadowMapping voor een Spot-light (b) Soft shadows via een PCF

(c) Cubic ShadowMapping voor een Point-light ineen BSPSector

(d) Shadow Volumes via Z-Pass methode

Figuur 9.1: Verschillende ShadowRenderers in de engine

Page 100: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Shadow Mapping leunt zeer goed aan bij wat we intuıtief aanvoelen over hoe shaduwenonstaan in een reele 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 enkijken welke punten zichtbaar zijn vanop die positie, onzichtbare punten zullen in schaduwliggen.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 depositie van de lichtbron, andere niet.Wanneer we dit voor ogen houden is het erg eenvoudig Shadow Mapping te verstaan, stel jeeen scene voor met 1 lichtbron:

1. Render de scene vanuit de positie van de lichtbron en sla de depth-buffer op in eentextuur, de shadowmap.

2. Projecteer die shadowmap vanaf de positie van het licht over de scene, zie ‘Projectivetexturing’ (p.92).

3. Render de scene vanuit de positie van de camera, zorg dat Depth-writing geactiveerd iswant we zullen de depth-values van die scene gebruiken in de volgende stap.

4. Converteer elk fragment naar het coordinatensysteem van de lichtbron en vergelijk dedepth-value van het fragment met die value die opgeslaan staat in de shadowmap, opdie plaats. Aan de hand van die vergelijking kunnen we bepalen of het fragment al danniet in shaduw ligt.

Een afbeelding, figuur 9.2 (p.99), maakt ongetwijfeld een stuk duidelijker wat er juistgebeurt.

9.1.1 Gebruikte GLSL-shader

ShadowMapping is het eenvoudigst te doen d.m.v. een shader. De gebruikte GLSL-shaderin de RenderEngine, ziet er als volgt uit en lijkt erg goed de GLSL-shader voor ProjectiveTexturing, mits een aantal kleine wijzigingen:

ShadowMapping.vert

varying vec4 projCoord ;

void main ( ){

gl TexCoord [ 0 ] = gl MultiTexCoord0 ;gl TexCoord [ 1 ] = gl MultiTexCoord1 ;

vec4 rea lPos = gl ModelViewMatrix ∗ g l Ver t ex ;projCoord = gl TextureMatr ix [ 1 ] ∗ r ea lPos ;

g l FrontCo lor = g l Co l o r ;

Page 101: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 9.2: Shadow mapping

g l P o s i t i o n = f t rans fo rm ( ) ;}

ShadowMapping.frag

uniform sampler2D baseTexture ;uniform sampler2DShadow shadowMap ;uniform vec4 c o l o r ;

vary ing vec4 projCoord ;

void main ( ){

const f l o a t transparancy = 0 . 5 ;

vec4 t e x e l = texture2D ( baseTexture , gl TexCoord [ 0 ] . s t ) ;

f l o a t rValue = shadow2DProj ( shadowMap , projCoord ) . r +transparancy ;

rValue = clamp ( rValue , 0 . 0 , 1 . 0 ) ;

vec3 coordPos = projCoord . xyz / projCoord .w;

Page 102: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

i f ( coordPos . x >= 0.0 &&coordPos . y >= 0.0 &&coordPos . x <= 1.0 &&coordPos . y <= 0.9 &&projCoord . z >=0.0)

{g l FragColor = t e x e l ∗ rValue ∗ g l Co l o r ;

}e l s e{

g l FragColor = t e x e l ∗ transparancy ∗ g l Co l o r ;}

}

Zoals vermeld, leent deze techniek zich uitstekend tot implementatie op hardware-niveauvooral omdat er in OpenGL een aantal hulpmiddelen beschikbaar zijn om die depth-test toteen goed einde te brengen en er speciaal textuurformaat bestaat om de depth-values van descene 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 image-based techniek (we gebruiken de data die gerenderd wordt in de depth-buffer) en kangebruikt worden voor willekeurige scenes.

• Een 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 projectie-richting staan.

• De scene moet verschillende keren gerenderd worden.

• Kwaliteit van de shaduwen is sterk afhankelijk van de afstand van het licht tot de sceneen de afstand tussen de near- en far-planes van de ViewFrustum van de lichtbron.

9.1.2 Shadow mapping via een FBO (Frame Buffer Object)

Een FBO laat toe rechtstreeks te renderen naar het textuurgeheugen op de videokaart, wateen snelheidswinst oplevert aangezien de textuur niet meer vanuit het RAM-geheugen naarde videokaart moet verplaatst worden. Deze functionaliteit werd toegevoegd als een extentieaan 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 FBOgeen invloed zal hebben op de Color-, depth en stencil-buffer en we dus geen glClear() moetenuitvoeren achteraf (wat opnieuw een snelheidswinst is).Een FBO kan gebruikt worden om te renderen naar zowel een RGB- als Depth-texture.

Page 103: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 = Bu f f e rU t i l s . c r e a t e I n tBu f f e r ( 1 ) ;EXTFramebufferObject . glGenFramebuffersEXT ( id ) ;fbID = id . get ( 0 ) ;

2. Creeer 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 wensente renderen!):

EXTFramebufferObject . glBindFramebufferEXT (EXTFramebufferObject .GL FRAMEBUFFER EXT, fbID ) ;GL11 . g lReadBuf fer (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:

i n t s t a tu s = EXTFramebufferObject . glCheckFramebufferStatusEXT (EXTFramebufferObject .GL FRAMEBUFFER EXT) ;

i f ( s t a tu s == EXTFramebufferObject .GL FRAMEBUFFER COMPLETE EXT)System . out . p r i n t l n ( ”FBO OK” ) ;

e l s eSystem . out . p r i n t l n ( ”FBO ERROR” ) ;

6. Maak frame-buffer klaar:

EXTFramebufferObject . glBindFramebufferEXT (EXTFramebufferObject .GL FRAMEBUFFER EXT, fbID ) ;

GL11 . g lC l ea r (GL11 .GL DEPTH BUFFER BIT) ;

7. Render depth-values in de scene naar de depth-texture in FBO:

render ( ) ;

Page 104: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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.

9.1.3 Spotlights

De manier waarop de schaduwen zullen geprojecteerd worden, hangt af van Projection-matrixvan 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 . g lLoadIdent i ty ( ) ;GLU. g luPe r spe c t i v e ( 60 . 0 f , 1 . 0 f , 50 .0 f , 512 .0 f ) ;

9.1.4 Directional lights

Uiteraard is bovenstaande methode niet goed om zonlicht voor te stellen (de schaduwen diede zon werpt zijn min of meer parallel aan elkaar). Een oplossing hiervoor zou kunnen zijn delichtbron erg ver van de scene te plaatsen, maar dit is niet praktisch aangezien het detail inShadowmap dan veel te klein wordt. Een betere oplossing is i.p.v. een perspectief-matrix tegebruiken een ortho-matrix te gebruiken als projection-matrix. Shaduwen zullen zo parallelgeprojecteerd worden zonder dat de lichtbron onwezenlijk ver van de scene moet staan:

GL11 . glMatrixMode (GL11 .GL PROJECTION MATRIX) ;GL11 . g lLoadIdent i ty ( ) ;GL11 . glOrtho (0 , shadowMapSize / 4 , 0 , shadowMapSize / 4 , 10 , 256 ) ;GL11 . g lT r an s l a t e f ( shadowMapSize / 2 / 4 , shadowMapSize / 2 / 4 , 0 ) ;

9.1.5 Point-lights via Cubic shadowmapping

Op het eerste zicht lijkt dat Shadow mapping niet mogelijk is voor point-lights maar wekunnen de techiek eenvoudig uitbreiden door de scene te renderen vanuit 6 Frustra (langspositieve en negatieve X-, Y- en Z-as) en de shadowmaps opslaan in een CubeMap. Wepassen 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.

9.1.6 Soft-shadows via PCF-filter

Zoals vermeld is Shadowmapping erg gevoelig voor aliasing, we kunnen dit gedeeltelijk opvan-gen door de shadowmap te filteren d.m.v. een PCF (Percentage Closest Filtering), waarbij wevoor elk beschaduwd fragment het gemiddelde berekenen van de 8 omliggende fragmenten.We kunnen de bovenstaande GLSL-shader voor shadowmapping eenvoudig uitbreiden meteen PCF:

Page 105: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 9.3: Shadow mapping voor een punt-licht en een aantal objecten in een scene

ShadowMappingPCF.vert

varying vec4 projCoord ;

void main ( ){

vec4 rea lPos = gl ModelViewMatrix ∗ g l Ver t ex ;

gl TexCoord [ 0 ] = gl MultiTexCoord0 ;gl TexCoord [ 1 ] = gl MultiTexCoord1 ;

projCoord = gl TextureMatr ix [ 1 ] ∗ r ea lPos ;g l FrontCo lor = g l Co l o r ;

g l P o s i t i o n = f t rans fo rm ( ) ;}

ShadowMappingPCF.frag

uniform sampler2D baseTexture ;uniform sampler2DShadow shadowMap ;vary ing 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 / 5 12 . 0 ;

Page 106: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

vec4 t e x e l = texture2D ( baseTexture , gl TexCoord [ 0 ] . s t ) ;

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 )

{g l FragColor = t e x e l ∗ shadowColor ;

}e l s e{

g l FragColor = t e x e l ;}

}

9.2 Shadow Volumes

Daar waar shadowmapping sterk aanleunt tegen hoe we zelf het proces van schaduwvorm-ing ervaren, zou je Shadow-volumes kunnen beschrijven als een wiskundige abstractie van hetproces. Voor een aantal mensen zal dit proces een stuk minder intuıtief zijn maar aangeziende techniek gebruik maakt van de Stencil buffer, en we voor elk model schaduwvolumesgenereren, 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 gebruiktworden (Sillhouete berekening is niet altijd mogelijk en/of te duur).

Page 107: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 danmoeten we voor elk model extra geometrie definieren (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 vande correcte ShadowVolume. De Shadow Volume van een object is de ruimtelijke figuur diemen 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 deschaduw 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 iseen intensieve opdracht voor de CPU, zeker voor objecten met veel polygonen, daarom wordtop het einde van dit hoofdstuk een manier gegeven die de shadowvolumes kan bepalen via eenshader, zodat de werklast verlegd wordt naar je GPU, die voor zulke opdrachten vele malensneller is dan een CPU.

9.2.1 Bepalen silhouette edges

Beschouw een scene zoals voorgesteld in figuur 9.4 (p.106). Zoals vermeld zijn de silhouette-edges die randen die de vorm van de schaduw zullen bepalen. De silhouette van het objectstaat 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 zeereenvoudig 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 staatnaar de lichtbron en een 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 poly-goon en de vector, bepaald van de lokatie van de lichtbron naar de lokatie een vertex van depolygoon, een hoek maakt die kleiner is dan 90 graden.

Page 108: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 9.4: Object en lichtbron in een scene

Figuur 9.5: Silhouette edges van het object

Page 109: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 9.6: Belichte polygoon

Om de silhouette-randen te kunnen bepalen, moeten we dus voor alle polygonen van hetobject bepalen of die gericht staan naar de lichtbron. Indien dat het geval is en een buur-polygoon 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 ( Po lygoonI sBe l i cht ( ) ){

//Voeg de randen ( een paar van v e r t i c e s ) waaruit de polygoon bes taat// toe aan een verzamel ing .VoegAanStackToe ( rand ) ;

// Contro l ee r o f de randen ( o f hun i nv e r s e ) d i e toegevoegd werden reeds//voorkwamen in de verzamel ing , i nd i en dat het geva l i s , v e rw i jde r dan// d i e bewuste rand ( en z i j n dubbel ) van de stack .Contro l ee rStack ( rand ) ;

}

}

Na uitvoering van dit algoritme zullen de randen die voorkomen in de verzameling, silhoutte-randen zijn.

Een beperking van bovenstaand algoritme zou kunnen zijn dat het object gesloten moetzijn: er mag geen polygoon bestaan die geen buur-polygoon heeft voor elke rand. Echter,

Page 110: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

dit vormt meestal geen probleem aangezien het sowieso gewenst is dat een object gesloten isindien 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 prob-leem voor zowel convexe- als concave-voorwerpen, ze moeten enkel gesloten zijn zodat je niet

”in” het model kan kijken.

9.2.2 Bepalen shadow-volumes

Nu we de silhouette randen kennen, rest ons enkel nog de Schaduw volumes te bepalen enzijn we klaar voor het eigenlijke werk: schaduwen renderen.We zullen de schaduw-volume vormen door de silhouette randen, die we verkrijgen, over eenbepaalde 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 tVector3 l i gh tPos ;// Po s i t i e van de e e r s t e ver tex van de randVector3 pos1 ;// Po s i t i e van de tweede ver tex van de randVector3 pos2 ;i n t a f s tand = 100 ;

//Bepaal de vectoren tussen de l i c h tb r on en de v e r t i c e s//van de rand .Vector3 v1 = Vector3 . Minus ( pos1 , l i gh tPos ) ;Vector3 v2 = Vector3 . Minus ( pos2 , l i gh tPos ) ;

Page 111: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

// Verleng d i e gevonden vec to r over de gewenste a f s tandnewPos1 = Vector3 . mult ip lyBySca lar ( v1 , a f s tand ) ;newPos2 = Vector3 . mult ip lyBySca lar ( v2 , a f s tand ) ;

Gebruik nu deze vertices, en die van de oorsprongkelijke silhoutte-randen om de schaduw-volume te renderen.

9.2.3 Renderen van de schaduwen

Nu de theorie duidelijk is, kunnen we de stap zetten naar de praktijk en onze schaduwenrenderen. Zoals vermeld, wordt gebruik gemaakt van de Stencil-buffer om de schaduwen terenderen, die gebruikt wordt om te tellen hoe vaak we een schaduw volume binnendringen enterug verlaten. Dit tellen kan op twee manieren gebeuren en staan gekend als de Z-pass enZ-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 aante passen. Merk op dat we twee render-passes nodig hebben voor het opvullen van de stencilbuffer:

• Render je volledige scene alsof het volledig in de schaduw zou staan (bijvoorbeeld dooralle lichten in je scene uit te schakelen)

• Enable de Stencil buffer, en schakel Color- en Depth-writes uit, zodat niet meer naar decolor- en depth-buffer geschreven wordt (die is immers opgevuld met de data van onzebeschaduwde scene )

• Render de front-faces van de schaduwvolumes en verhoog de waarde van de stencilbuffer voor elk punt dat slaagt voor de depth-test.

• Render de back-faces van alle shaduwvolumes en verlaag de waarde voor elk punt inde 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 waarde stencil buffer 0 is. Deze stap zal alle punten in de color-buffer (die de volledigeschaduw-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 temaken. In de afbeelding zie je een test-scene met zichtbare schaduwvolumes en de uiteindeli-jke 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 depunten die slagen voor de depth test.

Alles goed en wel, we hebben pixel-perfect shadows in onze scene, maar eenmaal we met

Page 112: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 9.8: Stencil buffer voor Shadow volumes

Page 113: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

onze camera een schaduw-volume binnengaan (en dus onze Near-clipping plane een schaduw-volume snijdt) zal de Z-pass methode mislukken en de stencil buffer verkeerd opvullen. Omdatwe een front-face zullen hebben van het Schaduw-volume dat de near-plane snijdt, zonder bi-jhorende 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 schaduwvolumekan binnendringen (bijvoorbeeld als de camera altijd boven de scene blijft hangen) perfectaanvaardbaar, maar voor een willekeurige scene is het duidelijk dat we een oplossing moetenvinden. 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 allelichten in je scene uit te schakelen)

• Enable de Stencil buffer, en schakel Color- en Depth-writes uit, zodat niet meer naarde color- en depth-buffer geschreven wordt.

• Render de back-faces van de schaduwvolumes en verhoog de waarde van de stencilbuffer voor elk punt dat niet slaagt voor de depth-test.

• Render de front-faces van alle schaduwvolumes en verlaag de waarde voor elk puntin 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 destencil buffer 0 is.

Zoals je ziet, vereist dit slechts een kleine aanpassing aan de Z-pass methode maar hetlegt 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 tegenereren zoals hierboven uitgelegd maar de bestaande geometrie van het object te gebruikenom de schaduwvolumes af te sluiten, als volgt:

• Sorteer de polygonen van het object, waarvoor we schaduw-volumes berekenen, in tweegroepen: diegene die gericht staan naar de lichtbron, en diegene die niet gericht staannaar de lichtbron.

• Gebruik de polygonen die gericht staan naar de lichtbron om de voorkant van deschaduwvolumes af te sluiten.

• Verplaats nu de vertices van de polygonen die gericht staan naar het licht weg van delichtbron (zoals je doet voor de vertices van de schaduwvolume) en gebruik de getrans-formeerde polygonen om de schaduw-volume achteraan af te sluiten.

Page 114: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 9.9: Gesloten schaduw-volume van het object

Page 115: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Verderop geef ik een methode om shaders te gebruiken om je gesloten schaduw-volumeste berekenen.

Zoals vermeld, zal de Z-fail methode het Near-plane clipping probleem oplossen maar hetintroduceert meteen een ander probleem: Far-plane clipping, maar die is gelukkig eenvoudigerop te lossen/vermijden. Het probleem met de Far-plane is dat een schaduwvolume deze nietmag 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 opvulten herstel de oude waarde later.

We hebben nu een methode die pixel-perfect schaduwen rendert, waar we geen near-planeclipping problemen meer hebben en er dus geen beperkingen meer zijn met betrekking tot depositie van de camera. De Z-Fail methoide wordt met succes toegepast in de Doom3-enginevan ID-software en vele andere 3D-engines die gebruik maken van Stencil shadows.

9.2.4 Shadow volumes met GLSL

Het zal direct duidelijk zijn dat het genereren van de Schaduw-volumes erg belastend is voorde CPU en het zou mooi zijn indien we die dure opdracht kunnen laten uitvoeren door deGPU, die gespecialiseerd is in zulke vertex-transformaties. Dit lijkt d.m.v. een vertex-shaderperfect mogelijk. Echter een vertex-shader kan enkel bestaande vertices manipuleren, ze kanonmogelijk nieuwe vertices maken. We moeten hier rekening mee houden en die extra ge-ometrie vooraf reeds toevoegen aan het model.

We moeten alle edges van de polygonen van het object waarvoor we schaduw-volumes voorwensen te berekenen, vervangen door een quad, die we dan zullen uitrekken in de vertex-shader. Het toevoegen van die extra geometrie gebeurt niet tijdens run-time maar het isduidelijk dat er veel meer vertices door de pipeline gestuurd zullen worden dan voorheen, ditkan een probleem vormen voor high-polygon models.Beschouw figuur 9.10 (p.113) waarbij we een edge vervangen hebben door een quad (merk opdat de tekening niet echt correct is omdat de Quad eigenlijk een lijn is (overstaande verticesvallen 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 zullenvormen. Merk op dat het met die methode niet meer nodig is expliciet de schaduwvolumesaf te sluiten aangezien dit nu automatisch gebeurt!

Page 116: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoe zorgen we nu dat we de juiste quads zullen uitrekken in de vertex-shader? We zullenhiervoor de normalen van de vertices van de quad gelijkstellen aan de normalen van de poly-gonen waartoe toe de vertices behoren, zie figuur 9.11 (p.114) en die zo doorgeven aan devertex-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 binnenkomendevertex de hoek berekenen tussen de normaal van die vertex en de licht-vector. Als die hoekgroter is dan 90 graden verplaatsen we die bewuste vertex over een bepaalde afstand, weg vanhet licht zoals in figuur 9.12 (p.114). Als de hoek kleiner is dan 90 graden laten we de vertexongemoeid.

Figuur 9.12: Vertices verplaatsen weg van het licht

Het is duidelijk dat enkel die quads zullen uitgerokken worden waarvan de normalen vantwee vertices gericht staan naar het licht en de twee andere niet, en dat is nu net de definitievan een silhouette edge! We hebben dus een methode die probleemloos gesloten schaduw-volumes kan genereren op de GPU.

De GLSL vertex-shader kan er als volgt uitzien:

//De p o s i t i e van de l i c h tb r on in onze sceneuniform vec3 l i gh tPos ;//Hoe ver we de schaduwvolumes wensen u i t te rekkenuniform f l o a t ext rus i onFacto r ;

Page 117: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

void main ( ){

//Bepaal de l i c h t v e c t o rvec3 l i g h tD i r e c t i o n = normal ize ( l i gh tPos − vec3 ( g l Ver t ex ) ) ;

//Bepaal de co s inus van de hoek tussen de normaal en l i c h t// vec to rf l o a t co sang l e = dot ( gl Normal , l i g h tD i r e c t i o n ) ;

// Sla de hu id ige ver tex opvec4 po s i t i o n = g l Ver t ex ;

//Als cos ( hoek ) <= 0.0 −> hoek >= 90i f ( co sang l e <= 0 . 0 ){

// Verp laats de ver tex weg van de l i c h tb r onpo s i t i o n = g l Ver t ex +

( g l Ver t ex − vec4 ( l ightPos , 1 . 0 ) ) ∗ ext rus i onFacto r ;p o s i t i o n .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 .g l P o s i t i o n = gl ModelViewProject ionMatr ix ∗ po s i t i o n ;

}

9.2.5 Soft-shadows door Light-jittering

Een nadeel van het renderen van schaduwen d.m.v. schaduw volumes is dat de schaduwenbijzonder 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 diteffect 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 kerenlichtjes te verplaatsen en de resultaten te blenden. Dit levert mooie soft-shadows maar ismoordend voor de fill-rate van je videokaart, aangezien je de schaduwvolumes moet her-renderen elke keer je de lichtbron verplaatst.

9.2.6 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 moetgebruiken in je applicatie (Z-Pass is analoog):

ShadowRenderer s r ;OBJLoader l oade r ;Light l i g h t ;SceneNode l ightNode ;Entity cube ;SceneNode cubeNode ;

Page 118: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 ) ) ;

l oade r = new OBJLoader ( ) ;cube = new Entity ( l oade r . load ( ”models /Cube . obj ” ) , aMater ia l ) ;

// ! ! !// Be l ang r i j k voor Z− f a i l methode ! Dit vervangt de edges in het//model door Quads zodat we gebru ik kunnen maken van een shader .//Dit i s n i e t nodig voor de ZPass methode .cube . rep laceEdgesWithTr iang les ( ) ;// ! ! !

// p l aa t s het l i c h t in j e scenel ightNode = new SceneNode ( ” l i g h t ” , l i g h t ) ;l ightNode . t r a n s l a t e (new Vector3 (0 , 10 , 0 ) ) ;getRootSceneNode ( ) . addChildNode ( l ightNode ) ;

// p l aa t s een ob j e c tcubeNode = new SceneNode ( ”cube” , cube ) ;cubeNode . t r a n s l a t e (new Vector3 (0 , 230 , −48));getRootSceneNode ( ) . addChildNode ( cubeNode ) ;

// Creer j e ShadowRenderer ,s r = new ShadowVolumeZFailRenderer ( ) ;s r . addLight ( l ightNode ) ;space . setShadowRenderer ( s r ) ;

Page 119: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Hoofdstuk 10

Reflection / Refraction

10.1 Reflection plane

Een onmisbaar effect in een moderne engine is het kunnen renderen van spiegelende op-pervlakken. In het onderdeel over portals vermelde ik reeds dat een portal zich kan gedragenzoals een spiegel, door de SectorManager aan de andere kant, gespiegeld, te renderen. Niette-genstaande 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 deStencil buffer en kan dikwijls niet zomaar gebruikt worden (omdat die bijvoorbeeld reeds ingebruik 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 textuuren die textuur te gebruiken op een vorm die de spiegel zal voorstellen. Deze methode heefthet grote voordeel dat we een off-screen render-target kunnen gebruiken (en dus de color-depth en stencil-buffer ongemoeid laten) en dat we het detail van de gespiegelde scene naarbelieven kunnen aanpassen door de grootte van de render-target aan te passen.

10.1.1 Werkwijze

Voor het renderen van de gereflecterende scene gaan we grotendeels te werk als het renderenvan een Mirror, waarbij we een Reflection-matrix zullen bepalen waarmee we de scene overeen willekeurig vlak kunnen spiegelen. Het enige verschil is dat we nu niet zullen renderennaar het scherm en de inhoud kopieren naar een RGB-textuur.

Zoals reeds besproken in het hoofdstuk over Portals ziet een reflection-matrix voor eenwillekeurig 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)2 0

0 0 0 1

Deze reflection matrix is enkel geldig voor een vlak in de oorsprong, dus we mogen nietvergeten 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

Page 120: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

te reflecteren over het vlak, waarbij we het assenstelsel plaatsen naar de lokatie van het vlak,de reflectie toepassen en het assenstelsel terugplaatsen:

REFL =

1 0 0 −p.x0 1 0 −p.y0 0 1 −p.z0 0 0 1

∗ M ∗

1 0 0 p.x0 1 0 p.y0 0 1 p.z0 0 0 1

Onthoud dat wanneer we de wereld reflecteren de orientatie van de polygonen ook omge-draaid wordt, we moeten dus de culling-methode aanpassen (i.p.v. de back-faces te cullendienen 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 scenemoeten we een kleine maatregel nemen. Stel dat we een quad hebben in onze wereld waaropwe de wereld willen spiegelen, dan mogen we niet zomaar de textuur erop ”plakken” aangeziende reflectie van de wereld onafhankelijk is van de positie van de camera. We dienen de textuurte projecteren op de quad in screen-space, door de textuur-coordinaten voor de reflection-textuur als volgt te bepalen in een GLSL pixel-shader:

vec4 screenSpaceCoord = gl ModelViewProject ionMatr ix ∗ g l Ver t ex ;

vec4 projCoord = screenSpaceCoord / screenSpaceCoord . q ;projCoord = ( projCoord + 1 . 0 ) ∗ 0 . 5 ;projCoord = clamp ( projCoord , 0 . 001 , 0 . 9 9 9 ) ;

De volledige vertex- en pixel-shaders die ik gebruik in de RenderEngine, zien er als volgtuit:

Reflection.vert

varying vec4 viewCoords ;

void main ( ){

//Bereken screen−space coord inatenviewCoords = gl ModelViewProject ionMatr ix ∗ g l Ver t ex ;

// Transformeer ver texg l P o s i t i o n = viewCoords ;

}

Reflection.frag

varying vec4 viewCoords ;uniform sampler2D r e f l e c t i o n ;

Page 121: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

void main ( ){

//Bereken c o r r e c t e textuur−coord inaten zodat// d i e overeenstemmen met texturemapping in// screen−spacevec4 projCoord = viewCoords / viewCoords . q ;projCoord = ( projCoord + 1 . 0 ) ∗ 0 . 5 ;projCoord = clamp ( projCoord , 0 . 001 , 0 . 9 9 9 ) ;

//Gebruik de r e f l e c t i o n −textuur met de berekende// coord inatenvec4 r e f l e c t i o nCo l o r = texture2D ( r e f l e c t i o n , projCoord . xy ) ;//Geef een k l e u r t j evec4 c o l o r = vec4 ( 0 . 6 , 0 . 5 , 0 . 5 , 1 . 0 ) ;

//Bepaal het u i t e i n d e l i j k fragment−k l eu r .g l FragColor = r e f l e c t i o nCo l o r ∗ c o l o r ;

}

Als de videokaart het ondersteunt is het beter te renderen naar een FBO (Frame BufferObject) omdat je dan rechtstreeks rendert naar een stuk geheugen in de videokaart. Bovendienhoeven 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.

10.1.2 Implementatie in de engine

Figuur 10.1: Een ReflectionPlane in een scene

In deze RenderEngine is het opnieuw erg eenvoudig gemaakt een reflecterend vlak toete voegen aan een scene. De klasse ReflectionPlane zal zo een vlak aanmaken, de correctereflection-textuur berekenen en de textuur-coordinaten correct aanpassen. Een reflection-Plane toevoegen aan een scene gebeurt als volgt:

SectorManager space ;SkyBox skyBox ;

Page 122: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

OBJLoader l oade r ;Entity cube ;SceneNode cubeNode ;

Re f l e c t i onP lane plane ;

pub l i c void setup ( ){

l oade r = new OBJLoader ( ) ;cube = new Entity ( l oade r . load ( ”models /CubeNormals . obj ” ) , aMater ia l ) ;

skyBox = new SkyBox (5000 ) ;

p lane = new Re f l e c t i onP lane ( eng ine . camera , //cameranew Vector3 (0 , 0 , 1 ) , //normaal van het vlak

0 , // p o s i t i e512 , // g roo t t e r e f l e c t i o n −textuur”data/Glass1 .bmp” ) ; // op t i on e l e textuur

space = new Galaxy SectorManager ( ”Galaxy” ) ;eng ine . posit ionCamera (−100 , 50 , −100, 20 , 50 , 0 , 0 , 1 , 0 ) ;eng ine . setRoot ( space ) ;

cubeNode = new SceneNode ( ”cube” , cube ) ;cubeNode . t r a n s l a t e (new Vector3 (0 , 0 , 1 0 0 ) ) ;

space . getRootSceneNode ( ) . addChildNode ( cubeNode ) ;

//Add a SectorManager to the plane .plane . s e c t o r = space ;

}

pub l i c void render ( ){

eng ine . c l e a r ( ) ;eng ine . posit ionCamera ( ) ;

// Creeer de r e f l e c t i o n textuur .p lane . c r e a t eRe f l e c t i onTex tu r e ( skyBox ) ;

eng ine . render ( ) ;

//Render het sp i e g e l end oppervlak .plane . render ( skyBox ) ;

}

10.2 Cubic environmental mapping

De bovenstaande methode is enkel bruikbaar voor vlakke spiegelende oppervlakken, wan-neer we ruimtelijke figuren willen gebruiken die de omgeving reflecteren moeten we anders te

Page 123: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

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 scenezullen renderen in 6 richtingen, parallel met de positieve en negatieve assen in het assens-telsel. Hierbij renderen we gewoon de kleurwaarden naar de texturen i.p.v. de depth-values.We definieren dus opnieuw 6 Frustums (1 voor elke richting), zie figuur 10.3 (p.121) renderennaar 6 render-targets en slaan het resultaat in een CubeMap. Hierna verwerken we dit in eenshader en mappen de CubeMap op een willekeurig object.

Figuur 10.3: Zes View-Frustums gedefinieerd rond een object voor environmental cube map-ping

Het is mogelijk de CubeMap af en toe (bijvoorbeeld elke 5 frames) te updaten zodat deomgeving in real-time zal gereflecteerd worden in het object.

De GLSL-shader die gebruikt wordt in deze RenderEngine, ziet er als volgt uit:

Page 124: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

CubeMapping.vert

uniform vec3 cameraPos ;

void main ( ){

gl TexCoord [ 0 ] = gl MultiTexCoord0 ;

vec4 pos = g l Ver t ex ;vec3 normal = gl Normal ;

//Bereken de view−vec to rvec3 view = normal ize ( cameraPos − pos . xyz ) ;// Re f l e c t e e r de v iewvector over de normaal//van de hu id ige ver texvec3 r e f l e x = r e f l e c t ( view , normal ) ;// Wijz ig de textuur−coord inatengl TexCoord [ 1 ] = gl TextureMatr ix [ 0 ] ∗ vec4 ( r e f l e x , 1 . 0 ) ;

// Transformeer de ver tex naar screen−space .g l P o s i t i o n = f t rans fo rm ( ) ;

}

CubeMapping.frag

//Onze CubeMapuniform samplerCube TexCube ;//Een op t i on e l e textuuruniform sampler2D tex ;

void main ( ){

// Indexeer de textuurvec4 t e x e l = texture2D ( tex , vec2 ( gl TexCoord [ 0 ] . x ,

gl TexCoord [ 0 ] . y ) ) ;// Indexeer de cubemapvec4 c o l o r = 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−k l eu rg l FragColor = co l o r + t e x e l ;

}

10.3 Refraction plane

Doorschijnende oppervlakken renderen, is erg eenvoudig door gebruik te maken van Alpha-blending maar sinds de komst van videokaarten die pixel-shaders ondersteunen is het mogelijkinteressantere en realistischere resultaten te bekomen. In realiteit is een venster meestal niet

Page 125: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

volledig doorzichtig maar zitten er oneffenheden in of is het glas wazig. Wanneer we dooreen 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 dielichtbreking zal simuleren.

Refraction kunnen we definieren als”de verandering in richting die licht ondervindt wan-

neer het zich verplaatst van een bepaald medium, naar een ander”, zoals een overgang vanlucht naar glas. Die lichtbreking is een gevolg van verschil in lichtsnelheid in de verschillendematerialen.

Figuur 10.4: Refraction

Om de lichtbreking te berekenen op een oppervlak zullen we gebruik maken van eenDUDV-map. In een normal-map worden de normalen van een object opgeslaan, in een DUDV-map worden simpeleweg de afgeleiden van die normalen bijgehouden. Een DUDV-map zaldienen als lookup-texture waarmee we in real-time kunnen berekenen hoe het licht zal brekenop een oneffen oppervlak.

Opmerking: er bestaat een bijzonder handige tool van ATI die toelaat een TGA-afbeeldingte transformeren naar normal-map en DUDV-map.

10.3.1 Werkwijze

De werkwijze is gelijkaardig als bij de Reflection Plane waarbij we een scene renderen naareen render-target en die verwerken in een pixel-shader. Hier renderen we niet het spiegelbeeldvan een scene maar renderen we doodgewoon wat achter het refraction-vlak ligt en mappendie opnieuw op het vlak in screen-space.

De gebruikte GLSL-refraction-shader in de RenderEngine ziet er als volgt uit:

Refraction.vert

varying vec4 re f rCoords ;vary ing vec4 normCoords ;

Page 126: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

(a) Normal map (b) DUDV map

Figuur 10.5: Voorbeeld Normal- en DUDV-map

varying vec4 viewCoords ;

void main ( ){

//Gebruik t ex ture l aye r1 voor de r e f r a c t i o n// textuurre f rCoords = gl MultiTexCoord1 ;//Gebruik t ex ture l aye r2 voor de dudv−mapnormCoords = gl MultiTexCoord2 ;

// Transformeer ver tex naar screen−space .viewCoords = gl ModelViewProject ionMatr ix ∗ g l Ver t ex ;

g l P o s i t i o n = viewCoords ;}

Refraction.frag

varying vec4 re f rCoords ;vary ing vec4 normCoords ;vary ing vec4 viewCoords ;

//Onze r e f r e c t i o n tex tureuniform sampler2D r e f r a c t i o n ;//Onze DUDV−mapuniform sampler2D dudvMap ;//Een op t i on e l e textuuruniform sampler2D texture ;

void main ( ){

//Een aanta l constanten d i e t o e l a t en de r e f r a c t i o n

Page 127: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

//aan te passen . Wijz ig deze naar be l i e v en to t j e een//goed r e s u l t a a t hebt .const f l o a t kD i s t o r t i on = 0 . 0 1 5 ;const f l o a t kRe f rac t ion = 0 . 0 5 ;

//Bepaal de textuurcoord inaat voor de dudv−map per−p i x e l en pas// een d i s t o r t i e toevec4 d i s tO f f s e t = texture2D (dudvMap , normCoords . xy ) ∗ kDi s to r t i on ;vec4 dudvColor = texture2D (dudvMap , vec2 ( re f rCoords + d i s tO f f s e t ) ) ;dudvColor = normal ize ( dudvColor ∗ 2 .0 − 1 . 0 ) ∗ kRe f rac t ion ;

//Bepaal de textuur−coord inaten van de r e f r a c t i o n textuur in// screen−spacevec4 projCoord = viewCoords / viewCoords . q ;projCoord = ( projCoord + 1 . 0 ) ∗ 0 . 5 ;// Wijz ig de coord inaat l i c h t j e s naarge lang de waarde in de//dudv mapprojCoord += dudvColor ;projCoord = clamp ( projCoord , 0 . 001 , 0 . 9 9 9 ) ;//Pas de gegeneree rde textuur−coord inaat toevec4 r e f r a c t i o nCo l o r = texture2D ( r e f r a c t i o n , projCoord . xy ) ;

//Voeg een k l e u r t j e toe , i nd i en gewenst .vec4 c o l o r = vec4 ( 0 . 9 , 0 . 9 , 0 . 9 , 1 . 0 ) ;

//Bepaal de u i t e i n d e l i j k fragment−k l eu r .g l FragColor = r e f r a c t i o nCo l o r ∗ c o l o r ;

}

Ook hier is het aan te raden een FBO te gebruiken als je hardware die ondersteunt.

10.3.2 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 vaneen reflection-plane:

Page 128: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

SectorManager space ;SkyBox skyBox ;

OBJLoader l oade r ;Entity cube ;SceneNode cubeNode ;

Re f ract ionPlane plane ;

pub l i c void setup ( ){

l oade r = new OBJLoader ( ) ;cube = new Entity ( l oade r . load ( ”models /CubeNormals . obj ” ) , aMater ia l ) ;

skyBox = new SkyBox (5000 ) ;

p lane = new Ref ract ionPlane ( eng ine . camera , //cameranew Vector3 (0 , 0 , 1 ) , //normaal van het vlak

0 , // p o s i t i e512 , // g roo t t e r e f l e c t i o n −textuur”data/myDUDV.bmp” , //De DUDV−map”data/Glass1 .bmp” ) ; // op t i on e l e textuur

space = new Galaxy SectorManager ( ”Galaxy” ) ;eng ine . posit ionCamera (−100 , 50 , −100, 20 , 50 , 0 , 0 , 1 , 0 ) ;eng ine . setRoot ( space ) ;

cubeNode = new SceneNode ( ”cube” , cube ) ;cubeNode . t r a n s l a t e (new Vector3 (0 , 0 , 1 0 0 ) ) ;

space . getRootSceneNode ( ) . addChildNode ( cubeNode ) ;

//Voeg een SectorManager toe aan de Ref ract ionPlane .plane . s e c t o r = space ;

}

pub l i c void render ( ){

eng ine . c l e a r ( ) ;eng ine . posit ionCamera ( ) ;

// Creeer de r e f l e c t i o n textuur .p lane . c r ea t eRe f rac t i onTexture ( skyBox ) ;

eng ine . render ( ) ;

//Render het r e f r a c t i o n −oppervlakplane . render ( skyBox ) ;

}

Page 129: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

10.4 Water-rendering

Nu we werkende implementaties hebben van reflection- en refraction-oppervlakken kun-nen we dit alles perfect combineren tot iets waar tegenwoordig bijzonder veel aandacht aanbesteed 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 wekijken, op het wateroppervlak hoe minder we zien wat er onder ligt en hoe meer we zien vande gereflecteerde omgeving. De hoeveelheid licht dat zo weerkaatst wordt, wordt beschrevendoor 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 decamera.Merk op dat deze berekening de inverse oplevert van de Fresnel-term, we moeten die dus noginverteren 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 eenpixel van het wateroppervlak, of hoe dichter de pixel bij de camera ligt, hoe meer we zullentonen van de refraction-textuur (en dus wat onder het water ligt). Hoe groter de hoek, hoeverder de pixel van de camera ligt, hoe meer we zullen renderen van de reflection-textuur.

Page 130: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Fog

Door vuildeeltjes die rondzweven in een realistisch water-lichaam zal water er mistig uitzienen zal hoe dieper we willen kijken, blijken dat we minder en minder zullen kunnen zien watonder het oppervlak ligt. Dit is een effect waar we rekening mee moeten houden om water re-alistisch te kunnen renderen, en zullen we simuleren door alles wat onder het wateroppervlakligt te renderen naar een depth-texture en naargelang de waarden die daar opgeslaan liggen,zullen we fog toevoegen d.m.v. een shader.

10.4.1 Werkwijze

Vooraleer we verder gaan lijkt het me nuttig een diagram te geven dat schematisch toontwelke 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 respec-tieve hoofdstukken. Daar wordt hier niet meer in detail op teruggekomen, maar ik zal tonenwat er gebeurt met behulp van de GLSL-shader die gebruikt wordt in de RenderEngine:

Water.vert

// Textuurcoordinaten van r e f r a c t i o n −textuurvarying vec4 re f rCoords ;// Textuurcoordinaten van normal−mapvarying vec4 normCoords ;//The ver tex in screen−spacevarying vec4 viewCoords ;// Camerapos it ie in Tangent−spacevarying vec4 viewTangetSpace ;

//Uniforme va r i abe l en d i e 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 b i tangent 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 vec to r van de ver tex naar de cameravec4 viewDir = cameraPos − g l Ver t ex ;

//Bereken camera in Tangent−spaceviewTangetSpace . x = dot ( viewDir , tangent ) ;viewTangetSpace . y = dot ( viewDir , biTangent ) ;viewTangetSpace . z = dot ( viewDir , normal ) ;viewTangetSpace .w = 1 . 0 ;

//Gebruik de multi−t ex ture coord inaten op laag 1 en 2

Page 131: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Figuur 10.8: Water-rendering schematisch voorgesteld

Page 132: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

r e f rCoords = gl MultiTexCoord1 ;normCoords = gl MultiTexCoord2 ;

// Transformeer ver tex in screen−space .viewCoords = gl ModelViewProject ionMatr ix ∗ g l Ver t ex ;

// Transformeer ver texg l P o s i t i o n = viewCoords ;

}

Water.frag

varying vec4 re f rCoords ;vary ing vec4 normCoords ;vary ing vec4 viewCoords ;vary ing vec4 viewTangetSpace ;

//Textuur met r e f l e c i euniform sampler2D r e f l e c t i o n ;//Textuur met r e f r a c t i o nuniform sampler2D r e f r a c t i o n ;//Normalmap textuuruniform sampler2D normalMap ;//DUDV−mapuniform sampler2D dudvMap ;//Textuur met depth−va lue suniform sampler2D depthMap ;//Kleur van het wateruniform vec4 waterColor ;

void main ( ){

//Constanten voor r e f r a c t i o nconst f l o a t kD i s t o r t i on = 0 . 0 1 5 ;const f l o a t kRe f rac t ion = 0 . 0 0 9 ;

// Lees waarde u i t DUDV−mapvec4 d i s tO f f s e t = texture2D (dudvMap , normCoords . xy ) ∗ kDi s to r t i on ;vec4 dudvColor = texture2D (dudvMap , vec2 ( re f rCoords + d i s tO f f s e t ) ) ;dudvColor = normal ize ( dudvColor ∗ 2 .0 − 1 . 0 ) ∗ kRe f rac t ion ;

// Lees waarde u i t Normal−mapvec4 normalVector = texture2D (normalMap , vec2 ( re f rCoords + d i s tO f f s e t ) ) ;normalVector = normalVector ∗ 2 .0 − 1 . 0 ;normalVector . a = 0 . 0 ;

// Normal i seer view−vec to r in Tangent−spacevec4 v i ewRe f l e c t i on = normal ize ( viewTangetSpace ) ;

//Bereken ge i nv e r t e e rd e Fre sne l term door het DOT−product// te nemen van

Page 133: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

vec4 i nve r t edFr e sn e l = vec4 ( dot ( normalVector , v i ewRe f l e c t i on ) ) ;//Bereken Fre sne l termvec4 fresne lTerm = 1.0 − i nv e r t edFr e sn e l ;

//Bereken textuurcoord inaten voor r e f r a c t i o nvec4 projCoord = viewCoords / viewCoords . q ;projCoord = ( projCoord + 1 . 0 ) ∗ 0 . 5 ;projCoord += dudvColor ;projCoord = clamp ( projCoord , 0 . 001 , 0 . 9 9 9 ) ;

// Lees r e f l e c t i e −textuurvec4 r e f l e c t i o nCo l o r = texture2D ( r e f l e c t i o n , projCoord . xy ) ;// Lees r e f r a c t i o n −textuurvec4 r e f r a c t i o nCo l o r = texture2D ( r e f r a c t i o n , projCoord . xy ) ;// Lees depth−textuurvec4 depthValue = texture2D (depthMap , projCoord . xy ) ;depthValue = vec4 (pow( depthValue . x , 2 5 6 . 0 ) ) ;

//Bereken ge i nv e r t e e rd e diepte−waarde .vec4 invDepth = 1 .0 − depthValue ;

//Bereken u i t e i n d e l i j k e r e f r a c t i o n −k l eu r ( z i e schema ! )r e f r a c t i o nCo l o r ∗= inve r t edFr e sn e l ∗ invDepth ;r e f r a c t i o nCo l o r += waterColor ∗ depthValue ∗ i nv e r t edFr e sn e l ;

//Bereken u i t e i n d e l i j k e r e f l e c t i e −k l eu rr e f l e c t i o nCo l o r ∗= fresne lTerm ;

//Tel u i t e i n d e l i j k e r e s u l t a t e n opg l FragColor = r e f r a c t i o nCo l o r + r e f l e c t i o nCo l o r ;

}

10.4.2 Water-rendering en FBO (Frame Buffer Object)

Uiteraard kan je de reflectie-, refraction- en depth-texturen, nodig voor het renderen van wa-ter, renderen d.m.v. een FBO. De setup van de FBO is volledig gelijkaardig aan het voorbeeldbesproken bij ShadowMapping maar voor de Reflectie-textuur moeten we een extra maatregelnemen.Wanneer we renderen naar een FBO worden de color-, depth- en stencil-buffer buiten beschouwinggelaten en renderen we rechtstreeks naar het geheugen van je videokaart. Uiteraard betekentdit dat we geen depth-tests kunnen uitvoeren en indien we wensen dat de gereflecteerde scenecorrect 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,r e f r a c t i on ID ,0 ) ;

//Voeg een depth−bu f f e r toedepthBufferHandle = Bu f f e rU t i l s . c r e a t e I n tBu f f e r ( 1 ) ;

Page 134: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

glGenRenderbuffersEXT ( depthBufferHandle ) ;glBindRenderbufferEXT (GL RENDERBUFFER EXT,

depthBufferHandle . get ( 0 ) ) ;glRenderbufferStorageEXT (GL RENDERBUFFER EXT,

GL11 .GL DEPTH COMPONENT,tex tu r eS i z e ,t e x tu r eS i z e ) ;

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.

10.4.3 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 zakenautonoom verwerken zodat je als gebruiker hier geen rekening mee hoeft te houden. EenWaterPlane toevoegen is bijzonder eenvoudig gehouden:

SectorManager space ;SkyBox skyBox ;WaterPlane water ;

pub l i c void in i tData ( ){

skyBox = new SkyBox (5000 ) ;water = new WaterPlane ( eng ine . camera , //De camera in de scene

new Vector3 (0 , 1 , 0 ) , //de normaal0 , // Tran s l a t i e165 , // breedte100 , // hoogte512 , // r e s o l u t i e van gebru ik t e render−t a r g e t s

Page 135: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

”WaterIndoor” , // Gebruikte mate r i a l ( shader )new Vector3 ( 0 . 2 f , 0 . 2 f , 0 . 6 f ) ) ; //Water−k l eu r

}

pub l i c void setupScene ( ){

space = new BSP SectorManager ( ”MyRoom” ) ;eng ine . posit ionCamera (−220 , 80 , −50, 20 , 20 , 0 , 0 , 1 , 0 ) ;eng ine . setRoot ( space ) ;

//Voeg s e c t o r toe aan het wateroppervlak , zodat d i e kan// g e r e f l e c t e e r d worden .water . s e c t o r = space ;

}

pub l i c void render ( ){

eng ine . c l e a r ( ) ;eng ine . posit ionCamera ( ) ;

//Render r e f l e c t i o n −, r e f r a c t i o n − en depth−t ex turewater . c r ea teRe f lRe f rTexture ( skyBox ) ;

skyBox . render ( eng ine . camera ) ;eng ine . render ( ) ;//Render het waterwater . render ( skyBox ) ;

}

Page 136: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Deel IV

Besluit

134

Page 137: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

10.5 Nabeschouwing

De implementatie van de asssenstelsel-onafhankelijke portal-based render-engine toont aandat de vooropgestelde architectuur weldegelijk werkt en bruikbaar is. De engine is flexibel,transparant en nieuwe renderers kunnen zonder veel problemen toegevoegd worden. Libera iseenvoudig in gebruik en een scene opbouwen kan met een aantal lijnen code.Bovendien bleek dat de geımplementeerde architectuur eenvoudig uit te breiden was met ver-schillende functionaliteiten zoals real-time shadow-rendering en dynamic clothing. Dit opentperspectieven voor eventuele uitbreidingen aan de engine in de toekomst.

Programmeren in Java had zijn voordelen tijdens de ontwikkeling van de engine. De Garbagecollection van Java is een genot, je niet te hoeven druk maken over het vrijgeven van gebruiktegeheugen maakt het mogelijk je als programmeur te focussen op het belangrijkste van hetproject: ontwerp van een goede architectuur en een zo goed mogelijke implementatie ervan.Java is bovendien systeem onafhankelijk, de engine werd zonder noemenswaardige problemenaan de praat gekregen op zowel een Mac OSX als een Ubuntu Linux systeem. Dit is uiteraardeen enorme troef.

Anderzijds, aangezien Java draait op een Virtuele Machine, riskeer je, door de extra indirectie-laag, niet de snelheid te halen van bijvoorbeeld een native C/C++ programma. Spijtig genoegheb ik niet de kans gehad de performantie van de engine te testen met een gelijkaardig prod-uct, maar het is een reeel probleem. Een ideale test zou zijn de engine te herimplementerenin C/C++ om zo een volledige performantie-test te kunnen doen.Een ander groot probleem is dat Java het concept van een ’unsigned’-type niet kent. Concreetbetekent dit dat je in een byte steeds maar een waarde in het interval [-127, 128] kan opslaan.Voor een aantal zaken vereiste dit kunstgrepen: Quake3 BSP-files maken bijvoorbeeld erg veelgebruik van unsigned-types, wat het parsen van die BSP-files bemoeilijkte. Alsook bij hetrenderen ervan moest rekening gehouden worden met het verschil in formaat. Java hanteertook een BIG-ENDIAN formaat, hier moet als programmeur uiteraard rekening mee moethouden.

Al bij al, denk ik dat ik aan het einde van de rit enkel kan besluiten dat Java wel degelijkgeschikt is voor het programmeren van een volwaardige 3D-engine.

Uiteraard gaat het er niet om in welke taal je programmeert, wat belangrijk is, is dat jede technieken, algoritmen, data-structuren en vele moeilijkheden die komen kijken bij 3D-programming begrijpt, efficient kan oplossen en, belangrijk, kan optimaliseren.

Page 138: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Bibliografie

[1] Lighthouse 3D. Glsl tutorial. http://www.lighthouse3d.com/opengl/glsl/, 2006.

[2] P. Baker. Shadow mapping: Casting curved shadows on curved surfaces.http://www.paulsprojects.net/tutorials/smt/smt.html, 2002.

[3] A. Baylis. Portal tutorial. http://www.alsprogrammingresource.com/portals tutorial.html,2002.

[4] [Niet bekend]. Matrices in opengl. http://www.levp.de/3d/oglmatrices.html, 2006.

[5] S. Brabec, T. Annen, and H. Seidel. Practical shadow mapping. http://www.mpi-sb.mpg.de/ tannen/papers/jgt 02.pdf, 2006.

[6] M. Christen. Opengl shading language tutorials.http://www.clockworkcoders.com/oglsl/tutorials.html, 2003.

[7] Codesampler. [niet bekend]. http://www.codesampler.com/source.htm, 2006.

[8] ATI Developer. [niet bekend]. http://www.ati.com/developer/index.html, 2006.

[9] Developer.nvidia.com. [niet bekend]. http://developer.nvidia.com/page/home.html, 2006.

[10] J. Dexter. Texturing heightmaps. http://www.gamedev.net/reference/programming/features/texturingheightmaps/2005.

[11] S. Dreijer. Bump mapping using cg. http://www.blacksmith-studios.dk/projects/downloads/bumpmappin using cg.php, 2006.

[12] H. Elias. Cloths and springs. http://freespace.virgin.net/hugo.elias/models/m cloth.htm,2006.

[13] W. Engel. Shader programming. http://www.gamedev.net/columns/hardcore/dxshader1/,2006.

[14] C. Everitt and M.J. Kilgard. Practical and robust stenciled shadow volumes for hardware-accelerated rendering. http://developer.nvidia.com/attach/6831, 2002.

[15] G. Foster. Understanding and implementing scene graphs.http://www.gamedev.net/reference/programming/features/scenegraph/, 2006.

[16] Gamasutra. The art & business of making games. http://www.gamasutra.com/, 2006.

136

Page 139: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

[17] Ultimate gameprogramming. [niet bekend]. http://www.ultimategameprogramming.com/,2006.

[18] Gametutorials. [niet bekend]. http://www.gametutorials.com/, 2006.

[19] K. Garrein. Realtime shadowing techniques. http://www.devmaster.net/articles/shadows/,2003.

[20] G. Gribb and H. Hartmann. Fast extraction of view-ing frustum planes from the world- view-projection matrix.http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf, 2003.

[21] J. Guinot. Bump mapping using glsl. http://www.ozone3d.net/tutorials/bump mapping.php,2005.

[22] H. and L. Automatic portal generation. http://www.gamedev.net/reference/programming/features/apg/default.asp2005.

[23] Humus. [niet bekend]. http://www.humus.ca/index.php?page=News, 2006.

[24] J. Kainulainen. An introduction to stencil shadows.http://www.devmaster.net/articles/shadow volumes/, 2004.

[25] H.Y. Kwoon. The theory of stencil shadows. http://www.gamedev.net/reference/articles/article1873.asp,2002.

[26] LWJGL. Java/opengl binding. http://www.lwjgl.org/, 2006.

[27] Mobilefish. Java web start. http://www.mobilefish.com/developer/javawebstart/javawebstart.html,2006.

[28] A. Nealen. Shadow mapping and shadow volumes.http://www.devmaster.net/articles/shadow techniques/, 2005.

[29] Matt’s Source Portal. Realtime cloth rendering.http://www.its.caltech.edu/ matthewf/Chatter/Equations.html, 2006.

[30] NeHe productions. [niet bekend]. http://nehe.gamedev.net/, 2006.

[31] D. Salvator. 3d pipeline tutorial, part i, ii en iii.http://www.extremetech.com/article2/0,1697,9722,00.asp, 2001.

[32] ShaderTech. Gpu programming. http://www.shadertech.com/cgi-bin/shaders.cgi?filter=-1&page=0, 2006.

[33] A.S. Shastry. Soft-edged shadows. http://www.gamedev.net/reference/articles/article2193.asp,2005.

[34] Bonzai software. [niet bekend]. http://www.bonzaisoftware.com/index.html, 2006.

[35] Terathon software. Computing tangent space basis vectors for an arbitrary mesh.http://www.terathon.com/code/tangent.html, 2005.

[36] R. Surdulescu. Cg shadow volumes. http://www.gamedev.net/reference/articles/article1990.asp,2003.

Page 140: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Deel V

Bijlage: UML-diagrammen

138

Page 141: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

RenderEngineSectorManager

AbstractApp

Sector

Portal FrustumShadowRenderer

SceneNode

MovableObject

-renderEngine1

StartScreen-startScreen

1

-frustum

1

-viewFrustum 1

-otherSide1

-portals

0..*

-shadowRenderer 0..1

-sector

1

-rootSceneNode

1

0..*-object1

-renderContainer

1

Figu

ur

10.10:M

ainU

ML

Page 142: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

SectorManagerSector

-sector

1

-sector 1

BSP_Sector Octree_Sector Galaxy_Sector

BSP_SectorManager

Octree_SectorManager

Galaxy_SectorManager

-sector 1-sector 1

BSPHeader

Octree

-octree1

BSPLump

BSPNode

BSPLeaf

BSPVisData-sector

1

Figuur 10.11: Sector(Manager) UML

Page 143: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

Portal

ShadowRenderer

ShadowMapCubicRenderer

ShadowMapDirectionalSpotRenderer

ShadowMapSpotRenderer

ShadowMapping ShadowVolumes

ShadowVolumeZPassRenderer ShadowVolumeZFailRenderer

Mirror Monitor

Figuur 10.12: ShadowRenderer en Portal UML

Page 144: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

MovableObject

-subMesh1..*

Entity

Mesh Material

SubMesh

Billboard Light ParticleSystem Cloth

BoundingBox-BBox

1

-mesh1 -material1

Figu

ur

10.13:M

ovableO

bject

UM

L

Page 145: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

RenderEngine RenderEngine.RenderContainer

getRootSector()()

addToContainer()

rootSector

getCurrentSector()

currentSector

SectorManager

currentSector.render(frustum)

Portal

isVisible(frustum)

visible

getOtherSide()

otherSide

otherSide.calculateFrustum()

otherside.addToContainer()

otherSide.render(frustum)

ForAllSectors

ForAllPortals

Figu

ur

10.14:Seq

uen

tiediagram

vande

render-p

ass

Page 146: Implementatie in Java van een Architectuur voor Real-time 3D …lib.ugent.be/fulltxt/RUG01/001/311/627/RUG01-001311627... · 2010. 6. 7. · Implementatie in Java van een Architectuur

SectorManager

render()

getRootSceneNode()

rootSceneNode

SceneNode

rootSceneNode.render(frustum)

Frustum

frustum.isVisible(boundingBox)

visible

ForAllChildren

getBoundingBox()

boundingBox

renderObject()

Figu

ur

10.15:Seq

uen

tiediagram

voor

cullin

gvan

Scen

eNodes