26
Tags:
c++
Skrevet af
Bruger #1474
@ 11.08.2008
Lys klasseIndtil nu har vi bare aflæst geometriske farver og renderet en simpel Gauraud shading. Vi vil gøre vores rendering meget mere realistisk ved at tilføje en lyskilde. Den mest enkle lyskilde er en PointLight. Hvis du forestiller dig en nøgen pære der lyser lige kraftig i alle retninger, så vil du havde en god ide om, hvad der menes med PointLight. Den indeholder en position som kan repræsenteres af en vektor. Vi vil også få brug for en farve til vores lyskilde. Faktisk får vi brug for mere end blot én farve. Vi skal i alt bruge tre farver som hedder følgende:
1) Ambient
2) Diffuse
3) Specular
Ambient farven er egenlig snyd. Den findes ikke i den virklige verden men er noget man i sin tid har bestemt der skulle indføres for at opnå en mere realistisk effekt. I den virklige verden vil det oftest aldrig blive helt mørkt. Der vil som regl altid være en lille smule lys der ganske svagt bliver reflekteret rundt. Selv en mørk nat er stort set aldrig helt mørkt. Stjernene og ikke mindst månen vil reflektere lys og vil oplyse ganske svagt. Den virtuelle verden er meget mere simpelt anlagt. Enten er der lys eller også er der ikke noget lys. For at ungå at denne kunstige og urealistisk belysning har man derfor valgt at indføre ambient lyset.
Diffuse farven kan ganske enkelt opfattes som lystes egenlige grundfarve. Det er derfor den mest dominerende farve af dem alle.
Specular farven kan få overflader til at se blanke ud. Kun når lyskilden står i en reflekterende vinkel mod observatøren, som i vores tilfælde er vores kamera eller viewport, vil specular farven være synlig.
Jeg har valgt at lave en speciel farve klasse for vores lyskilde. Den ser således ud:
#include "RGBA.h"
class LIGHTCOLOR
{
public:
RGBA Ambient;
RGBA Diffuse;
RGBA Specular;
LIGHTCOLOR( void );
};
LIGHTCOLOR::LIGHTCOLOR( void )
{
//Lav en standard opsætning af farver
Ambient.SetValues( 0.025, 0.05, 0.1, 1.0 );
Diffuse.SetValues( 0.8, 0.8, 0.8, 1.0 );
Specular.SetValues( 1.0, 1.0, 1.0, 1.0 );
}
Lad os se, hvordan hele vores lys klasse kommer til at se ud:
#include "Vector.h"
#include "LightColor.h"
class LIGHT
{
public:
VECTOR Position;
LIGHTCOLOR Color;
LIGHT( void );
LIGHT( const double X, const double Y, const double Z );
};
LIGHT::LIGHT( void )
{
}
LIGHT::LIGHT( const double X, const double Y, const double Z )
{
Position.SetValues( X, Y, Z, 1 );
}
For at beregne lysfaldet for en trekant skal vi bruge vertex klassens normal. Vi har én normal defineret for hver vertex. En normal er som regl altid vinkelret mod trekantens flade. Eller med andre ord, så vil normalen stikke ud fra trekantens flade med en vinkel på 90 grader.
For at beregne lysfladen vil man skulle finde vinklen mellem lyskilde og normal. Denne vinkel kan let beregnes ved at tage et prik produkt af normalen og lysretningen. Det vil give et cosinus vinkel. Denne funktion er allerede implementeret i vektor klassen og hedder: Dot, fordi prik produkt på engelsk hedder: dot product. Længden af normalen skal være en enhed lang. Funktionen: Normalize, som også ligger i vektor klassen, vil normalisere en vektor. Det vil sige at den sætter længden på en vektor til præcis 1 enhed. Hvis man ikke husker at normalisere en normal vil man ofte få en uønsket resultat.
Der findes et hav af forskellige materialer og lyskilder i den virklige verden der alle opføre sig på deres helt egen måde. Plastik reflektér lys på en anden måde end f.eks metal. Selv indenfor forskellige metal og plastik arter reflektér lys forskelligt. Når det kommer til stykket så er der ingen der har en korrekt formel på, hvordan lys bliver reflekteret af de forskellige materialer og overflader i forbindelse med computer genereret grafik. Vi kan højst sige at noget ligner eller ikke ligner. Lad os kigge lidt på de mest klassiske reflektions modeller.
Lambert ShadingLambert reflektions model er grundstenen til langt de fleste reflektions modeller af lys. Den benytter kun ambient og diffuse komponenterne. De fleste reflektions modeller er kun fokuseret omkring specular farven. Her er den matematisk formel for Lambert:
Umiddelbart kan denne formel se lidt skræmmende ud, hvis man ikke er så matematisk stærk, men den er faktisk ikke så slem at oversætte til kode. Vi vil lave en funktion i vores renderings klasse som vi kalder for Lambert:
//Denne funktion vil beregne Lambert reflektions model
LIGHTCOLOR RENDER::Lambert( VECTOR & RayNear, VECTOR & RayFar, VECTOR & Ray, VERTEX & V1, VERTEX & V2, VERTEX & V3, GEOMETRY & Geometry, LIGHT & Light )
{
//Interpolerer vertex normalerne for vores trekant
VECTOR N = Geometry.InterpolateNormals( Ray, V1, V2, V3 );
N.Normalize();
//Find vores fragment
VECTOR V;
V.FollowLine( RayNear, RayFar, Ray.Z );
//Find vores lys vektor og normalisér den
VECTOR L = Light.Position.Sub( V );
L.Normalize();
//Find intensiteten ved at tage prik produktet af vores lys vektor og normal
double Diffuse = L.Dot( N );
//Gem Diffuse farven og lad Specular være nul
LIGHTCOLOR Result;
Result.Diffuse.SetValues( Diffuse * Light.Color.Diffuse.R, Diffuse * Light.Color.Diffuse.G, Diffuse * Light.Color.Diffuse.B );
Result.Specular.SetValues( 0, 0, 0 );
//Husk at afklippe vores farve værdier mellem 0 og 1
Result.Diffuse.Clamp( 0, 1 );
return Result;
}
Som du kan se returner funktionen en lysfarve klasse. Denne funktion vil blive kaldt fra ShadePixel funktion så lad os tilpasse den funktion.
//Denne funktion vil blive kaldt for hver gang en fragment er blevet fundet!
RGBA RENDER::ShadePixel( VECTOR & RayNear, VECTOR & RayFar, VECTOR & Ray, VERTEX & V1, VERTEX & V2, VERTEX & V3, GEOMETRY & Geometry, TGAFILE & Texture, LIGHT & Light )
{
//Interpoler vertex farverne
RGBA VertexColor = Geometry.InterpolateVertexColors( Ray, V1, V2, V3 );
VertexColor.Clamp( 0, 1 );
//Interpoler tekstur koordinaterne
UVW Coord = Geometry.InterpolateTextureCoordinates( Ray, V1, V2, V3 );
RGBA TextureColor = Texture.GetPixel( Coord, false );
//Beregn Lambert reflektions model
LIGHTCOLOR LightColor = Lambert( RayNear, RayFar, Ray, V1, V2, V3, Geometry, Light );
//Bland dem sammen og returner farven
RGBA Result( Light.Color.Ambient.R * VertexColor.R * TextureColor.R + VertexColor.R * TextureColor.R * LightColor.Diffuse.R + LightColor.Specular.R,
Light.Color.Ambient.G * VertexColor.G * TextureColor.G + VertexColor.G * TextureColor.G * LightColor.Diffuse.G + LightColor.Specular.G,
Light.Color.Ambient.B * VertexColor.B * TextureColor.B + VertexColor.B * TextureColor.B * LightColor.Diffuse.B + LightColor.Specular.B, 1 );
//Husk at afklippe vores farve værdier mellem 0 og 1
Result.Clamp( 0, 1 );
return Result;
}
Lad os tilføje lyskilden til vores lille scene.
RENDER::RENDER( void )
{
GeometryCount = 3;
//Lav en cylinder
GeometryList[ 0 ].CreateCylinder( 20, 1, 3, 1, -1, -1, -22 );
//Lav en kasse
GeometryList[ 1 ].CreateBox( 2, 2, 2, +3, -1.5, -30 );
//Lav en plan flade
GeometryList[ 2 ].CreatePlane( 13, 13, 0, -2.5, -27 );
//Indlæs teksturen for vores trekant
Texture.Load( "Test.tga" );
//Lav en rødlig lyskilde
PointLight.Position.SetValues( 18, 10, 10 );
PointLight.Color.Diffuse.SetValues( 1, 0.75, 0.5 );
}
Glem ikke at definer lyskilden: PointLight, i renderings klassen! Siden prototypen er blevet udvidet med en ekstra parameter, skal vores nye lyskilde tilføjes til kommandoen i funtionen: RenderImage. Renderingen skulle nu gerne se således ud:
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)
coolt
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
5/5 point
P.S. Lige et lille spørgsmål... Hvor har du alt din viden fra?
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.
Genialt! Det var lige hvad jeg havde brug for!
Søren du laver generalt DE BEDSTE artikler!
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.
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
Dejligt at kunne læse noget på dansk ;-) Super godt skrevet.
Hmm, god artikel, men ringe du har lavet PRÆCIS den samme artikel, bare med C++!
Undskyld men Delphi!
*Mener
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
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
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?
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?
Jep
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.
Det kan jeg da
SIKKERT! Artiklen er meget god
Men synes bare det er urætfærdigt at man får Up for begge
Jonas:
De der UP, er det ikke bare nogle tal, og så ikke mere i det?
Altså jeg laver altid Forumindlæg uden point..
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
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
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
Rigtig god artikel.
Du har dog glemt at fortælle at man skal huske at ændre RenderImage til at bruge den nye ShadePixel()
RGBA Color = ShadePixel(RayNear, RayFar, RayPixel, *V1, *V2, *V3, GeometryList[J], Texture, PointLight);
Og Phong laver compile errors
Da du her
RGBA VertexColor = Geometry.InterpolateVertexColors(Ray, V1, V2, V3);
parser V1, V2 og V3 til InterpolateVertexColors funktionen. Den ta'r vectors, men V1, V2 og V3 er vertexes.
Eller... Nu bliver jeg forvirret.
Error 1 error C2664: 'GEOMETRY::InterpolateNormals' : cannot convert parameter 2 from 'VECTOR' to 'VERTEX &' renderer.cpp 91
Nej, okay. er omvendt. Funktionen forventer vertexes, men V1, V2 og V3 er vectors.
Hvordan den være?
Denne side er da håbløst bugged.
Hvordan [b]skal[/b] den være?
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.