3D software rendering med C++

Tags:    c++
Skrevet af Bruger #1474 @ 11.08.2008
- Har du set nogle af Pixar's flotte 3D animationer?
- Har du nogen sinde undret dig over, hvordan man kan lave disse flotte 3D billeder?
- Vil du gerne lære teknikken så du kan lave lignende billeder med dit eget program?


Forord

Før vi går i gang bør du vide at dette er et meget bredt og kompliseret emne. Det kræver at du har en god matematisk forståelse. Artiklen vil prøve at forklare de mest vigtige teorier, men der kan være områder, hvor artiklen tager for givet at læseren har den nødvendige matematiske baggrundsviden til at kunne forstå det helt. Som altid bør du væbne dig med tålmodighed!

Forvent IKKE at denne artikel vil give dig et et program der kan levere billeder i samme kvalitet som Pixar's animations film. Denne artikel vil give læseren grundstenen som kan udvides efter behov. Der forventes at læseren allerede har en del erfaring i at programmere og kendskab til den 3-dimensionelle verden.

Lad os starte helt fra bunden! Når vi snakker om at lave flotte billeder på en computer, bruger vi ofte betegnelsen: "Rendering". Vi kan derfor sige at vi "Rendere" et billede. Det er et engelsk ord som egenligt bare betyder at man gennemgår en process. I vores tilfælde er det altså at lave 3D billeder ved hjælp af en computer eller med andre ord, at genererer computer skabte billeder.

Der er to populære renderings teknikker, den ene bliver kaldt for "Rasterisation" og den anden bliver kaldt for "Raytracing". Matematikken bag disse to teknikker er egenligt meget ens langt hen ad vejen, men tankegangen og udførelsen er derimod meget forskellige. De begge er designet til at rendere komplekse 3D scener. De kan begge beregne skygger, reflektioner, refraktioner osv., de gør det bare på lidt forskellige måder. Derfor har de begge deres fordele og ulemper.

Denne artikel vil fokuserer på Raytracing fordi det er den mest lineær teknik. Det er også den teknik der kan lave de mest nøjagtige billeder fordi den ligger tættest op af den fysiske verden. Det er også den renderings metode der er lettest at udvide med avancerede effekter. Desværre er alt ikke guld og grønne skove. Raytracing teknikken er gennem tiden blevet beskyldt for at være utrolig langsom. Der er heldigvis blevet udviklet mange forskellige algoritmer som kan få renderings processen til at gå betydelig hurtigere. Takket være den store konkurrence blandt hardware producenterne bliver hardwaren hurtigere og hurtigere for hvert år. De sidste nyheder fra grafikkort producenterne, er, at de gerne vil implementere Raytracing teknikken på fremtidens grafikkort, så man kan opnå foto-realistisk grafik til spil. Så der ér altså alt muligt grund til at fokusere på Raytracing.

Vi vil bygge en række klasser fra bunden og skrive selve programmet i et konsol applikations projekt.

Introduktion til Raytracing

Han sagde >>Der skal være lys!<< og der blev lys.

Uden lys ville der kun være mørke og vi ville derfor ikke kune se noget som helst. Det er et faktum der er til at kunne forstå af de fleste. Men hvad er lys og hvorfor er det vigtigt for os at forstå, hvordan lys bevæger sig gennem rum når vi vil genererer computer skabte billeder?

Når vi taler om at rendere et computerskabt billede, så betyder det egenligt at vi vil simulere lysets adfærd. Lys er en energi kilde der rejser gennem rum i en meget høj fart. Faktisk rejser lys så hurtigt at man i mange år troede at lyset ikke rejste fra et punkt til et andet men snarer var en konstant element.

Når vi tænder for en el-pære i et lokale vil lokalet formegenligt blive oplyst med det samme. Vores hjerner er dog ikke hurtige nok til at kunne opfatte, hvad der egenligt foregår.
1) Lyset starter først fra gløden i el-pæren der spreder sig ud til alle sider.
2) Derefter rammer lystes stråler lokalets vægge og dens genstande.
3) I dét strålerne rammer de forskellige genstande bliver de kastet tilbage.
4) Nogle af lysstrålerne vil til sidst ramme vores øjne og først da kan vi se det oplyste lokale.

Vi starter altså fra lyskilden, der i dette tilfælde er en el-pære, og slutter rejsen ved vores øjne. Hvis vi skulle kopiér dette forløb nøjagtigt i en virtuel verden, ville vi skulle bruge utrolig mange data. Mange flere data end nogen computer er i stand til at kunne håndtere. Samtidigt vil langt de fleste at dataerne være spildt, fordi kun en lille del af lysets samlet stråler vil ramme vores øjne. For at løse dette problem, vil vi gå den modsatte vej. Vi vil derfor starte rejsen fra vores øjne og afslutte den ved lyskilden. På den måde koncentrerer vi os kun om de lystråler vi kan se og dermed er relevant for vores rendering.

