Bumpmapping
Multitextur
Grafikk
Kristian Pettersen / Student 2003
Forklaring av>Bumpmapping

Bumpmapping

Hva
pic11

Innføring i per-pixel-bumpmapping, forståelse av multitexturing, extensions, koordinat-rom, normal-maps og normaliserings-cube-maps.

Mitt prosjekt har vært å lage en per-pixel-bumpmappet terning som snurrer, belyst av en stasjonær hvit lyskilde med kun retningsbestemt lys.

Jeg har måttet lære om multitexturing, normal-maps, cube-maps, koordinat-rom og noe vektor- og matrise- matematikk. Jeg har også fått langt større innsikt i OpenGLs model for lys.

Jeg har brukt Visual C++ 6 som utviklingsverktøy, og GLUT for windows-kontroll. Koden er ikke objektorientert.

Introduksjon

Bumpmapping er lys. Når vi ser på en uregelmessig overflate i naturen, er uregelmessighetene i overflaten synlige for oss fordi styrken på det reflekterte lyset fra overflaten er avhengig av vinkelen på overflaten i forhold til lyskilden. Sagt enkelt vil en flate som er vendt mer bort fra lyskilden se mørkere ut enn en flate som er vendt mer mot lyskilden. Det menneskelige øye bruker disse lys og skygge-mønstrene til å tolke overflatens form.

I all grafikk er det en avveining mellom detaljer og naturtrohet på den ene siden, og ytelse på den andre. Hvis vi skulle gjøre en overflate naturtro med kun polygoner ville vi trenge tusenvis for selv en enkel overflate som en ruglete murstein eller overflaten av en appelsin.

Bumpmapping lar oss ha detaljer uten ekstra polygoner. Istedenfor å tegne inn høydene og fordypningene som skaper de lyse og mørke områdene på overflaten, så tegner vi de lyse og mørke områdene direkte, og lurer øyet til å tro at det er forhøyninger og fordypninger på overflaten.

Bumpmapping er lys. I OpenGL simulerer vi lys som skinner på overflater ved hjelp av matematikk. OpenGL regner ut hvilken farge et punkt skal ha, avhengig av materialfargen, lysfargen, lysvinkel, overflatens normal og andre faktorer. For å bumpmappe en overflate bruker vi de samme formlene, de samme faktorene. Men der vanlig OpenGL-lys bruker de faktiske overflate-normalene til polygonet, bruker bumpmapping normalene til den overflaten vi ønsker at polygonet skal se ut som. Det finnes ingen innebygd måte å gjøre bumpmapping på i OpenGL. Vi må regne lyset "for hånd".

Formen for bumpmapping denne modulen omhandler er kjent som tangent-space bumpmapping, per-pixel bumpmapping eller dot3 bumpmapping. Andre former for bumpmapping er environment-map bumpmapping og embossing. Da jeg startet dette prosjektet hadde jeg håpet å kunne dekke alle formene, men denne ene typen viste seg å være nok. Per-pixel bumpmapping (PBM) er uansett den mest avanserte og mest brukte formen for bumpmapping.

Mitt fokus har vært på å forstå teorien bak per-pixel bumpmapping og metodene og verktøyene som brukes. Jeg har valgt bort noen muligheter som ville gjort modulen vanskeligere eller mer rotete. Mitt mål har vært å lage en oversiktlig modul som forklarer alt til den dybde som er nødvendig. Jeg har prøvd å ikke bare lage en løsning for bumpmapping, men å lage en modul som viser klart og tydelig hvordan bumpmapping fungerer og gjøres. Forståelse har vært langt viktigere enn utseende.

Per-pixel bumpmapping er enkelt. Men det er kun enkelt når du forstår det. Lykke til.


Forkunnskaper

Før du prøver deg på bumpmapping anbefaler jeg at du har sett på og forstår det følgende:

  • GLUT brukt for windows-oppsett.
  • Børres moduler Teksturer, Lys og materialer, Skygge og glatting
  • Enkel matriseregning (forstår hva en matrise er og hva den brukes til)
  • Enkel vektorregning (Normalisering og utregning av en vektor fra to posisjoner)
  • Thereses modul Blending
  • Bruk av vertex-arrays til å tegne, samt texture coordinate-arrays for å legge på teksturer. (Se rødboka s. 67)

