Raytracing Codeboekje
Didier Collard Simon Koolstra
Stedelijk Gymnasium Johan van Oldenbarnevelt Profielwerkstuk
Raytracing
Door: Didier Collard Simon Koolstra
Begeleider: Drs. R.M. Boers
December 2009
Inhoudsopgave Voorwoord
3
Inleiding
4
1 Basis wiskunde 1.1 Vectoren . . . . . . . . . . . . . . 1.2 Matrices . . . . . . . . . . . . . . 1.3 Verzamelingenleer . . . . . . . . 1.4 Eenheidsobjecten . . . . . . . . . 1.5 Orthonormale basissen . . . . . . 1.6 Alternatieve co¨ ordinatensystemen 1.7 Steradiaal . . . . . . . . . . . . . 1.8 Kleuren in de raytracer . . . . . 1.9 Bronnen en code . . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
6 6 8 10 10 10 12 12 13 14
2 Objecten en rays 2.1 Objecten . . . . . 2.2 Rays . . . . . . . 2.3 En de raytracer? 2.4 Bronnen en code
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
15 15 19 19 19
3 Rayintersectie 3.1 Intersectie met impliciete oppervlakken 3.2 Intersectie met begrensde vlakken . . . . 3.3 En de raytracer? . . . . . . . . . . . . . 3.4 Bronnen en code . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
21 21 22 25 25
4 Camera en perspectief 4.1 Perspectief . . . . . 4.2 Camera . . . . . . . 4.3 En de raytracer? . . 4.4 Bronnen en code . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
26 26 27 29 30
5 Sampling 5.1 Aliasing . . . . . . . . . 5.2 Sampling . . . . . . . . 5.3 Gebruik van de samples 5.4 En de raytracer? . . . . 5.5 Bronnen en code . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
31 31 31 33 33 33
. . . .
1
INHOUDSOPGAVE
2
6 Licht 6.1 Rendervergelijking 6.2 Lichtbronnen . . . 6.3 Shading . . . . . . 6.4 Renderen . . . . . 6.5 En de raytracer? . 6.6 Bronnen en code .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
35 35 40 42 46 47 47
7 Transformatie 7.1 Verschillende transformaties . . . . . . . . . . 7.2 Transformaties in 2D . . . . . . . . . . . . . . 7.3 Transformaties in 3D . . . . . . . . . . . . . . 7.4 Rayintersectie met getransformeerde objecten 7.5 En de raytracer? . . . . . . . . . . . . . . . . 7.6 Bronnen en code . . . . . . . . . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
48 48 48 51 55 58 58
8 Geavanceerde Objecten en Intersecties 8.1 Cilinders . . . . . . . . . . . . . . . . . . 8.2 Polygon Meshes . . . . . . . . . . . . . . 8.3 En de raytracer? . . . . . . . . . . . . . 8.4 Bronnen en code . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
59 59 60 63 64
9 Textuur 9.1 Het proces . . . . . . . . . . . . . . . . . 9.2 Texturen op verschillende objecten . . . 9.3 Objecten met textuur op andere plekken 9.4 En de raytracer? . . . . . . . . . . . . . 9.5 Bronnen en code . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . in de wereld . . . . . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
65 65 66 68 69 69
10 Spiegelreflectie 10.1 Het principe . . . 10.2 Het model . . . . 10.3 En de raytacer? . 10.4 Bronnen en code
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
70 70 71 72 72
11 Breking 11.1 Het principe . . . 11.2 Het model . . . . 11.3 En de raytracer? 11.4 Bronnen en code
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
73 73 75 76 77
12 Conclusie 12.1 Samenvatting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2 Wat had nog meer gekund? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78 78 79
13 Onze raytracer 13.1 Werking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2 Gebruik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81 81 81
Bibliografie
88
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . .
. . . .
Voorwoord Sinds het begin van ons vijfde schooljaar op het Johan van Oldenbarneveltgymnasium zijn we bezig geweest met het ori¨enteren op een vervolgopleiding. Al gauw bleek dat wij beiden interesse hebben in zowel wiskunde als informatica. Geen onlogische keuze dus om voor een profielwerkstuk een onderwerp te zoeken dat met deze beide onderwerpen te maken heeft. Al snel hadden we zo’n onderwerp gevonden. We werden getipt over een masterclass van de Technische Universiteit Eindhoven over het onderwerp raytracing. Dit onderwerp bleek uitdagend en interessant en naar aanleiding van deze masterclass zijn we de weg vrij gaan maken voor het profielwerkstuk. Raytracing, zoals u waarschijnlijk niet zult weten, is een techniek voor het genereren van 2D beelden op een computer. De computer leest een beschrijving van een 3D wereld en zet dit door middel van uitgestuurde stralen, de zogenaamde ‘rays’, om in een, meestal erg realistisch, 2D plaatje. Dit klinkt op dit moment misschien nog allemaal erg abstract en onduidelijk, maar gaandeweg zullen we u uitleggen hoe dit nu precies werkt en hoe men een computer zover kan krijgen om dit uit te voeren. Als u het op dit moment nog niet hebt opgegeven, wensen we u veel plezier met het lezen van ons profielwerkstuk. Met vriendelijke groeten, Didier Collard & Simon Koolstra
3
Inleiding Al jaren is men in de moderne computerwetenschappen op zoek naar manieren om plaatjes met de computer zo realistisch mogelijk te maken. De filmindustrie, met al haar films vol special effects en onbestaande wezens, en de game-industrie, bijvoorbeeld, hebben veel baat bij de (redelijk) recent populair geworden methode van het raytracen. Door jarenlang onderzoek en steeds betere raytracing programma’s, zien we raytracing opkomen als de belangrijkste methode om plaatjes te renderen.1 Zoals gezegd is raytracing een manier om erg realistische plaatjes in 2D te maken. Het maken van tweedimensionale plaatjes is belangrijk omdat een scherm alleen afbeeldingen in tweedimensies kan laten zien. Een scherm heeft namelijk alleen een breedte en een hoogte! Raytracing is dan ook ontwikkeld uit de vraag hoe je zo goed en realistich mogelijk een driedimensionale sc`ene naar een tweedimensionale afbeelding kunt omzetten. In ons profielwerkstuk zullen we de basis van het raytracen uitleggen. Het belangrijkste principe van raytracing is dat het gebaseerd is op wat er in de echte wereld gebeurt. Het werkt met een model van de werkelijkheid, maar dan n´et iets anders. In het echt zien wij dingen, doordat er licht door deze objecten wordt weerkaatst. Dit licht bereikt ons netvlies en onze hersenen kunnen van deze impulsen een beeld maken: het beeld dat wij zien. Om een erg realistisch plaatje te renderen, zouden we dit kunnen nabootsen. We zouden vanuit elke lichtbron in een sc`ene in alle richtingen lichtstralen kunnen sturen en vervolgens kijken welke lichtstralen ons oog bereiken en hiermee een plaatje renderen. We zouden de stralen volgen, oftewel: “we would trace the rays”. Dit zou echter een hels karwei zijn. Om namelijk vanuit ´e´en lichtbron al in ´ alle richtingen stralen af te sturen zijn oneindig veel stralen nodig. Om dit te benaderen zouden we dus al duizenden, misschien wel miljoenen stralen nodig hebben. En dat is nog maar voor ´e´en punt, op ´e´en lichtbron. Daarbij bereikt meer dan 99% van deze stralen ons oog helemaal niet! Het zou Figuur 1: Het principe van raytracing [2] dus een gekkenwerk zijn om deze stralen toch allemaal te bekijken en zelfs met meerdere computers zou het tijden duren om een plaatje te renderen. In raytracing wordt het dus anders aangepakt. Bij het raytracen doen we precies het omgekeerde van wat er in de natuur gebeurt. In plaats van dat we gaan kijken waar alle lichtstralen van elke lichtbron terechtkomen, gaan we vanuit het oog kijken welke lichtstralen het oog bereiken. In plaats van het uitsturen van lichtstralen vanaf elke lichtbron, sturen we zogenaamde ‘rays’ uit vanuit het oog en kijken we wat er met deze rays in de sc`ene gebeurt. We volgen dus de stralen die we vanuit het oog uitsturen: “we trace the rays!” Deze stralen gaan dus precies in de tegenovergestelde richting van de lichtstralen in het echt. We bekijken vervolgens of een ray vanuit het oog een object raakt. Is dit het geval dan kunnen we op het punt waar de straal het object raakt weer kijken hoeveel licht er op dit punt valt, of er weerkaatsing optreedt en meer van dat soort aspecten. Vervolgens 1 Met
renderen bedoelen we het genereren van plaatjes door de computer.
4
INHOUDSOPGAVE
5
zullen we van al deze aspecten berekenen hoeveel effect ze hebben op de kleur van een bepaald punt en hiermee kunnen we voor elke pixel uiteindelijk een kleur berekenen. Doordat we zo dicht mogelijk bij de werkelijkheid blijven, zullen we uiteindelijk met al deze kleuren een erg realistisch plaatje kunnen bouwen. In figuur 1 zien we het principe van raytracing. Vanuit het oog sturen we rays uit, we zien er hier ´e´en afgebeeld. Deze ray raakt de groene bol. Vanaf het raakpunt met de groene bol worden weer rays uitgeschoten om te kijken of het punt licht ontvangt van lichtbronnen, of via reflectie van andere objecten. Het proces van het afschieten van rays en kijken wat er met deze rays gebeurt, is een proces, dat zich steeds herhaalt. Hierdoor is raytracen een rekenintensief en langdurig proces. Vaak gebeurt dit dus ook nog niet real-time2 , omdat de hedendaagse computers dit nog niet snel genoeg kunnen doen. Ondanks dit alles zijn er tegenwoordig al veel projecten gaande om raytracing toch real-time te kunnen laten voltrekken. Hoe het proces van raytracing precies werkt zullen we gaandeweg steeds verder uitleggen, waarbij we dieper ingaan op onderwerpen als licht, reflectie, breking, het plaatsen en raytracen van objecten en aliasing. Om alvast een voorproefje te krijgen van wat een ‘volgroeid’ raytracer voor plaatjes zou kunnen cre¨eren, kunt u een blik werpen op figuur 2. Onze raytracer Aansluitend op dit werkstuk hebben we onze eigen raytracer gebouwd, waarin alle theorie die in dit werk beschreven staat, wordt toegepast. Meer informatie over onze raytracer en het gebruik daarvan is terug te vinden in het laatste hoofdstuk op bladzijde 81. Op http://code.google.com/p/pwsraytracer/ kunt u de raytracer, de broncode en dit profielwerkstuk downloaden. Opmerking over figuren Tot slot hebben we nog een opmerking over de afbeeldingen die we in dit profielwerkstuk hebben gebruikt. Wij hebben bij elke afbeelding die we niet zelf hebben geproduceerd (dan wel met de raytracer, dan wel op andere wijze) een bronvermelding gegeven. Ieder figuur zonder verwijzing is dus een door onszelf gemaakte afbeelding.
Figuur 2: Een plaatje gerenderd door een raytracer. [3]
2 Real-time betekent dat het plaatje pas gerenderd wordt als het nodig is. Real-time zien we vooral bij interactieve zaken, zoals computerspellen en dergelijke.
Hoofdstuk 1
Basis wiskunde Voor het begrijpen van het proces van raytracing is een zeker basisniveau op het gebied van wiskunde nodig. Omdat niet alles als bekend mag worden verondersteld, hebben we dit hoofdstuk ingericht om een aantal wiskundige onderwerpen die veel voorkomen uit te leggen en begrijpelijk te maken. Een aantal onderwerpen die we w´el als bekend veronderstellen zijn: goniometrische functies (het werken met sinus, cosinus en tangens en al de bijbehorende rompslomp), integratie, differentiatie, basis meetkunde en werken in 3D ruimtes. De onderwerpen die wel aan de orde worden gesteld zijn: vectoren, matrices, eenheidsobjecten, een basis voor verzamelingenleer, orthonormale basissen en assenstelsels, alternatieve co¨ ordinatensystemen en de steradiaal. Tenslotte zullen we nog aangeven hoe kleuren gedefinieerd worden in onze raytracer.
1.1 1.1.1
Vectoren Wat zijn vectoren?
Vectoren zijn grootheden die een lengte en een richting hebben. De richting van een vector wordt vaak aangegeven door de vector als een pijl te tekenen. De lengte van de pijl geeft hierin de grootte aan. Een vector in een 2D stelsel met (bijvoorbeeld) een x en een y-as wordt gedefinieerd door twee waarden: de x-waarde en de y-waarde. Dit wordt dan als x volgt genoteerd: . Een voorbeeld van zo’n vector zie je in figuur 1.1. Deze y vector begint in de oorsprong en heeft de kentallen (x, y). Deze eindigt dus in punt (x, y). Een vector hoeft niet altijd in de oorsprong te beginnen en eindigt dus lang niet altijd op zijn kentallen (x, y). Een vector wordt door ons weergegeven als een letter met een pijltje erboven, bijvoorbeeld: ~a. De lengte van een vector kunnen p we berekenen met de stelling van Pythagoras: |~a| = x2 + y 2 . |~a| betekent: “de lengte van vector ~a”. In een 3D stelsel wordt een vector gedefinieerd door drie de x-waarde, kentallen: Figuur 1.1: Een vector x de y-waarde en de z-waarde. Deze zien er als volgt uit: y . De lengte van zo’n vanaf de oorsprong met richting x, y. z vector kan worden berekend door de stelling van Pythagoras in 3D toe te passen. p Dit gaat als volgt: |~a| = x2 + y 2 + z 2 .
6
HOOFDSTUK 1. BASIS WISKUNDE
1.1.2
7
Operaties met vectoren
Vectoren kunnen gewoon bij elkaar worden opgeteld en van elkaar worden afgetrokken. Er ontstaan dan nieuwe vectoren. Hieronder staat hoe dat gaat: ax bx ax + bx ~a + ~b = ay + by = ay + by az bz az + bz ax bx ax − bx ~a − ~b = ay − by = ay − by az bz az − bz Ook ontstaan er vectoren als punten van elkaar worden afgetrokken. Als we bijvoorbeeld het punt p hebben met co¨ ordinaten (px , py , pz ) en punt c met co¨ordinaten (cx , cy , cz ) en we trekken c van p af, krijgen we de vector met een richting van c naar p: px − cx cp ~ = p − c = py − cy pz − cz Verder kunnen vectoren niet door elkaar worden gedeeld. Wel kunnen vectoren met elkaar worden vermenigvuldigd. Dit kan op twee manieren: met het inwendig product (het inproduct) en met het uitwendig product (het uitproduct). Het inwendig product geeft ´e´en waarde, een getal. We geven het inproduct van twee vectoren als volgt aan: ~a • ~b en we kunnen dit op twee manieren berekenen: ~a • ~b = |~a||~b| cos θ ~a • ~b = ax bx + ay by + az bz Hierin is θ de hoek tussen de twee vectoren. Beide manieren komen op hetzelfde uit en het hangt van de situatie af welke wordt gebruikt. Het uitproduct geeft als uitkomst een vector die loodrecht op het vlak van de twee andere vectoren staat. Het uitproduct van twee vectoren geven we zo aan: ~a × ~b en berekenen we op de volgende manier: ay bz − az by ~a × ~b = az bx − ax bz ax by − ay bx Ook kan een vector worden vermenigvuldigd met of gedeeld worden door een gewoon getal. We krijgen dan: vax v~a = vay vaz ax ~a avy = v v az v
Ook kunnen we een negatieve vector nemen. Hierin is -~a dezelfde vector als ~a maar dan in tegengestelde richting.
1.1.3
Normaalvectoren
Een normaalvector is een vector die loodrecht op iets staat. We kunnen bijvoorbeeld een normaalvector van een vlak, een bol of een lijn nemen. Normaalvectoren zijn uitermate belangrijk bij het raytracen. We zullen een normaalvector op twee manieren bepalen. Twee vectoren die loodrecht op elkaar staan (dus die elkaars normaalvectoren zijn) maken een hoek van 90◦ met elkaar. Hieruit volgt dat het inproduct van deze twee vectoren nul is, want de cosinus van een hoek van 90 graden is nul. Hieruit volgt de volgende vergelijking voor de normaal van ~a: ~n • ~a = 0
HOOFDSTUK 1. BASIS WISKUNDE
8
Als we een normaalvector van een vlak willen hebben, kunnen we het uitproduct van twee vectoren binnen dit vlak gebruiken. Het uitproduct van ~a en ~b geeft immers een vector die loodrecht op beide vectoren staat. We krijgen dan dus: ~n = ~a × ~b Vaak zal de normaalvector een eenheidsvector moeten zijn (een vector met lengte ´e´en, zie 1.4). Om van een vector een eenheidsvector te maken, moeten we de vector delen door zijn eigen lengte.
1.2 1.2.1
Matrices Wat zijn matrices?
Matrices zijn verzamelingen van getallen. Een matrix bestaat uit een aantal rijen en een aantal kolommen waarin getallen worden gezet. Een voorbeeld van een matrix kan er zo uitzien: 1 3 5 2 4 6 Deze matrix heeft 2 rijen en 3 kolommen. Dit is dus een 2 × 3-matrix. Een vierkante matrix is een matrix met evenveel rijen als kolommen.
1.2.2
Operaties met matrices
Matrices kunnen bij elkaar worden opgeteld of van elkaar worden afgetrokken als ze allebei dezelfde afmetingen hebben. Dit gaat dan als volgt: a b e f a+e b+f + = c d g h c+g d+h a b e f a−e b−f − = c d g h c−g d−h Matrix A en matrix B kunnen met elkaar vermenigvuldigd worden, als A evenveel kolommen heeft als B rijen. Dit gaat dan als volgt: ax + by + cz x a b c d e f y = dx + ey + f z gx + hy + iz z g h i 22 28 1×1+2×3+3×5 1×2+2×4+3×6 1 2 1 2 3 4 5 6 3 4 = 4 × 1 + 5 × 3 + 6 × 5 4 × 2 + 5 × 4 + 6 × 6 = 49 64 7×1+8×3+9×5 7×2+8×4+9×6 76 100 5 6 7 8 9 Let op: de uitkomst is hier een 3 × 1-matrix en A × B is niet hetzelfde B × A! En matrices kunnen niet door elkaar gedeeld worden. We kunnen een matrix transponeren door van de rijen van de matrix de kolommen te maken, en van de kolommen de rijen. Dit gaat als volgt: 1 2 3 A= 4 5 6 1 4 AT = 2 5 3 6 1 2 3 B = 4 5 6 7 8 9 1 4 7 B T = 2 5 8 3 6 9
HOOFDSTUK 1. BASIS WISKUNDE
9
Van vierkante matrices kan ook een inverse matrix genomen worden. Voor de inverse matrix van een matrix A geldt dat we een eenheidsmatrix krijgen als we de operatie A × A−1 uitvoeren, maar dat we ook een eenheidsmatrix krijgen als we de operatie A−1 × A uitvoeren. Een voorbeeld staat hieronder. Algemeen geldt dat we van een matrix een inverse matrix kunnen nemen als de determinant (zie de alinea hieronder) niet gelijk is aan nul. 1 0 2 A = 0 1 6 0 0 1 1 0 −2 A−1 = 0 1 −6 0 0 1 1 0 2 1 0 −2 1 0 0 A × A−1 = 0 1 6 0 1 −6 = 0 1 0 0 0 1 0 0 1 0 0 1 1 0 −2 1 0 2 1 0 0 A−1 × A = 0 1 −6 0 1 6 = 0 1 0 0 0 1 0 0 1 0 0 1 De laatste belangrijke operatie die met een matrix kan worden uitgevoerd, is het bepalen van de determinant. We kunnen determinanten alleen nemen van vierkante matrices. Het berekenen van een determinant kunnen we het best uitleggen met twee voorbeelden: ´e´en van een 2 × 2-matrix en ´e´en van een 3 × 3-matrix: a b A= c d det(A) = ad − bc a b B= d e g h
c f i
det(B) = aei + bf g + cdh − ceg − bdi − af h
1.2.3
Regel van Cramer
De regel van Cramer is een formule voor het oplossen van een stelsel van lineaire vergelijkingen (vergelijkingen waarin de onbekenden alleen maar eerstegraads zijn) waarbij er evenveel vergelijkingen als onbekenden zijn. In deze formule wordt gebruikt gemaakt van matrices. Volgens de regel van Cramer schrijven we eerst alle formules in matrixvorm. Hierbij staan de co¨efficienten van de onbekenden in de co¨efficientenmatrix, de onbekenden in een matrix (met dus maar ´e´en kolom), die met de co¨efficientenmatrix vermenigvuldigd wordt en achter het =-teken staan de uitkomsten van de vergelijkingen ook in een matrix (met ook maar ´e´en kolom). Hierna kunnen we de onbekenden op de volgende manier berekenen: voor de n-de onbekende vervangen we in de co¨efficientenmatrix de n-de kolom door de uitkomstenmatrix. Hierna delen we de determinant van deze nieuwe matrix door de determinant van de originele co¨efficientenmatrix en het antwoord op deze operatie is de n-de onbekende. Zo kunnen we alle onbekenden bepalen. Dit klinkt waarschijnlijk nog een beetje vaag, daarom hebben we hier een voorbeeld van de regel van Cramer toegepast op een stelsel met twee vergelijkingen en twee onbekenden: 6x + 12y = 45
De matrix voor x wordt dan:
45 23
6 2
2x + 5y = 23 12 x 45 = × 5 y 23
12 6 . En voor y krijgen we: 5 2
45 . Hiermee krijgen we de volgende 23
HOOFDSTUK 1. BASIS WISKUNDE
10
oplossingen: 1 45 × 5 − 12 × 23 = −8 6 × 5 − 12 × 2 2 6 × 23 − 45 × 2 y= =8 6 × 5 − 12 × 2
x=
Met deze methode kunnen we relatief eenvoudig stelsels oplossen met een n aantal onbekenden en deze methode komt ook voor in het raytracen (zie 3.2.3).
1.3
Verzamelingenleer
Met verzamelingenleer willen we in dit werk eigenlijk alleen maar dat de lezer het kan volgen als wij zaken als verzamelingen opschrijven. Daarom zullen we ons hier beperken tot een lijst met verklarende tekens, die, indien nodig, kan worden nagelezen als er verzamelingen worden gebruikt. Al de volgende tabellen komen uit een onuitgegeven hoofdstuk over verzamelingenleer geschreven door onze wiskundeleraar, meneer Boers. [4] {} geven aan dat hierbinnen een verzameling staat : betekent letterlijk ‘waarvoor geldt’ ∈ betekent ‘zit in’ ∨ betekent ‘of’ ∧ betekent ‘en’ [a, b] geven een interval aan van alle getallen van a tot en met b, waarbij a en b bij het interval horen (a, b) geven een interval aan van alle getallen tussen a en b, waarbij a en b niet bij het interval horen ∞ betekent oneindig ∝ betekent ‘is rechtevenredig met’ Belangrijke verzamelingen om te onthouden zijn: de belangrijkste getallenverzamelingen N de natuurlijke getallen N = {0, 1, 2, 3, 4, . . .} Z de gehele getallen Z = {. . .√, −2, −1, 0, 1, 2, 3, . . .} R de re¨ele getallen, zoals π, −1 27 , 2, 431,5 log (3), et cetera.
1.4
Eenheidsobjecten
Eenheidsobjecten zijn bijzondere vormen van objecten. Er zijn een aantal belangrijke eenheidsobjecten die veel worden gebruikt bij het raytracen, namelijk: • Het eenheidsvierkant - een vierkant van afmetingen 1 × 1 • De eenheidsvector - een vector met lengte 1 • De eenheidscirkel - een cirkel met straal 1 • De eenheidsbol - een bol met straal 1 • De eenheidsmatrix - een vierkante matrix waarvan de waarden op de diagonaal van linksboven tot rechtsonder allemaal 1 zijn en de rest van de matrix waarde 0 heeft.
1.5 1.5.1
Orthonormale basissen Wat zijn orthonormale basissen?
Een orthonormale basis is een stelsel gevormd door drie vectoren, waarvan elk loodrecht op het vlak gevormd door de andere twee staat, en die allemaal eenheidsvectoren zijn. Voor een orthonormale basis, gevormd door
HOOFDSTUK 1. BASIS WISKUNDE
11
~a, ~b en ~c geldt dus: ~a = ~b × ~c ~b = ~a × ~c ~c = ~a × ~b |~a| = |~b| = |~c| = 1 Omdat het drie vectoren zijn, die niet parallel zijn en niet alledrie in hetzelfde vlak liggen, kan elke andere 3D vector uitgedrukt worden in een combinatie van deze drie vectoren. Orthonormale basissen worden vaak gebruikt in raytracing, omdat je met deze systemen op een willekeurige plek in de 3D-wereld een plaatselijk co¨ordinatensysteem op kunnen zetten. Dit wordt bijvoorbeeld gebruikt voor camera’s (zie hoofdstuk 4).
1.5.2
Het opzetten van een orthonormale basis
Het opzetten van een orthonormale basis uit twee vectoren die niet evenwijdig zijn werkt als volgt. Stel we hebben vector ~a en vector ~b en we willen een orthonormale basis opstellen met als vectoren w, ~ ~u en ~v , dan gaan we als volgt te werk. Eerst maken we w ~ door een eenheidsvector van ~a te maken. We delen dus ~a door zijn eigen lengte, zodat we een vector krijgen die dezelfde richting heeft als ~a maar een lengte heeft van ´e´en: w ~=
~a |~a|
Vervolgens kunnen we met het uitproduct ~u bepalen als een vector die loodrecht staat op het vlak dat gedefinieerd wordt door ~b en w. ~ ~b × w ~ ~u = ~ |b × w| ~ En tenslotte kunnen we de laatste vector voor de orthonormale basis berekenen met het uitproduct van w ~ en ~u. Omdat w ~ en ~u eenheidsvectoren zijn, is de uitkomst van het uitproduct automatisch ook een eenheidsvector. We hoeven deze dus niet meer te normaliseren1 . ~v = w ~ × ~u
1.5.3
Orthonormale assenstelsels
Een orthonormale basis met een punt O waar de vectoren elkaar ontmoeten is een orthonormaal assenstelsel. Met een orthonormaal assenstelsel kan we een plaatselijk co¨ordinatenstelsel worden opgesteld en dit is waar orthonormale basissen het meest voor worden gebruikt. Het opstellen van een orthonormaal assenstelsel gaat hetzelfde als het opstellen van een orthonormale basis, alleen is hier de oorsprong O bekend. Orthonormale assenstelsels kunnen we gebruiken om gemakkelijk nieuwe vectoren te bepalen vanuit het punt O van dit assenstelsel. In het nieuwe stelsel kunnen we dan makkelijk de richting van deze vector bepalen. Ook kunnen we deze vectoren makkelijk terugrekenen naar het ‘echte’ (x, y, z)-assenstelsel. Dit zorgt meestal voor minder rekenwerk en daarom wordt deze methode meestal verkozen boven het direct berekenen van de richting van de nieuwe vectoren in het (x, y, z)-stelsel, zonder gebruik te maken van orthonormale assenstelsels. du Stel we hebben een nieuwe vector d~ = dv bepaald in het orthonormale (u, v, w)-stelsel, dan kunnen dw we deze richting omrekenen naar het (x, y, z)-stelsel met de volgende formule: d~ = du ~u + dv ~v + dw w ~
(1.1)
Omdat ~u, ~v en w ~ in (x, y, z)-co¨ ordinaten zijn uitgedrukt, krijgt d~ de goede (x, y, z)-co¨ordinaten als je de componenten van d~ vermenigvuldigt met respectievelijk ~u, ~v en w ~ en die bij elkaar optelt. 1 het
normaliseren van een vector betekent dat je van deze vector een eenheidsvector maakt
HOOFDSTUK 1. BASIS WISKUNDE
1.6
12
Alternatieve co¨ ordinatensystemen
Behalve het bekende (x, y, z) co¨ ordinatensysteem zijn er nog andere manieren om punten in een 3D-wereld aan te geven. Een tweetal van deze co¨ ordinatensystemen gebruiken wij naast het (x, y, z)-systeem ook bij het raytracen en deze zullen we hier behandelen.
1.6.1
Cylindrische co¨ ordinaten
Het systeem van cylindrische co¨ ordinaten werkt met een afstand r, een hoek φ en de y-co¨ ordinaat. Een punt, beschreven met cylindrische co¨ordinaten ziet er dus zo uit: p(r, φ, y). De r is de afstand van het punt tot de y-as, loodrecht gemeten op de y-as. De hoek φ is een hoek in het (x, z)-vlak en wordt gemeten door p te projecteren op het vlak en de hoek te meten die de lijn Op0 maakt met de positieve z-as. Tenslotte is y het bekende y-co¨ ordinaat uit het (x, y, z)-stelsel (zie figuur 1.2). We kunnen de cilindrische co¨ ordinaten als volgt omrekenen naar (x, y, z)co¨ ordinaten:
1.6.2
x = r sin φ
(1.2)
y=y
(1.3)
z = r cos φ
(1.4)
Figuur 1.2: Cylindrische co¨ordinaten [1]
Bolco¨ ordinaten
Bij bolco¨ ordinaten wordt een punt gedefinieerd door de afstand r tussen het punt en de oorsprong van het stelsel, en door de twee hoeken θ en φ. φ is hierin dezelfde hoek als bij de cilindrische co¨ordinaten. Het is dus de hoek die het geprojecteerde punt in het (x, z)-vlak maakt met de positieve z-as. θ is de hoek tussen de ~ Hierin is O de oorsprong van het stelsel en p het punt dat we willen aanduiden. θ wordt gemeten y-as en Op. ~ en de y-as (zie figuur 1.3). vanaf de y-as en in het vlak, gedefinieerd door de hoek tussen de vector Op We kunnen de bolco¨ ordinaten als volgt omrekenen naar (x, y, z)-co¨ordinaten: x = r sin(φ) cos(θ) y = r sin(φ) sin(θ)
(1.5)
z = r cos φ
1.7
Steradiaal
De steradiaal is een eenheid voor de grootheid ruimtehoek. Waar een gewone radiaal een eendimensionale hoek is, die meestal gebruikt wordt in een tweedimensionale omgeving, is een steradiaal een tweedimensionale hoek, die gebruikt wordt in een drie- Figuur 1.3: dimensionale omgeving (zie figuur 1.4 (links)). De radiaal kan worden berekend uit Bolco¨ordinaten een booglengte (een ´e´endimensionale variabele) en de straal (ook een ´e´endimensionale [1] variabele) op de volgende manier: l θ= r De steradiaal vertoont eenzelfde verband met de tweedimensionale variant van de lengte: het oppervlak op een bol. Deze moet dan ook worden gedeeld door de straal van de bol in het kwadraat, omdat de straal een ´e´endimensionale variabele is, terwijl de steradiaal tweedimensionaal is. De steradiaal is dus als volgt te berekenen: A Ω= 2 r De omtrek van een cirkel met straal 1 is 2π. Zo is ook de radiaal van een volledige eenheidscirkel (hoek van 360 graden) 2π. Het oppervlak van een bol met straal 1 is 4π. Zo is ook de steradiaal van een volledige eenheidsbol 4π.
HOOFDSTUK 1. BASIS WISKUNDE
13
Figuur 1.4: Links: De ruimtehoek als rechthoekige vorm op een eenheidsbol. [5] Rechtsmidden: een ´e´endimensionale hoek in een tweedimensionaal stelsel: de radiaal. [6] Rechts: een tweedimensionale hoek in een driedimensionaal stelsel: de steradiaal. [6] Als we op een eenheidsbol een rechthoekige vorm nemen, kunnen we deze aangeven in steradialen . We kunnen deze echter ook nog op een andere manier aangeven, namelijk met bolco¨ordinaten. We hebben de hoek θ in het (x, y)-vlak, de hoek φ in het (x, z)-vlak en de straal r = 1. De lengte van de rechthoek zou in dit geval gelijk zijn aan de verandering in hoek θ (∆θ) en, mits de rechthoek op de x-as ligt, zou de breedte gelijk zijn aan de verandering in hoek φ (∆φ). Nemen we deze oneindig klein, dan krijgen we dθ en dφ. We mogen hier niet van het bijzondere geval uitgaan dat de rechthoek op de x-as ligt, dus zullen we het verband moeten vinden tussen dφ en de hoek onder welke de rechthoek ten opzichte van de x-as staat. Dit verband wordt gevormd door een factor sin θ. De breedte van de rechthoek wordt dus: sin θ · dφ (zie figuur 1.4 (rechts)). De oppervlakte van de rechthoek kunnen we dus alsvolgt berekenen: A = lengte × breedte = dθ sin θdφ = sin θdθdφ Maar deze oppervlakte is ook te berekenen met de verandering in steradiaal ω. Deze nemen we ook oneindig klein, waardoor we dω krijgen. We bepalen de oppervlakte dan op de volgende manier: A = dωr2 = dω Hieruit kunnen we het verband tussen de bolco¨ordinaten θ en φ en de steradiaal ω afleiden: dω = sin θdθdφ
(1.6)
Dit zullen we nodig hebben bij het belichtingsmodel.
1.8
Kleuren in de raytracer
Hoewel dit onderwerp eigenlijk vrijwel niets met wiskunde te maken heeft, hoort het wel bij de basis van de raytracer. Daarom hebben we toch besloten dit in hoofdstuk 1 in te voegen. Een kleur wordt in onze raytracer bepaald als een combinatie van de basiskleuren rood, groen en blauw. Een kleur moet gedefinieerd worden door een getal r, een getal g en een getal b, in die volgorde. ‘rgb’ geeft de verhouding aan waarin rood, groen en blauw worden gemengd. Zo zorgt rgb = (1, 0, 0) voor rood, want rood wordt met niets gemengd. rgb = (1, 0, 1) zorgt voor een paarstint, want rood en blauw worden in gelijke verhouding gemengd. Voor r, g en b geldt verder nog het volgende: 0≤r≤1 0≤g≤1 0≤b≤1 Het getal ´e´en geeft namelijk een ‘volledige’ hoeveelheid van die kleur aan en er kan nooit meer dan dat van ´e´en kleur worden toegevoegd aan het mengsel van kleuren. Op deze manieren zullen alle kleuren in de raytracer worden bepaald.
HOOFDSTUK 1. BASIS WISKUNDE
1.9
14
Bronnen en code
Bronnen Na elk hoofdstuk zullen we een lijst geven van nummers van bronnen die we gebruikt hebben voor het hoofdstuk. De lijst van bronnen is terug te vinden in onze bibliografie op pagina 88. De bronnen die we voor dit hoofdstuk gebruikt hebben zijn: [1], [5], [6], [4], [7], [8], [9], [10] en [11] Code Naast een verwijzing naar de bronnen, zullen we hier een verwijzing naar de broncode van de raytracer toevoegen. In de broncode waarnaar wordt verwezen, staan de onderwerpen die we in het hoofdstuk hebben behandeld geprogrammeerd. De code voor de vectoren en punten is terug te vinden in: point2d.h, point3d.h, vector3d.h2 . In rgbcolor.h staat de code voor de kleuren. Voor het rekenen met matrices hebben we de ‘eigen’ library gebruikt, die te vinden is op http://eigen.tuxfamily.org/.
2 Bij
de .h bestanden horen natuurlijk ook de .cpp bestanden
Hoofdstuk 2
Objecten en rays De twee belangrijkste elementen van het raytracen zijn de objecten en de rays. De objecten worden geraytracet en zorgen voor de sc`ene die moet worden afgebeeld. De rays gebruiken we om de sc`ene daadwerkelijk te raytracen. In dit hoofdstuk zullen we uitleggen hoe we een aantal objecten zo kunnen beschrijven dat we ze kunnen neerzetten in een wereld. Ook zullen we uitleggen wat rays zijn, en hoe we ze beschrijven.
2.1
Objecten
Om te kunnen raytracen, moeten we eerst een 3D-wereld bouwen, waarmee de computer kan werken. Om objecten in deze wereld te kunnen plaatsen en te kunnen kijken of rays ze raken, moeten we deze wiskundig kunnen beschrijven. Er zijn twee makkelijke manieren om objecten en oppervlakken te beschrijven. De ene manier is met impliciete vergelijkingen (die leiden tot impliciete oppervlakken) en de andere manier is met parametrische vergelijkingen (die leiden tot parametrische oppervlakten). Impliciete vergelijkingen zijn het tegenovergestelde van expliciete vergelijkingen. Een voorbeeld van een expliciete vergelijking is y = 5x. Hierbij is de y direct (expliciet) uitgedrukt in x. Een impliciete vergelijking zou de volgende kunnen zijn: y − 5x = 0. Hierin is y indirect (impliciet) uitgedrukt in x. Een impliciete vergelijking is dus een vergelijking in de vorm van R(x, y) = 0. Een parametrische vergelijking is een vergelijking waarin twee waarden, bijvoorbeeld de xwaarde en de y-waarde afhankelijk zijn van een parameter, bijvoorbeeld t. De x en y zijn hierbij onafhankelijk van elkaar. Wat belangrijk is om te onthouden van impliciete oppervlakken, is dat ze in een 3D wereld met een x-as, een y-as en een z-as gedefinieerd worden door een vergelijking in de vorm van: f (x, y, z) = 0 (2.1) Hierbij geldt dat f (x, y, z) < 0 het gebied aan de ene kant van de grens aanduidt, en f (x, y, z) > 0 het gebied aan de andere kant van de grens. Ook is het belangrijk om te onthouden dat impliciete oppervlakken ‘open’ of ‘gesloten’ kunnen zijn. De open oppervlakken strekken tot in het oneindige, zoals bijvoorbeeld een onbegrensd vlak, terwijl de gesloten oppervlakken een eindig oppervlak hebben, zoals bijvoorbeeld een bol. De makkelijkste objecten om te Figuur 2.1: Impliciet vlak raytracen zijn objecten die beschreven zijn met een impliciete vergelijking. [1]
2.1.1
Onbegrensde vlakken
Een vlak is een oneindig lang, plat oppervlak in een 3D wereld. Voorbeelden van vlakken zijn het (x, y)-vlak, ´ en manier het (x, z)-vlak en het (y, z)-vlak. Een vlak kan op verschillende manieren worden gedefinieerd. E´ om een vlak te defini¨eren, is om een punt in het vlak te nemen, en een normaalvector op het vlak. Dit is de manier die voor raytracen het handigst is, en die wij dus zullen gebruiken. Aangezien een vlak plat is, zijn de normaalvectoren op alle punten in het vlak aan elkaar gelijk. Het maakt dus niet uit op welk punt in het vlak de normaalvector wordt genomen. Om het vlak te defini¨eren zijn dus een normaalvector ~n en een in 15
HOOFDSTUK 2. OBJECTEN EN RAYS
16
het vlak gelegen punt a nodig. Er bestaat maar ´e´en vlak dat door punt a gaat met de richting aangegeven door ~n. Met deze twee waarden is het vlak dus vastgelegd. Om een impliciete vergelijking van een vlak op te ~ a stellen gebruiken we ook nog een punt p dat ergens in het vlak ligt. Hiermee kunnen we dan de vector p − bepalen die ook in het vlak ligt. Aangezien we weten dat de normaalvector loodrecht op het vlak staat, staat ~ a. Dus het inproduct van ~n en a − ~ p is gelijk aan deze ook loodrecht op elke vector in het vlak. Dus ~n⊥p − nul. Hiermee kunnen we de vergelijking van het vlak opstellen: ~ a) • ~n = 0 (p −
(2.2)
We kunnen deze vergelijking verder uitschrijven door de kentallen van de vectoren waarmee we werken in te vullen. Neemvoor ordinaten (x, y, z), voor het punt a de co¨ordinaten (ax , ay , az ) en voor ~n het punt p de co¨ nx de kentallen ny en vul dit in. Dit geeft het volgende: nz x − ax nx y − ay • ny = 0 z − az nz nx (x − ax ) + ny (y − ay ) + nz (z − az ) = 0 nx x + ny y + nz z − ax nx − ay ny − az nz = 0 Dit is makkelijker te schrijven als: Ax + By + Cz + D = 0
(2.3)
Hierin is A = nx , B = ny , C = nz en D = −ax nx − ay ny − az nz .
2.1.2
Begrensde vlakken
Om een begrensd vlak te defini¨eren gaan we uit van een oneindig vlak, en controleren we vervolgens of de punten in het oneindige vlak aan bepaalde voorwaarden voldoen. Voldoet een punt aan de voorwaarden, dan valt het dus binnen het begrensde vlak. Cirkels Voor begrensde vlakken in het algemeen bepalen we eerst de vlakvergelijking van het oneindige vlak waar het object in ligt, en nemen we vervolgens alleen de punten die er toe doen. Voor een cirkel zijn dit dus alle punten op een afstand r of minder van het middelpunt c afliggen. Hierin is r natuurlijk de straal van de cirkel. Een cirkel wordt dus uiteindelijk door drie zaken gedefinieerd: het middelpunt c, de straal r, en de normaalvector ~n. Deze normaalvector en c samen defini¨eren namelijk samen het onbegrensde vlak waar de cirkel in ligt, en de straal r en c samen bepalen welke punten van het onbegrensde vlak ook binnen de cirkel liggen. Hier hoeven we geen impliciete vergelijking van te maken, om dat deze gegevens al voldoende zijn om een cirkel te kunnen raytracen.(zie 3.2.1). Rechthoeken Voor de definitie van een rechthoek zullen we een punt en twee vectoren gaan gebruiken. Het punt p0 is het hoekpunt waar de twee vectoren ~a en ~b hun oorsprong hebben. Deze twee vectoren vormen twee zijden van de rechthoek en moeten dus loodrecht op elkaar staan. Uit de twee zijden kunnen we met het uitproduct een normaalvector berekenen. Deze volgt dus uit de andere gegevens. Een rechthoek wordt dus al gedefinieerd met ´e´en hoekpunt en twee zijden. Hiervoor geldt ook dat we geen impliciete vergelijking nodig hebben, omdat we met een aantal vergelijkingen kunnen bepalen of een punt op het onbegrensde vlak binnen de rechthoek ligt.
HOOFDSTUK 2. OBJECTEN EN RAYS
17
We noemen het punt in het onbegrensde vlak voor het gemak weer p. Dan kunnnen we met de volgende ongelijkheden bepalen of het punt p binnen de rechthoek ligt: 0 ≤ (p −~ p0 ) • ~a ≤ |~a|2 0 ≤ (p −~ p0 ) • ~b ≤ |~b|2
(2.4)
Dit behoeft waarschijnlijk nog enige uitleg. De ongelijkheden zijn erop gebaseerd dat het inproduct van twee vectoren ~k en m ~ te schrijven is als ~k • m ~ = kx · mx + ky · my + kz · mz . Doen we dit voor het inproduct van Figuur 2.2: Rechthoek met p , ~a, ~b o p −~ p0 en ~a, waarbij we p −~ p0 voor het gemak even P~ zullen noemen, en p [1] dan krijgen we: P~ • ~a = Px · ax + Py · ay + Pz · az Dit geldt voor een 3D-stelsel. We weten echter al van het punt p dat het in het vlak ligt (dat wordt namelijk gecontroleerd voordat we gaan kijken of het pun p ook binnen de rechthoek ligt), dus kunnen we uitgaan van een 2D-stelsel, gedefinieerd door de oorsprong p0 en de vectoren ~a en ~b. Dan geldt dus: P~ • ~a = Pa · aa + Pb · ab Hierbij is ab altijd nul omdat ~a en ~b de assen van het 2D-stelsel zijn waar we in werken (net zoals de y-co¨ ordinaat van de x-as altijd nul is). Dus geldt: P~ • ~a = Pa · aa Omdat ab altijd nul is, is de lengte van ~a te beschrijven als |~a| = aa . Als Pa · aa dan gelijk is aan |a|2 , dan is Pa = aa en valt de a-co¨ ordinaat van punt p dus op de zijde van het vierkant van afmetingen |~a| × |~a| met ~a als ´e´en van de zijden. Als Pa · aa ≤ |a|2 , dan ligt de a co¨ordinaat van p op deze zijde, ´of binnen dit vierkant, behalve als Pa < 0, want dan ligt het punt er alsnog naast, maar aan de andere kant. Op dezelfde manier is dit geheel voor ~b te beredeneren. Als een punt p dus aan beide vergelijkingen in vergelijking 2.4 voldoet, dan ligt de a co¨ordinaat dus ergens op de breedte van ~a en ligt de b co¨ordinaat ergens op de lengte van ~b. Zodra dit voor een punt het geval is, ligt het punt binnen de rechthoek met de twee zijden ~a en ~b. Tot slot hebben we nog de normaal van de rechthoek. De normaal van de rechthoek is te berekenen met het uitproduct van ~a en ~b. Deze moet dan echter nog wel genormaliseerd worden. We krijgen dus voor de normaal de formule: ~a × ~b (2.5) ~n = ~a × ~b Driehoeken Een driehoek is te defini¨eren als drie punten die niet op ´e´en lijn liggen. Als we een driehoek hebben met de ~ a, c − ~ b. Deze drie punten ~ a en c − punten a, b, en c, dan zijn de zijden van de driehoek de vectoren b − defini¨eren ook een onbegrensd vlak. Een driehoek ligt dan ook in een onbegrensd vlak en is altijd plat. De normaal is dus ook overal constant. Aangezien de normaal van de driehoek recht staat op het vlak dat door de twee zijden van de driehoek wordt gedefinieerd, kunnen we de eenheidsnormaal beschrijven als: ~n =
(b − a) × (c − a) |(b − a) × (c − a)|
(2.6)
Om te kijken of een bepaald punt p in de driehoek ligt, kijken we weer eerst of het in het vlak ligt. Zodra dit het geval is, moeten we kijken of het ook binnen de driehoek in dit vlak ligt. Hiervoor gebruiken we barycentrische co¨ ordinaten. Als een driehoek de hoekpunten a, b, en c heeft, kunnen we de barycentrische co¨ ordinaten van een punt op het vlak gedefinieerd door deze driehoek als volgt weergeven (zie figuur 2.3): p(α, β, γ) = αa + βb + γc
(2.7)
HOOFDSTUK 2. OBJECTEN EN RAYS
18
Om dan binnen te driehoek te zitten, is het logisch dat geen enkel punt verder dan een lengte van ´e´en van de zijden, in de richting van die zijde, weg mag zitten van de oorsprong. Dus gelden de volgende voorwaarden: α+β+γ =1
(2.8)
0 <α < 1 0 <β < 1
(2.9)
0 <γ < 1 Met deze ongelijkheden kunnen we bepalen of een punt op het vlak binnen de driehoek ligt. We hebben echter drie onbekenden, namelijk α, β en γ. Gelukkig kunnen we met vergelijking 2.8 een onbekende wegwerken. We kunnen namelijk α in de andere twee uitdrukkingen: α = 1 − β − γ. Dit invullen in vergelijking 2.7 geeft: p(α, β, γ) = (1 − β − γ)a + βb + γc p(α, β, γ) = a + βb − βa + γc − γa p(α, β, γ) = a + β(b − a) + γ(c − a)
(2.10)
De ongelijkheden in 2.9 kunnen we dan ook herschrijven. Hierbij verandert alleen de eerste: 0 <α < 1 0<1−β−γ <1 −1 < −β − γ < 0 1>β+γ >0 Het drietal wordt dus: 0 <β < 1 0 <γ < 1
(2.11)
0<β+γ <1 Als β = 0, dan hebben we een oneindige lijn door de hoekpunten a en c van de driehoek gedefinieerd (zie figuur 2.3). Dit is de γ-as. β = 0 invullen in vergelijking 2.10 geeft: p = a + γ(c − a)
(2.12)
~ a defini¨eren met Als we hierbij de ongelijkheid uit 2.11 erbij nemen, kunnen we stellen dat we de zijde c − 0 < γ < 1 en vergelijking 2.12. ~ a defini¨eren. Deze wordt dan: Op dezelfde manier kunnen we met γ = 0 en 0 < β < 1 de zijde b − p = a + β(b − a) Hiermee is de vergelijking uit 2.10 verklaard: dit is een combinatie van de vergelijkingen die de zijden γ en β aangeven. Zo kunnen we dus een driehoek en de zijden van een driehoek in barycentrische co¨ordinaten aangeven. Ook kunnen we hiermee bepalen of een bepaald punt binnen de driehoek valt, maar omdat dit nauw samenhangt met het snijden van rays met objecten, zullen we dit behandelen in 3.2.3 (zie pagina 23).
2.1.3
Bollen
Een bol is een... bol. Wiskundig gezien is een bol de verzameling van alle punten binnen een straal r van het middelpunt c. Oftewel B = {p : |p − c| ≤ r}. Bij het raytracen maken we alleen gebruik van het oppervlak van de bol. Oftewel de punten die precies op een afstand r van c liggen: O = {p : |p − c| = r}. Punt c wordt gedefinieerd door zijn drie co¨ ordinaten, dus c = (cx , cy , cz ). Een willekeurig punt p op het oppervlak van de bol heeft de co¨ ordinaten (x, y, z). Door de stelling van Pythagoras in 3D te gebruiken, kunnen we de lengte
HOOFDSTUK 2. OBJECTEN EN RAYS
19
p ~ c bepalen. Namelijk |p − c| = (x − cx )2 + (y − cy )2 + (z − cz )2 . Deze lengte is natuurlijk van vector p − weer gelijk aan de straal en dit vormt de definitie van een bol. Om het leesbaar te houden, is het handig om het wortelen weg te werken. We kwadrateren dus zowel de straal r als de afstand |p − c|. Dan krijgen we dus (x − cx )2 + (y − cy )2 + (z − cz )2 = r2 oftewel de impliciete vergelijking: (x − cx )2 + (y − cy )2 + (z − cz )2 − r2 = 0
(2.13)
Met deze vergelijking kunnen we een bol defini¨eren en dit is de definitie van een bol die wij zullen gebruiken. De normaal van een bol is tamelijk eenvoudig te bepalen. Deze is echter wel voor elk willekeurig punt op het oppervlak van de bol anders. Voor een punt op het oppervlak van de bol geldt namelijk dat de normaalvector gelijk is aan de vector van het middelpunt, naar het punt op de oppervlak van de bol. Omdat het voor raytracen handig is om een eenheidsnormaal te hebben, kunnen we deze vector nog delen door de straal, en we hebben een eenheidsnormaal. In formulevorm is dit: ~n =
2.2
~ c p− r
(2.14)
Rays
Een ray bij raytracing is een oneindig lange rechte Figuur 2.3: Driehoek met hoekpunten a, b, en c en een lijn, gedefinieerd door het punt o, de oorsprong en barycentrisch co¨ordinatenstelsel. [1] ~ de richting in de vorm van een eenheidsvector d, van het engelse ‘direction’. De parameter van een ray is t, waarbij t ∈ [−∞, +∞] en bij t = 0 zitten we in de oorsprong van de ray. Een willekeurig punt op de ray kunnen we dus defini¨eren als: p = o + td~
(2.15)
In raytracing hebben we vier verschillende typen rays. We onderscheiden de primaire rays, de secundaire rays, de schaduwrays en de lichtrays. Primaire rays worden vanuit de camera afgeschoten. Secundaire rays zijn de rays die zijn weerkaatst vanaf een object of iets dergelijks. Schaduwrays worden gebruikt om schaduwen aan te brengen en beginnen per definitie vanaf het oppervlak van een object. De lichtrays, tenslotte, beginnen in de lichtbronnen en zijn nodig voor sommige aspecten van verlichting. Hiermee hebben we de basis voor het gebruik van rays gelegd. In hoofdstuk 3 zullen we nog iets dieper ingaan op rays om te kunnen bepalen of ze objecten snijden.
2.3
En de raytracer?
Hoewel we met de informatie besproken in dit hoofdstuk nog helemaal niets kunnen raytracen, is het wel essenti¨ele stof voor het bouwen van een raytracer. In dit hoofdstuk hebben we geleerd hoe we een aantal simpele objecten wiskundig kunnen beschrijven en daarmee kunnen we eenvoudige sc`enes bouwen. We weten nu immers welke waarden we nodig hebben om de objecten, die in dit hoofdstuk zijn beschreven, aan te geven. Zonder sc`ene kunnen we natuurlijk niets raytracen, dus ondanks dat we nu een raytracer kunnen bouwen die niet kan raytracen (een waardeloze raytracer dus), was de informatie uit dit hoofdstuk toch een essentieel onderdeel van elke raytracer.
2.4
Bronnen en code
Bronnen Voor dit hoofdstuk hebben we de volgende bronnen gebruikt: [1], [12], [13] en [14].
HOOFDSTUK 2. OBJECTEN EN RAYS
20
Code De code voor een ray is terug te vinden ray.h. De code voor de objecten komt pas bij het volgende hoofdstuk.
Hoofdstuk 3
Rayintersectie H´et essenti¨ele onderdeel van raytracen is het bepalen of een ray een object raakt. Hoe dit kan worden bepaald, wordt in dit hoofdstuk uitgelegd. De eerste operatie die met een ray wordt uitgevoerd is om voor elk object in de 3D sc`ene te kijken of het object wordt geraakt door de ray. We zoeken vervolgens naar het raakpunt met de kleinste waarde van t1 , waarbij t niet nul mag zijn. Waarom mag t geen nul zijn? Als t nul zou mogen zijn, zou de ray een raakpunt kunnen hebben in zijn oorsprong. Dit geeft problemen bij bijvoorbeeld de een schaduwray, die per definitie zijn oorsprong heeft op een objectoppervlak. Deze zou dan als eerste raakpunt het object raken, waarvandaan het wordt afgeschoten, en dat is niet de bedoeling. Om dit te verkomen nemen we t in het interval t ∈ [, +∞), waarbij een klein, positief getal is, bijvoorbeeld = 10−4 .
3.1
Intersectie met impliciete oppervlakken
Om te kijken of een ray een impliciet oppervlak snijdt, kunnen we vergelijking 2.1 herschrijven. De x, y en z in de functie f (x, y, z) duiden een punt aan in een 3D wereld. Als we dit punt p noemen, geldt voor de co¨ ordinaten van p dat p = (x, y, z). We kunnen dus schrijven: f (p) = 0
(3.1)
Als een ray een impliciet oppervlak raakt, moet voor het bewuste punt gelden dat de co¨ordinaten van dit punt zowel aan de vergelijking van de ray, als aan de vergelijking van het vlak voldoen. Daarom kunnen we de vergelijking van de ray (2.15) substitueren in vergelijking 3.1. We krijgen dan: ~ =0 f (o + td)
(3.2)
Dit is de basis voor elke intersectie met impliciete oppervlakken.
3.1.1
Onbegrensde vlakken
Een impliciete vergelijking van een vlak was gegeven in vergelijking 2.2. Als we hierin de rayvergelijking substitueren krijgen we het volgende: (o + td~ − a) • ~n = 0 Hierin is o een punt, t een parameter, d~ een vector, a een punt en tenslotte ~n de normaalvector van het vlak. We kunnen de vergelijking herschrijven tot: (o − a)~n + td~ • ~n = 0 td~ • ~n = −(o − a)~n t= 1t
uit de rayvergelijking in 2.15
21
(a − o)~n d~ • ~n
(3.3)
HOOFDSTUK 3. RAYINTERSECTIE
22
In vergelijking 3.3 zijn o, a, ~n en d~ bekend. De onbekende t is dus al te berekenen. Als t is berekend, kunnen we deze invullen in 2.15 en hieruit het punt p berekenen. Het enige wat we nu nog moeten doen is kijken of t ∈ [, +∞). Als dit het geval is, hebben we een raakpunt voor het onbegrensde vlak. Wat nog wel even belangrijk is om op te merken is dat we een situatie krijgen waarin we gaan proberen te delen door nul. Namelijk als d~ • ~n = 0. Dit is als de ray loodrecht op de normaal van het vlak staat, dus als de ray evenwijdig loopt aan het vlak. In C++, de programmeertaal waarin we de raytracer schrijven, geeft delen door nul echter geen foutmelding, maar de uitkomst ‘infinity’, oftewel oneindig. Dit zorgt dus voor een raakpunt op een oneindige afstand, en dit wordt nooit gerenderd. Er treden dus geen problemen op bij het delen door nul in dit geval.
3.1.2
Bollen
Zoals we in hoofdstuk 2 hebben gezien, is de impliciete vergelijking voor een bol de volgende: (x − cx )2 + (y − cy )2 + (z − cz )2 − r2 = 0 (2.13). Deze kan ook worden geschreven als: (p − c) • (p − c) − r2 = 0
(3.4)
Want: x − cx x − cx (p − c) • (p − c) = y − cy • y − cy = (x − cx )(x − cx ) + (y − cy )(y − cy ) + (z − cz )(z − cz ) z − cz z − cz Als we in vergelijking 3.4 de p weer vervangen door vergelijking 2.15 te substitueren, krijgen we het volgende: (o + td~ − c) • (o + td~ − c) − r2 = 0
(3.5)
(td~ + (o − c)) • (td~ + (o − c)) − r2 = 0 ~ 2 + (2(o − c) • d)t ~ + (o − c) • (o − c) − r2 = 0 (d~ • d)t
(3.6)
Dit uitschrijven geeft:
Dit is een tweedegraadsvergelijking die op te lossen is met de ABC-formule: D = b2 − 4ac √ √ −b + D −b − D ∨ t= 2a 2a Hierin is a de co¨efficient van t2 , b de co¨efficient van t en c de term zonder t in vergelijking 3.6. Aangezien deze vergelijking behalve t weer alleen maar bekenden bevat, kunnen we t berekenen. Het enige wat dan nog rest is kijken of t ∈ [, +∞). Als dit ook waar is, hebben we de raakpunten met een bol.
3.2
Intersectie met begrensde vlakken
Om snijpunten met begrensde vlakken te bepalen, kijken we eerst of de ray een snijpunt heeft met het onbegrensde vlak waar het begrensde vlak in ligt. Vervolgens kijken we of het snijpunt binnen de grenzen van het begrensde vlak ligt. We zullen dit hier demonstreren voor de begrensde vlakken genoemd in hoofdstuk 2.
3.2.1
Cirkels
Als een cirkel gedefinieerd is met de normaalvector ~n, de straal r en het middelpunt c kunnen we al bepalen of een afgeschoten ray de cirkel raakt. Eerst bepalen we met de manier beschreven in 3.1.1 of de ray het vlak raakt waar de cirkel in ligt. Als hij dit vlak raakt, noemen we het punt waar de ray het vlak raakt p. Om te kijken of dit punt ook in de cirkel ligt, nemen we de vector van c naar p en bepalen we daar de lengte van.
HOOFDSTUK 3. RAYINTERSECTIE
23
Als deze lengte groter is dan de straal weten we dat het punt p buiten de cirkel ligt, maar is de lengte kleiner dan of gelijk aan de straal, dan ligt hij binnen de cirkel. Oftewel, om in de cirkel te liggen moet p aan de volgende voorwaarden voldoen: q ~ c| = (px − cx )2 + (py − cy )2 + (pz − cz )2 |p − ~ c| ≤ r |p − Alle waarden in deze vergelijkingen zijn al bekend. Punt p is met de rayvergelijking bepaald toen we keken of de ray het vlak raakte, middelpunt c staat in de definitie van de cirkel en de straal r ook. Hiermee kunnen we dus kijken of een ray de cirkel raakt.
3.2.2
Rechthoeken
Eerder is al gezegd dat alle punten in een rechthoek voldoen aan twee ongelijkheden, namelijk de ongelijkheden in 2.4. Met deze ongelijkheden kunnen we voor elk punt p op het vlak kijken of dit binnen de rechthoek ligt. Eerst kijken we met de methode beschreven in 3.1.1 of de ray een snijpunt heeft met het onbegrensde vlak dat gedefinieerd is door de rechthoek. Als dit het geval is, betekent dit dat punt p aan vergelijking 2.15 voldoet. We kunnen dus p substitueren in 2.4. We krijgen dan: 0 ≤ (o + td~ − p0 ) • ~a ≤ |~a|2 0 ≤ (o + td~ − p0 ) • ~b ≤ |~b|2
(3.7)
Omdat t al is berekend toen we keken of de ray een snijpunt met het onbegrensde vlak had, zitten er in 3.7 geen onbekenden meer. Ook weten we al dat t ∈ [, +∞). De ongelijkheden kunnen we dus gewoon controleren. Als aan allebei deze ongelijkheden is voldaan, weten we dat de ray een snijpunt heeft met de rechthoek.
3.2.3
Driehoeken
In 2.1.2 waren we al begonnen met de barycentrische co¨ordinaten. Om een snijpunt met een driehoek te berekenen zullen we daarmee verder werken. Om te kijken of een ray het vlak raakt waar de driehoek in ligt, gebruiken we de vergelijking die we hebben opgeschreven in 2.10. Om een snijpunt te hebben moet het punt p uit deze vergelijking ook weer voldoen aan het punt p uit de rayvergelijking. Dus kunnen we een substitutie uitvoeren waardoor we het volgende krijgen: o + td~ = a + β(b − a) + γ(c − a)
(3.8)
Dit verschilt van de vergelijkingen die we voor cirkels en rechthoeken moesten oplossen omdat er hier drie onbekenden zijn, namelijk t, β en γ. Als we dit anders schrijven kunnen we dit als een vergelijking schrijven met alleen vectoren en onbekende parameters erin, namelijk op de volgende manier. td~ − β(b − a) + γ(c − a) = a − o td~ + β(a − b) + γ(a − c) = a − o
(3.9)
Omdat we een vector krijgen als we twee punten van elkaar aftrekken, zijn (a − b), (a − c) en (a − o) vectoren. Dus kunnen we de vergelijkingen voor de x, y en z-waarden in drie verschillende vergelijkingen opschrijven. We krijgen dan het volgende: tdx + β(ax − bx ) + γ(ax − cx ) = ax − ox tdy + β(ay − by ) + γ(ay − cy ) = ay − oy
(3.10)
tdz + β(az − bz ) + γ(az − cz ) = az − oz Omdat dit een lineair stelsel is (alle onbekenden zijn eerstegraads), en er evenveel vergelijkingen als onbekenden zijn, kunnen we dit geheel oplossen met de regel van Cramer. Hiervoor schrijven we de drie vergelijkingen
HOOFDSTUK 3. RAYINTERSECTIE
24
op in matrices. Dit ziet er dan op de volgende manier uit: ax − bx ax − cx dx β a x − ox ay − by ay − cy dy γ = ay − oy az − bz az − cz dz t az − oz
(3.11)
Volgens de regel van Cramer kunnen we nu de eerste onbekende bepalen op devolgende manier. In de a x − ox matrix vervangen we de co¨efficienten in de eerste kolom door de vector ay − oy . Vervolgens delen we az − oz de determinant van deze matrix door de determinant van de originele co¨efficientenmatrix. Hiervan is de uitkomst dan, in dit geval, β. Zo kunnen we voor de tweede onbekende de co¨efficienten in de tweede kolom vervangen, en voor de derde onbekende de co¨efficienten in de derde kolom. De matrices met de vervangen kolommen zien er als volgt uit: ax − ox ax − cx dx B = ay − oy ay − cy dy az − oz az − cz dz ax − bx ax − ox dx C = ay − by ay − oy dy az − bz az − oz dz ax − bx ax − cx ax − ox T = ay − by ay − cy ay − oy az − bz az − cz az − oz Als we de originele co¨efficentenmatrix dan O noemen, kunnen we β, γ en t als volgt bepalen met de regel van Cramer: det(B) det(O) det(C) γ= det(O) det(T ) t= det(O)
β=
(3.12)
Omdat de determinanten van B, C, T en O erg lang worden om op te schrijven met de originele variabelen, vervangen we deze even door handiger variabelen, zodat de matrixvergelijking (3.11) er zo uit ziet: a b c β d e f g γ = h (3.13) i j k t l Hierin zijn de variabelen dus: a = ax − bx , b = ax − cx , c = dx , e = ay − by , f = ay − cy , g = dy , i = az − bz , j = az − cz , k = dz ,
d = ax − ox , h = ay − oy , l = az − o z .
HOOFDSTUK 3. RAYINTERSECTIE
Dan worden de verschillende matrices en a O = e i
25
hun b f j
determinanten dus: c g k
det(O) = af k + bgi + cej − cf i − bek − agj = a(f k − gj) + b(gi − ek) + c(ej − f i) d b c B = h f g l j k det(B) = df k + bgl + chj − cf l − bhk − dgj = d(f k − gj) + b(gl − hk) + c(hj − f l) a d c C = e h g i l k det(C) = ahk + dgi + cel − chi − dek − agl = a(hk − gl) + d(gi − ek) + c(el − hi) a b d T = e f h i j l det(T ) = af l + bhi + dej − df i − bel − ahj = a(f l − hj) + b(hi − el) + d(ej − f i) Zo kunnen we dus de onbekenden uitrekenen: d(f k − gj) + b(gl − hk) + c(hj − f l) β= a(f k − gj) + b(gi − ek) + c(ej − f i) a(hk − gl) + d(gi − ek) + c(el − hi) γ= a(f k − gj) + b(gi − ek) + c(ej − f i) a(f l − hj) + b(hi − el) + d(ej − f i) t= a(f k − gj) + b(gi − ek) + c(ej − f i)
(3.14)
Vervolgens hoeven we alleen nog maar te checken of β en γ wel aan de ongelijkheden in 2.11 voldoen en of t ∈ [, +∞) en als dit waar is, hebnen we het punt gevonden waar de ray de driehoek raakt.
3.3
En de raytracer?
Na dit hoofdstuk kunnen we in principe nog steeds niets raytracen, maar we hebben een stevige basis gelegd. We kunnen op dit moment een sc`ene bouwen, bestaande uit bollen, cirkels, rechthoeken, vlakken en driehoeken en we kunnen controleren of uitgestuurde rays objecten raken. Hoewel we nog geen rays kunnen uitzenden en dus nog geen plaatje kunnen renderen, is dat wel het enige wat nog ontbreekt om een volledig werkende, zeer eenvoudige raytracer te bouwen.
3.4
Bronnen en code
Bronnen De bronnen die we voor dit hoofdstuk hebben gebruikt zijn: [1] en [15]. Code De code voor de objecten is terug te vinden in: plane.h, rectangle.h, circle.h en sphere.h. In geometricobject.h staat de algemene interface voor een object.
Hoofdstuk 4
Camera en perspectief Om een realistische afbeelding te renderen, is het logisch dat we de 3D-wereld in perspectief willen zien. In dit hoofdstuk zullen we eerst uitleggen hoe perspectief werkt bij raytracen en daarna hoe we een virtuele camera maken, die we door de sc`ene kunt bewegen om de wereld vanuit verschillende punten te bekijken.
4.1
Perspectief
Perspectief is het weergeven van diepte. Dit gaat met raytracen net zoals in de echte wereld. Normaal kijken we vanuit ons oog (een punt) de wereld in. De stralen bereiken onze ogen allemaal onder een verschillende hoek. Dit gebeurt ook met raytracing. We schieten de stralen onder een hoek af vanuit het eyepoint (het ‘oog’) richting de pixel, die we aan het raytracen zijn. De stralen beginnen dus in het eyepoint en gaan door het scherm heen (zie figuur 4.1). De afstand d is de afstand tussen het scherm en het eyepoint. Punt e is het
Figuur 4.1: Afschieten van stralen vanuit het eyepoint door het scherm [1]
eyepoint. Door d en e te vari¨eren kunnen we verschillende soorten afbeeldingen krijgen. We kunnen: • d groter maken. Door d groter te maken, wordt het gebied dat we zien kleiner, het scherm staat immers verder weg. Hierdoor zoomen we in. • d kleiner maken. Door d kleiner te maken, wordt het gebied dat we zien groter. Hierdoor zoomen we uit. • e veranderen. Door e te veranderen, wordt d ook groter of kleiner. Hierdoor kunnen we dus ook in- of
26
HOOFDSTUK 4. CAMERA EN PERSPECTIEF
27
Figuur 4.2: Orthonormaal assenstelsel met e als oorsprong, u, v, w als assen. Rechtsonder het assenstelsel van de wereld. up ~ is dit geval gelijk aan ~v omdat die recht omhoog staat. [1]
uitzoomen. Doordat het eyepoint verandert, verandert echter ook het perspectief. De stralen worden nu immers vanaf een ander punt afgeschoten. Daarom kunnen we het best e constant houden. • De grootte van de pixels te veranderen. Door de pixels groter of kleiner te maken, veranderen we de grootte van het scherm. Ook hiermee kunnen we dus in- of uitzoomen. Om in en uit te zoomen kunnen we dus het best de d veranderen of de pixelgrootte veranderen.
4.2
Camera
Om van uit verschillende oogpunten de sc`ene te kunnen bekijken, moeten we een virtuele camera maken. Deze camera moeten we vrij kunnen bewegen. Vervolgens schiet de camera de rays de wereld in. De plek van de camera wordt bepaald door: • e Het eyepoint. De plek vanaf waar de rays worden afgeschoten. • l Het look-at-point. Het punt waar de camera naartoe kijkt. • up ~ De upvector, die de bovenkant van de camera aangeeft. • d De afstand tussen het scherm en de camera. De camera zorgt vervolgens voor het renderen van een pixel.
4.2.1
Orthonormale basis
Orthonormale basis berekenen We moeten eerst een orthonormale (u, v, w)-basis berekenen met het punt e als oorsprong. Het scherm is vervolgens parallel aan het vlak van ~u en ~v . Hiermee kunnen we vervolgens de richting van de ray binnen de orthonormale basis berekenen en deze vervolgens omrekenen naar wereld co¨ordinaten (zie figuur 4.2). ~ e. Voor de orthonormale basis nemen we de omgeDe camera kijkt richting l, de kijkrichting is dus l − keerde kijkrichting, zodat de w as ook hier naar ons toe komt. Dus geldt: w ~=
e−l |e − l|
(4.1)
HOOFDSTUK 4. CAMERA EN PERSPECTIEF
28
Vervolgens kunnen we met uitproducten de andere twee assen ~u en ~v berekenen:1 up ~ ×w ~ |up ~ × w| ~ ~v = w ~ × ~u
~u =
(4.2) (4.3)
Als de kijkrichting gelijk of tegengesteld is aan de upvector, moet er handmatig een orthonormale basis worden gekozen. De camera kan immers niet in de dezelfde richting kijken als de upvector. Als de camera in derichting van de upvector (dus recht omhoog, als de camera niet gerold heeft) kijkt wordt de basis 0 1 0 ~u = 0, ~v = 0, w ~ = 1 gekozen, als de camera tegengesteld aan de richting van de upvector kijkt 1 0 0 1 0 0 wordt de basis ~u = 0, ~v = 0, w ~ = −1 gekozen. 0 1 0 Camera draaien We kunnen de camera van richting veranderen door e en l aan te passen. Daarnaast kunnen we de camera nog rollen (zie roll in figuur 4.2). Dit doen we door de upvector aan te passen. De upvector is een eenheidsvector 0 die de bovenkant van de camera aangeeft. Als de camera recht staat is die 1. Door de x en y aan te 0 passen kunnen we de camera laten rollen. Omdat het een eenheidsvector is, is de lengte 1. We kunnen de waarden voor x en y voor hoek α in radialen dus bereken met de eenheidscirkel (de hoek α wordt gemeten vanaf de bovenkant van de eenheidscirkel en niet vanaf de rechterkant, daarom nemen we voor de x de sinus en de y de cosinus): sin α (4.4) up ~ = cos α 0
4.2.2
Richting van de ray berekenen
Met behulp van de orthonormale basis kunnen we vervolgens de ray berekenen. Het startpunt van de ray is het eyepoint dus: o = e. Vervolgens moeten we de richting van de ray berekenen. Daarvoor moeten we de uv , de vv en de wv co¨ ordinaten van de pixel op de orthonormale basis uvw berekenen. Het scherm ligt op afstand d in de diepte dus w = −d (w ~ is immers tegengesteld gericht aan de kijkrichting). uv en vv berekenen De uv en vv co¨ ordinaten hangen af van: • De resolutie hres bij vres • De pixelgroote s • De co¨ ordinaten van het samplepunt px en py (zie hoofdstuk 5.1, pagina 31)2 • De rij r en kolom c van de pixel waarvoor we de co¨ordinaten aan het berekenen zijn. 1 Omdat
w ~ en ~ u al eenheidsvectoren zijn, wordt ~v dat ook en hoeven we deze niet meer te normaliseren. samplepunt geeft een plaats binnen een pixel aan. Zo kunnen we later meerdere verschillende samples nemen. Zonder samples wordt de ray door het midden van de pixel afgeschoten, dan geldt px = 21 en py = 12 . 2 Het
HOOFDSTUK 4. CAMERA EN PERSPECTIEF
29
In figuur 4.3 staat een voorbeeld van een scherm van 8 bij 6 pixels. Het scherm ligt gecentreerd met de w as in het midden (de blauwe punt). We gaan vervolgens alle pixels af. Bij de pixel linksonder is c = 0 en r = 0. Het midden ligt aan de linkerkant van het vak waarvoor geldt c = 12 hres en r = 21 vres . Om de pixel om te rekenen naar co¨ ordinaten moeten we er dus respectievelijk 21 hres en 1 2 vres van aftrekken. Vervolgens tellen we daar nog het samplpunt bij op. Tenslotte moeten we het nog vermenigvuldigen met de pixelgrootte s, ´e´en vakje is immers s groot. Voor uv en vv geldt dus: 1 uv = s(c − hres + px ) 2 1 vv = s(r − vres + py ) 2
(4.5) (4.6)
Figuur 4.3: Een scherm van 8x6 pixels [1] uv De vector ~v die de richting van de ray aangeeft wordt dan vervolgens ~v vv −d Omrekenen naar wereldco¨ ordinaten Vervolgens moeten we de co¨ ordinaten nog omrekenen naar wereldco¨ordinaten, zodat ze we ze als richting voor de ray kunnen gebruiken. Omdat uvw een orthonormale basis aangeeft, kunnen we de vector als volgt omrekenen (d~ is de vector voor de kijkrichting in wereldco¨ordinaten): d~ = uv · ~u + vv · ~v − d · w ~
(4.7)
We hebben nu het beginpunt o en de richting d~ van de vector berekend. Sc` ene renderen De camera is tenslotte verantwoordelijk voor het afschieten van de rays en dus het feitelijk renderen van de sc`ene. De camera wordt steeds gevraagd om een pixel te renderen. Hij schiet vervolgens een ray af voor elke pixel. Als de ray een object raakt krijgt de pixel de desbetreffende kleur. Later zullen we die kleur door middel van lichtberekeningen bepalen.
4.3
En de raytracer?
Op dit moment kunnen we onze eerste echte raytracer bouwen. Nu we een camera kunnen neerzetten, rays kunnen uitsturen en perspectief in ons plaatje kunnen krijgen, kunnen we een raytracer bouwen die plaatjes kan renderen. Dit zijn echter nog lang niet de plaatjes die we willen renderen met een raytracer, omdat het ‘echtheidsgehalte’ nog vrij laag is. Een voorbeeld van een plaatje, gerenderd met het programma dat we nu kunnen schrijven, is te zien in 4.4. Het is duidelijk dat deze raytracer nog stukken beter kan.
Figuur 4.4: Een plaatje gerenderd met een zeer simpele raytracer
HOOFDSTUK 4. CAMERA EN PERSPECTIEF
4.4
Bronnen en code
Bronnen De bron die we voor dit hoofdstuk hebben gebruikt is: [1]. Code De code voor de camera is terug te vinden in camera.h. De camera gebruikt vervolgens de tracer, die in tracer.h en raycast.h staat om de rays af te schieten.
30
Hoofdstuk 5
Sampling Als we een bol gaan raytracen, zien we al direct het eerste probleem. Er treedt kartelvorming (aliasing) op. De lijnen die eigenlijk rond hadden moeten zijn, worden kartelig. In dit hoofdstuk zullen we uitleggen hoe dit komt en hoe we dit op kunnen lossen.
5.1
Aliasing
Aliasing ontstaat doordat een scherm bestaat uit een rooster van vierkantjes: pixels. Als we op dat rooster een schuin vlak willen aangeven, dan valt het schuine stuk tussen twee pixels in. De pixels zouden eigenlijk half geel, half grijs moeten zijn (zie figuur 5.2). Hetzelfde gebeurde bij de bol. Doordat de lijnen ‘tussen twee pixels in’ vallen, ontstaan er kartels. Wat natuurlijk een oplossing is, is om de afbeelding op zo’n hoge resolutie te renderen, dat we de kartels niet meer zien. We kunnen de resolutie Figuur 5.1: Aliasing bij een bol echter niet onbeperkt groter maken: het computerscherm heeft bijvoorbeeld maar een beperkte resolutie. We kunnen het probleem ook oplossen door meerdere ‘monsters’ voor ´e´en pixel te nemen. We sturen niet ´e´en ray uit voor een pixel, maar meerdere. We nemen dus meerdere ‘samples’ voor ´e´en pixel. Uiteindelijk hebben we meerdere kleuren berekend voor een pixel, waar we vervolgens het gemiddelde van nemen. Hierdoor krijgen de pixels een kleur die tussen de kleuren van de twee grenzende vlakken in zit en wordt de aliasing minder. Om de verschillende rays uit te sturen, moeten we eerst weten hoe we de samples nemen.
5.2
Sampling
Figuur 5.2: Twee schuinen vlakken, afgebeeld op 7x5 pixels [1] 1 Anti-aliasing
Er zijn verschillende methodes om te samplen. In deze paragraaf zullen we ze beschrijven en uitleggen hoe we ze in onze raytracer gebruiken. We hebben de samples nu alleen nodig voor het anti-aliasen1 , maar we zullen ze later ook voor andere processen binnen het raytracen gebruiken. Omdat het genereren van samplepunten tijd kost, berekenen we voor het raytracen al een aantal verzamelingen van een s aantal samples. Als er vervolgens ergens samples nodig zijn in onze raytracer, zorgt de samplecode ervoor dat er een samplepunt op een eenheidsvierkant wordt teruggegeven, die dus al eerder berekend is. Hiermee besparen we tijd op het raytracen.
is het tegenwerken van de kartelvorming.
31
HOOFDSTUK 5. SAMPLING
32
Figuur 5.4: Moir´e patterns die ontstaan (bijv. in de rode cirkels). Links geen sampling, midden regular sampling, rechts random sampling. [1]
5.2.1
Regular sampling
De makkelijkste manier om te samplen is regular sampling. De verschillende samples worden regelmatig verdeeld over een vierkant. We nemen dus op een regelmatige manier verschillende samples voor een pixel (zie figuur 5.3). Door de meerdere samples, wordt de aliasing minder. Dit werkt als volgt. We nemen een s aantal samples (het aantal samples moet een vierkants√ getal zijn2 ). n is dan n = s. We kunnen het eenheidsvierkant dus verdelen in n bij n hokjes. We gaan vervolgens alle hokjes af. We zijn dan steeds bij hokje (p, q) (zie figuur 5.3). Aangezien de hokjes op een eenheidsvierkant liggen, zijn ze n1 bij n1 groot. We willen steeds een ray schieten door het midden van het hokje, dus de co¨ordinaten zijn x = (p + 21 ) · n1 en y = (q + 21 )· n1 , of te wel x = p+0.5 en y = q+0.5 ordinaten n n . De co¨ worden vervolgens opgeslagen, zodat ze later gebruikt kunnen worden. Hoewel de kartels verdwijnen, verdwijnen niet al onze problemen. De zogenaamde moir´e patterns blijven bestaan (zie figuur 5.4). Moir´e patterns ontstaan doordat de samples op Figuur 5.3: Regular sampling. In dit voor- een regelmatige manier worden genomen. We nemen dan wel meerdere samples, maar doordat we ze op een regelmatige mabeeld: p = 3 en q = 1 [1] nier nemen, ontstaan er onbedoelde patronen op de afbeelding. Een oplossing hiervoor is om de samples willekeurig te nemen. In plaats van de patronen ontstaat er ruis, wat minder opvalt in een afbeelding.
5.2.2
Random sampling
In plaats van de samples regelmatig te verdelen, kunnen we dat ook willekeurig doen. De computer berekent twee willekeurig getallen a en b tussen de 0 en de 1. Deze kunnen direct gebruikt worden als co¨ordinaten op het eenheidsvierkant. De co¨ ordinaten zijn dus x = a en y = b. Doordat de samples willekeurig genomen worden, verdwijnen de moir´e patterns en komt er ruis voor in de plaats (zie figuur 5.4). Het nadeel hieraan is dat de punten volstrekt willekeurig verdeeld zijn, hierdoor kan het gebeuren dat er voor bepaalde stukjes van het eenheidsvierkant helemaal geen sample wordt genomen, terwijl er voor andere stukken van het eenheidsvierkant juist ontzettend veel samples worden genomen. Daardoor ontstaat er te veel ruis en kunnen we nog maar een deel van de afbeelding goed zien. 2 Een
vierkantsgetal S is een nummer waarvoor geldt: S = n2 en n ∈ N
HOOFDSTUK 5. SAMPLING
5.2.3
33
Jittered sampling
Het beste is om de eerste twee methodes te combineren. We verdelen de eenheidsvierkant weer in hokjes, maar in plaats van een sample midden in het hokje te nemen, plaatsen we ze op een willekeurige plek in het hokje. Hierdoor zorgen we ervoor, dat er wel samples voor alle delen van de eenheidsvierkant zijn, maar ook dat ze niet volstrekt regelmatig verdeeld zijn. Ook dit is simpel te implementeren. In plaats van de p + 0.5 en q + 0.5, nemen we p + a en q + b waarbij a en b weer willekeurige getallen tussen de 0 en de 1 zijn. Voor de co¨ ordinaten van het samplepunt geldt dus: 1 n 1 y = (q + b) · n
x = (p + a) ·
5.3
Gebruik van de samples
De samplers kunnen we vervolgens gebruiken bij het afschieten van de rays. Voor ´e´en pixel worden er met behulp van de samplers verschillende rays afgeschoten. De camera zal steeds om nieuwe samples vragen, die hij gebruikt om de rays af te schieten. Hij berekent voor elke sample de kleur en daar neemt hij vervolgens het gemiddelde van. We maken gebruik van jittered sampling bij het renderen van de uiteindelijke afbeeldingen, omdat dit de mooiste plaatjes oplevert. Voor de testafbeeldingen maken we gebruik van regular sampling met maar ´e´en sample per pixel, zodat we altijd een sample in het midden hebben. Dit doen we, omdat het renderen met minder samples veel sneller gaat en bij testafbeeldingen de kwaliteit nog niet van belang is. Daarnaast zouden we de samples nog kunnen gebruiken bij andere zaken binnen het raytracen. Deze technieken hebben we echter niet ge¨ımplementeerd in onze raytracer. We gebruiken sampling onder anderen bij: belichtingsmodellen (, depth of field en het renderen van halfschaduwen.
5.4
En de raytracer?
In dit hoofdstuk hebben we geleerd hoe we kartelrandjes kunnen voorkomen door samples te gebruiken. Op dit moment is het raytracen nog zo eenvoudig dat het nemen van samples nauwelijks uitmaakt voor het eindresultaat. Later, echter, als we belichting (hoofdstuk 6) en reflectie (hoofdstuk 10) en dergelijke gaan toevoegen aan onze raytracer, is sampling wel degelijk van belang. In figuur 5.5 zien we 10.3 aan de ene kant gerenderd zonder samples en aan de andere kant gerenderd met 25 samples. Het verschil is duidelijk te zien.
5.5
Bronnen en code
Bronnen De bronnen die we voor dit hoofdstuk hebben gebruikt zijn: [1] en [16]. Code De code voor de samplers is terug te vinden in: jitteredsampler.h, randomsampler.h en regularsampler.h In sampler.h staat de algemene interface voor een sampler.
HOOFDSTUK 5. SAMPLING
34
Figuur 5.5: Links: figuur 10.3, geraytracet zonder samples. Rechts: dezelfde sc`ene geraytracet met 25 samples
Hoofdstuk 6
Licht Tot nu toe hebben we alleen gekeken of de ray een object raakte en als dat zo was de pixel de kleur van het desbetreffende object gegeven. Hier houdt het proces natuurlijk niet op. Vanaf het snijpunt van het object van de ray gaan we de kleur van de pixel berekenen, die afhankelijk is van het licht wat hierop valt. Eerst zullen we kijken hoe licht (natuurkundig) in elkaar zit en hiermee de rendervergelijking opstellen. De rendervergelijking beschrijft hoe het licht dat een object reflecteert (naar de camera) afhankelijk is van de lichtbronnen in de sc`ene. Vervolgens zullen we een model gebruiken om dit te benaderen in onze raytracer.
6.1
Rendervergelijking
The rendering equation, of te wel de rendervergelijking is bedacht door David Immel en James Kajiya in 1986. De vergelijking beschrijft hoeveel licht er in de richting van een bepaald punt wordt weerkaatst. Als we hiermee bekijken hoeveel licht er in de richting van de camera wordt weerkaatst, kunnen we de kleur van een punt bereken. Raytracing probeert deze net als andere renderingsmethodes doen te benaderen. De vergelijking vormt daarom de basis voor alle computer graphics. De vergelijking luidt als volgt: Z Lo (p, ωo ) = Le (p, ωo ) + fr (p, ωi , ωo )Li (p, ωi )cosθi dωi (6.1) 2π
Hierbij is Lo de hoeveelheid weerkaatste licht (radiance). Le is het licht wat het object zelf uitzendt. De integraal geeft aan hoeveel licht een object weerkaatst. De fr geeft aan hoe een object licht weerkaatst en tenslotte geeft Li het inkomende licht aan. In deze paragraaf zullen we de vergelijking nader toelichten afleiden. We zullen vervolgens deze vergelijking gebruiken als uitgangspunt bij het modelleren van lichtverschijnselen.
6.1.1
Grootheden
Licht bestaat uit fotonen die door een lichtbron worden uitgezonden. De formule voor de energie van een foton is: hc Q= (6.2) λ Waarbij λ de golflengte, h de constante van Planck (h = 6, 6260 · 10−34 Js) en c de lichtsnelheid is (c = 2, 988 · 108 m · s−1 ). De flux (bij fotonen) is de hoeveelheid energie die per seconde door een oppervlak heen gaat en wordt gemeten in J · s−1 oftewel in Watt. ∆Q Φ= (6.3) ∆t De volgende logische vraag is dan hoeveel licht er op iets valt. We ‘meten’ de hoeveelheid licht die op een oppervlak valt. Je drukt dus de hoeveelheid licht E die op een oppervlak valt uit in W · m−2 . Dit noem je de irradiance. ∆Φ E= (6.4) ∆A
35
HOOFDSTUK 6. LICHT
36
De irradiance vertelt ons hoeveel licht er op een oppervlak valt, maar niets over waar het licht vandaan komt. We willen de hoeveelheid licht weten die op een oppervlak valt, uit een bepaalde richting. Dit heet de radiance L. We meten deze richting als een ruimtehoek in steradialen (symbool ω). We kunnen bedenken hoe we dit berekenen door ons voor te stellen hoe een radiancemeter zou werken. We plaatsen een irradiance meter op het oppervlak (deze meet dus hoeveel flux er in totaal op het oppervlak valt). Over de meter plaatsen we een kegel, zodat de meter alleen de stralen die uit een bepaalde ruimtehoek komen meet. (zie figuur 6.1). De meter meet maar Figuur 6.1: Radiancemeter staat op het opeen deel van de irradiance, dit noemen we ∆E. pervlak, kegel onder een hoek [16] De kegel staat in de richting van de ruimtehoek, terwijl de meter op het oppervlak staat. We moeten daarom het geprojecteerde oppervlak van ∆A op het oppervlak hebben (zie figuur 6.1). De ∆A is getekend in figuur 6.2, samen met de normaal en het geprojecteerde oppervlak AB. ∠B + ∠C + ∠A2 = 180◦ (hoekensom driehoek). Verder is ∠C = 90◦ dus ∠B + ∠A2 = 90◦ Aangezien n de normaal is geldt: ∠A1 + ∠A2 = 90◦ , dus ∠B = ∠A2 , oftewel BC ∠B = θ. Vervolgens volgt uit de cosinus dat AB = cosθ . Het geprojecteerde ∆A oppervlak is dus cosθ . Voor de radiance geldt dus: L=
Figuur 6.2: Geprojecteerde oppervlak [1]
∆E ∆ω · cos θ
(6.5)
Deze wordt uitgedrukt in W · m−2 · sr−1 . Bij het raytracing slaan we de radiance op als een RGB kleur. In de natuurkunde zijn de formules ook nog afhankelijk van de golflengte λ. Dit doen we bij het raytracen eigenlijk ook; we berekenen de radiance afzonderlijk voor drie golflengtes: rood, groen en blauw. De berekende radiance kunnen we dan direct als kleur gebruiken. Integralen met ruimtehoeken
Met de ruimtehoek kun je verschillende integraalberekeningen uitvoeren. Je schrijft een integraal over een ruimtehoek als volgt op: Z I = f (θ, φ) cos θdω Ω
De integraal is hier geschreven als een enkele integraal over de ruimtehoek. Dit is een korte notatie voor de dubbele integraal over θ en φ, omdat er voor een differenti¨ele ruimtehoek geldt dat: dω = sin θdθdφ (vergelijking 1.6 op pagina 13). De cosθ is voor het geprojecteerde oppervlak (zie figuur 6.2). Bij licht nemen we vaak de integraal over een halve bol, om zo bijvoorbeeld al het licht te berekenen dat op een oppervlak valt. Als de functie f (θ, φ) = cosn−1 θ is, dan kunnen we deze integraal exact oplossen. De oppervlakte van
HOOFDSTUK 6. LICHT
37
een halve bol is 2π, dus ω = 2π. De integraal wordt dan: Z I = cosn−1 θ · cos θdω 2π
Z
cosn θdω
= 2π
1
Z2π Z2 π = cosn θ sin θdθdφ 0
0 1
Z2π Z2 π − cosn θd cos θdφ = 0
0
Bij de laatste regel hebben we sin θ achter de d gebracht voor de substitutiemethode. We nemen nu cos θ = u, dan worden de grenzen cos( 12 π) = 0 en cos 0 = 1. Z2πZ0 = 0
−un dudφ
1
Z2π
1 − un+1 n+1
=
0
0
dφ 1
0 Z2π un+1 = − dφ n+1 1 0
Z2π −
=
0n+1 1n+1 −− dφ n+1 n+1
0
Z2π =
1 dφ n+1
0
2π 2π φ = = − n+1 0 n+1 Berekeningen met radiance en irradiance Met de radiance kun je vervolgens weer berekeningen uitvoeren. Uit 6.5 volgt dat: ∆E = L · ∆ω · cos θ Om nu de irradiance te berekenen, dus de hoeveelheid licht die uit alle richtingen op het oppervlak valt kunnen we een integraal gebruiken. We ‘meten’ de hoeveelheid licht uit een kleine ruimtehoek dω. We hebben nu een differentiaalvergelijking: dE = L · dω · cos θ Om de irradiance, de totale hoeveelheid te berekenen, moeten we dit dus integreren over alle richtingen waar licht vandaan komt (Ωi ). Z E = Li cos θi dωi (6.6) Ωi
HOOFDSTUK 6. LICHT
6.1.2
38
Weerkaatsing
We zien dingen doordat licht dat op een voorwerp valt gereflecteerd wordt naar onze ogen. Daarom moeten we voor het raytracen weten hoeveel licht een voorwerp weerkaatst in de richting van de camera. Dit is een deel van de totale hoeveelheid licht (irradiance) die op het voorwerp valt. Deze wordt in een bepaalde richting (radiance) naar onze ogen weerkaatst. Als er twee keer zo veel licht op een voorwerp valt, wordt er ook twee keer zo veel licht weerkaatst. Dit verband is dus recht evenredig. Voor een hoeveelheid irradiance dEi die op een voorwerp valt, geldt derhalve voor de hoeveelheid radiance dLo die het voorwerp weerkaatst: dLo ∝ dEi De BRDF (bidirectional reflectance distribution function) geeft de verhouding aan tussen de hoeveelheid radiance die in een bepaalde richting wordt weerkaatst en de totale hoeveelheid irradiance die op het oppervlak valt. Daarmee kunnen we dus berekenen hoeveel radiance er wordt weerkaatst. Hiervoor geldt: fr =
dLo dEi
De BRDF is afhankelijk van het punt waar het licht op valt (de BRDF kan verschillen per plek op het oppervlak), de richting van de radiance die op het voorwerp valt, ωi , en de richting van de radiance die het voorwerp weerkaatst, ωo . De irradiance dEi kunnen we uitdrukken in Li volgens de formule 6.5. De Li en Lo zijn vervolgens afhankelijk van het punt p waar het licht op valt en natuurlijk de richtingen ωi en ωo . Dit invullen geeft dan: fr (p, ωi , ωo ) =
dLo (p, ωo ) Li (p, ωi )cosθi dωi
(6.7)
De BRDF blijft hetzelfde voor licht dat in de richting ωi op het oppervlak valt en in de richting ωo wordt weerkaatst en voor het licht dat in de richting ωo invalt en in de richting ωi wordt weerkaatst. Als je dus ωi en ωo omdraait, blijft de BRDF hetzelfde. fr (p, ωi , ωo ) = fr (p, ωo , ωi ) Dit betekent ook dat je je als je BRDF bedenkt, je er voor moet zorgen dat het bovenstaande geldt. De BRDF geeft aan hoe een oppervlak eruit ziet; bijvoorbeeld of het spiegelend of mat is. Een materiaal gebruikt vaak verschillende BRDF’s om de uiteindelijke radiance en daarmee de kleur te berekenen. Je kunt de verschillende BRDF’s bij elkaar optellen om de totale radiance te berekenen. Reflectievergelijking Met behulp van de vergelijking 6.7 en een integraal kunnen we de hoeveelheid weerkaatst licht berekenen. Voor een differenti¨ele hoeveelheid radiance geldt: dLo (p, ωo ) = fr (p, ωi , ωo )Li (p, ωi )cosθi dωi Door deze vergelijking te integreren over de ruimtehoek Ωi waarop het licht invalt kunnen we het vervolgens berekenen. Z Lo (p, ωo ) = fr (p, ωi , ωo )Li (p, ωi )cosθi dωi Ωi
Om dan de totale hoeveelheid gereflecteerde radiance te berekenen moeten we voor de inkomende richting een halve bol nemen. Licht kan immers vanuit een halve bol op een punt vallen. We nemen dus Ωi = 2π. Z Lo (p, ωo ) = fr (p, ωi , ωo )Li (p, ωi )cosθi dωi (6.8) 2π
Deze vergelijking heet de reflectievergelijking en beschrijft hoeveel licht er in een bepaalde richting wordt gereflecteerd (bij raytracing hoeveel licht er vanuit een voorwerp naar de camera wordt gereflecteerd).
HOOFDSTUK 6. LICHT
39
Voor het licht, afkomstig van ´e´en lichtbron s kunnen we de integraal weghalen. We kijken immers nu maar naar ´e´en hoeveelheid radiance die op het punt valt, afkomstig van ´e´en lichtbron s. Voor de weerkaatste radiance geldt dan: Lo,s (p, ωo ) = fr (p, ωi,s , ωo )Li,s (p, ωi,s )cosθi,s (6.9) We kunnen dus de totale hoeveelheid geflecteerde licht, afkomstig van n lichtbronnen als volgt berekenen: Lo (p, ωo ) =
n X
fr (p, ωi,j , ωo )Li,j (p, ωi,j ) cos θi,j
(6.10)
j=1
Reflectance De reflectance ρ geeft de verhouding tussen de inkomende flux en de weerkaatste flux weer. Voor de reflectance geldt derhalve: ∆Φo (6.11) ρ= ∆Φi Uit de vergelijking voor de rradiance 6.6 volgt dat voor een differenti¨ele hoeveelheid flux geldt: Z E = Lo cosθi dωi Ωi
Z dΦi = dA ·
Li cosθi dωi Ωi
Ditzelfde geldt natuurlijk ook voor de weerkaatste flux. Hierin kunnen we vervolgens de reflectievergelijking invullen. Z dΦo = dA · Lo cosθo dωo Ωo
Z Z = dA ·
fr (p, ωi , ωo )Li (p, ωi )cosθi cosθo dωi dωo Ωo Ωi
Dit kan vervolgens in de vergelijking voor de reflectance worden ingevuld. dΦo dΦi R R dA · fr (p, ωi , ωo )Li (p, ωi )cosθi cosθo dωi dωo Ωo Ωi R = dA · Li cosθi dωi
ρ(p, Ωi , Ωo ) =
(6.12)
Ωi
R R =
Ωo Ωi
fr (p, ωi , ωo )Li (p, ωi )cosθi cosθo dωi dωo R Li cosθi dωi Ωi
Echte materialen reflecteren niet alle energie, een deel ervan wordt geabsorbeerd. Uit de wet van behoud van energie volgt dat een object nooit meer energie kan uitstralen dat dat er op valt. Daarom moet altijd het volgende gelden (voor de totale hoeveelheid gereflecteerde flux, dus van de halve bol om het punt heen Ωi = Ωo = 2π) ρ(p, 2π, 2π) < 1
HOOFDSTUK 6. LICHT
6.1.3
40
Rendervergelijking
Een object kan naast licht reflecteren, natuurlijk ook zelf licht uitzenden. Die hoeveelheid noemen we Le (p, ωo ) (de e van emitted). Nu kunnen we met behulp van de reflectievergelijking (6.8) de vergelijking voor de totale hoeveelheid radiance opstellen. Deze noemt men de rendervergelijking: Z Lo (p, ωo ) = Le (p, ωo ) + fr (p, ωi , ωo )Li (p, ωi )cosθi dωi (6.13) 2π
We kunnen de inkomende Li berekenen door een ray uit te sturen in de richting ωi en te kijken wat daar de radiance Lo is. We schrijven het eerste snijpunt van een ray met een object als volgt op rc (p, ωi ). (het eerste snijpunt van een ray die vanaf punt p wordt uitgestuurd in de richting ωi ). Voor de inkomende radiance geldt dus: Li (p, ωi ) = Lo (rc (p, ωi ), −ωi ) Als de ray niks raakt, wordt een zwarte kleur, oftewel een radiance van 0 teruggegeven. geeft: Z Lo (p, ωo ) = Le (p, ωo ) + fr (p, ωi , ωo )Lo (rc (p, ωi ), −ωi )cosθi dωi
1
Dit invullen (6.14)
2π
Deze vergelijking is een recursieve functie (Lo komt zowel links als rechts van het = teken voor). Deze vergelijking is dan ook niet exact op te lossen, maar wel op verschillende manieren te benaderen. Een simpel model hiervoor is het phong belichtingsmodel, welke we aan het eind van dit hoofdstuk beschrijven.
6.2
Lichtbronnen
Om te berekenen hoeveel licht er weerkaatst wordt, moeten we natuurlijk eerst defini¨eren waar licht vandaan komt. We defini¨eren de lichten met een kleur cl en een schaalfacor ls die aangeeft hoe sterk licht is (hoeveel radiance de lichtbron uitzendt). De lichtbronnen die we hier allemaal beschrijven zijn allemaal ‘ge¨ıdealiseerd’ en zouden niet werkelijk kunnen bestaan.
6.2.1
Figuur 6.3: Sc`ene gerenderd met ambient licht
Ambient licht
In de echte wereld krijgt bijna alles wel via diverse weerkaatsingen licht, hoewel deze niet rechtstreeks van een lichtbron vandaan komt. Bij het raytracen berekenen we in eerste instantie alleen maar de directe verlichting; licht dat rechtstreeks vanaf een lichtbron op een oppervlak valt en niet de indirecte verlichting die via weerkaatsingen op het oppervlak valt. Later zullen we wel de indirecte verlichting gaan berekenen. Voor nu moet we zorgen dat alle objecten toch een beetje licht krijgen. Dit noemen we een een ambient lichtbron. De hoeveelheid licht die objecten hiervan ondervinden is overal hetzelfde. Voor de hoeveelheid inkomende radiance voor ambient licht geldt dus: Li = ls · cl
(6.15)
Voor de gereflecteerde radiance geldt dan:2 Lo (p) = fhh · ls · cl 1 Eventueel
kan in plaats van zwart een andere achtergrondkleur worden teruggegeven. functie fhh is hierbij geen BRDF functie, maar de hemispeherical-hemispherical reflectance. Deze wordt later in dit hoofdstuk bij shading besproken. 2 De
HOOFDSTUK 6. LICHT
41
Er is altijd maar ´e´en ambient licht bron in de sc`ene aangezien deze overal hetzelfde is (en je dus twee ambient lichtbronnen ook tot ´e´en kunt samenvoegen). Voor de rest is Lo en fr alleen maar van het punt p afhankelijk (het ene materiaal kan ambient licht anders reflecteren dan een andere), maar niet van van de richting ωi en ωo omdat ieder object evenveel omgevingslicht ondervindt. Hier zit dus ook niet de cosinus factor in. In figuur 6.3 zie je hoe een sc`ene eruit ziet die alleen maar met ambient licht is gerenderd.
6.2.2
Directional licht
Lichstralen bij een directional licht lopen evenwijdig aan elkaar en komen daarom uit ´e´en richting. Hun lichtsterkte hangt niet af van de positie. Een directional light is bijvoorbeeld een goede benadering van de zon. Het wordt naast cl en ls gedefinieerd met een richting ~l. Deze richting ~l is tegengesteld aan de richting van de lichtstralen. De hoek die ~l met de normaal op het object maakt is de hoek θ uit de reflectievergelijking (zie figuur 6.4). Voor de gereflecteerde radiance van ´e´en licht d geldt dan: Lo,d (p, ωo ) = fr (p, ld , ωo ) · ls,d cl,d · cos θl,d
(6.16)
Voor een n aantal directional lichten kan de gereflecteerde radiance worden berekend met een som: Lo (p, ωo ) =
n X
fr (p, lj , ωo ) · ls,j cl,j · cos θl,j
j=1
In figuur (6.5) is een voorbeeld te zien van een sc`ene die met directional licht is gerenderd.
6.2.3
Puntlicht
De derde lichtbron is een puntlichtbron. Licht wordt vanaf ´e´en punt gelijk verspreid in alle richtingen. We geven de sterkte van het licht aan met de intensiteit I in flux per ruimtehoek (W · sr−1 ). We kunnen de totale hoeveelheid flux berekenen met een integraal over de hele bol (4π) om het punt heen. Deze is exact op te lossen (zie paragraaf 6.1.1). Z Z Φ = Idω = 2 · cos0 Idω = 2 · 4π
Figuur 6.4: De gedefinieerde richting en de richting van de lichtstralen (boven), de hoek θ met de normaal (onder) [1]
2π · I = 4πI 0+1
2π
Hiermee kunnen we vervolgens de irradiance berekenen. Voor de oppervlakte van een bol geldt Abol = 4πr2 , dus: ∆Φ 4πI I 1 E= = = 2 =I· 2 ∆A 4πr2 r r De irradiance neemt dus kwadratisch af als de afstand (r) tot de lichtbron toeneemt. Dit is de kwadratenwet uit de natuurkunde. We defini¨eren het licht voor de rest weer met een kleur cl en een schaalfacor ls . Voor de intensiteit geldt dan I = cl · ls . Dit kunnen we invullen in de vergelijking voor de BRDF (6.7), waarbij we nu de irradiance weten. Er geldt dan voor de gereflecteerde radiance van ´e´en lichtbron d: Lo,d (p, ωo ) = fr (p, ld (p), ωo ) · ls,d cl,d ·
1 · cos θl,d rd2
(6.17)
De totale hoeveelheid gereflecteerd licht van n lichtbronnen kunnen we weer berekenen met een som: Lo (p, ωo ) =
n X j=1
fr (p, lj (p), ωo ) · ls,j cl,j ·
1 · cos θl,j rj2
HOOFDSTUK 6. LICHT
42
Figuur 6.5: Sc`ene gerenderd met directional licht (links), puntlicht met kwadratenwet (midden) en puntlicht zonder kwadratenwet(rechts)
Merk op dat de richting van het inkomende licht l nu in tegenstelling tot bij directional light ook afhankelijk is van het punt waar het ligt op valt. De r12 bepaalt de afname van de lichtsterkte door de afstand. In de praktijk blijkt de kwadratenwet hiervoor niet zo goed te werken. Het oppervlak dicht bij het de bron wordt snel overbelicht (zie het einde van dit hoofdstuk) en de lichtsterkte neem snel af. Daarom kan je in onze raytracer aangeven of je de kwadratenwet wel wilt gebruiken. r12 staat dan ook los in de vergelijking, om aan te geven dat deze weggelaten kan worden. Bij figuur (6.5) is een voorbeeld te zien van een sc`ene die met een puntlicht is gerenderd, met en zonder de kwadratenwet.
6.3
Shading
We kunnen nu met behulp van de lichten en de vergelijkingen die we net hebben besproken de objecten in de sc`ene gaan shaden. Shading is het bepalen van de kleur van een object aan de hand van de verschillende lichtbronnen. We gebruiken hiervoor phong shading. Dit is een (simpel) model om de kleur van de objecten te bepalen en daarmee de rendervergelijking te benaderen. Het model is bedacht in 1973 door Bui Tuong Phong. Het bestaat uit drie componenten: het ambient deel, het diffuse deel en het specular deel (zie figuur 6.7). Als we een object alleen met het ambient Figuur 6.6: Perfecte diffuse deel en het diffuse deel renderen, krijgen we een mat oppervlak. Je zou weerkaatsing [1] hiermee bijvoorbeeld het licht dat op papier of mat hout valt kunnen renderen. Dit noemen we het ‘diffuse’ materiaal. We kunnen ook een object renderen met alle drie de dingen, dit noemt men het phong materiaal. Tenslotte zullen we bekijken of een object wel licht van een lichtbron kan krijgen of dat het in de schaduw staat.
Figuur 6.7: Het phong model [17]
HOOFDSTUK 6. LICHT
6.3.1
43
Diffuse
We beginnen met het diffuse deel, omdat we hieruit later ook het ambient deel kunnen afleiden. Het diffuse deel zorgt voor perfecte diffuse weerkaatsing; het inkomende licht wordt gelijkmatig in alle richtingen weerkaatst (zie figuur 6.6). We defini¨eren het diffuse oppervlak met de reflectance ρd . Om vervolgens de hoeveelheid weerkaatste radiance te kunnen berekenen, moeten we de BRDF weten. Omdat het licht in alle richtingen gelijkmatig wordt weerkaatst, is de BRDF niet afhankelijk van de richting van het licht. Daarom kunnen we voor de radiance die gereflecteerd wordt door een diffuse oppervlak Lo,d , de BRDF functie fd uit de integraal in de reflectievergelijking halen (6.9). In de BRDF functie zitten immers geen variabelen meer waarover ge¨ıntegreerd wordt. Vervolgens kunnen we vergelijking 6.6 hierin substitueren: Z Lo,d (p) =
fr,d (p, ωi , ωo )Li (p, ωi )cosθi dωi Ωi
Z = fr,d (p)
Li (p, ωi )cosθi dωi Ωi
= fr,d (p)Ei (p) fr,d (p) =
Lo,d (p) Ei (p)
De hoeveelheid weerkaatste flux is te berekenen met een integraal (zie paragraaf 6.1.2). Hier kunnen we Lr,d (p) uit de integraal halen, omdat deze niet afhankelijk is van de richting van het licht. De integraal die overblijft is vervolgens exact op te lossen. Ωo = 2π aangezien het licht in de halve bol om het punt heen weerkaatst wordt (zie figuur 6.6). Z dΦo = dA ·
Lo,d (p, ωo )cosθo dωo Ωo
Z = dALo,d (p) ·
cosθo dωo 2π
= dALo,d (p) ·
2π 1+1
= dALo,d (p)π Voor de inkomende flux geldt: dΦi = dAEi (p) We kunnen dit invullen in de vergelijking voor de reflectance (6.11). Hier komt nu de vergelijking voor de BRDF fr,d (p) in voor. ∆Φo ∆Φi dALo,d (p)π = dAEi (p) Lo,d (p) =π· Ei (p) = πfr,d (p)
ρd (p) =
fr,d (p) =
ρd (p) π
Hiermee hebben we de BRDF voor het diffuse deel van het model berekend en kunnen we de hoeveelheid weerkaatst licht berekenen. Aangezien een object alleen zijn eigen kleur cp weerkaatst, moeten we deze kleur
HOOFDSTUK 6. LICHT
44
als een factor in de vergelijking opnemen, anders zou het object niet de goede kleur krijgen. De uiteindelijke BRDF voor het diffuse deel wordt dus als volgt: fr,d (p) =
6.3.2
ρd (p) · cp π
Ambient
Het ambient deel zorgt voor de weerkaatsing van het ambient licht in de sc´ene. Omdat ambient licht van alle kanten komt, kunnen we hier geen BRDF voor gebruiken. In plaats daarvan gebruiken we de hemisphericalhemisperical reflactance, wat de verhouding aangeeft tussen het licht dat op de gehele halve bol binnenkomt en over de gehele halve bol wordt weerkaatst. Deze kunnen we berekenen met de formule voor de reflectance. We mogen hierbij Li uit de integraal halen, omdat deze constant is. Ook fr kunnen we uit de integraal halen omdat deze niet afhankelijk is van ωi en ωo . Ωo = Ωi = 2π, het gaat om de halve bol. We kunnen de integralen vervolgens weer exact oplossen. R R fhh (p) =
fr,d (p, ωi , ωo )Li (p, ωi ) cos θi cos θo dωi dωo R Li cos θi dωi
Ωo Ωi
Ωi
fr,d · Li ·
cos θi cos θo dωi dωo
2π 2π
=
=
R R R
fr,d Li
Li · cos θi dωi 2π Z Z · cos θi cos θo dωi dωo 2π 2π
Z
2π cos θo dωo 2 2π Z fr,d 2π = cos θo dωo · Li 2 fr,d · = Li
2π
=
fr,d 2π 2
2π 2π · · 2 2
= πfr,d = ρd (p) Hiermee kunnen we nu het ambient deel van het model berekenen, uitgaande van gedefinieerde ρd voor het oppervlak. Ook hier geldt weer dat we het nog keer de kleur van het oppervlak cp moeten doen.
6.3.3
Specular
De specular reflection laat de weerkaatsing van de lichtbron op het oppervlak zien. Hiervoor moeten we eerst de richting van de weerkaatsing berekenen. Uit de terugkaatsingswet van de natuurkunde weten we dat ∠i = ∠t. Hierdoor is de hoek wel bekend, maar nog niet de richtingsvector. Deze gaan we hier berekenen. De vector ~l geeft de richting van het invallende licht aan, ~n is de normaal en ~r is de richting van het teruggekaatste licht. Verder is θ de hoek met de normaal (zie figuur 6.8). Het zijn allemaal eenheidsvectoren. Omdat ~l, ~n en ~r allemaal in hetzelfde vlak liggen, moet ~r een lineaire combinatie van ~l en ~n zijn. Vervolgens nemen we aan beide kanten het inproduct met de normaal: ~r = a~l + b~n ~r • ~n = a~l • ~n + b~n • ~n Omdat het allemaal eenheidsvectoren zijn, levert het inproduct direct de cosinus op. Omdat de hoek van inval gelijk is aan de hoek van terugkaatsing leveren ~r • ~n en ~l • ~n allebei cos θ op. De normaal maakt
HOOFDSTUK 6. LICHT
45
Figuur 6.8: ~l, ~n, ~r en het vlak (links), ~n⊥ (midden), de hoek α met de kijkrichting ωo (rechts) [1]
natuurlijk een hoek van 0 graden met zichzelf en de cosinus hiervan levert 1 op. Dit kunnen we vervolgens verder gaan herleiden. cos θ = a cos θ + b cos 0 b = cos θ − a cos θ b = ~l • ~n − a~l • ~n b = (1 − a)~l • ~n Vervolgens kunnen we een eenheidsvector ~n⊥ loodrecht op ~n opstellen. Nu nemen we het inproduct met ~n⊥ . ~r • ~n⊥ = a~l • ~n⊥ + b~n • ~n⊥ cos(90 + θ) = a cos(90 − θ) + b cos(90) cos(90 + θ) = a cos(90 − θ) Deze hoeken schrijven we vervolgens in radialen op: cos(π + θ) = a cos(π − θ) Zoals we weten is cos(π + θ) = − cos(π − θ) en hieruit volgt dat a = −1. Dit kunnen we vervolgens invullen in de eerste vergelijking: ~r = a~l + b~n ~r = −1 · ~l + ((1 − −1)(~l • ~n)) · n ~r = −~l + 2(~n • ~l)~n
(6.18)
Specular licht Het specular licht is de weerkaatsting van de lichtbron op het oppervlak. Het licht valt in in de richting ~l en wordt weerkaatst in de richting ~r. Met de camera zien we het licht dat in de richting ωo van de camera wordt weerkaast (zie rechterplaatje bij figuur 6.8). De hoeveelheid licht (van de specular reflectie) die we kunnen zien met de camera is dus afhankelijk van de hoek α tussen ~r en ωo . Hoe groter ∠α, hoe kleiner de hoeveelehid. Phong modelleerde deze afname als volgt: (cos α)e , waarbij e de Phong exponent is die aangeeft hoe groot de specular weerkaatsing is. Voor e geldt tevens e ≥ o. Hiermee kunnen we de BRDF voor de specular weerkaatsing opstellen. 3 fr,d (p, ωi , ωo ) = ks (r • ωo )e
(6.19)
3 De BRDF voor specular geeft een reflectie van de lichtbron op het oppervlak aan. We hoeven dit dus niet keer de kleur van het oppervlak te doen
HOOFDSTUK 6. LICHT
46
Hier hebben we de hoek α met het inproduct tussen ~r en ωo berekent. ~r hebben we hierboven berekent. Daarnaast hebben we een factor ks toegevoegd die aangeeft hoeveel de specular reflectie bijdraagt aan de totale reflectie. Het samenvoegen van ambient, diffuse en specular licht geeft ons, zoals gezegd, het Phongmateriaal. Figuur 6.9 is een voorbeeld van een sc´ene gerenderd met drie bollen en een vlak, die alle het Phongmateriaal hebben.
6.3.4
Schaduw
Een object krijgt alleen licht van een lichtbron, als er ook daadwerkelijk licht van die lichtbron op het object kan vallen. Er moeten dus geen andere objecten tussen de lichtbron en het desbetreffende object staan. We kunnen dit uitvinden, door vanaf het object een ray richting de lichtbron te sturen, en te kijken of deze ray nog een ander object raakt voor het de lichtbron bereikt. Als het licht zich echter achter het oppervlak van het object bevindt, kan het u ¨berhaupt geen licht ontvangen. Voor de hoek α tussen de de normaal ~n en de richting van het licht ~l moet dus gelden dat −π < α < π, anders staat de lichtbron achter het oppervlak. Hierin is de hoek α in radialen. Dit betekent dat er voor inproduct moet gelden:4 ~n • ~l > 0 Figuur 6.9: Sc`ene met een puntDe normaal moet dan wel in de richting van de camera staan, zodat we lichtbron, een ambient lichtbron naar de goede kant van het oppervlak kijken. Als dat niet zo is, moeten en een directional lichtbron, vier we de normaal omdraaien. Dit gebeurt bijvoorbeeld als we de binnenkant objecten met het Phongmaterivan een cilinder renderen. De normaal van een cilinder wijst naar buiten aal en schaduwen. (van de cilinder af), maar als we de binnenkant van een cilinder renderen moet hij juist naar binnen wijzen. Om dit te voorkomen controleren of het inproduct tussen de normaal ~n en de richting van het weerkaastse licht naar de camera ω~o groter dan 0 is. Als dat niet zo is, draaien we de normaal om. Als het bovenstaande geldt gaan we vervolgens met behulp van rays kijken of er geen objecten in de weg staan. We schieten vanaf het punt p op het oppervlak een ray naar elk licht. Als die ray een object raakt moeten bedenken of het object tussen het oppervlak en het licht staat of pas achter het licht komt. Hiervoor moeten we dus kijken wat er dichterbij is, het object of de lichtbron. We kunnen dit controleren door te kijken of de t van de ray kleiner is dan de afstand tot de lichtbron. Bij directional licht hoeft dit natuurlijk niet, aangezien zo’n lichtbron oneindig ver weg staat. Met deze methode kunnen we dus bekijken of een oppervlak in de schaduw staat of niet. Doordat we ‘ge¨ıdealiseerde’ lichten gebruiken, ontstaan er alleen harde schaduwen. Een punt of een oppervlak krijgt of wel, of geen licht en er zit niets tussenin. In figuur 6.9 staat een voorbeeld van een sc`ene gerenderd met schaduwen. 5
6.4
Renderen
Alle in dit hoofdstuk beschreven aspecten hebben we nodig om het licht te kunnen renderen. Het hele proces gaat dus als volgt: 1. De camera schiet een ray af. Als de ray een object raakt gaan we op dat punt de kleur berekenen. 2. De hoeveelheid ambient licht wordt berekend. 3. Er worden schaduwrays naar elke lichtbron uitgestuurd. en ~l zijn immers eenheidsvectoren, dus als het inproduct kleiner is dan nul, geldt dat de hoek tussen ~ n en ~l groter is dan 90 graden, want de inverse cosinus van een negatief getal is groter dan negentig graden. 5 Om wel halfschaduwen te krijgen, zouden we gebruik kunnen maken van area lights. Hierbij geeft niet een punt, maar een heel oppervlak licht. Hier zijn wij echter niet meer aan toegekomen. 4~ n
HOOFDSTUK 6. LICHT
47
4. Voor elke lichtbron waarvan het licht het punt kan berekenen wordt de invallende radiance berekent. 5. Met behulp van de BRDF voor diffuse deel (in het geval van het duffse materiaal) of de BRDF voor het diffuse deel en het specular deel wordt de hoeveelheid weerkaatste radiance berekent. 6. De gereflecteerde radiance van alle lichtbronnen wordt bij elkaar opgeteld. Hieruit volgt dan direct de kleur die de camera ziet, dus de kleur van de pixel. Een oppervlak kan zoveel licht weerkaatsen, dat een of meer van de r, g, b waarden groter dan 1 wordt. Aangezien de waarden r, g en b tussen de 0 en de 1 moeten vallen, kunnen we zo geen kleur laten zien. Het punt is dan overbelicht. We kunnen dit op twee manieren oplossen. Ten eerste kunnen we alle punten die overbelicht zijn in een bepaalde kleur (rood of wit) veranderen, zodat je kunt zien welke punten overbelicht zijn. Dit is handig bij het bouwen van een afbeelding, maar geeft natuurlijk geen mooi plaatje. Daarom kun je ook instellen dat de raytracer de waarde van r, g en b aanpast. De r, g, b waarden worden allemaal gedeeld door de grootse waarde(r, g of b) zodat de kleur weer binnen het bereik valt.
6.5
En de raytracer?
In dit hoofdstuk hebben we de werking van licht in onze raytracer besproken. We hebben drie verschillende lichtbronnen besproken, en uitgelegd hoe schaduwen werken. In 6.10 zien we hoe de toegevoegde stof de raytrace uit figuur 4.4 be¨ınvloedt. Deze sc`ene is nu gerenderd met op alle objecten phongmateriaal, een ambient lichtbron, een directional lichtbron, een puntlichtbron en 25 samples (jittered sampling).
6.6
Bronnen en code
Bronnen De bronnen die we voor dit hoofdstuk hebben gebruikt zijn: [1], [16], [6], [17], [18] en [5]. Figuur 6.10: De simpele sc`ene uit figuur 4.4 gerenderd met Code licht. De code voor de lichten is terug te vinden in ambient.h, directional.h en pointlight. De interface voor een licht staat in licht.h. De materiaal klassen zijn verantwoordelijk voor het shaden. De code hiervoor is terug te vinden in matte.h, phong.h en material.h waar de interface te vinden is. Daarnaast wordt de ShadeRec klasse, te vinden in shaderec.h gebruikt voor het uitwisselen van informatie tussen licht, materiaal en oppervlak.
Hoofdstuk 7
Transformatie Het uitvoeren van transformaties op objecten kan een handige manier zijn om objecten te verplaatsen, te draaien of te vervormen. Vooral voor het verplaatsen van lastigere objecten, zoals voorkomen in hoofdstuk 8 is het handiger om deze objecten te verplaatsen door middel van transformaties. In dit hoofdstuk zullen we uitleggen hoe we transformaties uit kunnen voeren op objecten en hoe we deze vervolgens kunnen raytracen.
7.1
Verschillende transformaties
Er zijn verschillende soorten transformaties. Zo hebben we onder andere translatie, verschalen, rotatie, spiegeling en glijspiegeling. Wij zullen alleen translatie, verschalen, rotatie en spiegeling behandelen. Laten we beginnen met het uitleggen van wat deze verschillende transformaties eigenlijk allemaal inhouden. • Verschalen: Verschalen is het vergroten of verkleinen van een object. • Spiegeling: Spiegeling is het spiegelen van een object in een bepaalde lijn of een bepaald punt. Wij zullen alleen spiegeling in de x-as, de y-as of de z-as behandelen. • Rotatie: Rotatie is het draaien van een punt rond een bepaald punt of een bepaalde lijn. Wij zullen alleen rotatie rond de x, de y en de z-as behandelen. • Translatie: Translatie is het verplaatsen van een punt (of object) in de wereld, in een gegeven x-richting, een gegeven y-richting en een gegeven z-richting. In de volgende paragrafen zullen we eerst translaties in 2D behandelen, omdat dit het begrijpen van 3D translaties erg zal vereenvoudigen. Vervolgens zullen we de translaties verder tillen naar 3D.
7.2
Transformaties in 2D
Als we punt p gaan transleren in een 2D wereld (Oxy ), dan kunnen we de getransleerde co¨ordinaten als volgt opschrijven: x0 = ax + by y 0 = cx + dy
(7.1)
Hierin zijn x0 en y 0 allebei een combinatie van de x en de y en twee bepaalde factoren voor deze waarden. De enige uitzondering hierop is translatie, waarbij x0 en y 0 kunnen worden beschreven als een constante opgeteld bij de originele x en y-waarden. Maar hier komen we later op terug. We kunnen 7.1 in een vergelijking van matrices opschrijven. We krijgen dan: 0 x a b x = (7.2) y0 c d y De matrix met a, b, c en d is de transformatiematrix. We zullen al onze transformaties door middel van transformatiematrices uitvoeren. 48
HOOFDSTUK 7. TRANSFORMATIE
7.2.1
49
Verschalen
Verschalen is het vergroten of het verkleinen van een object. Dit betekent dus dat we van het object voor elk punt de x en de y een factor groter of kleiner maken. We kunnen dit doen door voor de transformatiematrix bij een verschaling van V (a, b), dus x verschalen met een factor a en y verschalen met een factor b, de volgende matrix te nemen: a 0 V (a, b) = 0 b Want als we dit invullen in 7.2 krijgen we het volgende: 0 x a 0 x ax = = y0 0 b y by
7.2.2
Spiegeling
Bij spiegeling in de x-as, verandert het teken (+ of -) van de y-co¨ordinaat van een punt. Zo wordt een negatieve y-co¨ordinaat positief en een positieve y-co¨ ordinaat wordt negatief. Dit geldt natuurlijk ook bij spiegeling in de y-as, maar dan voor de x-co¨ ordinaat (zie figuur 7.1). Zo kunnen we de spiegelingen in respectievelijk de x en de y-as met de volgende transformatiematrices uitvoeren: −1 0 Sx = 0 1 1 0 Sy = 0 −1
7.2.3
Rotatie
Rotatie is een van de moeilijkere translaties. We zullen de transformatie- Figuur 7.1: De blauwe cirmatrix voor rotatie uitleggen met behulp van een feit dat we elke vector kel wordt gespiegeld in de y-as. p(x, y) wordt p(−x, y). als volgt kunnen schrijven: a 1 0 =a +b (7.3) b 0 1 Als we een vector ~v met kentallen (1, 0) roteren over een hoek figuur 7.2). Voor de geroteerde vector v~R geldt dus: cos θ cos θ v~R = = sin θ sin θ
θ, worden de kentallen van ~v (cos θ, sin θ) (zie 0 1 0 0
Als we een vector w ~ met kentallen (0, 1) roteren over een hoek θ, worden de kentallen van w ~ (− sin θ, cos θ) (zie figuur 7.2). We krijgen dus: − sin θ 0 − sin θ 0 w~R = = cos θ 0 cos θ 1 a Deze allebei invullen in vergelijking 7.3 geeft voor een vector ~a met geroteerd over hoek θ: b 1 cos θ 0 0 0 − sin θ a~R = a +b 0 sin θ 0 1 0 cos θ a cos θ 0 0 0 − sin θ = + 0 sin θ 0 b 0 cos θ cos θ − sin θ a = sin θ cos θ b
HOOFDSTUK 7. TRANSFORMATIE
50
Om iets over een hoek θ te laten roteren, gebruiken we de volgende transformatiematrix: cos θ − sin θ R(θ) = sin θ cos θ We zien dat zowel x0 als y 0 afhankelijk zijn van zowel de originele x-co¨ordinaat als de originele y-co¨ordinaat.
Figuur 7.2: Links: Het roteren van een vector met kentallen (1, 0). Rechts: Het roteren van een vector met kentallen (0, 1).
7.2.4
Translatie
Tot slot hebben we translatie. Translatie is in theorie de makkelijkste translatie om uit te voeren. De dx vergelijkingen voor een translatie van punt p met (x, y) zien er namelijk als volgt uit: dy x0 = x + dx y 0 = y + dy In matrixvorm wordt dit: 0 x d x = x + y0 dy y Hoewel dit misschien de makkelijkste transformatie is, zorgt hij toch voor een probleem. We willen namelijk vaak een aantal translaties achter elkaar uitvoeren. Dit gaat het makkelijkst als de translaties allemaal een standaard patroon volgen. Daarbij willen we ook graag dat we de inverse matrix kunnen nemen van een transformatiematrix, om redenen die later duidelijk zullen worden. Dit kan alleen met een vierkante matrix. We willen dus voor de transformatiematrix een vierkante matrix hebben, die wel, als we hem steeds met andere getallen invullen, voor elke transformatie kunnen gebruiken. Dit probleem wordt opgelost door een transformatiematrix in 3D te nemen voor 2D transformaties. We nemen hiervoor aan dat het (x, y)-vlak in een imaginair 3D-stelsel ligt met een W -as erbij. Ook nemen we aan dat voor alle punten in het (x, y)-vlak geldt dat W = 1.
7.2.5
De projectieve wereld
Zoals gezegd gaan we voor translaties in 2D een 3D-wereld gebruiken. Deze 3D-wereld noemen we een projectieve wereld. De drie assen in deze wereld noemen we X, Y en W . Co¨ordinaten uitgedrukt in (X, Y, W ) zijn homogene co¨ ordinaten. Ons 2D-vlak (x, y) heeft in deze projectieve wereld zijn oorsprong in het punt
HOOFDSTUK 7. TRANSFORMATIE
51
(uitgedrukt in homogene co¨ ordinaten) (0, 0, 1). Hierbij zijn de x en de X-as parallel aan elkaar en de y en de Y -as ook, zodat zowel de x en X-co¨ ordinaten als de y en de Y -co¨ordinaten altijd aan elkaar gelijk zijn. We behandelen nu elk punt in het (x, y)-vlak alsof het bestaat in de projectieve wereld. Voor elk punt in (x, y) geldt dus nu dat we het uitdrukken als (X, Y, 1). Met deze gegevens kunnen we alle transformaties in het (x, y) vlak uitvoeren met een vermenigvuldiging van een 3 × 3-matrix en een 3 × 1 matrix: de co¨efficientenmatrix en de matrix met de co¨ordinaten van het punt dat we transformeren. Ook kunnen we zo een punt transleren in het vlak met een vierkante matrix en een matrixvermenigvuldiging. Zo kunnen we voor translatie, verschalen, rotatie en spiegeling de volgende transformatiematrices nemen: 1 0 dx T (dx , dy ) = 0 1 dy 0 0 1 a 0 0 V (a, b) = 0 b 0 0 0 1 cos θ − sin θ 0 cos θ 0 R(θ) = sin θ 0 0 1 −1 0 0 Sx = 0 1 0 0 0 1 1 0 0 Sy = 0 −1 0 0 0 1 Dit kan allemaal worden gecontroleerd door deze transformatiematrices in te vullen in de algemeen geldende transformatieformule: p0 = T p (7.4) Hierin is p het originele punt, uitgedrukt in (X, Y, W ), p0 het getransformeerde punt, uitgedruk in (X, Y, W ) en T de transformatiematrix. In al deze transformaties blijf de W -co¨ordinaat van het resultaat 1. p0 houdt dus altijd de co¨ordinaten (x, y, 1), wat het makkelijk maakt om dit punt in het (x, y) stelsel aan te duiden.
7.3
Transformaties in 3D
De methodes die we hebben gebruikt om 2D-transformaties uit te voeren, zijn eenvoudig om te zetten naar 3D-transformaties. Hier zullen we ook met transformatiematrices gaan werken die ´e´en dimensie meer hebben dan de originele punten. We zullen dus gaan werken met 4 × 4 en 4 × 1 matrices. Ook hier geldt vergelijking 7.4. Het enige wat we nog hoeven te doen is de transformatiematrices van de verschillende transformaties om te zetten naar 4 × 4-matrices.
7.3.1
Translatie
Hoewel we in 2D zijn ge¨eindigd met translaties, zullen we er hier mee beginnen. De vier dimensionale transformatiematrix voor translaties is namelijk heel eenvoudig te bedenken. Deze ziet er namelijk zo uit: 1 0 0 dx 0 1 0 dy T (dx , dy , dz ) = 0 0 1 dz 0 0 0 1
HOOFDSTUK 7. TRANSFORMATIE
52
Dit invullen in vergelijking 7.4 geeft: p0 = T p 0 1 x y 0 0 0 = z 0 0 1
0 0 1 0 0 1 0 0
x + dx x dx dy y = y + dy dz z z + dz 1 1 1
Figuur 7.3: Links: Een ongetransformeerde driehoek. Rechts: De driehoek getransleerd met dx = −150, dy = −50 en dz = −55.
7.3.2
Verschalen
Figuur 7.4: Links: Een ongetransformeerde bol. Midden: Een bol verschaald met a = 2, b = 1 en c = 1. Rechts: een bol verschaald met a = 1.5, b = 2.5 en c = 1. Ook de transformatiematrix voor het verschalen van een object is eenvoudig af te leiden uit de drie dimensionale matrix die we in 7.2.5 hebben gebruikt. De transformatiematrix voor het verschalen van een
HOOFDSTUK 7. TRANSFORMATIE
53
object in 3D ziet er als volgt uit: a 0 0 0 0 b 0 0 V (a, b, c) = 0 0 c 0 0 0 0 1 Als we dit invullen in vergelijking 7.4 zien we dat het goede geprojecteerde punt eruit komt.
7.3.3
Rotatie
Figuur 7.5: Links: Een ongetransformeerde driehoek. Rechts: De driehoek geroteerd over de z-as met 90◦ ( 12 π radialen) In 2D hebben we alleen geroteerd om de oorsprong van het 2D-stelsel. In 3D hebben we echter drie verschillende assen waar we alledrie om kunnen roteren. Als we om de x-as roteren, roteren we het object in het (y, z)-vlak. Hierbij blijft de x-co¨ ordinaat dus constant en kunnen we om y 0 en z 0 te berekenen dezelfde waarden gebruiken als in 7.2.3. Dit leidt tot de volgende transformatiematrices om te roteren om respectievelijk de x, de y en de z-as: 1 0 0 0 0 cos θ − sin θ 0 Rx (θ) = 0 sin θ cos θ 0 0 0 0 1 cos θ 0 sin θ 0 0 1 0 0 Ry (θ) = − sin θ 0 cos θ 0 0 0 0 1 cos θ − sin θ 0 0 sin θ cos θ 0 0 Rz (θ) = 0 0 1 0 0 0 0 1 Ter illustratie zullen we het punt p(1, 2, 3) roteren over de y-as over een hoek van 90 graden, oftewel
1 2π
HOOFDSTUK 7. TRANSFORMATIE
54
radialen. x cos θ 0 sin θ 0 x0 y0 0 1 0 0 y 0 = z − sin θ 0 cos θ 0 z 1 0 0 0 1 W 1 1 cos 2 π 0 sin 2 π 0 1 2 0 1 0 0 = − sin 12 π 0 cos 12 π 0 3 0 0 0 1 1 3 0 0 1 0 1 0 1 0 0 2 2 = −1 0 0 0 3 = −1 1 0 0 0 1 1
In figuur 7.5 is een driehoek te zien die met negentig graden is geroteerd over de z-as.
7.3.4
Spiegeling
Tot slot hebben we spiegeling. Spiegeling in 3D betekent dat we het teken van ´e´en van de co¨ordinaten veranderen (dus van negatief naar positief of andersom). Dit doen we in 3D voor respectievelijk de x, y en de z-co¨ ordinaten op de volgende manier: −1 0 0 0 0 1 0 0 Sx = 0 0 1 0 0 0 0 1 1 0 0 0 0 −1 0 0 Sy = 0 0 1 0 0 0 0 1 1 0 0 0 0 1 0 0 Sz = 0 0 −1 0 0 0 0 1
Figuur 7.6: Links: Een ongetransformeerde driehoek. Linksmidden: De driehoek gespiegeld in de x-as. Rechtsmidden: De driehoek gespiegeld in de y-as. Rechts: De driehoek gespiegeld in de z-as.
7.3.5
Opmerkingen over transformaties
Belangrijk om op te merken is dat de volgorde waarin we transformaties uitvoeren van belang is voor de uiteindelijke uitkomst. Zo krijgen als we eerst transleren en vervolgens roteren het object op een andere plek
HOOFDSTUK 7. TRANSFORMATIE
55
in de wereld dan als we eerst roteren en daarna transleren. Over transformaties in de raytracer moet dus goed worden nagedacht. Een andere belangrijke opmerking is dat we zelfs bij meerdere transformaties eigenlijk maar ´e´en formule willen hebben, namelijk de formule in 7.2. Dit kan door al deze transformatiematrices samen te voegen tot ´e´en transformatiematrix. Dit doen we door ze met elkaar te vermenigvuldigen. Hier moeten we er wel weer aan denken dat matrix A met matrix B vermenigvuldigen iets anders oplever dan matrix B met matrix A vermenigvuldigen. Als we bijvoorbeeld de translatie (dx , dy ) en de verschaling S(a, b) willen uitvoeren, kan dat op twee manieren: 1 0 dx a 0 0 a 0 dx T (dx , dy ) × S(a, b) = 0 1 dy 0 b 0 = 0 b dy 0 0 1 0 0 1 0 0 1 a 0 0 1 0 dx a 0 adx S(a, b) × T (dx , dy ) = 0 b 0 0 1 dy = 0 b bdy 0 0 1 0 0 1 0 0 1
We zien dat T × S neerkomt op eerst een verschaling en dan een verplaatsing en dat S × T neerkomt op eerst een verplaatsing en dan een verschaling. De transformatie die voor het ×-teken staat gebeurt dus als eerst en de transformatie die achter het ×-teken staat komt erna. We krijgen dus voor de uiteindelijke transformatiematrix bij n transformaties die op nummer uitgevoerd moeten worden (dus transformatie 1 eerst, dan transformatie 2 enzovoort tot transformatie n) de volgende formule: T = Tn Tn−1 Tn−2 ...T2 T1
7.4
(7.5)
Rayintersectie met getransformeerde objecten
Rayintersectie met getransformeerde objecten is een lastige opgave. We hebben namelijk geen vergelijking voor het getransformeerde object. We hebben alleen het ongetransformeerde object en de stappen die we met dit object ondernemen. Het is met deze informatie veel makkelijker om in plaats van het originele object te transformeren, een inverse transformatie op de ray uit te voeren. Vervolgens kunnen we met deze invers getransformeerde ray een snijpunt met het object berekenen. Dit punt kunnen we vervolgens weer transformeren om de plek te krijgen waar de ongetransformeerde ray het getransformeerde object zou raken. Als we dan ook de normaal op het punt meetransformeren, hebben we daarmee het snijpunt en de normaal en kunnen we daarmee verder renderen. We werken dus volgens de volgende stappen: 1. We voeren de inverse transformaties uit op de ray. 2. We zoeken het snijpunt van de invers getransformeerde ray met het ongetransformeerde object. 3. We berekenen de normaal op dit snijpunt. 4. We transformeren het snijpunt en de normaal van het snijpunt om het punt, en de normaal op dat punt, te krijgen waar de originele ray het getransformeerde object zou raken.
7.4.1
Inverse transformaties
Om een inverse transformatie uit te voeren op een ray, moeten we zowel de oorsprong van de ray (o), als ~ invers transformeren. Het transformeren van punten en vectoren gaat niet precies de richtingsvector (d) hetzelfde. Dit is omdat een punt een vaste plaats aangeeft in de wereld, terwijl de vector een richting 2 aangeeft. Een vector heeft dus geen vaste plek. Zo kan een vector ~a = op de x-as liggen, maar zou hij 0 ook op y = 1 kunnen liggen. Zowel voor een punttransformatie als voor een vectortransformatie hebben we echter transformatiematrices nodig, en in dit geval dus inverse transformatiematrices.
HOOFDSTUK 7. TRANSFORMATIE
56
Inverse transformatiematrices Hoewel er formules bestaan om inverse matrices te bepalen, hebben we deze niet nodig. Het is voor de transformatiematrices die wij gebruiken namelijk heel makkelijk om inverse transformatiematrices te bedenken zonder dat er (veel) wiskunde aan te pas komt. We krijgen dan namelijk voor de inverse transformatiematrices de volgende matrices: 1 0 0 −dx 0 1 0 −dy T −1 (dx , dy , dz ) = 0 0 1 −dz 0 0 0 1 1 0 0 0 a 0 1 0 0 b V −1 (a, b, c) = 0 0 1 0 c 0 0 0 1 1 0 0 0 0 cos θ sin θ 0 Rx−1 (θ) = 0 − sin θ cos θ 0 0 0 0 1 cos θ 0 − sin θ 0 0 1 0 0 Ry−1 (θ) = sin θ 0 cos θ 0 0 0 0 1 cos θ sin θ 0 0 − sin θ cos θ 0 0 Rz−1 (θ) = 0 0 1 0 0 0 0 1 De inverse spiegelingstransformatiematrices staan hier niet bij, omdat de inverse matrices van deze matrices gelijk zijn aan de gewone matrices. Bij de inverse rotatiematrices maken we gebruik van het feit dat sin2 θ + cos2 θ = 1. Het invers transformeren van de ray Om een ray invers te transformeren, nemen we aan dat de originele ray die we uitschieten getransformeerd is. Van vergelijking 2.15 maken we dus: p0 = o + td~ (7.6) Hierbij verhouden punten op de invers getransformeerde ray zich natuurlijk met punten op de echte ray op de volgende manier (hierin is p0 een punt op de echte ray en p een punt op de invers getransformeerde ray, en dus ook op het ongetransformeerde object): p0 = T p Hierin is T de transformatiematrix. Er geldt dus voor een punt op de invers getransformeerde ray: p = T −1 p0 Voor p0 gold vergelijking 7.6 dus geldt voor p op de invers getransformeerde ray: ~ = T −1 o + tT −1 d~ p = T −1 (o + td) Oftewel: p = o0 + td~0 o0 = T −1 o d~0 = T −1 d~
HOOFDSTUK 7. TRANSFORMATIE
57
Nu zijn we aangekomen op het punt waar we een punt en een vector invers moeten kunnen transformeren. Het verschil is, zoals al gezegd, dat een punt een vaste plaats aangeeft in een wereld, terwijl een vector een, niet-plaatsgebonden, richting aangeeft. Kortom: we kunnen punten wel transleren, maar vectoren niet. Bij de homogene co¨ ordinaten hoeven we een vector dus niet vast te zetten op W = 1. We krijgen dan voor o0 en 0 ~ d: ox m00 m01 m02 m03 m m m m 10 11 12 13 oy o0 = m20 m21 m22 m23 oz 1 0 0 0 1 dx m00 m01 m02 m03 m m m m 10 11 12 13 dy d0 = m20 m21 m22 m23 dz 0 0 0 0 1 Vervolgens kunnen we met de normale snijpuntsberekeningen uit hoofdstuk 3 of hoofdstuk 8 gebruiken om het snijpunt met de invers getransformeerde ray met het ongetransformeerde object en de normaal op dat punt te berekenen. Deze kunnen we vervolgens transformeren en voil`a: we kunnen een getransformeerd object raytracen. Het transformeren van de normaal en het snijpunt In de praktijk zullen we in het raytraceprogramma de transformatiematrix niet berekenen: we nemen namelijk direct de inverse transformatiematrix. We gebruiken de inverse transformatiematrix om de ray te transformeren. Hiermee hebben we een t berekend. Deze t kunnen we invullen in de rayvergelijking van de ongetransformeerde ray om het snijpunt te krijgen met het object als het getransformeerd zou zijn. Hiervoor hebben we dus geen transformatiematrix nodig, want het snijpunt is zo te berekenen. Een normaal transformeren blijkt echter een lastiger project te zijn. Een normaal transformeren met de transformatiematrix geeft namelijk niet altijd de goede normaal voor het getransformeerde object (zie figuur 7.7). Maar een raaklijn blijft w´el een raaklijn aan het getransformeerde object. We zullen gebruiken dat de normaal loodrecht op de raaklijn staat om te bepalen hoe we de normaal dan wel moeten transformeren. ~n • ~r = 0
(7.7)
Figuur 7.7: Een normaal transformeren met dezelfde transformatiematrix als het object geeft niet altijd de goede normaal voor het getransformeerde object. M n is hierin de normaal die met de transformatiematrix is getransformeerd. N n is de echte normaal van het getransformeerde object. M t is de getransformeerde raaklijn. [19]
Hierin is ~n de normaal van het originele object en ~r de raaklijn van het originele object. We zullen vanaf nu de vectoren beschouwen als matrices. We kunnen matrices alleen met elkaar vermenigvuldigen als de ene matrix even veel rijen heeft als de andere kolommen heeft. We zullen dus ´e´en van de vectoren uit 7.7 moeten transponeren. In matrixvorm wordt 7.7 dus: nT r = 0 Hierin zijn n en r dus matrices. De raaklijn kunnen we met de transformatiematrix T vermenigvuldigen om de getransformeerde raaklijn te krijgen. We zullen de transformatiematrix waarmee we de normaal vermenigvuldigen N noemen. We noemen ook de getransformeerde raaklijn tT en de getransformeerde normaal nN We moeten een waarde van N vinden zodat: nTN rT = N nT T r = 0 We weten dat voor een willekeurige matrix geldt dat als we deze met een eenheidsmatrix vermenigvuldigen, we dezelfde matrix houden. Dus: nT Ir = 0
HOOFDSTUK 7. TRANSFORMATIE
58
Hierin is I een eenheidsmatrix. Een eenheidsmatrix kunnen we ook schrijven als een vermenigvuldiging van een matrix met zijn ge¨ınverseerde matrix. Als we I vervangen door T −1 T krijgen we: nT T −1 T r = 0 (T −1 nT )(T r) = 0 (T −1 nT )rT = 0 Hieruit volgt: nTN = T −1 nT nN = (T −1 )T n = T −T n Kortom, we zullen de normaal moeten vermenigvuldigen met de getransponeerde inverse matrix. Het transponeren van een matrix is heel eenvoudig, dus het probleem van het bepalen van de normaal voor het getransformeerde object is hiermee opgelost. Opmerking over volgorde van inverse transformaties In vergelijking 7.5 zagen we dat we als we een uiteindelijke transformatiematrix maken, we de transformatie die als laatst moet worden gedaan voorop moeten zetten. Bij inverse transformatiematrices doen we precies het omgekeerde van wat we doen bij gewone transformaties. Om de uiteindelijke inverse transformatiematrix te krijgen, zetten we dan ook de inverse transformatie die het eerst moet gebeuren gewoon voorop. We krijgen dus voor de uiteindelijke inverse transformatiematrix: −1 T −1 = T1−1 T2−1 ...Tn−1 Tn−1
7.5
En de raytracer?
We hebben in dit hoofdstuk geleerd hoe we objecten kunnen draaien, spiegelen in ´e´en van de assen, vergroten of verkleinen, en verplaatsen. Voor de objecten die we nu kennen (de objecten die we in hoofdstuk 2) is eigenlijk alleen het verschalen van belang, maar bij de objecten die we in hoofdstuk 8 erbij zien komen, zijn ook de andere transformaties erg handig. Ook voor het gebruik textuur zijn transformaties erg belangrijk. In figuur 7.8 zien we wat er kan gebeuren als we gaan spelen met transformaties op de twee bollen uit figuur 6.10. Hier hebben we de rode bol verschaald met S(3; 2, 5; 1, 5), vervolgens met 50 graden over de z-as geroteerd en hebben we tot slot de translatie T (0, 50, 0) uitgevoerd.De blauwe bol hebben we eerst in de z-as gespiegeld en getransleerd met T (150, 0, 100). Verder verschilt de sc`ene niets van de sc`ene uit 6.10.
7.6
Bronnen en code
Bronnen De bronnen die we voor dit hoofdstuk gebruikt hebben zijn [1], [20], [19], [10] en [21]. Code De code voor het transformeren is te vinden in geometricobject.h.
Figuur 7.8: De sc`ene uit figuur 6.10 na een aantal transformaties.
Hoofdstuk 8
Geavanceerde Objecten en Intersecties We hebben al eerder eenvoudige objecten, zoals vlakken en bollen, behandeld, maar om realistische plaatjes te maken hebben we natuurlijk meer nodig dan dat. In dit hoofdstuk zullen we ook uitleggen hoe we een cilinder in onze raytracer kunnen krijgen en hoe we buiten de raytracer gemaakte modellen kunnen laten inlezen door de raytracer.
8.1 8.1.1
Cilinders Het defini¨ eren van een cilinder
Een cilinder is een object met een as en loodrecht op deze as overal eenzelfde cirkel als doorsnede. Een voorbeeld is te zien in figuur 8.1. Laten we als voorbeeld eens een simpele cilinder nemen met de as op de y-as. De doorsnedecirkel ligt dan op of evenwijdig aan de x, z-as. De straal van deze cirkel noemen we r. De formule voor de cirkel is een 2D versie van de 3D bol. Dus: (x − cx )2 + (z − cz )2 = r2 . Aangezien het middelpunt op de y-as ligt, is cx = 0 en cz = 0. Dus de definitie van de cirkel wordt: x2 + z 2 = r 2
(8.1)
Als we een oneindig lange cilinder willen, kunnen we de cilin der nu als volgt defini¨eren: (x, y, z) : x2 + z 2 = r2 ∧ y ∈ [−∞, ∞] Meestal gaat het echter niet om een oneindig lange cilinder, maar om een eindige. Dus van y0 tot Dan geldt de definitie y1 . (x, y, z) : x2 + z 2 = r2 ∧ y ∈ [y0 , y1 ] . Voor de cirkel die een deel van de cilinder bepaalt geldt de volgende vergelijking: Om een cilinder in de sc`ene te plaatsen, moeten we dus van deze Figuur 8.1: Een cilinder [1] cilinder de straal geven en twee waarden van y waar de cilinder tussen zit. Daarbij mogen we opmerken dat we met de bovenstaande gegevens de cilinder automatisch met als as de y-as nemen. Om de cilinder op andere plekken in de wereld te plaatsen, zullen we een cilinder met zijn oorsprong op de y-as moeten transformeren (zie hoofdstuk 7).
8.1.2
Rayintersectie met een cilinder
De x-co¨ ordinaat en de z-co¨ ordinaat van een punt op een ray zijn te beschrijven met de volgende formules, afgeleid uit ergelijking 2.15: px = ox + dx t pz = oz + dz t
59
HOOFDSTUK 8. GEAVANCEERDE OBJECTEN EN INTERSECTIES
60
Dit invullen in vergelijking 8.1 geeft: (ox + dx t)2 + (oz + dz t)2 − r2 = 0 o2x + d2x t2 + 2ox dx t + o2z + d2z t2 + 2oz dz t − r2 = 0 (d2x + d2z )t2 + 2(ox dx + oz dz )t + o2x + o2z − r2 = 0 Hierin zijn alle waarden behalve t bekend en dus kunnen we t berekenen. Vervolgens hoeven we alleen nog maar te kijken of py ∈ [y0 , y1 ]. En dan hebben we een raakpunt met de cilinder. De normaal van een cilinder in een raakpunt is ook eenvoudig te bepalen. Deze wijst namelijk vanaf de as van decilinder recht naar buiten. De vector van het middelpunt van de cilinder naar de rand is deze: px ~a = 0 . We normaliseren deze en nemen de genormaliseerde vector als normaalvector. We delen de pz vector hiervoor door zijn eigen lengte, welke gelijk is aan de straal r. De normaalvector ziet er dus als volgt uit: px /r ~n = 0 (8.2) pz /r
8.2
Polygon Meshes
Het is in theorie mogelijk om met de cilinder en de objecten in hoofdstuk 2 alle mogelijke vormen en objecten te maken. Het zou echter een hoop tijd kosten om ingewikkelde objecten, zoals auto’s of schooltassen, telkens zelf in het raytracingprogramma te bouwen met dit soort objecten. Ook zou het een overbodig proces zijn, want er zijn veel betere programma’s gebouwd om dit soort modellen te maken en er zijn ook vele gratis modellen te downloaden op internet die stukken beter zijn dan de modellen die we zelf zouden maken in ons programma. Om deze redenen willen we het programma ook modellen laten inlezen. Dit betekent dat we een model, gemaakt buiten het programma, kunnen invoeren en raytracen. Hiervoor zullen we eerst iets moeten uitleggen over modelleren. Dit zullen we echter zo beknopt mogelijk houden, omdat we niet zelf willen gaan modelleren, maar alleen bestaande modellen willen gebruiken.
8.2.1
Wat zijn polygon meshes?
Figuur 8.2: Polygon meshes in schema [22] De modellen die we gaan gebruiken zullen worden weergegeven als zogenaamde ‘polygon meshes’. Dit betekent dat het model is opgebouwd uit ‘polygons’. Elk model begint met punten. Zo bestaat het model voor een driehoek uit drie punten. Een punt in een model noemen we een vertex. Een verbinding tussen twee vertices noemen we een ‘edge’, oftewel een rand. Dit is een rechte lijn. Een verzameling van edges is een ‘face’, een aangezicht. Zo is een driehoek een face bestaande uit drie vertices en drie edges. Een ‘polygon’
HOOFDSTUK 8. GEAVANCEERDE OBJECTEN EN INTERSECTIES
61
is een verzameling van faces. Een aantal polygons samen vormt tenslotte een ‘surface’, een oppvervlak. Het gebruik van surfaces is soms handig maar vaak niet noodzakelijk. Voor een overzicht, zie figuur 8.2. Wij zullen alleen werken met objecten die uit driehoekjes bestaan, dus alleen met faces. Dit is omdat wij in onze raytracer niet het raytracen van andervormige faces hebben opgenomen.
8.2.2
.OBJ
Om de polygon meshes te kunnen importeren in ons programma, moeten deze natuurlijk wel beschreven worden. Wij zullen modellen gaan gebruiken die beschreven staan in een bestandstype dat bekend staat als ‘.OBJ’. In een .OBJ bestand staat simpelweg een lijst van elementen die in het object voorkomt, te beginnen met de vertices. Na de vertices volgen de normalen op de vertices, eventueel de UV co¨ordinaten (voor texturen) en tenslotte de faces. In Code 8.1 staat een mogelijk .OBJ bestand. Eerst komen alle vertices (v) en vervolgens de normalen (vn). Vervolgens volgen de faces, bestaande uit drie vertices, samen met hun bijbehorende normaal. In dit geval bestaat de eerste face uit vertex 1 met normaal 2, vertex 7 met normaal 2 en vertex 5 met normaal 2. Code 8.1: Een mogelijk .OBJ-bestand 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
# cube . o b j v 0.0 0.0 v 0.0 0.0 v 0.0 1.0 v 0.0 1.0 v 1.0 0.0 v 1.0 0.0 v 1.0 1.0 v 1.0 1.0
0.0 1.0 0.0 1.0 0.0 1.0 0.0 1.0
vn 0 . 0 0.0 1.0 vn 0 . 0 0 . 0 −1.0 vn 0 . 0 1.0 0.0 vn 0 . 0 −1.0 0 . 0 vn 1 . 0 0.0 0.0 vn −1.0 0 . 0 0.0 f f f f f f f f f f f f
1/2 1/2 1/6 1/6 3/3 3/3 5/5 5/5 1/4 1/4 2/1 2/1
7/2 3/2 4/6 2/6 8/3 4/3 7/5 8/5 5/4 6/4 6/1 8/1
5/2 7/2 3/6 4/6 7/3 8/3 8/5 6/5 6/4 2/4 8/1 4/1
Omdat we alleen .OBJ bestanden gebruiken die volledig uit driehoekjes bestaan, kunnen we deze al raytracen. We hoeven ze alleen nog maar in te lezen in de raytracer. Er ontstaat echter een probleem als er licht en schaduw in het spel komt.
8.2.3
Vloeiende versus strakke belichting
Figuur 8.3 geeft al aan met wat voor probleem we te maken krijgen. Voor het linker plaatje is de standaardtechniek voor rayintersectie gebruikt en zijn de afzonderlijke driehoeken uit het OBJ-bestand geraytracet alsof ze niets met elkaar te maken hebben. In het rechter plaatje is een techniek gebruikt waarbij w´el reke-
HOOFDSTUK 8. GEAVANCEERDE OBJECTEN EN INTERSECTIES
62
Figuur 8.3: Strakke belichting tegenover vloeiende belichting [23] ning is gehouden met het feit dat deze mesh ´e´en object is, opgebouwd uit meerdere objecten. Logischerwijs raytracen we liever met de techniek van het rechter plaatje, omdat de resultaten duidelijk beter zijn. Wat gaat er namelijk mis met de linker techniek? Het belichtingsproces van een object bij het raytracen werkt met de normaal van het object. Een driehoek heeft op elk punt dezelfde normaal en er ontstaat dus een knik tussen de normalen van twee driehoeken die aan elkaar grenzen maar niet dezelfde normaal hebben. Hierdoor zien we ook in de belichting van de theepot knikken ontstaan in de kleur die de pot krijgt. Om dit te voorkomen kunnen we werken met een gemiddelde normaal, zoals is gedaan bij de rechter theepot. Het werken met een gemiddelde normaal gaat als volgt. In het OBJ bestand staat voor elk hoekpunt van een driehoek de normaal gegeven. Vervolgens kunnen we hiermee de gemiddelde normaal berekenen. Hiervoor grijpen we even terug naar de barycentrische co¨ordinaten. De barycentrische co¨ordinaten van het punt in de driehoek zijn namelijk bekend door de manier waarop we het raakpunt van de driehoek met de ray hebben berekend: β en γ zijn berekend, en aangezien α = 1 − β − γ (zie vergelijking 2.8), weten we ze alle drie. Wat we vervolgens doen om de ‘normaal’ te bereken is het volgende: we nemen een gewogen gemiddelde van de normalen van de drie vertices, die de hoekpunten van de driehoek zijn. Dit doen we door de normaal op het hoekpunt waar β = 0 geldt te vermenigvuldigen met de β-co¨ordinaat van het punt, de normaal op het hoekpunt waar γ = 0 geldt te vermenigvuldigen met de γ-co¨ordinaat van het punt en de normaal op het hoekpunt waar 1 − β − γ = 0 geldt te vermenigvuldigen met 1 − β − γ. Vervolgens tellen we deze vectoren bij elkaar op en normaliseren we ze. Stel: we hebben een driehoek met de hoekpunten a, b, en c. Dan hebben we op deze punten de normaalvectoren n~a , n~b en n~c . Om dan de ‘normaal’ van een punt p binnen de driehoek te krijgen doen we wat we net hebben beschreven: (1 − pβ − pγ )n~a + pβ n~b + pγ n~c n~p = |(1 − pβ − pγ )n~a + pβ n~b + pγ n~c | Dit geeft ons de eenheidsnormaal voor p en met deze normaal kunnen we het belichtingsproces uitvoeren. Dit geeft kleuren die zorgen voor realistischer lichteffecten voor het model.
8.2.4
Bounding boxes
Hoewel we ingewikkelde modellen, bestaande uit een grote hoeveelheid driehoekjes, wel kunnen raytracen, kost het soms erg veel tijd om te kijken of een ray al deze driehoekjes raakt. We kunnen dit proces sneller maken door een zogenaamde ‘bounding box’ om het object heen te zetten. Dit is een balk die we om het object heenzetten. We hoeven dan eerst alleen te kijken of de rays de balk raken, en pas als dit het geval is hoeven we de driehoekjes die zich binnen de balk bevinden te raytracen. Het plaatsen van een bounding box is tamelijk eenvoudig. Uit een OBJ-bestand kunnen we de hoogste en laagste waarden van x, y en z vinden die het object heeft. Laten we als voorbeeld de code uit 8.1 gebruiken. Hier zien we dat de hoogste x van een vertex in het bestand een waarde van 1 heeft. De laagste x-waarde is −1, de laagste en hoogste y-waarden zijn respectievelijk −1 en 1 en de laagste en hoogste z-waarden zijn respectievelijk −1 en 1. Nu weten we dat voor dit object geldt: (x, y, z) ∈ [−1, +1] × [−1, +1] × [−1, +1]. Hieruit kunnen we twee co¨ ordinaten van hoekpunten van de bounding box halen: de twee uiterste co¨ordinaten,
HOOFDSTUK 8. GEAVANCEERDE OBJECTEN EN INTERSECTIES
63
namelijk (−1, −1, −1) en (1, 1, 1). Elk punt dat een combinatie is van deze x, y en z co¨ordinaten is een hoekpunt van de bounding box. Dit geeft ons acht hoekpunten. Een balk bestaat uit zes rechthoeken. Als we een balk gaan raytracen, raytracen we dus ook eigenlijk zes rechthoeken. Om een rechthoek te kunnen raytracen moeten we eerst de waarden hebben die een rechthoek voor ons defini¨eren. Dit zijn twee zijden en het punt waar de zijden samenkomen (zie 2.1.2, pagina 16). Hiervoor hebben we maar zes zijden en twee hoekpunten van de bounding box nodig. De hoekpunten die we gebruiken zijn gewoon en A = (x0 , y0 , z0 ) X = (x1 , y1 , z1 ). Hierbij zijn x1 , y1 , en z1 de maximale x, y en z waarden en x0 , y0 en z0 de minimale x, y en z waarden. De zijden zijn de zes zijden die in deze hoekpunten samenkomen (in elk hoekpunt drie). Dit zijn dus de volgende vectoren: x1 − x0 ~a = 0 0 0 ~b = y1 − y0 0 0 ~c = 0 z1 − z0 x0 − x1 ~u = 0 0 0 ~v = y0 − y1 0 0 w ~ = 0 z0 − z1 De vlakken met hun vectoren en het punt dat ze definieert zijn dan als volgt: • Vlak A - ~a, ~b, punt A en normaal ~n = ~a × ~b • Vlak B - ~a, ~c, punt A en normaal ~n = ~a × ~c • Vlak C - ~b, ~c, punt A en normaal ~n = ~b × ~c • Vlak D - ~u, ~v , punt X en normaal ~n = ~u × ~v • Vlak E - ~u, w, ~ punt X en normaal ~n = ~u × w ~ • Vlak F - ~v , w, ~ punt X en normaal ~n = ~v × w ~ Deze vlakken vormen samen de bounding box, die we vervolgens kunnen raytracen.
8.3
En de raytracer?
Op dit moment hebben we alle objecten behandeld die we in onze raytracer willen gebruiken. We hebben de wiskundige figuren: de bollen, vlakken, rechthoeken, cirkels, driehoeken en nu de cilinders. Daarnaast kunnen we modellen, beschreven in .OBJ-files, inlezen en raytracen. Ook hebben we uitgelegd hoe we om ingewikkelde objecten een bounding box kunnen zetten om ze sneller te kunnen renderen. In figuur 8.4 hebben we drie cilinders toegevoegd aan de sc`ene in 6.10.
HOOFDSTUK 8. GEAVANCEERDE OBJECTEN EN INTERSECTIES
8.4
64
Bronnen en code
Bronnen De bronnen die we voor dit hoofdstuk hebben gebruikt zijn: [1], [23] en [22]. Code De code voor een cylinder is terug te vinden in cylinder.h, de code voor een .OBJ-files in mesh.h en objfactory.h.
Figuur 8.4: De sc`ene uit figuur 6.10 met drie extra cilinders.
Hoofdstuk 9
Textuur Tot nu toe hebben we het alleen over objecten gehad, die we ´e´en kleur gaven. We maakten de bol blauw bijvoorbeeld, of de cirkel geel. Het is echter natuurlijk veel leuker en mooier om een voorwerp ook anders te kunnen kleuren. Bijvoorbeeld paars met witte stippen en een goud randje. Hiervoor gebruiken we texturen. Texturen zijn plaatjes die we plaatsen op de objecten. Zo kunnen we bijvoorbeeld een kaart van de wereld op een bol plaatsen, waardoor de bol een wereldbol lijkt. Hoe dit gaat zullen we in dit hoofdstuk bespreken.
9.1
Het proces
Zodra een textuur is aangebracht is hij heel gemakkelijk te gebruiken. De code van de textuur geeft namelijk simpelweg een kleurcode terug, waarmee de kleur van het punt, dat wordt geraytracet, bepaald wordt. De moeilijkheid met texturen zit hem dus in het aanbrengen ervan. Dit gaat als volgt in zijn werk. Voor het aanbrengen van de textuur gebruiken we zogenaamde textuurco¨ordinaten. Dit zijn genormaliseerde co¨ ordinaten van het originele plaatje. Het zijn dus co¨ordinaten die binnen het interval [0, 1] vallen. Dus: (u, v) ∈ [0, 1] × [0, 1]. Voor de co¨ ordinaten van een punt op het originele plaatje met een resolutie van (hres , vres ) geldt het volgende: (xp , yp ) ∈ [0, hres − 1] × [0, vres − 1]. Voor de relatie tussen (u, v) en (xp , yp ) geldt dan: xp = (hres − 1)u yp = (vres − 1)v
(9.1)
Zo kunnen we xp en yp makkelijk bepalen voor elke u en v. De u en v zullen we moeten bepalen voor het object waar we textuur op aanbrengen, en zo kunnen we dus punten op het object koppelen aan een punt op de textuur, waarmee we het punt op het object de kleur van het textuur kunnen geven. Dit proces zullen we duidelijker maken met het voorbeeld van een rechthoek.
9.1.1
Rechthoekvoorbeeld
In dit voorbeeld gaan we een textuur aanbrengen op een rechthoek in het (x, z)-vlak met (x, z) ∈ [−1, +2] × [−1, +1]. Elk raakpunt met deze rechthoek heeft de co¨ordinaten (x, 0, z), want hij staat evenwijdig met de y-as. Nu geldt voor u en v (hierin is x0 weer de kleinst mogelijke x-waarde, z0 de kleinst mogelijke z-waarde, en x1 en z1 respectievelijk de grootst mogelijke x en z-waarden): z − z0 z1 − z0 x − x0 v= x1 − x0
u=
65
HOOFDSTUK 9. TEXTUUR
66
Oftewel: z+1 2 x+1 v= 3
u=
Met vergelijking 9.1 kunnen we nu de punten van het textuur ‘binden’ aan hun respectievelijke punten op de rechthoek, waardoor we een plaatje tevoorschijn zien komen op de rechthoek. Bij het raytracen zullen we altijd uitgaan van een rechthoek van 1 bij 1, gelegen in het (x, z)-vlak met het punt p0 op de oorsprong van het Oxyz vlak. Zo krijgen we namelijk dat u = z en v = x. Hierdoor zijn textuurberekeningen heel makkelijk.
9.2
Texturen op verschillende objecten
In 9.1.1 is al uitgelegd hoe we texturen kunnen aanbrengen op rechthoeken; in dit stuk zullen we uitleggen hoe dit werkt voor andere objecten. Zolang een object wiskundig gezien plat is, geeft textuur aan brengen geen problemen. Als een voorwerp niet ‘plat’ is, wordt het lastiger om het textuur te geven, omdat de textuur dan wordt vervormd als hij op het object wordt geplakt. De definitie van een wiskundig gezien plat voorwerp is vrij ingewikkeld, maar het komt op het volgende neer: als we een vel papier in een vorm kunnen krijgen zonder te scheuren of te vouwen dan is het een plat object, zo niet dan is het niet plat. Zo is een open cilinder een plat object en een bol niet. We kunnen namelijk niet met een vel papier een bol vouwen zonder dat het gaat kreukelen. We zien dan ook dat we een textuur makkelijker kunnen aanbrengen op een cilinder dan op een bol. Daarom zullen we nu eerst laten zien hoe je een textuur aanbrengt op een cilinder.
9.2.1
Textuur op een cilinder
Als we een cilinder nemen met als straal 1, als as de y-as en y ∈ [−1, +1] wordt deze cilinder bepaald door de cilindrische co¨ ordinaten φ ∈ [0, 2π] en y ∈ [−1, +1] (zie 8.1.1). Hierin geldt: φ = tan−1 (x/z) Als we een cilinder uitrollen, krijgen we een rechthoek. Hierin is de hoogte y en de lengte is de omtrek van de cilinder. Om op een willekeurig punt de co¨ordinaten uit te drukken gebruiken we dit. De lengte van de cilinder is de booglengte horende bij een hoek van 360 graden, oftewel een hoek van 2π radialen, wordt berekend in de volgende formule: l =r×θ (9.2) Hierin is r de straal van de cirkel en θ de middelpuntshoek, die bij onze cilinder φ is. Aangezien bij onze cilinder de straal 1 was, is de u makkelijk te berekenen met de volgende formule: u = φ/2π Want φ is de booglengte (en r = 1, dus r × φ = φ) en 2π is de totale lengte. Dus geldt nu u ∈ [0, 1]. Variabele v is ook heel makkelijk te bereken, omdat y net zo’n waarde is als x en z waren voor de rechthoek in 9.1.1. Dus geldt voor v: y − yo 2 y+1 v= 2 v=
Hiermee kunnen we voor alle punten op de cilinder een punt op het plaatje van de textuur vinden en kunnen we textuur aanbrengen op een cilinder.
HOOFDSTUK 9. TEXTUUR
67
Voor een cilinder met een andere straal is dit natuurlijk ook makkelijk te doen. Dan verandert alleen de formule voor u. Deze wordt dan: u=
r×φ 2πr
Hoewel het in de bovenstaande vergelijking lijkt of u onafhankelijk is van r, zullen we toch bij texturen standaard uitgaan van een straal van 1. Bij bepaalde waarden van r kan het namelijk bij andere objecten zo uitkomen dat bepaalde functies geen goede uitkomst meer geven. Zo krijgen we bij textuur op een bol te maken met cos−1 y. Dit kan alleen voor y ∈ [−1, 1], dus moet gelden: −1 ≤ r ≤ 1. Daarom zullen we bij texturen aanbrengen altijd uitgaan van de standaardobjecten.
9.2.2
Textuur op een bol
Al is het moeilijker om een textuur mooi op een bol te krijgen, is het natuurlijk niet onmogelijk. Om op een bol de positie van een punt aan te geven zullen we hier gaan werken met bolco¨ordinaten. Aangezien de φ hetzelfde is als de φ bij de cilindrische co¨ordinaten, hebben we ook dezelfde formule voor u. Als we kijken naar een cirkel met als middelpunt (0, 0, 0) en als straal r = 1 kunnen we u uitdrukken als: u = φ/2π We houden dus alleen θ over om te bespreken. Voor de y-co¨ordinaat van een punt op de cirkel met middelpunt (0, 0, 0) en r = 1 geldt: y = cos θ Dus: θ = cos−1 y De inverse cosinus geeft altijd een waarde tussen 0 en π. De hoogste waarde die θ dus kan hebben is π. Om die te passen in [0, 1] zullen we θ dus moeten delen door π. We kunnen dit echter niet zonder meer doen en de uitkomst als v gebruiken, zoals dat bij φ wel kon, want θ wordt gemeten vanaf de positieve y-as. Dus hoe groter θ, hoe kleiner de y-waarde wordt. We moeten dus iets omkeren. Er geldt altijd: θ/π ∈ [0, 1]. Dus om de juiste y-waarden te krijgen, kunnen we de volgende formule gebruiken: v =1−
θ π
Het aftrekken van πθ van 1 zorgt ervoor dat de v-waarde kleiner wordt als θ groter wordt, wat dus precies is wat we nodig hadden. Wat is nu het probleem met het aanbrengen van een textuur op een bol? We zien het probleem ontstaan als we een textuur op de bol hebben geplakt. Laten we bijvoorbeeld het baksteenmotief uit figuur 9.1 (boven) op een bol aanbrengen. Als we dit raytracen krijgen we het plaatje uit figuur 9.1 (onder). Hoewel het bij dit motief niet zo’n heel groot probleem is, zien we dat het motief wordt vervormd. Ook zien we dat het motief erger wordt vervormd naarmate we dichter bij de polen van de bol komen. Gelukkig kan ook dit probleem worden opgeloast. We kunnen namelijk een textuur vervormen voordat we het op de bol plakken. Hoe we dit kunnen doen is al in de zestiende eeuw uitgevonden door de Vlaamse cartograaf Gerardus Mercator, die bezig was met kaarten en wereldbollen. Hoe dit precies in zijn werk gaat zullen we hier niet bespreken, aangezien wij zelf geen texturen zullen maken, maar slechts willen dat door anderen gemaakte texturen in onze raytracer kunnen worden ge¨ımplementeerd. Figuur 9.1: Boven: We raden dan ook af om met deze raytracer een willekeurig plaatje als textuur voor Een baksteenmotief. een bol te gebruiken en raden aan om hiervoor een plaatje te gebruiken dat speciaal Onder: Het baksteenis gemaakt als boltextuur. motief rond een bol. [24]
HOOFDSTUK 9. TEXTUUR
9.2.3
68
Textuur op een cirkel
Als we op een cirkel een volledig rechthoekig plaatje zouden willen krijgen, zou dit een ontzettend vervormd plaatje worden. Daarom hebben wij ervoor gekozen om een uitsnede van het plaatje te gebruiken als wij op een cirkel texturen gaan aanbrengen, zoals in figuur 9.2. Een deel van het plaatje blijft dus ongebruikt. Door deze methode te gebruiken wordt niet alleen het plaatje niet vervormd als we de textuur aanbrengen, maar wordt ook het bepalen van de (u, v)-co¨ordinaten op een punt op de cirkel makkelijker. Als voorbeeld zullen we een cirkel nemen in het (x, z)-vlak met als middelpunt (0, 0, 0) en als straal 1. De laagste waarde x-waarde die nog op de cirkel ligt is dan −1, de hoogste x-waarde is 1, de laagste z-waarde is −1 en de hoogste z-waarde is 1. De (u, v) co¨ ordinaten kunnen we dan beschrijven alsof we met een vierkant te maken hebben: z+1 z − z0 = z1 − z0 2 x − x0 x+1 v= = x1 − x0 2
u=
Een aantal (u, v)-co¨ ordinaten worden dus nooit gebruikt, want die liggen helemaal niet op de cirkel. Een punt met u = 1 en v = 1 is bijvoorbeeld niet op de cirkel te vinden (zie figuur 9.2). Zo krijgen we een cirkelvormige uitsnede van het plaatje op de cirkel.
9.2.4
Textuur op polygon meshes
Hoewel het leuk en soms handig is om texturen aan te brengen op de wiskundige objecten die we hiervoor behandeld hebben, is het waarschijnlijk belangrijker om textuur aan te kunnen brengen op ge¨ımporteerde modellen. In de praktijk zullen we namelijk meestal de 3D-sc`ene opbouwen uit modellen die we in andere programma’s hebben gemaakt of van internet hebben gedownFiguur 9.2: Links: Hoekjes worden ‘afgesneden’ als we een plaatje als textuur load. voor een cirkel gebruiken. Rechts: Het punt (u, v) = (1, 1) ligt niet op de cirkel. Om een .OBJ-bestand van een textuur te voorzien, moeten in dit bestand de u en v-co¨ordinaten voor elke vertex gegeven zijn. Hiermee zijn u en v voor elk hoekpunt van elke driehoek bekend. Om vervolgens (u, v) voor een bepaald raakpunt binnen een driehoek te bepalen, gebruiken we dezelfde methode als we voor de normaal van een punt hebben gebruikt in 8.2.3. We weten β en γ, dus we kunnen het gewogen gemiddelde van de (u, v)-waarden van alle drie de hoekpunten nemen om de (u, v)-co¨ ordinaten op het raakpunt te berekenen.
9.3
Objecten met textuur op andere plekken in de wereld
Zoals een oplettende lezer al zal hebben opgemerkt, hebben we hier alleen texturen besproken voor specifieke objecten die op bijzondere plaatsen in de wereld liggen. Het is namelijk erg lastig om op een willekeurige andere plek in de wereld de (x, y, z)-co¨ ordinaten om te rekenen naar (u, v)-co¨ordinaten. Als we dus een object met textuur op een andere plek in de wereld willen krijgen, zullen we transformaties moeten toepassen op de ‘standaardobjecten’.
HOOFDSTUK 9. TEXTUUR
69
We hebben in dit hoofdstuk texturen op rechthoeken, bollen, cirkels, cilinders en polygon meshes besproken. De standaardobjecten waar we vanuit moeten gaan bij het aanbrengen van texturen staan hieronder. De standaardobjecten zijn: 1 0 • Een rechthoek met ~a = 0, ~b = 0 en p0 = (0, 0, 0). 0 1 • Een cilinder met r = 1 en de y tussen −1 en 1. • Een bol met r = 1 en c = (0, 0, 0). 0 • Een cirkel met normaal 1, r = 0
1 2
en c = ( 12 , 0, 12 ).
Bij polygon meshes gaat het aanbrengen van textuur automatisch goed, omdat de textuurco¨ordinaten in het .OBJ-bestand gegeven zijn en we alleen de gemiddelden daarvan hoeven te berekenen. Meestal is het zo dat er bij een model een textuur hoort die wordt bijgeleverd. Andere texturen voor het model maken is een moeilijke en tijdrovende taak, die buiten de raytracer moet worden vervuld. Hier zullen wij dus verder niet op in gaan.
9.4
En de raytracer?
In dit hoofdstuk hebben we geleerd hoe we texturen aan kunnen brengen op objecten. Dit zorgt voor een aantal interessante nieuwe mogelijkheden. Plaatjes kunnen we nu alweer een stuk realistischer maken dan ze eerst waren, door ze te ‘versieren’ met een plaatje of een motief. Ook hebben we geleerd dat we om texturen aan te brengen op objecten die op een willekeurige plek in de wereld staan, we uit moeten gaan van de standaardobjecten en we deze moeten transformeren, omdat anders de texturen niet werken. In figuur 9.3 hebben we een wereldboltextuur op de voorste bol uit de bekende sc`ene geplakt.
9.5
Bronnen en code
Bronnen De bronnen die we voor dit hoofdstuk gebruikt hebben zijn: [1], [25] en Figuur 9.3: De sc`ene uit 7.8 met [24]. een wereldboltextuur op de voorste bol. Code De code voor een textuur is terug te vinden in texture.h, colortexture.h en imagetexture.h. De objecten zijn verantwoordelijk voor het berekenen van de uv-co¨ordinaten, dit is te vinden in de bestanden rectangle.h, circle.h, cylinder.h en mesh.h. Tenslotte vragen de materialen de texturen om de kleur. Dit is te vinden in de bestanden voor materialen: material.h, diffuse.h en phong.h.
Hoofdstuk 10
Spiegelreflectie Het licht dat ons oog bereikt, komt niet altijd van een voorwerp dat het direct weerkaatst vanuit een lichtbron. Het komt vaak voor dat objecten indirect licht ontvangen: ze krijgen licht dat is weerkaatst door andere objecten. Zo zie je in een spiegel een weerspiegeling van jezelf, doordat de spiegel het licht, dat je lichaam richting de spiegel weerkaatst, terugkaatst naar je oog, waardoor je dit licht ziet. In dit hoofdstuk zullen we beschrijven hoe we in een raytracer rekening kunnen houden met spiegelreflectie.
10.1
Het principe
Spiegelreflectie is een speciale vorm van reflectie: het licht wordt gereflecteerd zoals een spiegel licht reflecteert. Dit betekent dat een evenwijdige lichtbundel, als die gereflecteerd wordt, een evenwijdige lichtbundel blijft. Ook geldt hier dat de hoek van inval gelijk is aan hoek van terugkaatsing. We gaan bij spiegelreflectie als volgt te werk: zodra een primaire ray Figuur 10.1: Spiegelreflectie bij (dus afgeschoten vanuit de camera) een object raakt, kijken we of er vanuit twee bollen en een vlak. de reflectierichting licht komt. Dit doen we door middel van het uitsturen van een nieuwe ray in de richting waarvoor geldt dat de hoek tussen de normaal en de nieuwe ray gelijk is aan de hoek tussen de normaal en de primaire ray. Van deze ray volgen we vervolgens het verloop. Er zijn voor deze ray vier mogelijkheden: 1. De nieuwe ray raakt geen enkel object meer. We krijgen dus de achtergrondkleur. 2. De nieuwe ray raakt een lichtbron. Het uitgestuurde licht van de lichtbron in de richting van het punt op het object moet worden berekend. 3. De nieuwe ray raakt een object waarin niets wordt gereflecteerd (een niet-reflectief object). Het directe licht dat op dit punt van het niet-reflectieve object valt en in de richting van het punt op het originele object wordt teruggekaatst moet worden berekend. 4. De nieuwe ray raakt een reflectief object. Het directe licht dat op dit punt van het reflectieve object valt en in de richting van het punt op het originele object wordt teruggekaatst moet worden berekend. Ook moeten we een nieuwe ray uitzenden vanaf het reflectieve object en hetzelfde proces hiervoor uitvoeren.1 De radiance die terug wordt gegeven door deze nieuwe ray moet worden opgeteld bij het andere, dierecte, teruggekaatste licht. Na deze stappen kan het indirecte licht op het object worden opgeteld bij het licht dat het object direct van de lichtbronnen ontvangt om een totale hoeveelheid licht te verkrijgen. 1 Omdat dit proces in theorie een oneindig aantal keren zou kunnen doorgaan, kun je in onze raytracer aangeven hoeveel keer je dit proces maximaal wil laten herhalen.
70
HOOFDSTUK 10. SPIEGELREFLECTIE
71
Omdat stap 4 het hele proces nog een keer uitvoert, kan dit proces in een sc`ene met veel objecten erg vaak uitgevoerd moeten worden. Dit zorgt voor veel intensief rekenwerk waardoor het relatief lang duurt om een sc`ene met reflectie te renderen. Hier moet worden opgemerkt dat wij in dit werkstuk alleen omgevingslicht, directional licht en puntlicht behandelen. De bronnen van deze soorten lichten kunnen geen van allen door een ray worden geraakt, dus optie twee komt in onze raytracer niet voor. Dit zou wel van toepassing zijn als we bijvoorbeeld een lamp willen raytracen: een lamp is namelijk een object dat zelf licht uitzendt.
10.2
Het model
10.2.1
De rendervergelijking
Zoals hierboven al is beschreven kunnen we de totale hoeveelheid licht die vanaf een punt in een bepaalde richting wordt weerkaatst als volgt beschrijven: Lo (p, ωo ) = Ldirect (p, ωo ) + Lindirect (p, ωo )
(10.1)
Hierin is Ldirect (p, ωo ) te bereken met de stof behandeld in hoofdstuk 6. Voor reflectie geldt voor Lindirect (p, ωo ) de algemene formule: Z Lindirect (p, ωo ) = fr (p, ωi , ωo )Li (rc (p, ωi ), −ωi ) cos θi dωi (10.2) 2π
Dit is een ingewikkelde formule, maar wij kunnen deze sterk vereenvoudigen, omdat we alleen spiegelreflectie bekijken. Dit zullen we hieronder doen.
10.2.2
Indirect licht bij spiegelreflectie
Om het indirecte licht bij spiegelreflectie te berekenen, moeten we ten eerste de nieuwe ray opstellen in de richting waarvandaan indirect licht zou invallen. Omdat het gaat om een spiegelreflectie weten we dat θi = θo = θr : de hoek van de inkomende ray met de normaal is even groot als de hoek van de uitgaande ray met de normaal. De uitgaande ray zullen we hier de ‘reflected’ ray noemen, om verwarring met directe verlichting te voorkomen. Ook weten dat φi = φo ± π, want de uitgaande straal gaat precies aan de andere kant van de normaal van het object weg. Uit 6.18 kunnen we dan afleiden dat de nieuwe ray met de volgende vergelijking te beschrijven is: r~i = −r~r + 2(~n • r~r )~n Hierin zijn r~i en r~r allebei van het vlak af gericht. r~i is de ‘incoming ray’. Het is dus de ray in de richting waar het indirecte licht vandaan komt. r~r is de ‘reflected ray’. Het is de ray die in de richting van de camera gaat (dit is dus −~r, als ~r de ray is die uit de camera is afgeschoten). Ook weten we bij spiegelreflectie dat de indirecte radiance, die van een reflectief object komt, maar uit ´e´en richting komt, namelijk in de richting van het punt op het originele object. Hiervoor hoeven we dus geen integraal te gebruiken. We kunnen de gereflecteerde radiance in de richting ωo beschrijven als: Lindirect (p, ωo ) = fr,s (p, ωi , ωo )Li (p, ωi ) cos θi
(10.3)
Hierin is Li het inkomende licht op het reflecterende object. De fr,s is de ‘perfect specular BRDF’, de BRDF van perfect gespiegeld licht. Omdat we hier alleen het simpele model voor spiegelreflectie zullen uitleggen, nemen we aan dat deze BRDF een constante is, afhankelijk van de reflectieco¨efficient kr en de reflectiekleur cr .2 Hierin is kr ∈ [0, 1].De perfect specular BRDF wordt dan: fr,s = kr cr Dit invullen in 10.3 geeft: Lindirect (p, ωo ) = kr cr Li (p, ωi ) cos θi
(10.4)
2 Een ander model dat kan worden gebruikt bevat zogenaamde Fresnel vergelijkingen, waarin f r,s afhankelijk is van de hoek θi . Dit zullen wij echter niet in dit verslag behandelen, bij gebrek aan tijd.
HOOFDSTUK 10. SPIEGELREFLECTIE
72
De BRDF moet echter voor alle hoeken nul zijn, behalve voor de hoeken waarvoor geldt dat hoek van inval gelijk is aan hoek van uitval. Dit zullen we echter niet in de formules aangeven. In de code houden we rekening met dit aspect door alleen de berekeningen uit te voeren voor de waarden waarbij geldt θi = θr en φi = φo ±π. Als we ook hieraan hebben gedacht (hoewel we dit alleen in de code zullen doen en niet in de formules) hebben we nog ´e´en probleem. In de rendervergelijking krijgen we namelijk te maken met een factor cos θi . De hoeveelheid weerkaatste radiance richting de camera is echter niet afhankelijk van de hoek van inval bij spiegelreflectie.3 Deze moeten we dus nog uit de vergelijking zien te halen. Dit kunnen we eenvoudigweg doen door vergelijking 10.4 te delen door cos θi . We krijgen dan: Lindirect (p, ωo ) = kr cr Li (p, ωi ) Lindirect (p, ωo ) = kr cr Li (p, ωi )
cos θi cos θi (10.5)
Met deze formules kunnen we spiegelreflectie implementeren in onze raytracer.
10.3
En de raytacer?
In dit hoofdstuk hebben we besproken hoe we een eenvoudige vorm van reflectie in onze raytracer kunnen bouwen: Figuur 10.2: Sc`ene met reflectie, getiteld: ”De spiegelreflectie. Hiermee kunnen we een stuk interessan- drie voetballers”. tere plaatjes renderen dan we hiervoor konden, zoals bijvoorbeeld figuur 10.2. In figuur 10.3 zien we wat er met de sc`ene uit 8.4 gebeurt als we het groene vlak en de twee bollen reflectief maken. Het groene vlak heeft hier een reflectieco¨efficient van 0,5, de rode bol een reflectieco¨efficient van 0,4 en de blauwe bol een reflectieco¨efficient van 0.7.
10.4
Bronnen en code
Bronnen De bronnen die we voor dit hoofdstuk gebruikt hebben zijn: [1] en [26]. Code De code voor dit hoofdstuk is terug te vinden in reflective.h.
Figuur 10.3: De sc`ene uit figuur 8.4 gerenderd met reflectie.
3 Bij bepaalde andere vormen van reflectie is deze factor wel van belang. Deze zullen we echter niet in dit profielwerkstuk behandelen.
Hoofdstuk 11
Breking Breking is een veel voorkomend verschijnsel in de natuur. Het is een verschijnsel dat optreedt als licht overgaat van het ene materiaal in het andere. Als we bijvoorbeeld door glas kijken treedt er breking op. Een duidelijk voorbeeld is te zien als we een potlood in een glas water zetten, zie figuur 11.1. Het potlood lijkt hier ‘gebroken’. Bij elke instantie waarbij we door een transparant voorwerp kijken, komt het licht ‘gebroken’ in onze ogen. In dit hoofdstuk zullen we een relatief eenvoudig model uitleggen om breking in raytracing te benaderen. We zullen alleen zogenaamd ‘perfect specular’ breking behandelen.
11.1
Het principe
Breking komt voor bij het raytracen van transparante objecten. Hier gaat het begrip ‘brekingsindex’ een rol spelen. De brekingsindex (η) is een eigenschap die per stof verschilt. Het is een verhoudingsgetal van de lichtsnelheid (c) in vacu¨ um en de lichtsnelheid in de stof (vstof ) en wordt gegeven door de volgende vergelijking: ηstof =
c vstof
Figuur 11.1: Een voorbeeld van breking. [27] Wanneer een lichtbundel een doorzichtig medium binnentreedt is er een constante verhouding tussen de sinus van de hoek van inval en de sinus van de hoek van breking, de brekingshoek. Dit heet de ‘Wet van Snellius’. Deze verhouding is gelijk aan de brekingsindex, dus geldt: ηstof =
sin θi c = sin θt vstof
Hierin staat de t in θt voor ‘transmitted’. Ook geldt voor een overgang van de ene stof in de andere dat de brekingsindex ten opzichte van de ene stof tot de andere voldoet aan de volgende vergelijking: ηA→B =
ηA ηB
(11.1)
Wij werken echter niet met de ‘´echte’ natuurkunde, want wij zijn aan het raytracen: wij bekijken waar lichtstralen vandaan zouden kunnen komen. Als wij dus rays gaan schieten, zullen we niet formule 11.1 voor η moeten gebruiken, maar het omgekeerde hiervan. Want de rays gaan ‘tegen het licht in’. Dus de brekingsindex waar wij mee zullen werken als we rays uitsturen zullen we als volgt berekenen: ηA→B,rays =
73
ηB ηA
(11.2)
HOOFDSTUK 11. BREKING
74
Figuur 11.2: Een sc`ene gerenderd met ambient, directional en puntlicht, ´e´en transparante bol (links: η = 1, rechts: η = 1, 1), ´e´en bol met Phongmateriaal en een vlak (Phongmateriaal). Met deze vergelijking kunnen we berekenen hoe licht gebroken wordt als het van stof A in stof B overgaat, want hieruit kunnen we het volgende afleiden: ηA→B =
ηB sin θi = ηA sin θt
(11.3)
In ‘Realistic Ray Tracing, Second Edition’ van P. Shirley en R. Morley (2003) staat een afleiding, waaruit we een vergelijking voor de richting van een lichtstraal die gebroken wordt kunnen berekenen. [16] Deze zullen we gebruiken om deze richting te berekenen en deze luidt als volgt: ~t = 1 r~i − (cos θt − 1 cos θi )~n η η
(11.4)
Hierin is ~t de gebroken (‘transmitted’) lichtstraal, η de brekingsindex van de stof waaruit de lichstraal komt naar de stof waar de lichstraal ingaat, r~i is de invallende lichtstraal en ~n is de normaal, tot slot is θt de hoek van breking en θi de hoek van inval. Voor cos θi geldt natuurlijk: cos θi = ~n • r~i Voor cos θt geldt (ook deze komt uit het boek van Shirley en Morley): r 1 cos θt = 1 − 2 (1 − cos2 θi ) η
(11.5)
Door deze constructie is ~t automatisch een eenheidsvector. Er zijn bij breking twee verschillende opties, namelijk dat de lichtstraal naar de normaal toe gebroken wordt, of dat de lichtstraal van de normaal af gebroken wordt. Uit 11.3 kunnen we afleiden dat het licht naar de normaal toe breekt als ηB > ηA , dus als het licht van een stof met een kleinere brekingsindex naar een stof met een grotere brekingsindex gaat. Zo kunnen we ook afleiden dat als ηB < ηA het licht van de normaal af wordt gebroken. In figuur 11.2 zien we links een sc`ene waarin transparantie optreedt. We zien de groene bol met Phongmateriaal dwars door de rode, transparante bol heen. Hier is de brekingsindex van de transparante bol 1, dus kijken we er doorheen, zonder dat het licht van richting wordt veranderd. Rechts zien we in figuur 11.2 dezelfde twee bollen, maar dan met een brekingsindex 1,1 voor de transparante bol. Hier zien we duidelijk dat het beeld van de groene bol in de rode vervormd wordt. Dit is een voorbeeld van geraytracete breking.
11.1.1
Totale reflectie
Als we eens een situatie bekijken waarbij er breking van de normaal af optreedt, dus θr > θi . Dan komt er een moment waar op θi < 90◦ en θt > 90◦ . Bij θt > 90◦ is er echter helemaal geen sprake meer van breking.
HOOFDSTUK 11. BREKING
75
De ~t gaat dan namelijk de andere stof helemaal niet binnen maar weerkaatst van het oppervlak. We noemen de hoek van inval waarbij θt = 90◦ de ‘grenshoek’, oftewel θg . Wat gebeurt er nu met ~t als θi > θg ? Het antwoord is dat ~t dan niet bestaat. De reden hiervoor is dat de energie die met de invallende ray wordt meegenomen verdeeld wordt over ~t, de gebroken lichtstraal, en ~r het gedeelte van het licht dat gereflecteerd wordt. Bij θi > θg geldt dat alle energie in de gereflecteerde lichtstraal ~r gaat zitten. ~t heeft dan dus geen energie meer en bestaat dus niet. We hadden al kunnen verwachten dat er zo’n limiet was voor θt aangezien de vergelijking van de cosinus van deze hoek aan de vergelijking in 11.5 moet voldoen. Dit is een wortelfunctie en deze heeft dus een (verticale) asymptoot, wat namelijk moet gelden is dat dat, wat onder het wortelteken staat, groter moet zijn dan nul, want anders is de uitkomst een imaginair getal. Een voorwaarde voor breking is dus de volgende ongelijkheid: 1 (11.6) 1 − 2 (1 − cos2 θi ) > 0 η Gaat deze ongelijkheid niet op, dan hebben we te maken met totale reflectie.
11.2
Het model
Uit hoofdstuk 10 hadden we voor de rendervergelijking het volgende gehaald (vergelijking 10.1: Lo (p, ωo ) = Ldirect (p, ωo ) + Lindirect (p, ωo ) Hier kunnen we nu Lindirect opdelen in een gebroken en een gereflecteerde component: Lindirect (p, ωo ) = Lr (p, ωo ) + Lt (p, ωo ) Hierin is Lr (p, ωo ) het gereflecteerde licht en Lt (p, ωo ) het gebroken (‘transmitted’) licht. Net zoals bij Lr , kunnen we voor Lt een moeilijke formule geven, die we vervolgens kunnen vereenvoudigen. Deze formule luidt als volgt:1 Z Lt (p, ωo ) = ft,s (p, ωi , ωo )Lo (rc (p, ωi ), −ωi ) |cos θi | dωi (11.7) 2π −
Bij breking hebben we de radiance aan de andere kant van het oppervlak nodig, hiervoor gebruiken we niet de BRDF, maar de BTDF. Waar de BRDF de verhouding aangeeft van de hoeveelheid radiance, die wordt weerkaatst naar een bepaalde richting, met de hele irradiance, geeft de BTDF de verhouding aan van de hoeveelheid radiance, die gebroken wordt naar een bepaalde richting, met de hele irradiance (zie figuur 11.3). In vergelijking 11.7 is ft,s (p, ωi , ωo ) de functie van de BTDF. De BTDF geeft dus de verhouding aan tussen de hoeveelheid licht die gebroken wordt en de hoeveelheid invallende irradiance. Deze verhouding is afhankelijk van de brekingsindices van de twee stoffen, waar we mee te maken hebben, en met een brekingsco¨efficient. Zo kunnen we voor de BTDF de volgende formule opstellen: ft,s = kt
ηt2 ηi2
(11.8)
Hierin is ηt de brekingsindex van de stof waar het licht ingaat en ηi de brekingsindex van de stof waar het licht vandaan komt. Hoewel 11.8 een simpele vergelijking is, is het makkelijk om de verkeerde ηt en ηi te nemen. Figuur 11.3: BRDF en BTDF Laten we nou bijvoorbeeld eens een ray nemen die van stof A naar stof B [26] gaat. Stof A heeft als brekingsindex ηout en B ηin . In figuur 11.4 gaat een 1 Opmerking: onder de integraal staat in tegenstelling tot de lichtvergelijkingen in vorige hoofdstukken 2π − in plaats van 2π. Dit is omdat we het hier hebben over de integraal van de halve bol aan de andere kant van het oppervlak.
HOOFDSTUK 11. BREKING
76
primaire ray, r~0 , over van stof A naar stof B en later weer van stof B naar stof A. Vanaf het punt a wordt de gebroken ray t~1 uitgestuurd en in het punt b, waar ray t~1 weer van stof B over gaat naar stof A, wordt de ray t~2 uitgestuurd. De dikke blauwe pijlen geven de richting van het licht aan. We zien dat deze tegen de richting van de rays in gaat. In situatie a zouden we de brekingsindex van het licht kunnen berekenen in . Voor de ray doen we het precies met 11.1. We zouden dan de volgende brekingsindex krijgen: ηB→A = ηηout ηout andersom, dus krijgen we: ηA→B = ηin . Dus in de functie voor de BTDF moeten we in situatie a voor ηt juist ηout nemen en voor ηi juist ηin . Op punt b is het natuurlijk precies andersom: hier moeten we voor ηt de ηin gebruiken en voor ηi de ηout . In 11.8 is kt tot slot de brekingsco¨efficient. Zoals in 11.1.1 al is besproken wordt de energie van het licht bij het raken van het oppervlak verdeeld over het gereflecteerde licht en het gebroken licht. Dus moeten de brekingsco¨efficient en de reflectieco¨efficient (zie 10.2.2) samen de co¨efficient van het totale invallende licht zijn. Dus geldt kr + kt = 1. Om vervolgens de functie voor de radiance die via breking bij ons raakpunt met de ray komen te krijgen, vermenigvuldigen we de ft,s met het invallende licht. Dan krijgen we voor het gebroken licht: Lt (p, ωo ) = kt
ηt2 Li (p, ωi ) ηi2
(11.9)
Figuur 11.4: Een ray gaat door Hierin is Li (p, ωi ) = Lo (rc (p, ωi ), −ωi ) (zie 11.7). Dit is de hoeveelheid een transparant object. [1] radiance die we tegenkomen langs de gebroken ray ~t. Waar we verder nog aan moeten denken is dat we moeten aannemen dat er alleen gebroken licht komt vanuit de richting ~t, we hebben hier immers te maken met ‘perfect specular’ breking. We moeten dus aannemen dat de BTDF voor alle richtingen behalve richting ~t nul is. Dit zullen we doen door de berekeningen simpelweg alleen uit te voeren voor de richting, die aan de gestelde voorwaarden voldoet. Het laatste wat we over breking moeten zeggen, is dat breking een proces in gang zet dat een enorme hoeveelheid rays teweeg kan brengen. Daarom zullen we bij het raytracen werken met een maximum diepte voor rays. Dit betekent dat we alleen rays tot de nde orde zullen raytracen en daarna zullen stoppen. We kunnen hier niet zeggen dat we maar een vast aantal rays zullen raytracen, omdat het aantal rays bij breking en reflectie niet zonder meer vaststaat. Laten we bijvoorbeeld een primaire ray r~0 beschouwen die een transparant object raakt in het punt a. In a treedt geen totale reflectie op, dus zullen er twee extra rays moeten worden uitgestuurd. De gereflecteerde ray r~1 , een ray van de eerste orde, raakt een reflecterend object en we zullen hiervoor nog een reflected ray moeten bouwen. Deze ray r~2 is een ray van de tweede orde en raakt, bijvoorbeeld, de achtergrond. Hier zijn we klaar. Dan hebben we van ray r~0 nog de gebroken ray die geconstrueerd werd. Dit is de ray t~1 , deze ray is van de eerste orde. Deze raakt de binnenkant van het geraakte object en er treedt totale reflectie op. Er is dus alleen een reflected ray. We construeren r~3 , een ray van de tweede orde. Deze raakt het object weer van binnen, maar hier treedt geen totale reflectie op. Er komt dus een gebroken ray en een teruggekaatste ray, deze twee zijn van de derde orde. Als we nu een maximale diepte n hebben ingesteld, zal dit proces stoppen na de rays van de nde orde, waarna er dus geen nieuwe rays meer afgevuurd worden. Dit is nodig om de rendertijden nog een beetje in te perken. De zogenaamde ‘max depth’ is in onze raytracer in te stellen door de gebruiker. Een lage max depth zorgt voor kortere rendertijden dan een hoge max depth, maar een hoge max depth zorgt voor mooiere reflectie- en brekingseffecten.
11.3
En de raytracer?
In dit hoofdstuk hebben we geleerd hoe we transparante transparante objecten in onze raytracer kunnen nabootsen. We hebben hier een simpele vorm van breking behandeld. Hiermee kunnen we dan ook weer een aantal bijzondere plaatjes maken. In figuur 11.5 zien we de sc`ene uit 10.3 met in plaats van een reflectieve rode bol, een transparante rode bol. Links is de rode bol perfect transparant, hij heeft een brekingsindex van 1. Rechts is er sprake van breking. De brekingsindex van de rode bol is in dit geval 1,2. Een ander voorbeeld van transparantie in onze raytracer is te zien in “Ballen op een stapel”, in figuur 11.6.
HOOFDSTUK 11. BREKING
Figuur 11.5: Links: Rode bol met η = 1. Rechts: Rode bol met η = 1.2.
Figuur 11.6: Tien transparante ballen op een stapel.
11.4
Bronnen en code
Bronnen De bronnen die we voor dit hoofdstuk hebben gebruikt zijn: [1], [27], [16], [26], [28] en [29]. Code De code voor dit hoofdstuk is terug te vinden in transparent.h.
77
Hoofdstuk 12
Conclusie In dit concluderende hoofdstuk zullen we nog ´e´en keer terugkijken op wat we met ons profielwerkstuk hebben bereikt en vooral ook wat we niet hebben bereikt. We zullen dus een korte samenvatting geven van wat we hebben besproken en uitleggen wat er nog allemaal kan worden gedaan om een n´og betere raytracer te bouwen.
12.1
Samenvatting
In dit profielwerkstuk hebben we behandeld hoe we een basis raytracer kunnen maken en de theorie achter de raytracer uitgelegd. Zo kunnen wij nu bollen, vlakken, rechthoeken, driehoeken, cilinders en .OBJ-modellen raytracen (hoofdstukken 2, 3 en 8). We hebben uitgelegd hoe we kunnen bepalen vanaf welk punt we naar de sc`ene kijken (hoofdstuk 4) en hoe we kartelvorming kunnen tegengaan (hoofdstuk 5). We zijn verder gegaan met een model voor lichten in een raytracer (hoofdstuk 6). We hebben ambient lichtbronnen, directional lichtbronnen en puntlichtbronnen behandeld. Daarnaast hebben we het model van Phong behandeld en hebben we uitgelegd hoe we schaduwen in een raytracer kunnen nabootsen. Ook hebben we uitgelegd hoe we met het belichtingsmodel eenvoudige vormen van reflectie (hoofdstuk 10) en breking (hoofdstuk 11) kunnen renderen. Verder kunnen we nu driedimensionale objecten transleren, roteren om de x-as, de y-as en de z-as, spiegelen in de x, de y en de z-as en verschalen (hoofdstuk 7) en kunnen we tot slot texturen aanbrengen op objecten om ze mooier te maken (hoofdstuk 9). Figuur 12.1: Een sc`ene, geraytracet met In figuur 12.1 zien we een sc`ene, geraytracet met onze eigen onze eigen raytracer. raytracer. In deze afbeelding zit een groot deel van de stof die we in dit profielwerkstuk behandeld hebben. We zien verschillende objecten: een bol, drie vlakken en vier rechthoeken. Ook zien we alle drie de verschillende lichtbronnen: een ambient lichtbron, een directional lichtbron en een puntlichtbron. We hebben op de bol een textuur aangebracht. Om deze bol op deze plaats en van deze grootte te krijgen hebben we dus moeten transformeren. We zien reflectie in de vloer en in de spiegels. We zien diffuus materiaal op de muren en Phongmateriaal op de randen van de spiegels. Verder zien we dat de wereldbol transparant is en dat het licht wordt gebroken. We zien namelijk de vervorming van de randen van de spiegels door de bol.
78
HOOFDSTUK 12. CONCLUSIE
79
Verklaring van het plaatje op de voorpagina In deze paragraaf zullen we uitleggen hoe we aan het plaatje zijn gekomen dat op de voorpagina is afgebeeld. In feite zien we dezelfde afbeelding als in 12.1. Het enige verschil is dat we de transparante wereldbol hebben vervangen door een marmeren ‘standbeeld’ met diffuus materiaal. Het model voor de man die nu tussen de spiegels staat is een .OBJ-model, dat men kan vinden in de bronvermelding. [30] Het model bestaat uit 45.149 vertices, 45.149 normalen en 86.617 driehoekjes. Elke keer dat een ray de bounding box om het model heen raakte, moesten er dus 86.617 driehoekjes geraytracet worden. Het renderen van deze afbeelding duurde dan ook 21 uur en 23 minuten.1
12.2
Wat had nog meer gekund?
Met alles wat we hebben gedaan kunnen we een hele hoop mooie plaatjes maken, maar ´echt realistisch wordt het nog net niet. Het is mogelijk om een nog betere raytracer te maken die bijvoorbeeld plaatjes kan maken zoals in figuur 12.2. We zouden bijvoorbeeld in plaats van alleen spiegelreflectie ook andere vormen van reflectie kunnen behandelen, zoals ‘glossy reflection’, ‘metallic reflection’ en ‘blurry reflection’. Ook hadden we nog dieper in kunnen gaan op breking. Wij hebben namelijk alleen breking in ´e´en richting behandeld, terwijl licht dat door een oppervlak gebroken wordt, lang niet altijd onder dezelfde hoek gebroken wordt. Zo hebben wij in ons lichtmodel nergens rekening gehouden met het feit dat de verschillende golflengten van verschillende kleuren licht ook anders worden gebroken. Ook hadden we in onze raytracer nog een andere lichtbron kunnen behandelen: de oppervlaktelichtbron. Dit Figuur 12.2: Een geraytracete sc`ene. [31] is een object dat licht uitzendt. Bijvoorbeeld een lichtgevende bol, of een lichtgevend vlak. Hiermee hadden we bijvoorbeeld echte lampen in onze sc`ene kunnen neerzetten en hiermee realistischer schaduwen kunnen nabootsen. Daarnaast hadden we nog de techniek van het ‘path tracen’ kunnen uitleggen. Bij path tracing worden er vanaf elk snijpunt een aantal rays in willekeurige richtingen uitgestuurd. Deze rays worden vervolgens net zo lang gevolgd (getracet) totdat ze een lichtbron tegenkomen. Op deze manier wordt de hoeveelheid licht die een punt bereikt dus nog beter benaderd dan wij tot nu toe hebben gedaan. Path tracing is een erg rekenintensief proces maar kan tot verbluffende resultaten leiden. In figuur 12.3 (links) zien we het resultaat van een simpele sc`ene waar path tracing op is toegepast. We zien een zweem van de weerkaatsing van de oranje muur op de grond achter de rechter transparante bol. Dit soort effecten zijn ontstaan door path tracing. Een andere techniek die we niet hebben besproken, maar die we wel graag hadden willen toevoegen is de techniek die “depth of field” heet, oftewel scherptediepte in het Nederlands. Met deze techniek zouden we kunnen scherpstellen op een bepaald gebied van de sc`ene, net zoals we kunnen scherpstellen met een echte fotocamera. In figuur 12.2 zien we deze techniek terug: de kan en glazen die achter in de sc`ene staan zijn vaag. Er is dus scherpgesteld op een gebied daarv´o´or, waarin bijvoorbeeld de dobbelstenen en de wijnglazen staan. Deze staan namelijk wel scherp op de afbeelding. Er zijn nog veel meer bekende raytracingtechnieken die we hadden kunnen uitleggen als we meer tijd hadden gehad, maar raytracing is dan ook een techniek die nog lang niet uitontwikkeld is. Er zijn namelijk nog vele details in de natuur die we nog niet hebben kunnen reproduceren met raytracers en zelfs de beste raytracer die op dit moment op de markt is, is nog niet ‘af’. Toch wordt raytracing steeds vaker gebruikt, want hoewel het een rekenintensieve rendermethode is, is het op dit moment wel de meest realistische rendermethode. 1 Voor
het renderen werd een AMD Athlon 64 X2 Dual Core Processor 4400+ (2CPUs), 2.3GHz processor gebruikt.
HOOFDSTUK 12. CONCLUSIE
80
Figuur 12.3: Links: Een sc`ene gerenderd door een raytracer die path tracing ondersteunt. [32] Rechts: Een deels geraytracete sc`ene uit de bekende film: The Lord of the Rings: the Fellowship of the Ring. [33] Raytracing zien we inmiddels terug in de meeste moderne animatiefilms en ook voor de special effects in speelfilms wordt raytracing volop gebruikt. Zo zijn de gezichten van sommige wezens uit films als ‘Lord of the Rings’ deels maskers, maar ook deels geraytracet (zie figuur 12.3 (rechts)). Wij hebben in ons profielwerkstuk geprobeerd om de basis van dit ingewikkelde proces uit te leggen, en hopen dat u zich als lezer nu enigszins bekend voelt met de elementaire onderdelen van het raytracen.
Hoofdstuk 13
Onze raytracer Met de theorie die in de werkstuk staat beschreven hebben we onze eigen raytracer gebouwd. In dit hoofdstuk staat beschreven hoe hij werkt en hoe je hem kunt gebruiken. In figuur 13.2 is een screenshot van de raytracer te zien.
13.1
Werking
Wij hebben onze raytracer geschreven in de programmeertaal C++ samen met het ‘QT framework’. Daarnaast hebben we de ‘Eigen’ library gebruikt voor het rekenen met matrices.1 De raytracer leest eerst een script in om de sc`ene te bouwen. Het script wordt in ECMAscript, een soort javascript, geschreven. De raytracer rendert vervolgens de sc`ene en kan deze exporteren. De complete sc`ene kan samen met de instellingen in een .RAY-bestand worden opgeslagen. We hebben de raytracer geschreven op Windows, maar aangezien C++ en QT op meerdere besturingssystemen werken zou het programma ook op Linux en Mac moeten werken. De raytracer is, evenals de broncode, te downloaden op http://code.google.com/p/pwsraytracer/ Onze raytracer is uitgeveven onder de ‘GNU General Public License versie 3.0’. [34] Deze houdt in het kort in, dat iedereen ons programma gratis mag verspreiden en aanpassen, onder de voorwaarde dat de broncode wordt meegeleverd. Voor het programmeren met QT hebben we gebruik gemaakt van de bronnen [35] en [36]. Figuur 13.1: De instellingen voor de raytracer
13.2
Gebruik
13.2.1
Instellingen
In het rechtervenster kun je de instellingen van de raytracer aanpassen (zie figuur 13.1). De volgende instellingen zijn aan te passen: • Resolution. De resolutie van de afbeelding 1 Zie
http://qt.nokia.com/ en http://eigen.tuxfamily.org/
81
HOOFDSTUK 13. ONZE RAYTRACER
82
• Sample method, Number of samples en Sample sets. De methode van sampling (regular, random, jittered). Met number of samples kun je het aantal samples per pixel de instellen. Sample sets is het aantal verzamelingen samples dat gegenereerd wordt (de standaardwaarde 83 is meestal goed). • Pixel size. De grootte van een pixel. Door deze groter te maken zoom je uit, door deze kleiner te maken zoom je in. • Max depth. Hiermee wordt ingesteld hoevaak er maximaal nieuwe rays kunnen worden uitgezonden (bij transparantie en spiegelreflectie). • Eyepoint, Look at point en Distance to screen. Hiermee kun je het eyepoint (waar de camera staat) en het look at point (waar de camera naar toe kijkt) instellen. Met distance to screen stel je de afstand van het scherm tot het eyepoint in. • Roll angle. Hiermee stel je in onder welke hoek de camera moet rollen (zie het hoofdstuk over camera en perspectief op bladzijde 26). • Ambient RGB en Ls: de kleur en sterkte van de ambient lichtbron. • Out-of-gamut. Hiermee wordt ingesteld wat de raytracer doet bij overbelichting: de raytracer brengt de waarde van de kleuren weer binnen het bereik (max to one) of de raytracer maakt alle overbelichte punten wit of rood (clamp to white of clamp to red). Met de renderknop kun je tenslotte de raytracer laten renderen. Met de update knop laat de raytracer alvast zien wat er tot dan toe gerenderd is. Met de knop stop kun je de raytracer laten stoppen en met clear kun je het scherm weer leegmaken.
13.2.2
Licht
In het scriptvenster moet de sc`ene worden beschreven in een scriptbestand. Je begint het beschrijven van een object met ‘.create();’ en eindigt met ‘.finish();’ Elke regel moet met een ‘;’ worden afgesloten. Regels worden commentaar door er ‘//’ voor te zetten. Je kunt een heel stuk tekst commentaar maken door het tussen ‘/* dit is commentaar */’ te zetten. Directional licht Een directional licht wordt gedefinieerd door een kleur, de lichtsterkte en de richting. De richting is hierbij van het object naar het licht toe (zie pagina 41). Je maakt een directional light als volgt aan: 1 2 3 4 5
directional . create ( ) ; //Maak een d i r e c t i o n a l l i c h t b r o n directional . setColor ( 1 , 1 , 1 ) ; // Kleur directional . setLs ( 1 . 6 ) ; // L i c h t s t e r k e directional . setDirection ( 1 , 2 , 2 ) ; // R i c h t i n g directional . finish ( ) ; // F i n i s h , d i t z o r g t h e t d i r e c t i o n a l l i c h t wordt afgemaakt en t o e g e v o e g d aan de s c e n e
Puntlicht Een puntlicht wordt gedefinieerd door een kleur, de lichtsterkte en een punt. 1 2 3 4 5
pointLight . create ( ) ; pointLight . setColor ( 1 , 1 , 1 ) ; pointLight . setLs ( 2 ) ; pointLight . setLocation ( 0 , 1 5 0 , 0 ) ; // L o c a t i e van de l i c h t b r o n pointLight . finish ( ) ;
Als je wilt, kun je instellen dat de kwadratenwet moet worden gebruikt (distance attanuation). De lichtsterke moet dan wel veel hoger zijn, omdat deze zeer snel afneemt.
HOOFDSTUK 13. ONZE RAYTRACER
1 2
83
pointLight . setLs ( 3 3 0 0 0 0 ) ; pointLight . setDistAtt ( true ) ;
13.2.3
Objecten
Voor elk object moeten we het materiaal en de kleur instellen (texturen komen aan het eind van dit hoofdstuk). Daarnaast kunnen we elk object transformeren. De kleur en het materiaal stel je als volgt in: 1 2 3 4 5
. setColor ( 1 , 0 , 0 ) ; // Kleur van h e t o b j e c t i n r , g , b . materialMatte ( ka , kd ) ; // R e f l e c t a n c e voor h e t ambient d e e l en h e t d i f f u s e d e e l . materialPhong ( ka , kd , ks , phongexp ) ; // S t e r k t e van de s p e c u l a r r e f l e c t i e , g r o o t t e van de s p e c u l a r r e f l e c t i e . materialReflective ( ka , kd , ks , phongexp , kr , r , g , b ) ; // S t e r k t e van de s p i e g e l w e e r k a a t s t i n g en de w e e r k a a t s t i n g s k l e u r r , g , b . materialTransparent ( ka , kd , ks , phongexp , kr , r , g , b , kt , eta ) ; // S t e r k t e van de t r a n s p a r a n t i e en de b r e k i n g s i n d e x
De kleur moet eerst worden ingesteld en daarna pas het kan het materiaal worden ingesteld. Je kiest natuurlijk maar ´e´en van de vier materialen. Hieronder staan voorbeelden van de verschillende materialen, aangebracht op een bol: 1 2 3 4
sphere . materialMatte ( 0 . 2 , 0 . 5 ) ; sphere . materialPhong ( 0 . 2 , 0 . 5 , 0 . 2 , 1 0 ) ; sphere . materialReflective ( 1 , 0 . 8 , 0 . 8 , 2 0 , 0 . 6 , 1 , 1 , 1 ) ; sphere . materialTransparent ( 0 . 0 , 0 . 2 , 0 . 2 , 1 0 0 , 0 . 6 , 1 , 1 , 1 , 0 . 8 , 1 . 2 ) ;
De translaties werken als volgt: 1 2 3
. scale ( x , y , z ) ; //x , y , z v e r s c h a l e n . rotate ( x , y , z ) ; // Roteren om de x , y , z a s ( i n graden ) . translate ( x , y , z ) ; // T r a n s l e r e n met x , y , z .
Hieronder staan voorbeelden van de verschillende transformaties. 1 2 3
sphere . scale ( 1 , 2 , 1 ) ; // Bol wordt i n de y−r i c h t i n g 2 k e e r zo g r o o t sphere . rotate ( 9 0 , 0 , 4 5 ) ; // R o t a t i e van 90 graden om de x−a s en van 45 graden om de z− as sphere . translate ( 0 , 1 0 0 , 0 ) ; // T r a n s l a t i e ( 0 , 1 0 0 , 0 )
Om te spiegelen kun je verschalen met −1. Daarnaast moet het object natuurlijk worden begonnen met .create() en .finish(). Bol Een bol wordt gedefinieerd door zijn middelpunt en de straal. En bol voer je als volgt in: 1 2 3 4 5 6
sphere . create ( ) ; sphere . setColor ( 1 , 0 , 0 ) ; sphere . setRadius ( 5 0 ) ; // S t r a a l sphere . setCenter ( 0 , 5 0 , 5 0 ) ; // Middelpunt ( x , y , z ) sphere . materialPhong ( 0 . 2 , 0 . 5 , 0 . 2 , 1 0 ) ; sphere . finish ( ) ;
HOOFDSTUK 13. ONZE RAYTRACER
84
Vlak Een vlak wordt gedefinieerd door een punt en en de normaal. Een vlak voer je als volgt in: 1 2
plane . setNormal ( 0 , 1 , 0 ) ; // Normaal plane . setPoint ( 0 , 0 , 0 ) ; //Een punt waar h e t v l a k doorheen g a a t
Hierbij zijn create(), finish(), setColor() en het materiaal weggelaten, omdat dit bij elk object op dezelfde manier gaat. Cirkel Een cirkel wordt gedefinieerd door het middelpunt, de straal en de normaal. 1 2 3
circle . setNormal ( 1 , 1 , 1 ) ; // Normaal circle . setPoint ( 2 0 0 , 4 0 , 3 0 ) ; // Middelpunt circle . setRadius ( 3 0 ) ; // S t r a a l
Rechthoek Een rechthoek wordt gedefinieerd door een punt en twee vectoren die de zijden aangeven. Je dient er zelf voor te zorgen dat deze vectoren loodrecht op elkaar staan (de normaal wordt door de raytracer berekend). 1 2 3
rectangle . setPoint ( 1 0 0 , 1 0 0 , 0 ) ; // Punt rectangle . setA ( 1 0 0 , 0 , 0 ) ; // Vector 1 rectangle . setB ( 0 , 1 0 0 , 0 ) ; // Vector 2
Cylinder Een cylinder wordt gedefinieerd door de straal en de hoogte. Je geeft de hoogte aan als een minimale en een maximale co¨ordinaat. Om de cylinder op een andere plek te krijgen, moet hij worden getransformeerd. 1 2 3 4
cylinder . setRadius ( 2 5 ) ; //De s t r a a l van de c y l i n d e r cylinder . setY ( 0 , 4 0 0 ) ; //De minimale en de maximale y waarde −> de h o o g t e cylinder . rotate ( 0 , 0 , 9 0 ) ; // T r a n s f o r m a t i e cylinder . translate ( 2 0 0 , 2 2 5 , 0 ) ; // T r a n s f o r m a t i e
Triangle Een driehoek wordt gedefinieerd door drie hoekpunten, vertices. 1 2 3
triangle . setV0 ( 0 , 0 , 0 ) ; // Hoekpunt 0 triangle . setV1 ( 0 , 2 0 0 , 0 ) ; // Hoekpunt 1 triangle . setV2 ( 3 0 0 , 0 , 0 ) ; // Hoekpunt 2
.OBJ bestanden Onze raytracer bevat een simpele .OBJ bestandenlader. Deze werkt alleen voor .OBJ-bestanden die uit driehoeken bestaan. Daarnaast moet ook de normaal in de .OBJ-bestanden staan. De code controleert verder niet of het een goed bestand is. Fouten in het .OBJ-bestand kunnen dan ook tot rare resulaten of crashes leiden. Het laden van een .OBJ-bestand gaat als volgt: 1
obj . setFile ( "y :\\ obj \\ cool . obj " ) ;
Het .OBJ-bestand moet in dezelfde map als het .ray-bestand staan, anders moet de volledige locatie worden ingevoerd. Hierbij moet je een \ als een dubbele \\ schrijven.
HOOFDSTUK 13. ONZE RAYTRACER
85
Textures Onze raytracer kan textures inlezen en over een bol, een cylinder, een rechthoek, een cirkel en over een .OBJ-bestand heen plaatsen (mits de (u, v) co¨ordinaten in het .OBJ-bestand staan). Je kunt pas een texture inlezen, nadat het materiaal is aangemaakt. Er moet geen middelpunt, straal, lengte etc. worden ingegeven, aangezien textures alleen over standaardobjecten heen kunnen worden geplaatst. Daarom moet er met transformaties worden gewerkt om het object te veranderen. De raytracer maakt als er niks wordt ingesteld automatisch een standaardobject aan. Dit gaat als volgt: 1 2 3 4 5
sphere . create ( ) ; sphere . scale ( 1 0 0 , 1 0 0 , 1 0 0 ) ; // In p l a a t s van s t r a a l 100 sphere . materialMatte ( 1 , 1 ) ; // E e r s t m a t e r i a a l sphere . setTexture ( "y :\\ obj \\ bol . jpg " ) ; //Dan t e x t u u r sphere . finish ( ) ;
De bestandsnaam moet hierbij op dezelfde manier als bij obj worden aangegeven (met \\).
13.2.4
Scripten
Het sc`enebestand is een ECMAScript bestand. Daarom kun je hiermee ook echt programmeren2 . Een voorbeeld hiervan is het gebruik van een lus. Hieronder is een voorbeeld, waarin 34 bollen in een ellipsvorm worden neergezet. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
var var var var for {
i =0; j = 6.28/34; x = 0; y = 0; ( i =0;i < 6 . 2 8 ; i+=j ) x = Math . cos ( i ) ; y = Math . sin ( i ) ; x ∗= 2 5 0 ; y ∗= 4 0 0 ; sphere . create ( ) ; sphere . setColor ( 1 , 1 , 1 ) ; sphere . setRadius ( 2 2 . 5 ) ; sphere . setCenter ( x , 2 2 . 5 , y ) ; sphere . materialReflective ( 0 . 2 , 0 . 8 , 0 . 3 , 1 5 , 0 . 6 , 1 , 1 , 1 ) ; sphere . finish ( ) ;
}
13.2.5
Voorbeeld
Tenslotte zullen we een voorbeeld geven van een complete sc`ene in de raytracer. De afbeelding die met deze code gerenderd is, is te zien in figuur 11.5 (rechts) (pagina 77). 1 2 3 4 5 6 7 8 9
plane . create ( ) ; plane . setColor ( 0 , 1 , 0 ) ; plane . setNormal ( 0 , 1 , 0 ) ; plane . setPoint ( 0 , 0 , 0 ) ; plane . materialReflective ( 0 . 2 , 0 . 4 , 0 . 4 , 1 0 , 0 . 5 , 1 , 1 , 1 ) ; plane . finish ( ) ; sphere . create ( ) ; sphere . setColor ( 1 , 0 , 0 ) ; 2 Voor meer informatie over programmeren met ECMAScript kun je zoeken naar ECMAScript en javascript. Bijvoorbeeld op: http://doc.trolltech.com/4.5/ecmascript.html en http://w3schools.com/js/js loop for.asp
HOOFDSTUK 13. ONZE RAYTRACER
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
sphere . setRadius ( 5 0 ) ; sphere . setCenter ( 0 , 5 0 , 5 0 ) ; sphere . materialTransparent ( 0 . 2 , 0 . 4 , 0 . 4 , 1 0 , 0 , 1 , 1 , 1 , 1 , 1 . 2 ) ; sphere . finish ( ) ; sphere . create ( ) ; sphere . setColor ( 0 , 0 , 1 ) ; sphere . setRadius ( 1 0 0 ) ; sphere . setCenter ( 0 , 1 0 0 , − 1 0 0 ) ; sphere . materialReflective ( 0 . 2 , 0 . 4 , 0 . 4 , 1 0 , 0 . 7 , 1 , 1 , 1 ) ; sphere . finish ( ) ; cylinder . create ( ) ; cylinder . setColor ( 0 . 5 4 9 , 0 . 4 2 5 , 0 . 3 5 6 ) ; cylinder . materialPhong ( 0 . 2 , 0 . 4 , 0 . 4 , 1 0 ) ; cylinder . setRadius ( 2 5 ) ; cylinder . translate ( −150 ,0 , −50) ; cylinder . setY ( 0 , 2 0 0 ) ; cylinder . finish ( ) ; cylinder . create ( ) ; cylinder . setColor ( 0 . 5 4 9 , 0 . 4 2 5 , 0 . 3 5 6 ) ; cylinder . materialPhong ( 0 . 2 , 0 . 4 , 0 . 4 , 1 0 ) ; cylinder . setRadius ( 2 5 ) ; cylinder . translate ( 1 5 0 , 0 , − 5 0 ) ; cylinder . setY ( 0 , 2 0 0 ) ; cylinder . finish ( ) ; cylinder . create ( ) ; cylinder . setColor ( 0 . 5 4 9 , 0 . 4 2 5 , 0 . 3 5 6 ) ; cylinder . materialPhong ( 0 . 2 , 0 . 4 , 0 . 4 , 1 0 ) ; cylinder . setRadius ( 2 5 ) ; cylinder . translate ( 0 , 0 , − 5 0 ) ; cylinder . setY ( 0 , 4 0 0 ) ; cylinder . rotate ( 0 , 0 , 9 0 ) ; cylinder . translate ( 2 0 0 , 2 2 5 , 0 ) ; cylinder . finish ( ) ;
directional . create ( ) ; directional . setColor ( 1 , 1 , 1 ) ; directional . setDirection ( 1 , 2 , 2 ) ; directional . setLs ( 2 . 5 ) ; directional . finish ( ) ; pointLight . create ( ) ; pointLight . setColor ( 1 , 1 , 1 ) ; pointLight . setLocation ( −50 , 3 0 0 , 5 0 ) ; pointLight . setLs ( 2 . 9 ) ; pointLight . finish ( ) ;
In figuur 13.2 is te zien hoe deze sc`ene door de raytracer wordt gerenderd.
86
HOOFDSTUK 13. ONZE RAYTRACER
Figuur 13.2: Screenshot van de raytracer
87
Bibliografie [1] K. Suffern, Ray Tracing from the Ground Up.
A K Peters, Ltd., 2007.
[2] E. Deloget, “GCDC 07,” oktober http://www.gamedev.net/reference/business/features/gcdc07/default.asp
2007.
Op:
[3] “Crytek’s Cevat Yerli Speaks on Rasterization and Ray Tracing,” april 2008. Op: http://www.thetechlounge.com/news/12625/Cryteks-Cevat-Yerli-Speaks-on-Rasterization-andRay-Tracing/ [4] R. Boers, “Verzamelingenleer,” 2008, extra hoofdstuk geschreven voor een vijfdejaars Wiskunde D cluster. [5] C. Wynn, “An Introduction to BRDF−Based http://developer.nvidia.com/object/BRDFbased Lighting.html [6] M. Zwicker, “Computer Graphics II, http://graphics.ucsd.edu/courses/cse168 s06/ucsd/lecture08.pdf
Lighting,”
Rendering,”
2000. 2006.
Op: Op:
[7] “Kruisproduct.” Op: http://nl.wikipedia.org/wiki/Uitwendig product [8] “Determinant.” Op: http://nl.wikipedia.org/wiki/Determinant [9] “Regel van Cramer.” Op: http://nl.wikipedia.org/wiki/Regel van Cramer [10] “Inverse matrix.” Op: http://nl.wikipedia.org/wiki/Inverse matrix [11] “Steradiaal.” Op: http://nl.wikipedia.org/wiki/steradiaal [12] “Implicit fucntion.” Op: http://en.wikipedia.org/wiki/Implicit function [13] “Dependent and independent variables.” Op: http://en.wikipedia.org/wiki/Dependent variable [14] “Parametervergelijking.” Op: http://nl.wikipedia.org/wiki/Parametervergelijking [15] “Barycentrische coordinaten.” Op: http://nl.wikipedia.org/wiki/Barycentrische coordinaten [16] P. Shirley en R. K. Morley, Realistic Ray Tracing, Second Edition.
A K Peters, Ltd., 2003.
[17] “Phong shading.” Op: http://en.wikipedia.org/wiki/Phong shading [18] “Rendering equation.” Op: http://en.wikipedia.org/wiki/Rendering equation [19] P. Shirley, M. Ashikhmin, M. Gleicher, S. R. Marschner, E. Reinhard, K. Sung, W. B. Thompson, en P. Willemsen, Fundamentals of Computer Graphics, 2nd ed. A K Peters, Ltd., 2005. [20] “Verschalen (meetkunde).” Op: http://nl.wikipedia.org/wiki/Verschalen (meetkunde) [21] “Transformation (geometry).” Op: http://en.wikipedia.org/wiki/Transformation (geometry) [22] “Polygon meshes.” Op: http://en.wikipedia.org/wiki/Polygon meshes
88
BIBLIOGRAFIE
89
[23] “Flat, Gouraud and Phong http://ezekiel.vancouver.wsu.edu/˜cs442/lectures/gouraud/gouraud.pdf [24] H. Fisher, “Images in 3D Graphics.” Op: 3d/index.html
Shading.”
Op:
˜ http://cs.anu.edu.au/Hugh.Fisher/3dteach/comp2720-
[25] M. Steiner, “De wereldkaart van Gerardus Mercator,” Digitaal Erfgoed Nederland, december 2004. Op: http://www.cultuurwijzer.nl/cultuurwijzer.nl/cultuurwijzer.nl/i000441.html [26] “Bidirectional scattering distribution function.” http://en.wikipedia.org/wiki/Bidirectional scattering distribution function
Op:
[27] “Demoproeven Pradem Leuven,” Katholieke http://fys.kuleuven.be/pradem/demoproeven/prf111b.html
Universiteit
Leuven.
Op:
(model
wbx).”
Op:
[28] “Lichtbreking.” Op: http://nl.wikipedia.org/wiki/Lichtbreking [29] “Brekingsindex.” Op: http://nl.wikipedia.org/wiki/Brekingsindex [30] “Whole body color 3d scanner samples http://www.cyberware.com/products/scanners/wbxUVs.html
[31] B. Cromwell, “Optics,” 1998. Op: http://www.lightandmatter.com/html books/5op/ch01/ch01.html [32] “Path tracing.” Op: http://de.wikipedia.org/wiki/Path Tracing [33] “Parallel processing, what it means http://blogs.ign.com/the ultimate samurai/2007/12/13/74209/
to
you.”
Op:
[34] “GNU General Public License.” Op: http://www.gnu.org/licenses/gpl.html [35] J. Blanchette en M. Summerfield, C++ GUI Programming with Qt4, Second Edition. 2008. [36] “Qt Reference Documentation.” Op: http://qt.nokia.com/doc/4.5/
Prentice Hall,