Men hvordan kan vi vide, hvor mange lysstråler der rammer os?
Det kan vi egenligt heller ikke! Det vil altid blive et gæt. Det eneste vi kan vide med sikkerhed, er, at jo flere stråler vi benytter i vores renderings forløb, desto mere detaljeret et billede vil vi genererer. Siden vi bruger en computer skærm til at vise vores renderet billede med, bliver vi nødt til at repræsentere vores rendering i pixels. Den mest simple måde, er, at lade en pixel repræsentere én lysstråle. Hvis vores billede format er 320x240 vil vi skulle bruge 76.800 lysstråler. Det kan umiddelbart lyde som mange lysstråler, men ikke desto mindre er de nødvendige for at kunne rendere hele vores billede. Faktisk vil du opleve at det er en meget lav opløsning i forbindelse med Raytracing. Resultatet af sådan en opløsning vil altid blive meget pixeleret, specielt for 3D objekter der befinder sig langt væk fra vores kamera eller viewport. Dette er grundidéen bag Raytracing, hvis du forstår dette er du allerede godt hjulpet på vej.

For at opsummere det hele kan vi sige at Raytracing egenligt bare går ud på at følge lysstråler, der er blevet spredt af en lyskilde, men vi bevæger os i den modsatte retning. Vi går faktisk mod lyset, for at sige det lidt firkantet! Hver pixel i vores færdig renderet billede vil repræsenter minimum én lysstråle. Afhængigt af lystrålernes styrke vil de tilsammen afspejle en given virtuel verden.

Dette billede illustrerer teknikken bag Raytracing.



Den blå pil repræsenterer én lystråle. Læg mærke til at vi starter fra en given pixel i vores billede og slutter til sidst ved vores lyskilde.

I vores første forsøg vil vi ikke fokusere så meget på, hvor realistisk vi kan rendere en lyskilde. Vi er mere interesseret i at kunne se nogle genstande. Når dét er lykkes, kan vi derefter prøve at simulere et realistisk lysfald. Først skal vi lave en række klasser der kan indeholde 3-dimensionelle data.


Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (26)

User
Bruger #13520 @ 01.09.08 21:15
coolt
User
Bruger #12871 @ 13.09.08 13:32
Wow!
Det er ikke hverdag man falder over en så gennemført tutorial. Du forklarer tingene rigtig godt og detaljeret uden at du forventer at folk er indforstået med hvad det lige er du mener. Jeg har arbejdet med 3D før i 3D's MAX og har tit tænkt på hvordan man kunne programmere sådan noget selv. Så du skal virkelig have mange tak for at vise mig hvordan man evt. kunne gøre det.
Keep up the good work :D
5/5 point
P.S. Lige et lille spørgsmål... Hvor har du alt din viden fra?
User
Bruger #1474 @ 17.09.08 14:12
Mange tak for jeres kommentar.

Jeg har min viden fra forskellige bøger, websites og selvfølgeligt mit universitet. Jeg er netop blevet færdig som: Computer Spil Programmør på et Engelsk universitet. Da jeg altid har arbejdet med real-time rendering, har jeg i et stykke tid gået og fået lyst til at prøve software rendering. Håber at I kan drage nytte af min artikel.
User
Bruger #12364 @ 06.10.08 11:38
Genialt! Det var lige hvad jeg havde brug for!
User
Bruger #14855 @ 21.04.09 19:29
Søren du laver generalt DE BEDSTE artikler!
User
Bruger #11164 @ 07.05.09 01:31
Jeg har et forslag til din vektor klasse. Jeg benytter operator overloading til ting som at addere, subtrahere og skalarproduktet. Jeg synes det gør brugen af vektorer meget mere overskuelig :) Ellers thumbs up.
User
Bruger #1474 @ 07.05.09 12:56
Jeg plejer også at bruge operator overloading i mine vektor klasser men jeg ville skrive C++ koden så den let kunne sammenlignes med Delphi koden (Se den samme artikel for Delphi: http://www.udvikleren.dk/Delphi/Article.aspx/319/ ). Operator overloading er en forholdsvis ny feature i Delphi derfor har jeg ikke inkluderet den i Delphi versionen. I tilfælde af at en C++ programmør er interesseret i at lære Delphi (eller modsat) eller bare er interesseret i at se forskellen mellem de to sprog burde disse artikler være et godt udgangspunkt.


Mange tak for jeres kommentar. Hold jer endelig ikke tilbage hvis I har forslag, kritik eller ros :)


PS: Glem nu ikke rate mine artikler :P
User
Bruger #15008 @ 24.05.09 21:27
Dejligt at kunne læse noget på dansk ;-) Super godt skrevet.
User
Bruger #14855 @ 17.06.09 15:32
Hmm, god artikel, men ringe du har lavet PRÆCIS den samme artikel, bare med C++!
User
Bruger #14855 @ 17.06.09 15:33
Undskyld men Delphi!
User
Bruger #14855 @ 17.06.09 15:33
*Mener
User
Bruger #1474 @ 17.06.09 17:10
Jonas:: Hvorfor ser du det som et negativt ting at have en artikel der viser implementationen i flere sprog? Skal Delphi udviklere ikke havde chancen for at implementere denne artikel? :)