Bumpmapping

Teori

For å bumpmappe en overflate er det to ting som må gjøres. Vi må endre overflatenormalene til hver pixel, og vi må regne ut 'lysligningen' for hver pixel basert på den nye overflatenormalen. Det er det hele. Lysligningen er beskrevet i Børres modul 'lys og materialer'. Som vi ser, er den omfattende.

bigform

For å gjøre prosjektet overkommelig har jeg valgt bort mesteparten av lysligningen. Jeg vil lage en bumpmap-løsning med kun en lyskilde, kun retningsbestemt lys, og kun hvitt lys. Med disse begrensningene blir lysligningen vi må regne ut for hver pixel slik:

smallform

N og L er begge vektorer. N er overflatenormalen, og L er lysretningen. Md er fargen på overflaten. Vi skal altså ta prikkproduktet av overflatenormalen med lysretningen og multiplisere resultatet med overflatefargen.


Praksis

For å bumpmappe et objekt trenger vi det følgende:

Bumpmapping er en dynamisk teksturerings-teknikk. Lys-effekten tegnes på med teksturer. Men siden vi skal ha animasjon av objektet, er det ikke nok å tegne på en statisk tekstur. Vi må regne ut den komplette teksturen for hver gang det bumpmappede objektet tegnes. På grunn av dette er 90 prosent av bumpmapping-koden direkte relatert til tekstur-delen av programmet. Både lysretnings-vektoren L, overflatenormalen N og overflatefargen Md må lagres som teksturer for at vi skal kunne bumpmappe objektet.

Teksturen for N har vi, i form av en normal-map-tekstur som lagrer alle overflatenormalene. Også teksturen for Md har vi, i form av en vanlig tekstur, i dette tilfellet helt hvit..

L har vi ikke. Det vi har er posisjonen til lyskilden. Men vi kan ikke regne med den direkte. For det første er den en posisjon, ikke en vektor, og for det andre er den uttrykt i et annetkoordinat-system (coordinate-space) enn overflatenormalene i vår normal-map-tekstur. Normal-map-teksturens overflatenormaler er uttrykt i tangent-koordinater der lyskildens posisjon er uttrykt i verdens-koordinater.

Vår første problemstilling er da å få lys-kilde-posisjonen omregnet til en lyskilde-vektor fra hver pixel uttrykt i tangent-koordinater og å få dette lagret som en tekstur.

Først må vi få regnet om posisjonen til lyskilden fra verdens-koordinater til objekt-koordinater. Dette er for å få posisjonen inn i samme koordinatsystem som hjørnene til objektet, slik at vi kan regne ut vektoren fra hvert hjørne. Siden modelview-matrisen regner om fra objekt-koordinater til verdens-koordinater kan vi brukeden inverterte modelview-matrisentil å regne om i motsatt regning.

Når vi har lyskilde-posisjonen i objekt-koordinater regner vi ut lysvektorene, vektorene fra hvert hjørne av objektet til lyskilden. Disse vektorene blir så regnet om til tangent-koordinater ved hjelp av en tangent-matrise.

Lysvektorene, nå uttrykt i tangent-koordinater lagres så som tredimensjonale teksturkoordinater i en texture-coordinate-array.

Denne lysvektor-arrayen brukes som input til en Cube-map-tekstur. Cube-map-teksturen tar hver lysvektor og normaliserer den. Så tar cube-map-teksturen de samme normaliserte lysvektorene for hvert hjørne av en flate og bruker dem til å bygge en ny tekstur som inneholder den interpolerte lysvektoren for hver pixel i flaten.

Denne nye teksturen er den vi var ute etter. Teksturen for L. Den inneholder da lysvektorene fra hver pixel til lyskilden, uttrykt i tangent-koordinater.

(Koden her kan virke noe forvirrende, siden cube-map-delen foregår samtidig som alle teksturene kombineres i multi-texturing-delen.)