Mange tak for rosen.
Jeg modtager gerne ris såvel som ros :)
User
Bruger #14855 @ 19.06.09 23:29
Mener bare at du har lavet 2 af de samme artikler og fået dobblet point for det, du kunne bare ha' lavet det hvor artikel navnet var: "3D software rendering med C++ og Delphi" :) Men synes det er ret mærkeligt :)
User
Bruger #1474 @ 20.06.09 19:10
Hvorfor skulle jeg blande to sprog sammen når de er delt i to adskilte teknologier på dette forum? I så fald hvilken udviklings teknologi skulle jeg så poste dem under? :) Er det fordi du mener at jeg har fået for mange points for de artikler? :)
User
Bruger #1474 @ 20.06.09 20:00
Hvorfor skulle jeg blande to sprog sammen når de er delt i to adskilte teknologier på dette forum? I så fald hvilken udviklings teknologi skulle jeg så poste dem under? :) Er det fordi du mener at jeg har fået for mange points for de artikler? :)
User
Bruger #14855 @ 21.06.09 21:00
Jep :)
User
Bruger #1474 @ 21.06.09 23:01
Jamen så må du jo snakke med de admins der har ansvar for artikeler her på forummet og tage problemstilling op hos dem. Det kan jeg næsten ikke gøre noget ved. Håber trods alt du kan bruge mine artikler til noget.
User
Bruger #14855 @ 22.06.09 14:39
Det kan jeg da SIKKERT! :) Artiklen er meget god :) Men synes bare det er urætfærdigt at man får Up for begge :)
User
Bruger #14103 @ 01.08.09 17:13
Jonas:

De der UP, er det ikke bare nogle tal, og så ikke mere i det?:)
Altså jeg laver altid Forumindlæg uden point..:)
User
Bruger #13937 @ 15.09.09 17:39
Hey. Virkelig gennemført beskrivelse, og tror bestemt at jeg skal ha undersøgt mere på emnet.

Anyway. Har et par spørgsmål:

Den første kodestump på s. 4, som starter således: "//Den officelle TGA fil header!". Skal den gemmes som TGA.h? For hvis det er tilfældet så prøver den jo på at include sig selv :S

Og kan det her på udvikleren lade sig gøre at hente de filer som bliver brugt/lavet i artiklerne? For det ville da være en fordel hvis man sidder ligesom mig og roder rundt i hvordan det hele skal sidde sammen så man har en virkende bund at starte på, og at man ved fejl evt. ved fejl ved at det ikke er koden, men noget setup :)

Og til sidst. Dette burdet ikke have noget problem med at compile på en ubuntu-installation?

mvh. martin :)
User
Bruger #1474 @ 15.09.09 20:21
Hej Martin,

Stukturen som jeg har kaldt for: FILEHEADER, er den officielle TGA fil header. Den skal ligge øverst i header filen TGA.h således den kan genkendes af TGA klassen. Du kan godt lægge den i en separat header fil, hvis du har lyst til det, men så skal den selvfølgelig inkluderes i TGA header filen inden TGA klassen defineres. Jeg vil nok anbefale at lægge den inden i selve TGA klassen da denne TGA fil format kun benyttes af TGA klassen! Med andre ord så er alt koden i den refererede tekst boks implementeringen af hele TGA klassen og kan skrives i en header fil eller en header med tilhørende cpp fil.

Angående kilde kode, du kan sende mig en udvikler post her på forummet med din mail adresse. Så vil jeg sende projektet til dig i en zip fil.

Alle andre er selvfølgelig også velkommen til at sende mig deres mail adresse.

Projektet skulle være platforms uafhængig. Der bliver ikke brugt nogle operative kommandoer eller API'er af nogen form, så ja, hvis din kompiler ellers kan kompile ganske almindelige console applikationer, så burde du også kunne kompilere dette projekt. Lad mig vide hvis du støder ind i nogle problemer.

Søren Klit Lambæk
User
Bruger #1927 @ 19.02.10 12:08
Rigtig god artikel.

Du har dog glemt at fortælle at man skal huske at ændre RenderImage til at bruge den nye ShadePixel()

Fold kodeboks ind/udKode 




Og Phong laver compile errors

Da du her
Fold kodeboks ind/udKode 


parser V1, V2 og V3 til InterpolateVertexColors funktionen. Den ta'r vectors, men V1, V2 og V3 er vertexes.
User
Bruger #1927 @ 19.02.10 12:10
Eller... Nu bliver jeg forvirret.

Fold kodeboks ind/udKode 
User
Bruger #1927 @ 19.02.10 12:12
Nej, okay. er omvendt. Funktionen forventer vertexes, men V1, V2 og V3 er vectors.

Hvordan den være?
User
Bruger #1927 @ 19.02.10 12:13
Denne side er da håbløst bugged.

Fold kodeboks ind/udKode 
User
Bruger #1474 @ 22.02.10 10:21
Hej Morten, jeg er ikke helt med paa hvad du mener. Baade InterpolateNormals() og InterpolateColors() tager vertexer (Vertices). Kan du ikke give mig noget mere sammenhaengene kode saa jeg kan se mere detaljeret hvad du goer. Tak :)
Du skal være logget ind for at skrive en kommentar.
t