Når vi så har alle de tre nødvendige teksturene, bruker vi multitexturing for å kombinere teksturene på overflaten. Extensionen ARB_texture_env_combine lar oss ta prikk-produktet av hver sammenfallende pixel i to teksturer. Vi bruker dette for å kombinere normal-map-teksturen som holder N og output-teksturen fra cube-map-teksturen som holder L.

Deretter lar vi fargeteksturen multipliseres med resultatet av kombineringen.

Når vi så tegner objektet er det bumpmappet. Vi bruker vertex-arrays for å tegne objektet, men det er intet annet spesielt med selve tegningen.



Verktøy og Forklaringer

Multitexturing

Multitexturing er et verktøy for å kombinere to eller flere teksturer på samme polygon. Det er mulig å få noen av de samme effektene ved å tegne polygonet to (eller flere) ganger og så bruke blending, men dette er mindre effektivt, da vi må gjennom nesten hele rendering-prosessen to eller flere ganger.

For å kunne bruke multitexturing må vi ha hardware-støtte for det. Grafikk-kort med mer enn en tekstur-enhet (texture-unit) er nødvendig. Disse er seriekoblet, så hver tekstur-enhet kombinerer sin tekstur med resultatet fra den foregående enheten og sender så resultatet videre til neste. Hver tekstur-enhet kan holde en tekstur samt en tekstur-funksjon (texture-function) som beskriver hvordan teksturen skal kombineres inn.

Antall texture-enheter på grafikk-kortet på maskinen testes ved glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&maxTexelUnits), hvor maxTexelUnits er variabelen du ønsker å lagre antall texture-units i.

Teksturene kan kombineres med hverandre på mange forskjellige måter, kjent som tekstur-funksjoner. De basale tekstur-funksjonene er beskrevet i den røde boka på side 402. Mer avanserte tekstur-funksjoner er gitt i ekstensjonene GL_ARB_texture_env_crossbar/combine/add/dot3. (Se SGIs OpenGL extension registry.)

Før du begynner å bruke multitexturing og teksturfunksjoner er det nyttig å kunne blending og blending-funksjonene, da mye av matematikken og konseptene er de samme.

(Her må nevnes at combine-extensionen kan bryte seriekoblingen av tekstur-enhetene og bruke to vilkårlige tekstur-enheter som kilder.)


Koordinatsystemer (Coordinate Spaces)

I OpenGL jobber vi i forskjellige koordinatsystemer. Verdens-koordinater (World-space) er koordinatsystemet vi starter med og kommer tilbake til ved et kall til glLoadIdentity(). Objekt-koordinater (Object-space) er koordinat-systemet vi tegner et objekt i etter at vi har endret posisjonen med kall til glTranslate og glRotate. Hvert objekt har sitt eget objekt-koordinatsystem.

I alt snakker vi om fem typer koordinatsystemer i OpenGL. Tangent-koordinater (Tangent space), Objekt-koordinater (også kjent som modell-koordinater), Øye-koordinater (Eye-space) og Clip-space. Men vi kommer bare til å bruke tangent, objekt og werdens-koordinater i denne modulen.

Grunnen til at koordinatsystemer er relevant i denne sammenhengen er at de forskjellige vektorene vi skal bruke til å regne ut lyset har sin opprinnelse i forskjellige koordinatsystemer, og det er ikke mulig å regne direkte med vektorer fra forskjellige koordinatsystemer. Lysposisjonen, for eksempel, er definert i verdens-koordinater, mens overflatenormalen er i tangent-koordinater. Vi er nødt til å forstå koordinatsystemer for å kunne regne om posisjoner og vektorer fra et koordinatsystem til et annet.

Omregning fra et koordinatsystemer til et annet skjer ved bruk av matriser. Den velkjente ModelView matrisen konverterer vektorer og posisjoner fra objekt-koordinater til verdens-koordinater og videre til øye-koordinater (siden model-transformasjoner og view-transformasjoner modifiserer samme matrise, og i grunn er samme ting)


Tangent-Koordinater (Tangent-space)

(Også kjent som normal-space og texture-space)

newtspace

Jeg har allerede forklart objekt og verdens-koordinater. Men hva er da Tangent-koordinater? Tangent-koordinater er koordinatsystemet til hvert hjørne i et objekt. Men tangent-koordinater er annerledes en de to andre koordinatsystemene vi har sett på. Istedenfor x,y og z akser har vi t,b, og n. Her er n overflatenormalen, t er overflatetangenten parallelt med retningen der s øker på overflaten (s er den horisontale tekstur-koordinaten. (Se s. 405 i den røde boka. "Assigning Texture Coordinates)) , og b er overflatetangenten som står i nitti grader på begge de to andre aksene.

Hvis du skal bumpmappe en annen figur enn en terning må du regne ut tangent-koordinatsystemet for hvert hjørne på egen hånd. Paul Baker viser en måte å gjøre dette på i sin tutorial. (På bunnen av siden).

Tangent-koordinatsystemet for hvert hjørne av et objekt er statisk og trenger bare regnes ut en gang, eller settes direkte inn i programmet hvis du allerede har dem regnet ut (som tilfellet er med min terning).


Tangent-Matrisen

. Det finnes ingen predefinert matrise for å konvertere fra objekt-koordinater til tangent-koordinater eller omvendt. Men hvis vi kjenner aksene i det ene koordinatsystemet som vektorer i det andre, kan vi bygge en matrise som konverterer vektorer fra det andre til det ene. Altså, hvis vi kjenner aksene til tangent-koordinatsystemet for en vertex beskrevet som vektorer i object-koordinater, kan vi lage en matrise som konverterer vektorer fra objekt-koordinater til tangent-koordinater. Hvis vi skriver x, y og z komponentene av S, T og N aksene til tangent-koordinatsystemet som xS, yS, zS, Tx, osv., vil tangent-matrisen se slik ut:

(Sx,Sy,Sz)
(Tx,Ty,Tz)
(Nx,Ny,Nz)

Ved å multiplisere en vektor i objekt-koordinater med denne matrisen vil den bli konvertert til tangent-koordinater. En posisjon, derimot, kan ikke konverteres av denne matrisen.

Hvert hjørne i et objekt vil vanligvis ha sin egen tangent-matrise. Men ikke i mitt eksempel. Der deler de fire hjørnene på en flate den samme tangentmatrisen.


Normal-Maps

normal192
En normal-map-tekstur er en vanlig todimensjonal RGB tekstur. Men istedet for farger lagrer den overflatenormaler. Hver normal er en vektor med tre komponenter i henholdsvis x, y og z-retning. Hver vektorkomponent lagres som en fargekomponent. x i R, y i G og z i B. Siden overflatenormalen er en enhetsvektor, vil de tre vektorkomponentene x,y og z alle ligge mellom 1 og 1. Men vi kan ikke lagre negative fargeverdier i en tekstur, så fargekomponentene må regnes ut som R =(x+1)/2, G = (y+1)/2 og B = (z+1)/2. Normal-maps har vanligvis en hovedsaklig blåaktig farge, da en overflatenormal som står rett opp vil ha vektorkomponenter (0, 0, 1) og dermed rgb-representasjon (0.5, 0.5, 1).
texture

Normal-map-teksturen jeg bruker i denne modulen laget jeg fra teksturen til høyre. Jeg brukte nVidias NormalMapFilter for Adobe Photoshop. Dette verktøyet kan lage en normal-map fra ethvert bildeformat Photoshop kan åpne. Det kan lastes ned her.

Normal-maps lastes inn i programmet og bindes til tekstur-enheter på samme måte som vanlige teksturer.

Cube-Maps

Cube-maps er en form for tredimensjonal tekstur der vi istendenfor en teksturflate har seks, ordnet som en terning. Normaliserings-cube-maps brukes til normalisering av vektorer, og er spesielle da teksturen som til slutt legges på polygonet ikke er selve cube-map teksturen.

Jeg skal prøve å forklare.

Normalisering-cube-maps er lagd for slike situasjoner som dette, da vi trenger å få en per-vertex vektor normalisert (eller noe annet som vi har per-vertex som kan lagres som tre komponenter gjort noe annet matematisk med) og linjært interpolert over pixelene i polygonet.

Normaliserings-cube-map-teksturen jeg har brukt lagrer vektorer på samme måte som en normal-map. Men istedenfor simulerte overflatenormaler inneholder en pixel her den normaliserte utgaven av vektoren fra origo til pixelen, der origo ligger i midten av terningen.

cubemap2

En cube-map-tekstur tar en vektor som input, setter vektoren inn i seg selv med utgangspunkt i origo, regner ut hvilken pixel av cube-map-teksturen vektoren vil treffe, og gir som output verdien lagret i denne pixelen. (Se figur)

En normalisert vektor har samme retning som før den ble normalisert. Derfor har alle vektorer som normaliseres til den samme vektoren også samme retning. Dette betyr at enhver vektor fra origo i en normaliserings-cube-map vil gå gjennom pixelen som inneholder den normaliserte utgaven av seg selv.

En cube-map tar tredimensjonale textur-koordinater, siden den er en tredimensjonal tekstur. For en normaliserings-cube-map er disse koordinatene vektorer med tre komponenter.

En normaliserings-cube-map gjør to ting med vektorene den blir gitt. For det første normaliserer den dem. For det andre tar den lysvektorene for hvert hjørne i hver flate og skaper en ny tekstur som er de normaliserte lysvektorene interpolert over pixelene i hver flate, slik at hver pixel i flaten har en normalisert lysvektor som er sånn cirka retningen til lyskilden fra den pixelen, definert i tangent-koordinater.

For å initialisere en bumpmap må vi regne ut vektoren fra origo til hver pixel, normalisere den, og lagre den i pixelen. Dette gjøres for hver av de seks sidene. (Se kildekoden.)


Invertering av Modelview-Matrisen

For å regne om vektorer eller posisjoner fra et koordinat-rom til et annet, der vi allerede har en matrise som konverterer en vei, kan vi invertere matrisen og dermed konvertere i motsatt retning.

Jeg skal ikke gå inn i den matematiske teorien for inverterte matriser her. Jeg vil kun vise hvordan vi skaper den inverterte model-matrisen i OpenGL..

Siden vi vet nøyaktig hvordan modelview-matrisen ble modifisert, lager vi den inverterte matrisen ved å ta de samme glRotate og glTranslate kallene og gjør dem i motsatt rekkefølge og invertert. Koden her burde vise godt hva jeg mener.

   //Position cube/viewpoint
   glTranslatef( 0.0f, 0.0f, -6.0f);
   glRotatef(y, 0.1f, 0.3f, 0.0f);


   // **** build the inverted modelview matrix ****
   glPushMatrix();         //Store the current matrix
   glLoadIdentity();       // Reset matrix

   // To build the inverted matrix, do the same
   // rotations and translations, only negated and
   // in reverse order
   glRotatef(-y, 0.1f, 0.3f, 0.0f);
   glTranslatef(0.0f, 0.0f, 6.0f);

   // Save the inverted model matrix
   glGetFloatv(GL_MODELVIEW_MATRIX, invModMat);
   // Pop the stack to get back to the uninverted matrix
   glPopMatrix();

OpenGL Extensions

Moderne grafikk-maskinvare har mye funksjonalitet som ikke eksisterte da OpenGL først ble designet. Denne funksjonaliteten er dermed ikke støttet i den originale OpenGL APIen. Dette var dog forutsett av designerne, og extensions-mekanismen i OpenGL er deres svar på dette problemet.Hver OpenGL extension tilføyer ny funksjonalitet og de programmerings-messige verktøyene for å kunne bruke den. Programmerings-messig gir en extension tilgang til to ting: Nye attributter som kan brukes i funksjonskall (tokens), og nye funksjoner.

Tokens kan brukes rett fram så snart vi har sjekket at grafikk-maskinvaren programmet kjøres på har støtte for extensionen tokenet tilhører. Nye funksjoner, derimot, er (i Windows) noe mer innfløkt.

For å bruke extensions er det tre ting du må gjøre:

  • Inkludere glext.h, glxext.h og/eller wglext.h i prosjektet, avhengig av hvilke extensions du skal bruke. Lastes ned fra SGIs OpenGL Extension registry
  • Legge inn en sjekk for at extensionen(e) du bruker er støttet av maskinen som kjører programmet. For å gjøre dette må vi få tak i listen over extensions maskinvaren støtter, GL_EXTENSIONS. Så kan vi parse denne for å finne en string som matcher extensionen(e) vi ønsker å bruke. Hvis extensionen(e) ikke er støttet, må programmet enten stoppes, eller så må vi programmere inn alternative måter å få lignende effekter på. Jeg lånte denne delen av koden fra NeHe.
  • (Hvis extensionen har nye funksjoner du skal bruke i tillegg til tokens) Lag en funksjonspeker og få et function entry point som funksjonspekeren skal peke til.
    PFNGLACTIVETEXTUREARBPROC  glActiveTextureARB=NULL;
    glActiveTextureARB=(PFNGLACTIVETEXTUREARBPROC)
       wglGetProcAddress("glActiveTextureARB");
    
    

I denne modulen vil vi bruke fire extensions. Disse er: GL_ARB_multitexture, GL_ARB_texture_env_combine, GL_ARB_texture_cube_map, og GL_ARB_texture_env_dot3.


Multipass-rendering

Det eneste programeksempelet for PBM jeg har funnet på internet brukte multi-pass-rendering (MPR). Et pass er en gjennomgang av rendering-pipelinen. MPR vil altså si at vi tegner et objekt (eller en hel scene) flere ganger, og så blender sammen resultatene. På maskinvare med for få tekstur-enheter eller uten multitexturing-muligheter i det hele tatt kan dette være den eneste muligheten for å skape mer avanserte tekstur-effekter. PBM-programeksempelet jeg fant var laget for hardware med kun to tekstur-enheter, og brukte MPR for å blende sammen (modulere/multiplisere) farge-teksturen med prikkprodukt-resultatet.

Om Programmet

Programmet er lite, enkelt, og rett fram. (Koden er fremdeles noe rotete, dog.) Det har ingen form for interaktivitet.

For å kunne kjøre programmet kreves det et skjermkort som støtter extensionene som brukes, og som har minimum tre tekstur-enheter. Et GForce3 eller nyere kort skulle klare dette fint.

Programmet er utviklet på en P4 2.4GhZ maskin med et Radeon 9700 Pro skjermkort. På en svakere maskin kan initialiseringen ta litt tid.


Mulige Utvidelser

Her er noen ideer for å utvide denne modulen.

  • Bumpmapping av andre objekter enn terninger.
  • Bumpmapping med specular lys.
  • Andre typer bumpmapping (environment-map-bumpmapping, embossing).

Brukt, lest og lagd

Moduler:

[1] [2] [3] [4] [5] [6]

Exe-fil:

C++ kode:

  • Main.cpp

Video:

Takk til Therese for en kjempemodul om Blending. Jeg stjal all html-en din. :)

Bidrag fra Fredrik Danielsen, Java eksempel for Bumpmapping: BumpFrame.zip. Fredrik er en tidligere student som har skrevet modulen: Terreng.

Referanser
  1. NeHe Lesson: 22Jens SchneiderNeHe productionsnehe.gamedev.net/data/lessons/lesson.asp?lesson=2214-04-2010
  1. Bump MappingWikipediaen.wikipedia.org/wiki/Bump_mapping14-04-2010
  1. Bump MappingBrian Lingardweb.cs.wpi.edu/~matt/courses/cs563/talks/bump/bumpmap.html14-04-2010
  1. Per-Pixel LightingPhilip TaylorMicrosoftmsdn.microsoft.com/en-us/library/ms81049414-04-2010
  1. AMD Developers CentralAMDATI: Programmer for å generere normal-maps og DUDV-mapsdeveloper.amd.com/Pages/default.aspx14-04-2010
  1. nvidia Developers ZoneNVIDIAdeveloper.nvidia.com/14-04-2010
Vedlikehold
Skrevet av Kristian Pettersen, juni 2003.
Redaksjonell tilpassing til dette vevstedet Børre Stenseth, september 2003.
Fredrik Danielsen har bidratt med Java code
Forklaring av>Bumpmapping
til toppen