3D-texturmaterial med hjälp av Cg 3D materials with Cg Niklas Ottosson Madeleine Gusdal
EXAMENSARBETE 2003 Institutionen för data- och elektroteknik Department of computer and electronic engineering Examinator Per Zaring CHALMERS LINDHOLMEN UNIVERSITY COLLEGE Göteborg, Sweden 2003
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 2 av 28
Sammanfattning Vi har i detta examensarbete tittat på hur man kan använda Nvidias nya programmeringsspråk, Cg, för att på ett enklare sätt komma åt kraften i hårdvaruaccelererad grafik. Innan har man varit tvungen att programmera grafikkorten med assemblerkod men med Cg har programmeringen lyfts upp till en högre, och betydligt enklare, nivå. Vår uppgift omfattade skapandet av en samling trovärdiga texturmaterial, tillhörande Cg-program (två stycken) för styrning av grafikkortet samt ett visningsprogram som skal till Cg-programmen. Våra material skulle, enligt uppdragsgivaren Chalmers Lindholmens VR-Studio, ge illusionen av att ha en 3dimensionell struktur. Den teknik vi valde för att åstadkomma detta bygger kort på att man sparar information om hur texturen skall skuggas i en, så kallad, normaltextur. Våra Cg-program, ett vertexprogram och ett fragmentprogram, använder vi sedan för att styra grafikkortets uppritningsrutiner till att använda våra beräkningsmodeller istället för dess egna inbyggda. Ovanstående, tillsammans med vårt visningsprogram för laddning av Cgprogrammen till grafikkortet och hantering av inparametrar, ger ett godkänt resultat på plana ytor men ej på godtyckliga 3D-modeller som vi hade hoppats på. Vi har undersökt problemet och kommit fram till att vår lösning för ljusberäkningarna tyvärr är felaktig. Den ger vid olika betingelser felaktiga skuggor i våra texturmaterial. Vi har i slutet på denna avhandling givit exempel på troliga lösningar på problemet.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 3 av 28
Abstract We have in this thesis studied how to use Nvidia’s new programming language, Cg, as an easier way to access the power of hardware accelerated graphics. Earlier, it was necessary to program the graphics card with assembler, but with Cg comes a higher, and significantly easier, level of programming. Our assignment included the creation of a collection of “believable” materials with Cg programs (two such) for controlling the graphics card, together with an application which serves as a shell for the Cg programs. Our materials should, according to the client Chalmers Lindholmen’s VR-Studio, give the illusion of having 3-dimensional structure. The method we chose to manage this, in brief accounts, is built upon saving information of how the shading of the material should be in a, so called, normal-map. Our Cg programs, a vertex program and a fragment program, are then used to get the graphics card’s drawing routines to use our calculation models instead of its own. The above, together with our application for loading the Cg-programs for the graphics card and handling incoming parameters, give an acceptable result on flat surfaces but not on just any 3D model as we had hoped. We have examined the problem and discovered that our solution for lightning calculations unfortunately is erroneous. It gives, at various conditions, incorrect shading in our materials. We give, in the end of this thesis, examples of possible solutions to this problem.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 4 av 28
Förord Vi vill ge ett stort tack till Mikael Johansson, Mattias Roupé och Claes Wernemyr på Chalmers Lindholmens VR-Studio för deras stöd, uppmuntran och hjälp. Vi vill också tacka Dennis Saluäär på Volvo för materialprover och Peter Lundin för högst användbara synpunkter och hjälp med dokumentationen. Slutligen, ett stort tack till Anders Ottosson och Daniel Fjällholm. Utan deras stöd hade denna utbildning varit svår att genomföra.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 5 av 28
Innehållsförteckning 1. Inledning och bakgrund ........................................................................... 7 1.1 Inledning..........................................................................................................7 1.2 Problem och mål..............................................................................................7 1.3 Syfte.................................................................................................................8 1.4 Avgränsningar .................................................................................................8 1.5 Tidigare studier av ämnet ................................................................................8
2 Teori ............................................................................................................ 9 2.1 Texturer ...........................................................................................................9 2.1.1 Grundtexturer och normaltexturer .................................................................... 9 2.1.2 Placering av texturer ....................................................................................... 10
2.2 Beskrivning av Cg ........................................................................................ 10 2.2.1 Bakgrund.......................................................................................................... 10 2.2.2 Användning ...................................................................................................... 10 2.2.3 Profiler och API:er .......................................................................................... 11 2.2.4 Dataflödet i Cg (den grafiska kanalen) ........................................................... 12 2.2.5 Vertex- och fragmentprogram.......................................................................... 13 2.2.6 Användning av Cg-Språket .............................................................................. 14 2.2.7 Jämförelse av C och Cg ................................................................................... 14 2.2.8 Annan tillgänglig teknik................................................................................... 15
2.3 Grunder i 3D-visualisering........................................................................... 15 2.3.1 Koordinatsystem .............................................................................................. 15 2.3.2 Belysning.......................................................................................................... 16
3. Metod ....................................................................................................... 18 3.1 Skapande av texturer .................................................................................... 18 3.2 Visningsprogram .......................................................................................... 18 3.3 Cg-program .................................................................................................. 19
4. Resultat .................................................................................................... 20 4.1 Skapade texturer ........................................................................................... 20 4.1.1 Läder och läderimitation ................................................................................. 20 4.1.2 Tyg.................................................................................................................... 21 4.1.3 Plaster .............................................................................................................. 21
4.2 Visningsprogrammet: CG_Viewer............................................................... 22 4.2.1 Parameterhantering......................................................................................... 22 4.2.2 Anrop till Cg-kompilator (runtime) och laddning av grafikkortet................... 24 4.2.3 Visning av resultat ........................................................................................... 24 4.2.4 CG_Viewers begränsningar............................................................................. 24
4.3 Cg-program .................................................................................................. 25
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 6 av 28
5 Slutsats och diskussion ............................................................................ 26 5.1 Sammandrag................................................................................................. 26 5.2 Utvärdering av arbetet .................................................................................. 26 5.3 Diskussion .................................................................................................... 26 5.4 Förslag till lösningar på problem ................................................................. 27 5.5 Förslag till utveckling av examensarbetet.................................................... 27
Källförteckning ........................................................................................... 28 Bilaga 1 – Begrepp och förklaringar Bilaga 2 – Visningsprogrammet Bilaga 3 – Vertexprogrammet Bilaga 4 – Fragmentprogrammet Bilaga 5 – "Cg in two pages"
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 7 av 28
1. Inledning och bakgrund
1.1 Inledning Visualisering för att kunna fastställa hur en modell kommer att se ut i verkligheten är något som alltid använts. Förr i tiden fick man visualisera från ritningar på papper, men numera används datorer som med Virtual Reality, virtuell verklighet, ger en bättre känsla för hur ett färdigt objekt kommer att upplevas. Att kunna känna på ett objekt, vrida och vända på det och se hur det ser ut i sin naturliga omgivning, är en stor fördel framför att få en teckning av det, vare sig det gäller framställandet av en stol eller en ny stadsdel. VR är ett sätt att visa så kallade 3D-objekt i en 3D-miljö. 3D-objekt är uppbyggda med hjälp av knytpunkter (eng. term ’vertex, vertices’), som binder samman hörnen i en mängd triangulära ytor. Dessa sammanbundna ytor bildar det fullständiga objektet. Ytorna i sin tur är uppbyggda av bildpunkter, så kallade pixlar. Den stora skillnaden mellan VR och en 3D-animation, t.ex. filmen Final Fantasy, ligger i att man i VR-modellen själv kan röra sig eller det visade föremålet fritt, då det är realtidsrenderat. En 3D-animation är förrenderad – varje bild i animationen har gjorts av en högupplöst bild som i förtid har satts ihop till en film. Detta medför att datorn inte behöver arbeta lika mycket vid visningen, och animationen kan därför hålla en betydligt högre realism än en VR-modell. Men det interaktiva inslaget i VR-modellen gör att denna teknik är väldigt intressant trots den lägre nivån på realism.
knytpunkter
bildpunkter
Vad det här examensarbetet har behandlat är metoder att lägga på strukturer och ytor på VR-objekt, för att få en mer verklighetsrelaterad upplevelse i realtid. Detta har gjorts med det nya programmeringsspråket ”C for graphics”, Cg, med vilket vi kan manipulera färger och former på våra 3D-objekt genom att styra datorns grafikkort hur objektets knytpunkter och pixlar skall visas. Hur detta går till beskrivs vidare i kapitel 2.
1.2 Problem och mål Det har nyligen utvecklats ett nytt programmeringsspråk (Cg) för avancerad 3D-grafik. Språket öppnar möjligheter att på ett relativt enkelt sätt skapa hårdvaruaccelererad grafik. Vi fick ett problem av VR-studion på Chalmers Lindholmen, som bestod i att skapa texturer med hjälp av Cg. Vår uppgift var att med hjälp av Cg, ett antal grafiska verktyg och ett flertal programmeringsbibliotek skapa texturer, ett visningsprogram samt Cg-program för dessa texturer. Målet är att kunna skapa texturer som ger illusionen av att ha en 3-dimensionell
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 8 av 28
struktur och att kunna applicera dessa texturer på godtyckliga 3D-modeller genom användningen av Cg.
1.3 Syfte Syftet med denna rapport är att presentera våra resultat och ge exempel på hur man skapar texturer enligt målet ovan. Dessa texturer kan vara av intresse för alla industrier där man behöver se resultatet innan man börjar arbeta
1.4 Avgränsningar Denna uppsats kommer endast kortfattat att ta upp Cg-profilerna (se nedan) vp20, fp20, vp30 och fp30, och går ej igenom andra profiler. Vår inriktning har varit att texturerna skall användas med vp30 och fp30. Vp20 och fp20 har endast använts för enklare testning.
1.5 Tidigare studier av ämnet Vid tidpunkten för arbetets början fanns en mycket begränsad mängd information av Cg. Bland denna exempelvis ”Cg in two pages”, bilaga 5. Nvidia hade också en mängd färdiga Cg-program som vi har gått igenom för att studera språkets uppbyggnad. Vi har också under utbildningens gång läst kursen VR-teknik där vi fått grundläggande kunskaper om 3D-visualisering.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 9 av 28
2 Teori 2.1 Texturer 2.1.1 Grundtexturer och normaltexturer I kapitel 4 finnes de 3D-material vi skapat. Till varje material hör en grundtextur och en normaltextur. Grundtexturen visar färgerna för materialet (en helt vanlig bild utan skuggor eller ljus) medan en normaltextur beskriver för Cg-programmet hur normalerna är riktade. En normal är den vektor som går rätvinkligt från ett plans yta. Pilarna pekar i normalernas riktning. De som pekar rätt mot ljusvektorn ger ”sin bit” av grundtexturen starkast ljussättning och vice versa. Detta ger intrycket att texturen har struktur.
Ljusvektor
Normaltextur Grundtextur
Normaltexturerna är fysiskt sett en bild, uppbyggd av färger, där varje färg har ett RGBvärde. Detta innehåller mängd Rött, Grönt och Blått färgen består av, till exempel (1, 0, 0) för ren röd och (1, 1, 0) för ren gul. RGB-värdet läses in i Cg-programmet och omvandlas till ett XYZ-värde, som ger en vektor för hur normalen skall vara riktad. Ett värde i området 0.0 < 0.5 ger en negativt riktad normal i motsvarande x, y eller z-led och ett värde i området 0.5 ≤ 1.0 ger en positivt riktad normal. Exempelvis ger en pixel färgad med RGB-värde (0.5, 0.5, 1) en normal som är riktad rakt ut från materialet (0, 0, 1). Figuren till höger är en färgkarta som visar i vilken riktning normaler med vissa färger står (x, y, z).
y 1 -1
0 -1
1 z -1
1 x
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 10 av 28
Normaltexturen ger grundtexturen ett intryck av en strukturerad yta, där ljus och skugga förflyttas beroende på var ljuskällan befinner sig, såsom ett äkta material beter sig.
2.1.2 Placering av texturer Ett problem som framkommer när man skall texturera (applicera texturen på) 3Dmodeller är att texturer har två dimensioner och att modellerna normalt har tre dimensioner. Då en 3D-yta vanligtvis inte är helt plan måste texturen sträckas ut eller tryckas ihop på vissa ställen. För att veta hur texturerna skall läggas på (placering, utsträckning, krympning m.m.) så har man sedan länge använt sig av så kallade texturkoordinater. Dessa koordinater anger punkter på 3D-modellen där man vill att texturen skall fästas. Texturen breder sedan ut sig mellan dessa punkter.
2.2 Beskrivning av Cg 2.2.1 Bakgrund Det har länge varit möjligt att programmera hur man vill att GPU-baserade (Graphics Processing Unit – grafikberäkningsenhet) grafikkort skall bete sig, men man har varit låst till fasta algoritm- och assemblerinstruktioner. Detta har visat sig vara ett stort hinder i utvecklandet av hårdvarugrafik (realtid) då det tar lång tid att implementera och kräver stor kunskap i grafikkortets arkitektur och GPU:ns tillgängliga instruktioner. Cg är uppbyggt med C som grund och är byggt för att ge programmerare och 3Dkonstnärer en möjlighet att komma åt snabbheten i hårdvaruaccelererad grafik utan att behöva lära sig ett tidsödande lågnivåspråk såsom assembler. Cg ger möjligheten att kontrollera ett objekts form, färg och rörelse på knytpunkts- och bildpunktsnivå. Cg är ett mycket specialiserat språk och kan således inte ersätta andra språk såsom C och C++. Cg’s användningsområde är att programmera moderna grafikkort, från tredje generationen och uppåt. Tredje generationens grafikkort började med GeForce 3 kort (Nvidias chipset) som kom tidigt i början av 2001. Dessa kort har dock endast begränsat stöd för Cg då de endast klarar av s.k. vertexprogram (se nedan). Fullt stöd för Cg kom först tidigt 2003 med GeForce FX kort (Nvidias chipset) och Radeon 9800 kort (ATI:s chipset).
2.2.2 Användning Cg kommer främst till sin rätt i den mycket grafikkrävande digitala nöjesbranchen och främst då i spelindustrin där kravet på mer och mer realistisk grafik i spelen ökar från år till år. I filmindustrin finns även krav på extremt snabb grafik då fler och fler filmer
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 11 av 28
använder sig av digitala realtidseffekter för att få fram sitt budskap. Cg kan förenkla användningen av avancerade realtidseffekter för båda dessa miljardindustrier. Cg kan också vara ett utmärkt redskap för andra typer av hårdvaruaccelererad realtidsgrafik såsom grafiska simulatorer och avancerade presentationer.
2.2.3 Profiler och API:er Då Cg vänder sig till en speciell typ av processor, GPU, och inte till en vanlig processor som t.ex. en CPU (som sitter i en vanlig PC) så finns behov av att införa begreppet profiler. En profil är en beskrivning av vilken hårdvara (GPU-baserat grafikkort) och vilken mjukvara (API) som skall användas för det program man önskar köra. Exempel på olika GPU’er kan t.ex. vara de som sitter i Geforce 4 Ti och Geforce FX dvs. den hårdvara som skall styras. Här är en lista över några av de profiler som finns idag: Profilnamn vp20
Programtyp vertexprogram
API OpenGL
fp20
fragmentprogram
OpenGL
vs_1_0
vertexprogram
DirectX 8
ps_1_0
fragmentprogram
DirectX 8
vp30
vertexprogram
OpenGL
Grafikkort Nvidia Geforce 3 till och med Nvidia Geforce Ti 4800 Nvidia Geforce 3 till och med Nvidia Geforce Ti 4800 Nvidia Geforce 3 till och med Nvidia Geforce Ti 4800 Nvidia Geforce 3 till och med Nvidia Geforce Ti 4800 Nvidia GeForce FX
fp30
fragmentprogram
OpenGL
Nvidia GeForce FX
De två vanligaste API:erna (API = application programming interface) i detta sammanhang innefattar DirectX (Microsoft) och OpenGL (Silicon Graphics). En API är i detta fall ett programmeringsbibliotek av funktioner för att styra de grafiska data (objekt, ljus m.m.) som skickas till grafikkortet. (Notera att man med Cg bestämmer vad grafikprocessorn skall göra med all denna data. Det är alltså inte Cg som skickar in data). DirectX utvecklas av Microsoft och är ett komplett programmeringsbibliotek för ljud, musik, bild (2D/3D), spelenheter och nätverkskommunikation. Den del som används vid skapandet av stödapplikation för Cg kallas Direct3D och den innehåller klassbibliotek för hantering av 3D-objekt samt ett bibliotek för visning av 3D scener. [Källa 11] OpenGL utvecklas av Silicon Graphics (SGI) och är ett programmeringsbibliotek för datorgrafik. OpenGL stöder både 2D och 3D scener. I detta examensarbete använder vi oss utav en s.k. wrapper-klass byggd på OpenGL kallad OpenSceneGraph. [Källa12]
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 12 av 28
En wrapper-klass är ett bibliotek av färdiga klasser och funktioner som är gjorda för att förenkla hanteringen av ett krångligare bibliotek (OpenGL). En wrapper är normalt specialiserad för en viss typ av uppgifter medan en API normalt måste vara mycket generell för att kunna täcka in alla tänkbara användningsområden. Till OpenSceneGraph använder vi också ett hjälpklassbibliotek, osgNV, och en hjälpklass, NVMeshMender. OsgNV sköter om hanteringen av parametrar (indata) till Cg-programmen samt laddningen av den assemblerkod som vi skapar till grafikkortet (närmare förklaring nedan). NVMeshMender sköter de beräkningar på 3D-objekt som krävs för att kunna applicera våra texturmaterial på dessa objekt. Anledningen till att vi just valde OpenGL som API i denna uppgift är främst för att VRstudion önskade att vi skulle använda fria programmeringsbibliotek. Vi har dessutom under utbildningens gång (dataingenjör, Chalmers Lindholmen) genomgått en kurs i just OpenGL och då också wrappern OpenSceneGraph och osgNV. Det fanns på så sätt stor möjlighet till hjälp i vårt arbete.
2.2.4 Dataflödet i Cg (den grafiska kanalen) För att förstå hur flödet av data i dagens grafikkort fungerar kan man tänka sig ett löpande band med olika stationer där alla bidrar med sin del till slutprodukten och där resultatet efter en stations arbete är indata i nästa. Den grafiska kanalen har just ett sådant löpande band, där produkten är den färdiga bilden. På vägen till färdig bild så flödar först knytpunkter (som senare blir bildpunkter) genom en rad olika processer. Förenklat kan man säga att det går till så här: 1. Knytpunkterna får sin färg och position 2. Primitiverna byggs upp (linjer dras mellan knytpunkterna) 3. Primitiverna fylls med fragment (pixlar som kanske kommer att finnas med i slutprodukten). Knytpunkternas färg interpoleras nu mellan de olika knytpunkterna som är i samma primitiv. Om textur finnes så läggs även denna på här. 4. Efter detta så visas bilden på skärmen.
Denna bild visar en förenklad bild av flödet i den grafiska pipelinen [Källa 1] . Numreringen till ovanstående text går från 1 (längs till vänster) till 4 (bilden längst till höger)
De delar vi kan påverka genom Cg är nr 1 och nr 3. Dessa två olika delar på det löpande bandet ger upphov till två olika typer av program: vertexprogram (från eng. vertex = toppunkt i en vinkel) och fragmentprogram (från eng. fragment = liten del).
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 13 av 28
Nedanstående flödesschema visar en översiktsbild på hela dataflödet från applikationen till skärmen. 3D-applikation eller spel
1
3D APIinstruktioner
2
3D API: OpenGL eller Direct3D CPU – GPU gräns
GPU instruktions- och dataström
3
Ström med knytpunkter
GPU Processeringsstart
Originalknytpunkter
Ihopsatta polygoner, linjer och punkter
5 Primitiverna byggs upp
Ström med fragment
6
Primitiverna fylls med fragment
Uppdatering av pixlar
8
Pixelberäkningar på synliga ytor
9 Bildskärmen
Originalfragment
7
4 Vårt vertexprogram
Ändrade knytpunkter
Vårt fragmentprogram
Ändrade fragment
Förklaring till bilden med hjälp av ett exempel: Säg att vi i ett program vill rita upp en triangel på skärmen och att vi i ett vertexprogram vill ändra färgen på triangeln till blå (den är röd från början). (1) Det första som då händer är att vår applikation/spel anropar funktionen för att rita upp en röd triangel i den API vi använder. (2) API:n skickar då vidare denna förfrågan till grafikkortet genom en rad instruktioner (exempelvis: hur många punkter, vilka punkter som skall sitta ihop samt vilken färg punkterna skall ha). (3) Nu när knytpunkterna kommit in i den grafiska pipelinen så kan vi ändra vår triangel till blå genom att ändra samtliga knytpunkters färg från röda till blå (4). Efter detta så byggs vår triangel upp genom att det dras linjer mellan knytpunkterna (5) och ytan som då bildas fylls med fragment (6). Om vi nu vill så skulle vi här kunna ändra färgen på vår triangel ytterligare (genom att styra om strömmen av fragment till vårt fragmentprogram och byta färg på varje fragment i triangeln (7)) men det vill vi inte denna gång (den skulle ju vara blå). (8) Nu utförs en rad beräkningar för att bestämma vilka fragment som skall bli synliga och därefter ritas vår blå triangel upp på skärmen (9).
2.2.5 Vertex- och fragmentprogram Vertexprogram hanterar grafikobjektets knytpunkter genom manipulation av deras färg och position. Ändrar man färg på en knytpunkt så kommer den färgen att interpoleras med närliggande knytpunkters färger. Viktigt att veta här är att ett objekts tesseleringsgrad har mycket stor betydelse för resultatets kvalitet. Objekt som har hög tesseleringsgrad (d.v.s. är uppbyggda av många små trianglar) passar bra för manipulation av vertexprogram då lågtesselerade (d.v.s. uppbyggda av ett fåtal trianglar) objekt ofta får en triangelyta eftersom det inte finns någon interpoleringen mellan de olika färgerna i skarvarna mellan trianglarna. Att ändra en knytpunktsposition påverkar alltid samtliga vektorer som den knyter ihop. Detta används ofta för animeringar. Fragmentprogram hanterar grafikobjektets pixlar. Även här handlar det om färg och position. Om man ändrar en pixels färg så påverkar den inte omkringliggande pixlar. Samma gäller vid ändring av position. Normalt när man arbetar med fragmentprogram så
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 14 av 28
har man ett vertexprogram som körs innan och vidarebefordrar viktig data till fragmentprogrammet (flödet går från vertex till pixel och aldrig tvärtom).
2.2.6 Användning av Cg-Språket Som vi tidigare nämnt så är Cg ett specialspråk till för att styra hur grafikkortet skall hantera den indata som det får. För att kunna göra detta behöver Cg hjälp. Detta främst för att Cg inte självt har rutiner för att sända koden till grafikkortet men också för att Cg även saknar rutiner för att hantera parametrar. Till dessa syften används programmeringsbiblioteken DirectX eller OpenGL som båda har inbyggda rutiner för att hantera parametrar och för att sända styrkod till grafikkortet. När man använder Cg så skapar man först en applikation byggt på något utav ovanstående programbibliotek. Denna applikation har då hand om hämtandet av objekt att visa/använda, parametrar att använda, laddning av styrkod till grafikkortet samt visning av resultatet. Det är först efter denna applikation är klar som man börjar med själva Cg-programmet. Innan ett Cg-program kan användas så måste det kompileras till assembler (GPU:n liksom en CPU innehåller en assemblator som endast förstår assemblerspråk). Detta kan väljas att kompileras antingen i runtime eller innan visningsprogrammet körs. Både DirectX och OpenGL stöder båda modellerna. Fördelen med att låta koden kompileras runtime är att man slipper kompilera om sin applikation varje gång man gör en förändring i Cg-koden.
2.2.7 Jämförelse av C och Cg Grunden från C känns tydligt igen i varje Cg-program. Alla innehåller de en main()funktion och man ser de vanliga C strukturerna såsom if-satser och for-loopar lite här och var. Det som skiljer sig mest vid första anblicken är att Cg har ett antal nya datatyper. Inom grafikprogrammering är vektorer och matriser nödvändiga varvid dessa finns som egna datatyper i Cg. Exempel: float3 = variabel innehållandes 3 flyttalsvärden float4x4 = en 4x4 matris av flyttal. En annan användbar detalj är att man i Cg kan använda sig av så kallad swizzling, vilket vill säga att man direkt kan ange vilka index i en variabel (om fler än en) man vill använda/ändra. Exempel: float4 variabel1 = float4(1.0,0.0,0.5,0.0); float2 variabel2 = variabel1.xz // Detta flyttar över värdet 1.0 från position x = 1 och värde 0.5 från position z = 3 (xyzw eller rgba gäller för att representera de olika platserna)
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 15 av 28
float3 variabel3 = variabel2.xxx //Fungerar också. Nu tas värdet på plats 1 och fyller samtliga platser i den nya variabeln Förutom dessa mindre skillnader så är det främst tankesättet i Cg som skiljer från C. När man programmerar ett grafikkort så måste man tänka på att man programmerar ett flöde av data (knytpunkter och bildpunkter), se ”Dataflödet i Cg” ovan. En annan viktig detalj är Cg-programmens storlek. Ett C-program kan bli flera tusen kilobyte stort medan storleken på ett Cg-program sällan når över 1000 tecken. Tillåten storlek på ett Cg-program bestäms av den profil man använder (hårdvarans minnesstorlek). Man vill normalt att storleken skall vara så liten som möjligt (så få assemblerinstruktioner som möjligt) då större program tar längre tid att exekvera. Hastighet är något mycket viktigt att tänka på när man skriver Cg-program. Dessa program körs normalt tusentals gånger per sekund. Varje bit icke optimerad kod kommer då att minska hastigheten drastiskt. För någon som tidigare programmerat i C blir inte steget långt till Cg. Mycket görs på samma sätt i C som i Cg. Se även ”Cg in two pages”, bilaga 5.
2.2.8 Annan tillgänglig teknik Som vi tidigare nämnde så har det gått att programmera grafikkortskretsar en längre tid men man har då varit tvungen att förlita sig på assemblerinstruktioner. Detta är fortfarande en fullt användbar teknik. Det är även troligt att denna äldre teknik kommer att användas även i framtiden då många programmerare fortfarande är vana att arbeta med assembler i dessa sammanhang. En annan möjlighet att nå samma resultat utan användning av Cg är att använda Microsofts DirectX 9.0 High-Level Shading Language (HLSL) [Källa 11]. Skillnaderna mellan Cg och HLSL är dock ganska små då Nvidia och Microsoft haft ett nära samarbete med att ta fram Cg.
2.3 Grunder i 3D-visualisering 2.3.1 Koordinatsystem Då objekten i en 3D scen sällan är i samma koordinatsystem så måste vi transformera de olika koordinaterna till ett gemensamt koordinatsystem för att kunna utföra de beräkningar vi behöver. Nedan är en förenklad bild av vad Object space och World space är. Notera att figuren visar förhållandet mellan de olika koordinatsystemen i 2D och ej 3D, men eftersom principen är den samma i 2D som 3D (man lägger endast till en z-axel) så tror vi att denna bild gör det lättare att se sambandet.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 16 av 28
Förklaring till figuren: Den grå rutan ligger ovanpå en större yta. Rutmönstren skall här föreställa de 2 objektens egna koordinatsystem. Krysset som är utritat på den grå ytan är vår exempelkoordinat. På den grå ytan har denna punkt koordinaterna 2,3 (X,Y) men om vi skall tala om vilka koordinater den har på den större ytan så kan vi inte längre använda vårt lilla egna (object space – objektets egna koordinatsystem) koordinatsystem utan vi måste då omvandla till den större ytans koordinatsystem som i detta fall blir vårt World space koordinatsystem. Vår exempelpunkt får då koordinaterna 5,21 (X,Y) i World space. Varför måste vi veta vad vår punkt har för World space koordinater? Eftersom vi behöver utföra en hel del beräkningar på samtliga våra knytpunkter på 3D modellerna i scenen så måste vi ha en gemensam grund. Denna gemensamma grund är World space. Räknar man på koordinater från olika koordinatsystem så blir resultatet nonsens.
Ett sätt att konvertera mellan koordinatsystem, t.ex. från Object space till Tangent space (koordinatsystemet för knytpunkternas tangenter) är att tillverka en så kallad transformationsmatris. Nedan visas en tabell med de transformationsmatriserna vi behövde i denna uppgift. transformationsmatris ModelViewInverse ModelViewProjection ObjToTangentSpace**
transformerar från World space Object space Object space
till Object space Projection space* Tangent space
*Projection space är det koordinatsystem vi har för vår kamera i scenen, dvs.vår kameras interna koordinatsystem inuti World space. **Denna transformationsmatris tillverkar vi inne i vårt vertexprogram genom att ta knytpunktens normal, binormal och tangent och sammanföra dem i en 3x3 matris (se vertexprogrammet i bilaga 3)
Transformationen går sedan till så att man tar de koordinater man vill transformera och multiplicerar dem med transformationsmatrisen (matrismultiplikation).
2.3.2 Belysning Det finns två grundläggande sätt att belysa objekt i en virtuell 3D-värld: diffus- och spekulärbelysning. Diffus (från eng. term diffuse = spridande) belysning är ett sätt att belysa ett objekt så att ljuset reflekteras jämt åt alla håll. Detta ger effekten av att materialet ser matt ut. För att få denna effekt så använder man följande ekvation (förenklad):
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 17 av 28
diffuse = materialets diffusa färg (RGB) * ljusets färg (RGB) * punktens normal (XYZ) * ljusvektorn (XYZ) Spekulär (från eng. termen specular = speglande) belysning är ett sätt att belysa objekten så att det mesta av ljuset reflekteras av ytan. Detta får ytan att se blank ut. Följande (förenklade) ekvation beskriver hur man får denna belysning i varje punkt: specular = materialets spekulära färg (RGB) * ljusets färg (RGB) * punktens normal (XYZ) * punktens halvvinkelvektor (XYZ) Halvvinkelvektorn är den vektor som ligger mitt emellan ljusvektorn och vår vyvektor (kameran). normalvektor halvvinkelvektor vyvektor
ljusvektor
modellyta
Förutom ovanstående belysningsekvationer så har möjligheten att kunna räkna ut ljuset i en enskild bildpunkt varit central för oss. Ett sätt att göra detta på är att ta reda på normalen, binormalen och tangenten i den enskilda punkten, bygga en transformationsmatris och sedan räkna ut belysningen i Tangent space.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 18 av 28
3. Metod Då tiden bedömdes vara den största begränsande faktorn valde vi en metod som, när vi väl hade löst vårt största problem med programmeringen, skulle göra det enkelt för oss att snabbt bygga ut vårt texturbibliotek.
3.1 Skapande av texturer Metoden kommer från en av våra uppdragsgivare från Chalmers Lindholmens VR-studio och bygger på teorin i 2.1.1. Denna teori tillämpar vi på följande sätt: Genom att låta en textur vara informationsbärare av normalernas vinkling kan vi på ett enkelt sätt räkna ut ljussättningen i varje punkt. På detta sätt skapar vi illusionen av att modellen har struktur. Denna struktur beskriver vi i vår normaltextur med hjälp av färger (RGB-värden). För att helt kunna följa arbetet med skapandet av grundtexturer och normaltexturer bör läsaren ha arbetat med Adobe Photoshop [Källa 13], och ha grundläggande förståelse för programmets funktioner. Ett problem med att skapa texturer har att göra med visuell upprepning av texturen. Då man vill spara minne har man normalt små texturer som sedan upprepas över hela ytan, och detta leder till att man kan se upprepade mönster där det inte skall finnas sådana. För att förminska detta problem har vi arbetat med bilder på 1024x1024 pixlar, stora nog att upprepningen inte är helt uppenbar. Ett annat problem har att göra med kanterna på texturerna vid upprepning över ytan. Om texturens högerkant inte stämmer överens med vänsterkanten, och överkanten inte stämmer med underkanten, kan man tydligt se skarvarna i en upprepad textur. För att förhindra detta problem har texturbilden (1) kopierats till fyra lager, och sedan har mitten på dessa lager lagts i vart och ett av de fyra hörnen (2). Alla kanter stämmer nu överens med varandra, och de övergångslinjer som fås längs mittenlinjerna kan målas över med Photoshops klonverktyg (3).
1.
2.
3.
3.2 Visningsprogram Då väldigt lite finns dokumenterat om användningen av OpenSceneGraph som programmeringsbibliotek har vi främst fått hjälp av VR-studion och OpenSceneGraphs mailinglista. Om det inte funnits några färdiga lösningar har vi återigen prövat oss fram med trial-and-error.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 19 av 28
3.3 Cg-program Då Cg var ett helt nytt språk för oss så fick vi först sätta oss in i grunderna, och som med vilket annat programmeringsspråk som helst, göra lättare uppgifter och läsa oss till tankesättet i hur Cg verkar. För att komma vidare när vi fastnat på programmeringsproblem har vi läst och lagt in inlägg på forum för Cg-programmerare [Källa 4], och rådfrågat VR-studions personal. Om det inte funnits några färdiga lösningar har vi prövat oss fram med trial-and-error.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 20 av 28
4. Resultat Det här examensarbetet resulterade i tre olika delar, bestående av texturer, visningsprogram och Cg-program. Nedan berättar vi om dessa i mer detalj.
4.1 Skapade texturer Här följer de texturer vi skapade med hjälp av teorin i 2.1.1 och metoden som beskrivs i 3.1. För att helt kunna följa arbetet med skapandet av grundtexturer och normaltexturer bör läsaren ha arbetat med Adobe Photoshop, och ha grundläggande förståelse för programmets funktioner.
4.1.1 Läder och läderimitation Leather wheel Detta skall ge intrycket av grovt, äkta läder. En provbit av lädret skannades in och gjordes till en normaltextur med Nvidias normal-map filter för Adobe Photoshop. Sedan förstärktes och modifierades färgnivåerna med Photoshops funktioner levels och color balance. Grundtexturen gjordes genom att applicera filtret ”Gaussian blur” på en kopia av den inskannade provbiten.
Grundtextur
Normaltextur
Textur
Artificial leather En imitation av läder. Grundtextur och normaltextur har gjorts som ovan.
Grundtextur
Normaltextur
Textur
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 21 av 28
4.1.2 Tyg Wave Då detta tyg är mönstrat passade det bättre att behålla den inskannade bilden som grundstruktur, och göra en normaltextur med svag strukturering.
Grundtextur
Normaltextur
Textur
4.1.3 Plaster Foil footplate Denna plast var idealisk att använda färgkartan som referens på, när normaltexturen gjordes. Med en inskannad bild som mall målades varje cirkel i färgerna från färgkartan på så sätt att de skulle verka upphöja sig över ytan. För att inte få en så jämn yta lades filtret Add noise med inställningen monocromatic på.
Grundtextur
Normaltextur
Textur
Trikåplast I stort sett användes samma teknik som vid lädermaterialen, men då den här plasten har väldigt svag struktur räckte det med små förändringar i normaltexturen som Nvidias Photoshopfilter gjorde.
Grundtextur
Normaltextur
Textur
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 22 av 28
4.2 Visningsprogrammet: CG_Viewer Detta program, kallad CG_Viewer (bilaga 2), används för att visa våra material på våra 3D objekt. CG_Viewer har, i stort, hand om följande delar: • • •
Parameterhantering, både till visningsprogrammet och Cg-programmet Anrop (runtime) till cgc.exe för kompilering och laddning av Cg-programmen till grafikkortet. Visning av resultatet via ett visningsfönster
Slutligen skall vi också ta upp visningsprogrammets tillkortakommanden. Nu följer en mer detaljerad beskrivning av våra olika punkter:
4.2.1 Parameterhantering CG_Viewer indata I första skedet gäller detta de parametrar som visningsprogrammet tar som indata för att underlätta felsökning. Syntaxen är som följer: CG_Viewer.exe <3D objekt namn> <3D objektets namn> är det fullständiga namnet på det 3D objekt man vill ladda till visningsprogrammet ex. cube.3ds. OBS! CG_Viewer är endast testat med 3DS objekt (3DS är Discreet’s 3D Studio Max filformat). fullständiga namnet på den textur som innehåller normalvärdesdatan ex. normal.jpg. Giltiga format bestäms av OpenSceneGraph’s plugins. Vi har testat .bmp (MS Windows) och .jpg (Joint Photographic Experts Group) med goda resultat. OpenSceneGraph stöder de flesta stora bildformat idag. fullständiga namnet på den textur man önskar få se med strukturen från normaltexturen på. Giltiga format se ”…normaltextur” ovan. CG_Viewer.exe är baserat på vp20, fp20 och vp30, fp30 profilerna och då det ej har varit någon fördel för oss att använda kombinationerna vp20, fp30 och vp30, fp20 så behöver man här endast ange endera 20 eller 30 för att ange profil. CG_Viewer kör alltid både ett vertexprogram och ett fragmentprogram. Här anger man endera en etta (1) eller en nolla (0). Etta för att tala om för programmet att man vill se samtliga knytpunkters normal (blå), binormal(grön) och tangent(röd). Nollan anger att man ej vill se denna information. OBS! Om man anger
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 23 av 28
en etta så kommer programmet ej att ladda några Cg-program eller texturer utan kommer endast att visa de färgade vektorerna. Denna funktionalitet är främst till för felsökning. Notera att < och > endast finns med i texten för att visa de olika parametrarna – de skall ej vara med vid inmatning. Indata till vertex- och fragmentprogramen Varje vertexprogram behöver få en strid ström av knytpunkter matade till sig på samma sätt som ett fragmentprogram behöver en ström av bildpunkter. Det är lätt att tro att detta är allt som behövs, men detta är bara en liten del av all den information som ett vertexeller fragmentprogram behöver. Nedan är en lista över vilka indata som CG_Viewer matar våra Cg-program med: Vertexprogrammet: • • • •
Varje knytpunkts koordinat (XYZ) Varje knytpunkts normalkoordinat (XYZ) 2 stycken transformationsmatriser (projektionsmatris och en inverterad projektionsmatris) Tangentkoordinater (ett set (XYZ) per knytpunkt)
Fragmentprogrammet: •
2 texturer (normaltexturen och grundtexturen)
Förutom dessa parametrar matas även fragmentprogrammet av grafikkortet med bland annat (baserat på data från vertexprogrammet): •
Varje bildpunkts koordinat (XYZ)
Fragmentprogrammet får även en del indata direkt från vertexprogrammet som vi definierat: • • • • •
2 set texturkoordinater (ett för normaltexturen och ett för grundtexturen.) Halvvikelvektorns riktningskoordinat för varje bildpunkt (XYZ) En ljusvektor som talar om vilken riktning ljuset har i vår scen (XYZ) Varje punkts diffusa färg Varje punkts spekulära färg
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 24 av 28
4.2.2 Anrop till Cg-kompilator (runtime) och laddning av grafikkortet CG_Viewer har hand om att mata kompilatorn (cgc.exe) med nödvändiga data (mestadels inställningar beroende på profilval) och att sedan ta resultatet från kompilatorn och ladda grafikkortet via speciella drivrutiner inbyggda i osgNV. Vi har valt att låta vårt visningsprogram anropa Cg-kompilatorn (cgc.exe) under körningen av CG_Viewer eftersom vi då slipper kompilera om detta varje gång vi gjort ändringar i Cg-koden. Då våra Cg-program är mycket små så är tiden vid uppstart av visningsprogrammet försumbar. Det är viktigt att förstå att visningsprogrammet och Cg-programmen är två skilda saker. CG_Viewer matar Cg-programmen med data under den tid de och visningsprogrammet körs.
4.2.3 Visning av resultat CG_Viewer har slutligen också hand om att visa resultatet. Detta görs genom att vi skapar ett fönster som grafikkortets utdata kopplas till via OpenGL→OpenScenGraph→vårt fönster. Detta fönster har en del inbyggda funktioner som man enklast får reda på genom att trycka på tangenten ’h’ på tangentbordet. Då får man upp en lista i kommandofönstret över vilka funktioner som är tillgängliga. Några av de viktigaste funktionera är möjligheten att se hur snabbt grafikkortet ritar upp modellerna (antal uppdateringar i sekunden mätt i Hz) och även möjligheten att se modellerna i ”wireframe mode” d.v.s. utan ifyllda trianglar. Detta är väldigt bra när man felsöker.
4.2.4 CG_Viewers begränsningar Även om resultatet från GC_Viewern ser bra ut så vill vi här påpeka att det ibland uppträder en felaktighet i resultatet. För att beskriva detta tänker vi oss att vi lägger på en av våra texturer på en kub. Följande resultat kommer då att ges: • • •
Framsidan (den mot oss) och ovansidan kommer då att ge oss korrekt skuggning Sidorna kommer att ha skuggning, men i fel led (i x-led när det borde vara i yled). Detta är naturligtvis felaktigt. Det borde vara samma som skuggningen av framsidan. Baksidan och botten kommer att ge samma skuggning som framsidan och ovansidan förutom att effekten här kommer att vara inverterad. Detta är felaktigt. Borde ej vara inverterad.
Anledningen till detta fel ligger i sättet som vi räknar ut och använder tangentkoordinaterna. Vi använder här en hjälpklass (NVMeshMender) för att räkna ut tangenterna men tyvärr så ger denna oss inte helt korrekta värden. En anledning till detta
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 25 av 28
är för att vi räknar ut varje sida i en kub för sig d.v.s. NVMeshMender vet då inte att samtliga sidor skall sitta ihop. Vi har idag ingen möjlighet att räkna ut alla sidor tillsammans. Förslag till lösningar på tangentproblemet finns i 5.4.
4.3 Cg-program Förutom ett visningsprogram och texturer så har vi också behövt skapa två stycken Cgprogram. Dessa program använder vi för att via den teknik som beskrivs under 3.1 lägga på texturerna på 3D-modellerna. Det första är ett vertexprogram (bilaga 3) som räknar ut: • • • •
Vyvektorn (den vektor som beskriver ifrån vilket håll vi tittar på scenen) Ljusvektorn (den vektor som beskriver från vilket håll ljuset kommer från) Halvvinkelvektorn (se 2.3.2) Samtliga transformationer till och från World space, Tangent space, Object space och Projection space (se 2.3.1).
Resultatet från dessa uträkningar tar sedan vårt fragmentprogram (bilaga 4) hand om och använder när det skall applicera texturerna på 3D-modellerna. I vertexprogrammet sätter vi även den diffusa och spekulära färgen som fragmentprogrammet använder när det räknar ut ljuset i varje punkt. I fragmentprogrammet läggs texturerna på och ljuset för varje punkt räknas ut. Efter detta slutar vår kontroll över uppritningen och resultatet visas upp i fönstret på vårt visningsprogram.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 26 av 28
5 Slutsats och diskussion 5.1 Sammandrag Vi har i detta examensarbete tittat på hur man praktiskt kan använda Nvidias nya programmeringsspråk, Cg, för att skapa texturmaterial som ger illusionen av vara 3dimensionella. Vi har för detta ändamål skapat flertalet texturmaterial (dock ej så många som var avsett), två Cg-program för styrning av grafikkortet samt ett visningsprogram för laddning och underhåll av ovanstående Cg-program. Den grundläggande tekniken för hur man åstadkommer ovanstående illusion ligger i att man skannar in det verkliga materialet, applicerar ett filter som färgkodar strukturinformationen för materialet och slutligen använder denna färgkodning när vi sedan belyser vår modell i VR scenen. Vid testning visade det sig att de tangenter vi fick från NVMeshMender var felaktiga. Under vissa förhållanden faller skuggorna i strukturerna felaktigt. Vi har beskrivit felet och föreslår olika lösningar på problemet.
5.2 Utvärdering av arbetet Då vi hade så gott som inga förkunskaper inom ämnet gick en stor del av tiden till att söka information och felsöka våra lösningar, detta då projektet blev alltmer komplext allt eftersom arbetet fortskred. Mycket tid fick läggas på visningsprogrammet och att förstå normaltexturers uppbyggnad. Då läroboken inom Cg-programmering kom först i slutet av andra veckan av de tio i examensarbetet, ägnades dessa två första veckor åt visningsprogrammet, inskanning av material och studerande av Cg Toolkits User’s Manual. Därefter förflöt arbetet som förväntat fram tills vecka åtta, då problemet med vår beräkningsmodell för tangenterna uppstod. På grund av tidsbrist valde vi att redovisa arbetet utan att ha funnit någon lösning på problemet med visningsprogrammet.
5.3 Diskussion Vi anser att examensarbetet har gått mycket bra med tanke på den begränsade information vi funnit i ämnet. Det har också funnits begränsade resurser vad det gäller programmerbara grafikkort som klarar av både vertex- och fragmentprogram. Då vi har denna typ av grafikkort privat, så valde vi att arbeta till största del hemifrån. Under tiden vårt arbete framskred inskaffade VR-Studion två stycken fullt programmerbara grafikkort (GeForce FX), så de slutliga testerna har kunnat göras under tänkta förhållanden.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 27 av 28
Vidare finner vi att examensarbetet har varit mycket stimulerande och lärorikt att arbeta med. Vi har idag goda kunskaper om hur VR fungerar och tror oss kunna konstruera mindre Cg-program, texturer och 3D-applikationer.
5.4 Förslag till lösningar på problem Då vi har stött på problem vad gäller tangentriktningarna som leder till felaktigheter i visningen av texturmaterialen, har följande förslag som skulle kunna rätta till dessa felaktigheter uttänkts. En lösning till problemet skulle kunna vara att skriva en ny klass som tar tangentriktningarna och som ger möjligheten att räkna på hela objekt på en gång. Detta skulle kunna ge korrekt skuggning på en kub men det är svårt att säga hur det skulle påverka andra 3D-modeller. En annan trolig lösning skulle kunna vara att byta ut OpenSceneGraph mot Microsofts DirectX då denna API har bättre stöd för tangentriktningar än OpenSceneGraph (för tillfället). Ett sätt skulle här vara att bygga om det DirectX demo som följer med NVMeshMender så att det även inkluderar stöd för Cg-program. En tredje lösning är att vänta på att Marco Jez (skapare av osgNV) släpper nästa version av osgNV (> 0.5.6) som då kommer att ha inbyggt stöd för automatisk uträkning av tangenter till samtliga objekt i scenen. Vi tror att detta blir den smidigaste lösningen på problemet, även om det antagligen kommer att betyda en större ombyggnad av visningsprogrammet.
5.5 Förslag till utveckling av examensarbetet Det finns många olika metoder för att skapa strukturerade texturer, och även om vår metod tar kort tid och får ett trovärdigt resultat, så är den kanske inte den bästa. Vi stod i valet mellan att använda Cg till att visa normaltexturer som struktur, eller att faktiskt flytta pixel för pixel så att ytan inte längre var plan, utan hade riktig struktur. Detta hade dock, med våra förkunskaper, tagit alldeles för lång tid för att vara en användbar metod till detta examensarbete. Vi vill däremot upplysa om att metoden finns, och att den borde ge ett väldigt bra resultat.
Examensarbete: 3D-texturmaterial med hjälp av Cg Madeleine Gusdal och Niklas Ottosson
Sida 28 av 28
Källförteckning 1.
Randima Fernando och Mark J.Kilgard, The Cg Tutorial, Addison-Wesley, Boston februari 2003, ISBN 0-321-19496-9
2.
ACM SIGGRAPH Education section http://www.siggraph.org/education/materials/HyperGraph/mapping/surface0.htm (informationen senast hämtad den 9 maj 2003)
3.
NVIDIA Corporation, Cg Toolkits User’s Manual Release 1.1 March 2003.
4.
Cg shaders , www.cgshaders.com (informationen senast hämtad den 9 maj 2003)
5.
Robert Osfield, OpenSceneGraph, http://www.openscenegraph.org , (informationen senast hämtad den 9 maj 2003)
6.
Marco Jez, osgNV, http://osgnv.sourceforge.net/, (informationen senast inhämtad den 9 maj 2003)
7.
NVIDIA Corporation developers section, http://developer.nvidia.com , (informationen senast inhämtad den 9 maj 2003)
8.
Philip Taylor, Per-Pixel Lightning, Microsoft Corporation, november 13 2001, http://msdn.microsoft.com/library/default.asp?url=/library/enus/dndrive/html/directx11192001.asp (informationen senast inhämtad 9 maj 2003)
9.
Michael Johansson och Mattias Roupé, Chalmers Lindholmens VR-Studio 2003
10.
Sim Dietrich, NVMeshMender, NVIDIA Corporation 2002, http://developer.nvidia.com/view.asp?IO=NVMeshMender (informationen senast inhämtad 9 maj 2003)
11.
DirectX, Microsoft Corporation, http://www.microsoft.com/windows/directx/ (informationen senast inhämtad 9 maj 2003)
12.
OpenGL, Silicon Graphics, http://www.sgi.com/software/opengl/ (informationen senast inhämtad 9 maj 2003)
13.
Adobe Photoshop, http://www.adobe.com/products/photoshop/main.html (informationen senast inhämtad 9 maj 2003)
Bilaga 1 Begrepp och förklaringar
Bilaga 1 – Begrepp och förklaringar API Bildpunkt Cg Chipset Diffus Fragment Förtidsrenderat GPU Hårdvaruaccelererad grafik
Högnivåspråk
Lågnivåspråk Realtidsrenderat Rendering Runtime Spekulär Vertex Transformationsmatris
Application Programming Interface. Minsta enheten ett grafikkort kan visa. Är kopplat till rådande upplösning. C for graphics. En uppsättning kretsar för att utföra en specifik uppgift. I detta fall uppritning och färgläggning av bildpunkter. Svengelska för belysningstermen diffuse. En potentiell bildpunkt, dvs. en bildpunkt som kanske kommer visas. Rendering som utförs innan visning. Oftast använd inom t.ex. animation med 3D-grafik. Ger inget utrymme för åskådaren att påverka resultatet. Graphics Processing Unit (se CPU). Grafik som ritas upp utan hjälp av datorns CPU. Detta innebär att samtliga beräkningar som krävs utförs av grafikkortets processor (GPU). Datorns CPU används här endast för att mata GPU:n med grafikdata. Ett programmeringsspråk som syntaktiskt sett ligger närmare hur en människa tänker, och är mer förklarande än ett lågnivåspråk. Gör det enklare för programmerare att skriva och förstå program. Ett programmeringsspråk som syntaktiskt sett är närmare en dators tankesätt. Kan vara svårt att förstå för en programmerare som inte är insatt i ett skrivet program. Rendering som görs i verklig tid, oftast då ett program med 3D-grafik körs. Åskådaren kan själv vara med och påverka vad och i vilka vinklar som visas. Beräkningsprocessen som framställer en färdig bild av råmaterial (geometriska former). Här sätts en ljuskälla och alla skuggor beräknas. Under den tid en applikation körs befinner den sig i runtime. Svengelska för belysningstermen specular. En knytpunkt för 2 eller fler vektorer (linjer) som tillsammans med 2 andra knytpunkter bildar en triangel. Inom Cg-språket använder man olika koordinatsystem för att hålla reda på 3D-objekt. För att kunna göra korrekta beräkningar måste man ofta med hjälp av en matematisk formel förflytta, transformera sig från ett koordinatsystem till ett annat. Till detta använder man transformationsmatriser.
Bilaga 2 Visningsprogrammet
H:\Ex-jobb\Bilagor\Bilaga2.cpp #include #include #include #include #include #include #include
1
#include #include #include #include #include #include
#include #include #include #include #include
void setupBump( osg::Node *pNode, osgNVCg::Program *cgProgram, bool drawTngNormBi ); void drawLines( osg::Geode *pGeode, osg::Vec3Array *lineArray, int color );//color: 1=red, 2=green, 3=blue void setup_VP_FP(osg::StateSet *ss,osgNVCg::Program *vprogram,char *normlaMap, char * baseMap, char *profile); ///////////////////////////////MAIN///////////////////////////////// void main (int argc, char **argv){ //Parameter: osg::Group *root = new osg::Group; //1 = modellnamn (cube.3ds är grund) //2 = normalmap ( foil_footplate_normal_map.jpg) osgNVCg::Program *cgProgram = new osgNVCg::Program;//3 = basemap( foil_footplate_base_map.jpg som grund) //4 = profil på format: 20 eller 30 (20 är grund ) char *filnamn = "cube.3DS"; //5 = tangent/normal/binormal on/off (off som grund) char *normalMap = "foil_footplate_normal_map.jpg"; char *baseMap = "foil_footplate_base_map.jpg"; char *profile = "20"; bool withOrWithout = 0; if(argc > 1 ){ filnamn = argv[1]; } if(argc > 2){ normalMap = argv[2]; } if(argc > 3){ baseMap = argv[3]; } if(argc > 4){ profile = argv[4]; } if(argc > 5){ ör tng/bin/norm withOrWithout = argv[5]; } osg::Node *figur = osgDB::readNodeFile(filnamn);
// Testar om vi anget filnamn // Testar om vi anget normaltextur // Testar om vi anget grundtextur // Testar om vi anget profil //Testar om vi anget boolskt värde f
// Läser in 3D modellen
std::cerr << "Loading " << filnamn << std::endl; setupBump(figur, cgProgram, withOrWithout); // Beräknar tangenterna -> cgProgram ( VaryingParameter) osg::StateSet *ss = figur->getOrCreateStateSet(); if(!withOrWithout) { setup_VP_FP(ss,cgProgram,normalMap,baseMap,profile);//Förbereder all
H:\Ex-jobb\Bilagor\Bilaga2.cpp UniformParameters och profiler } root->addChild(figur); rootnoden osgGLUT::Viewer viewer; viewer.addViewport(root); viewer
2
// "Fäster" 3D modellen till // Skapar en viewer // "Fäster" rootnoden till vår
viewer.open(); viewer.run();
} ///////////////////////////////////////////////////////////////////////// // setupBump har hand om följande funktioner: // * Använda NVMeshMender för att räkna ut knytpunkternas tnagenter // * Ta ovanstående tangenter och skicka in dem i vertexprogramet via VaryingParameter // * Anropa (om true) drawLines funktionen för att rita upp tangenter, binormaler och normaler // // I ovanstående process krävs många delmoment: // * Traversera nodträdet för att finna Geometry objekten (där i finns knytpunkterna och ytindexlistan) // * Skicka in knytpunkterna och ytindexlistan till NVMM som då beräknar tangenterna // * Hämtar tangenterna och konverterar osg:Geometry till osgNVCg::CgGeometry // * Skickar in tangenterna via CgGeometry och addParameter // // Indata: noden som håller trädet där Geometry objekten finns, vertexprogramhandtaget // och slutligen variabeln som talar om att vi skall rita tng/bin/norm eller inte // Funktionen har inga utdata i vanlig bemärkelse ///////////////////////////////////////////////////////////////////////// void setupBump( osg::Node *pNode, osgNVCg::Program *cgProgram, bool drawTngNormBi ) { NVMeshMender myMender; NVMeshMender::VAVector input; NVMeshMender::VAVector output; NVMeshMender::VertexAttribute attribut; if (!pNode){ std::cerr << "Detta var ingen Node!" << std::endl;
} osg::Geode *pGeode = dynamic_cast(pNode); osg::Group *pGroup = dynamic_cast(pNode); if (pGeode) { int num_mesh = pGeode->getNumDrawables(); satsen
// Så vi vet hur många i for-
std::cerr << "Geode med " << num_mesh << " drawables" << std::endl; for (int i = 0; i < num_mesh; i++) { // För varje Drawables så hämtar vi geometrin osg::Drawable *pDraw = pGeode->getDrawable(i); osg::Geometry *pGeometry = dynamic_cast(pDraw); if (pGeometry) { std::cerr << "----------------------------" << std::endl; osg::Vec3Array *vertArray = pGeometry->getVertexArray();// Hämtar vertexArray ( knytpunkterna) // från Geometry output.clear(); attribut.Name_ = "position"; indata vi ger NVMM output.push_back(attribut); attribut.Name_ = "indices"; output.push_back(attribut); // Bygger upp indata till NVMM \\ // --Går igenom knytpunkterna-if(vertArray) { int nOEVertArray = vertArray->getNumElements(); attribut.Name_ = "position";
// Ställer in vilka
H:\Ex-jobb\Bilagor\Bilaga2.cpp
3
std::cerr << "Geometry med " << nOEVertArray << " elements\nVertices:" <<
std::endl;
for(int i = 0; i < nOEVertArray ;i++) {
kytpunkter
// Hämtar alla
attribut.floatVector_.push_back((*vertArray)[i].x());//Lägger till värde attribut.floatVector_.push_back((*vertArray)[i].y()); attribut.floatVector_.push_back((*vertArray)[i].z());
input array
std::cerr << (*vertArray)[i] << std::endl; } input.push_back(attribut); // Lägger till denna vektor till NVMM's
attribut.floatVector_.clear(); } // --Går igenom ytindexlistan-attribut.Name_ = "indices";
setet
ytindex
osg::PrimitiveSet *primSet = pGeometry->getPrimitiveSet(0);//Hämtar första std::cerr << "Getting indices from primSet..." << std::endl; for(unsigned int r = 0; r < primSet->getNumIndices(); r++){//Hämta alla attribut.intVector_.push_back(primSet->index(r));
ytindex }
//Fyll vektor med
std::cerr << primSet->index(r) << std::endl;
input.push_back(attribut); NVMM's input array attribut.intVector_.clear();
// Lägger till denna vektor till
//////////////////////NVMM Munge()////////////////////// bool bSuccess = myMender.MungeD3DX( input, // vektor med positioner och ytindex output, // vektor med resultat 1.25f, // tangent space smooth angle(radians) 3.141592654f/2.5f NULL, // ingen textur pålagd NVMeshMender::FixTangents,// fixar speglade och felaktiga tangenter NVMeshMender::DontFixCylindricalTexGen,// använd inte vertexduplicering NVMeshMender::WeightNormalsByFaceSize // väg normalerna med triangelstorleken ); if ( !bSuccess ) { // Kontrollerar om Munge() lyckades eller ej std::cerr << "Munge() failed!"<
for(unsigned int n = 0; n < output.size(); n++){ // Löper igenom hela ut if(output[n].Name_ == "tangent") { // Hämtar tangenter std::cerr << "Tangents found..." << std::endl; tangents = new osg::Vec3Array(output[n].floatVector_.size()); for(unsigned int j = 0; j < output[n].floatVector_.size() / 3 ; j++){ std::cerr << output[n].floatVector_[j * 3] << " " << output[n].floatVector_[j * 3 + 1] << " " << output[n].floatVector_[j * 3 + 2] << std::endl; (*tangents)[j].set(output[n].floatVector_[j * 3], output[n].floatVector_[j * 3 + 1], output[n].floatVector_[j * 3 + 2]); }
} if(output[n].Name_ == "normal") { std::cerr << "Normals found..." << std::endl;
// Hämtar normaler
H:\Ex-jobb\Bilagor\Bilaga2.cpp
4
normals = new osg::Vec3Array(output[n].floatVector_.size()); for(unsigned int j = 0; j < output[n].floatVector_.size() / 3 ; j++){ std::cerr << output[n].floatVector_[j * 3] << " "<< output[n].floatVector_[j * 3 + 1] << " "<< output[n].floatVector_[j * 3 + 2] << std::endl; (*normals)[j].set(output[n].floatVector_[j * 3], output[n].floatVector_[j * 3 + 1], output[n].floatVector_[j * 3 + 2]); }
} if(output[n].Name_ == "binormal") { // Hämtar binormaler std::cerr << "Biormals found" << std::endl; binormals = new osg::Vec3Array(output[n].floatVector_.size());
for(unsigned int j = 0; j < output[n].floatVector_.size() / 3 ; j++){ //std::cerr << "Binormal X "<< output[n].floatVector_[j * 3] << std::
endl;
//std::cerr << "Binormal Y "<< output[n].floatVector_[j * 3 + 1] << std
::endl;
//std::cerr << "Binormal Z "<< output[n].floatVector_[j * 3 + 2] << std
::endl;
(*binormals)[j].set(output[n].floatVector_[j * 3], output[n].floatVector_[j * 3 + 1], output[n].floatVector_[j * 3 + 2]);
} } if(output[n].Name_ == "position") { // Hämtar knytpunkterna std::cerr << "Positions found" << std::endl; positions = new osg::Vec3Array(output[n].floatVector_.size()); for(unsigned int j = 0; j < output[n].floatVector_.size() / 3 ; j++){ //std::cerr << "Position X "<< output[n].floatVector_[j * 3] << std::
endl;
//std::cerr << "Position Y "<< output[n].floatVector_[j * 3 + 1] << std
::endl;
//std::cerr << "Position Z "<< output[n].floatVector_[j * 3 + 2] << std
::endl;
(*positions)[j].set(output[n].floatVector_[j * 3], output[n].floatVector_[j * 3 + 1], output[n].floatVector_[j * 3 + 2]);
} } if(output[n].Name_ == "tex0") { // Hämtar texturkoordinater std::cerr << "Texture coordninates found" << std::endl; tex0 = new osg::Vec3Array(output[n].floatVector_.size()); for(unsigned int j = 0; j < output[n].floatVector_.size() / 3 ; j++){ std::cerr << output[n].floatVector_[j * 3] << " "<< output[n].floatVector_[j * 3 + 1] << " "<< output[n].floatVector_[j * 3 + 2] << std::endl; (*tex0)[j].set(output[n].floatVector_[j * 3], output[n].floatVector_[j * 3 + 1], output[n].floatVector_[j * 3 + 2]); }
} if(output[n].Name_ == "indices") { // Hämtar ytidnex std::cerr << "Indices found..." << std::endl; indices = new osg::Vec3Array(output[n].floatVector_.size());
}
}
for(unsigned int j = 0; j < output[n].floatVector_.size() / 3 ; j++){ std::cerr << output[n].floatVector_[j * 3] << " "<< output[n].floatVector_[j * 3 + 1] << " "<< output[n].floatVector_[j * 3 + 2] << std::endl; (*indices)[j].set(output[n].floatVector_[j * 3], output[n].floatVector_[j * 3 + 1], output[n].floatVector_[j * 3 + 2]); }
if(drawTngNormBi){ // Vill vi rita tangenter, binormaler och normaler? ///////////////////////////draw tangents (red)////////////////////////////////////
H:\Ex-jobb\Bilagor\Bilaga2.cpp
5
osg::Vec3Array *tngLines = new osg::Vec3Array(vertArray->size() * 2);
)[q].z());
for(unsigned int q = 0; q < vertArray->size();q++ ) { (*tngLines)[q * 2].set((*vertArray)[q].x(),(*vertArray)[q].y(),(*vertArray (*tngLines)[q * 2 + 1].set((*tangents)[q].x() + (*vertArray)[q].x(), (*tangents)[q].y() + (*vertArray)[q].y(), (*tangents)[q].z() + (*vertArray)[q].z()); } drawLines(pGeode, tngLines, 1 );
////////////////////////////draw normals (green)/////////////////////////////////////// osg::Vec3Array *normLines = new osg::Vec3Array(vertArray->size() * 2); for(unsigned int q = 0; q < vertArray->size();q++ ) { (*normLines)[q * 2].set((*vertArray)[q].x(),(*vertArray)[q].y(),(* vertArray)[q].z()); (*normLines)[q * 2 + 1].set((*normals)[q].x() + (*vertArray)[q].x(), (*normals)[q].y() + (*vertArray)[q].y(), (*normals)[q].z() + (*vertArray)[q].z()); } drawLines(pGeode, normLines, 2 ); /////////////////////////////draw binormals (blue)/////////////////////////////////// osg::Vec3Array *binormLines = new osg::Vec3Array(vertArray->size() * 2); for(unsigned int q = 0; q < vertArray->size();q++ ) { (*binormLines)[q * 2].set((*vertArray)[q].x(),(*vertArray)[q].y(),(* vertArray)[q].z()); (*binormLines)[q * 2 + 1].set((*binormals)[q].x() + (*vertArray)[q].x(), (*binormals)[q].y() + (*vertArray)[q].y(), (*binormals)[q].z() + (*vertArray)[q].z()); } drawLines(pGeode, binormLines, 3 ); } /////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////// konvertera Geometry till CgGeometry //////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////// osgNVCg::CgGeometry *cgGeometry = new osgNVCg::CgGeometry; if(true){//om denna false så används alla värden från NVMM att sätta CgGeometry objekten std::cerr << "Setting cgGeometry with values from pGeometry" << std::endl; cgGeometry->setVertexArray(pGeometry->getVertexArray());//Flyttar knytpunkterna cgGeometry->setNormalArray(pGeometry->getNormalArray());//Flyttar normalerna cgGeometry->setNormalBinding(pGeometry->getNormalBinding());//Flyttar bindningstypen cgGeometry->setPrimitiveSetList(pGeometry->getPrimitiveSetList()); if(pGeometry->getNumTexCoordArrays() > 1) {//Om mer än en texturkoordinatsvektor for(unsigned int i = 0; i < pGeometry->getNumTexCoordArrays(); i++){ cgGeometry->setTexCoordArray(i, pGeometry->getTexCoordArray(i)); } std::cerr << "Found MORE THAN ONE TexCoordArray" << std::endl; } else{ cgGeometry->setTexCoordArray(0, pGeometry->getTexCoordArray(0)); // Flyttar texturosg::Array *texCoord = cgGeometry->getTexCoordArray(0); // koordinaterna if(!texCoord) { std::cerr << "Getting TexCoordArray Faild!" << std::endl; } else{ // Behövs för vp20 profile - i vp30 kan man duplicera "kanalerna" std::cerr << "Duplicating texture coordinates for normal map" << std: :endl; cgGeometry->setTexCoordArray(1,cgGeometry->getTexCoordArray(0)); } }
H:\Ex-jobb\Bilagor\Bilaga2.cpp
6
if(pGeometry->getNumPrimitiveSets() > 1){ // Om mer än en uppsä ttning ytindex std::cerr << "Found MORE THAN ONE primitiveSet" << std::endl; for(unsigned int i = 1; i < pGeometry->getNumPrimitiveSets(); i++){ cgGeometry->setPrimitiveSet(i, pGeometry->getPrimitiveSet(i)); } } else{ cgGeometry->setPrimitiveSet(0,pGeometry->getPrimitiveSet(0)); // Flytar ytindex } } else{ std::cerr << "Setting cgGeometry with values from Munge()" << std::endl; cgGeometry->setVertexArray(positions); cgGeometry->setNormalArray(normals); cgGeometry->setNormalBinding(pGeometry->getNormalBinding()); cgGeometry->setPrimitiveSetList(pGeometry->getPrimitiveSetList()); cgGeometry->setTexCoordArray(0,tex0); cgGeometry->setTexCoordArray(1,tex0); cgGeometry->setPrimitiveSet(0, pGeometry->getPrimitiveSet(0)); } pGeode->replaceDrawable(pGeometry, cgGeometry); //Byter ut osg::Geomtery mot osgNVCg::CgGeomtery /////////////////////////////////////////////////////////////////////////////////////// ///////////Skickar in tangenterna i vertexprogrammet via CgGeometry//////////////////// /////////////////////////////////////////////////////////////////////////////////////// osgNVCg::VaryingParameter *tngParam = new osgNVCg::VaryingParameter( cgProgram, "IN.T"); tngParam->set(tangents); cgGeometry->addParameter(tngParam); //////////////////////////////////////////////////////////////////////////////////////// } } } } else if (pGroup) { int num_children = pGroup->getNumChildren(); std::cerr << "Group med " << num_children << " barn" << std::endl; for (int i = 0; i < num_children; i++) { osg::Node *pChild = pGroup->getChild(i);
}
setupBump(pChild, cgProgram, drawTngNormBi);//Om vi ej hittar nått geometry objekt så } // fortsätter leta längre ner via rekursion }
////////////////////////////////// setup_VP_FP /////////////////////////////////////////// // // setup_VP_FP tar ett stateSet, ett programobjekt, filnamn till en normaltextur, filnamn till en grundtextur // och slutligen också ett textsträng med profilnummer. Funktionen "kopplar" programen till objektet samt har // hand om skapandet av indata (parametrar). Om fler program använder denna funktion så fyller du i // alla parametrar som behövs (för alla) // // void setup_VP_FP( osg::StateSet *ss,osgNVCg::Program *vprogram,char *normalMap,char * baseMap,char *profile){ // Skapar och laddar grundtexturen osg::Texture2D *OrdinaryMap = new osg::Texture2D; OrdinaryMap->setImage(osgDB::readImageFile(baseMap)); OrdinaryMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); OrdinaryMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); ss->setTextureAttributeAndModes(0,OrdinaryMap,osg::StateAttribute::OVERRIDE | osg:: StateAttribute::ON);
H:\Ex-jobb\Bilagor\Bilaga2.cpp // Skapar och laddar normaltexturen osg::Texture2D *NormalMap = new osg::Texture2D; NormalMap->setImage(osgDB::readImageFile(normalMap)); NormalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); NormalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); ss->setTextureAttributeAndModes(1, NormalMap, osg::StateAttribute::OVERRIDE | osg:: StateAttribute::ON); // VERTEX PROGRAM if(profile != "30"){ std::cerr << "Using profile vp20" << std::endl; vprogram->setProfile(osgNVCg::Program::VP20); // Sätter profil } else{ vprogram->setProfile(osgNVCg::Program::VP30); // Sätter profil } vprogram->setFileName("bump6v.cg"); // Anger i vilken fil som vertexprogrammet kan hittas vprogram->addUniformParameter("model_view_proj")->set(osgNVCg::UniformParameter:: MODELVIEW_PROJECTION); vprogram->addUniformParameter("inverse_model_view")->set(osgNVCg::UniformParameter:: MODELVIEW, osgNVCg::UniformParameter::INVERSE); // FRAGMENT PROGRAM osgNVCg::Program *fprogram = new osgNVCg::Program; if(profile != "30"){ std::cerr << "Using profile fp20" << std::endl; fprogram->setProfile(osgNVCg::Program::FP20); // Sätter profil } else{ fprogram->setProfile(osgNVCg::Program::FP30); } fprogram->setFileName("bump6f.cg"); // Anger vilken fil som innehåller fragmentprogrammet fprogram->addUniformParameter("OrdinaryMap")->set(OrdinaryMap); // Skickar in texturerna... fprogram->addUniformParameter("NormalMap")->set(NormalMap);
}
// Kopplar vårt context till scenen. ss->setAttributeAndModes(vprogram->getContext());
/////////////////////////////drawLines//////////////////////////////////// // Funktion som ritar upp linjer i olika färger (röd, grön, blå) // Indata: geode (den vi fäster linjen på), en lineArray som anger koordinaterna samt // slutligen också en färg (som en int) // void drawLines( osg::Geode *pGeode, osg::Vec3Array *lineArray, int color ) { osg::Geometry *Geom = new osg::Geometry(); Geom->setVertexArray(lineArray); // Set vertex array // set the colors osg::Vec4Array* colors = new osg::Vec4Array; if(color == 1) { colors->push_back(osg::Vec4(1.0f,0.0f,0.0f,1.0f)); } else if(color == 2){ colors->push_back(osg::Vec4(0.0f,1.0f,0.0f,1.0f)); } else if(color == 3){ colors->push_back(osg::Vec4(0.0f,0.0f,1.0f,1.0f)); } Geom->setColorArray(colors); Geom->setColorBinding(osg::Geometry::BIND_OVERALL); // set the normal in the same way color. osg::Vec3Array* normals = new osg::Vec3Array; normals->push_back(osg::Vec3(0.0f,-1.0f,0.0f)); Geom->setNormalArray(normals); Geom->setNormalBinding(osg::Geometry::BIND_OVERALL);
7
H:\Ex-jobb\Bilagor\Bilaga2.cpp
8
Geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES,0,lineArray->size())) ; }
pGeode->addDrawable(Geom);
Bilaga 3 Vertexprogrammet
Bilaga3.cg struct a2v { float4 float3 float2 float3 float3 };
Position : POSITION; //i Normal : NORMAL; //i TexCoord : TEXCOORD0; //i TexCoord1 : TEXCOORD1;//i T : TEXCOORD2; //i
struct v2f { float4 float3 float3 float3 float3 float3 float3 };
Position : POSITION; //i "projection space" TexCoord0 : TEXCOORD0; //i "object space" TexCoord01 : TEXCOORD1; //i "object space" LightVector : TEXCOORD2; //i "tangent space" HalfAngleVector : TEXCOORD3;//i "tangent space" DiffMat : COLOR0; //den "diffusa" färgen SpecMat : COLOR1; //den "speculära" färgen
"object "object "object "object "object
v2f main(a2v IN, uniform float4x4 model_view_proj, space" uniform float4x4 inverse_model_view ) { v2f OUT;
space" space" space" space" space"
// "object space" -> "projection // "world space" -> "object space"
// Skickar texturcoordinaterna // vidare till fragmentprogrammet OUT.TexCoord0.xy = IN.TexCoord.xy; OUT.TexCoord01.xy = IN.TexCoord1.xy; // Sätter upp vår transformationsmatris // från "object space" -> "tangent space" float3x3 objToTangentSpace; objToTangentSpace[0] = IN.T; objToTangentSpace[1] = cross(IN.Normal, IN.T); //Binormal objToTangentSpace[2] = IN.Normal; // Vyvektor i "object space" float4 ViewVectorTMP = normalize(mul(inverse_model_view, float4(0, 0, 1, 1))); float3 viewVector = ViewVectorTMP.xyz; // Ljusvektor i "object space" float4 LightVectorTMP = normalize(mul(inverse_model_view, float4(0, 0, 1, 1))); float3 lightVector = LightVectorTMP.xyz; // Beräknar halvvinkelvektorn float3 halfAngleVector = normalize(lightVector + viewVector); // Konverterar halvvinkelvektorn från // "object space" -> "tangent space" OUT.HalfAngleVector.xyz = normalize(mul(objToTangentSpace, halfAngleVector)); // Konverterar ljusvektorn från // "object space" -> "tangent space" float3 lightVectorTS = normalize(mul(objToTangentSpace, lightVector)); OUT.LightVector.xyz = lightVectorTS.xyz; // Sätter ljusvektorn till ut-strömmen OUT.DiffMat = float3(0.1,0.1,0.1); OUT.SpecMat = float3(0.6,0.6,0.6); // Konverterar positionen från "object space -> "projection space" OUT.Position = mul(model_view_proj, IN.Position); return OUT; // Returnerar alla variabler (skickar dem till ut-strömmen) }
Bilaga 4 Fragmentprogrammet
Bilaga4.cg struct v2f { float4 float3 float3 float3 float3 float3 float3 };
Position : POSITION; //i "projection space" TexCoord0 : TEXCOORD0; //i "object space" TexCoord01 : TEXCOORD1; //i "object space" LightVector : TEXCOORD2; //i "tangent space" HalfAngleVector : TEXCOORD3;//i "tangent space" DiffMat : COLOR0; //Den diffusa färgen SpecMat : COLOR1; //Den "speculära" färgen
float4 main(v2f IN, uniform sampler2D OrdinaryMap, uniform sampler2D NormalMap) : COLOR0 { //Hämtar färgen på position xy från vår grundtextur float4 ordinaryTMP = tex2D(OrdinaryMap, IN.TexCoord0.xy); float3 ordinary = ordinaryTMP.xyz; //Hämtar färgen (som ovan) och expanerar normaltexturen från [0,1] -> [-1,1] float4 bumpNormalTMP = 2 * (tex2D(NormalMap, IN.TexCoord01.xy) - 0.5); float3 bumpNormal= bumpNormalTMP.xyz; //Beräknar den diffusa komponenten float diff = dot(bumpNormal.xyz, IN.LightVector.xyz); //Alternativt sätt att beräkna den diffusa komponenten //float diff = max(dot(bumpNormal.xyz, IN.LightVector.xyz),0); //Beräknar den "speculära" komponenten float spec = dot(bumpNormal.xyz, IN.HalfAngleVector.xyz); //Alternativt sätt att beräkna den "speculära" komponenten //float spec = max(dot(bumpNormal.xyz, IN.HalfAngleVector.xyz),0); float3 final; final = diff*IN.DiffMat + ordinary + spec*IN.SpecMat; //Lägger ihop alla komponenter return float4(final, 1.0); //Returnerar den slutgiltiga färgen }
Bilaga 5 ”CG in two pages”
Cg in Two Pages Mark J. Kilgard NVIDIA Corporation Austin, Texas January 16, 2003 The out modifier indicates that clipPosition, oColor, oDecalCoord, and oLightMapCoord parameters are output by the program. The semantics that follow these parameters are therefore output semantics. The respective semantics indicate the program outputs a transformed clip-space position and a scaled color. Also, two sets of texture coordinates are passed through. The resulting vertex is feed to primitive assembly to eventually generate a primitive for rasterization.
1. Cg by Example Cg is a language for programming GPUs. Cg programs look a lot like C programs. Here is a Cg vertex program: void simpleTransform(float4 objectPosition : POSITION, float4 color : COLOR, float4 decalCoord : TEXCOORD0, float4 lightMapCoord : TEXCOORD1, out float4 clipPosition : POSITION, out float4 oColor : COLOR, out float4 oDecalCoord : TEXCOORD0, out float4 oLightMapCoord : TEXCOORD1, uniform float brightness, uniform float4x4 modelViewProjection) { clipPosition = mul(modelViewProjection, objectPosition); oColor = brightness * color; oDecalCoord = decalCoord; oLightMapCoord = lightMapCoord; }
Compiling the program requires the program source code, the name of the entry function to compile (simpleTransform), and a profile name (vs_1_1). The Cg compiler can then compile the above Cg program into the following DirectX 8 vertex shader: vs.1.1 mov oT0, v7 mov oT1, v8 dp4 oPos.x, c1, v0 dp4 oPos.y, c2, v0 dp4 oPos.z, c3, v0 dp4 oPos.w, c4, v0 mul oD0, c0.x, v5 The profile indicates for what API execution environment the program should be compiled. This same program can be compiled for the DirectX 9 vertex shader profile (vs_2_0), the multi-vendor OpenGL vertex program extension (arbvp1), or NVIDIAproprietary OpenGL extensions (vp20 & vp30).
1.1 Vertex Program Explanation The program transforms an object-space position for a vertex by a 4x4 matrix containing the concatenation of the modeling, viewing, and projection transforms. The resulting vector is output as the clip-space position of the vertex. The per-vertex color is scaled by a floating-point parameter prior to output. Also, two texture coordinate sets are passed through unperturbed. Cg supports scalar data types such as float but also has first-class support for vector data types. float4 represents a vector of four floats. float4x4 represents a matrix. mul is a standard library routine that performs matrix by vector multiplication. Cg provides function overloading like C++; mul is an overloaded function so it can be used to multiply all combinations of vectors and matrices.
The process of compiling Cg programs can take place during the initialization of your application using Cg. The Cg runtime contains the Cg compiler as well as API-dependent routines that greatly simplify the process of configuring your compiled program for use with either OpenGL or Direct3D.
1.2 Fragment Program Explanation
Cg provides the same operators as C. Unlike C however, Cg operators accept and return vectors as well as scalars. For example, the scalar, brightness, scales the vector, color, as you would expect.
In addition to writing programs to process vertices, you can write programs to process fragments. Here is a Cg fragment program: float4 brightLightMapDecal(float4 color : COLOR, float4 decalCoord : TEXCOORD0, float4 lightMapCoord : TEXCOORD1, uniform sampler2D decal, uniform sampler2D lightMap) : COLOR { float4 d = tex2Dproj(decal, decalCoord); float4 lm = tex2Dproj(lightMap, lightMapCoord); return 2.0 * color * d * lm; } The input parameters correspond to the interpolated color and two texture coordinate sets as designated by their input semantics.
In Cg, declaring a parameter with the uniform modifier indicates that its value is initialized by an external source that will not vary over a given batch of vertices. In this respect, the uniform modifier in Cg is different from the uniform modifier in RenderMan but used in similar contexts. In practice, the external source is some OpenGL or Direct3D state that your application takes care to load appropriately. For example, your application must supply the modelViewProjection matrix and the brightness scalar. The Cg runtime library provides an API for loading your application state into the appropriate API state required by the compiled program. The POSITION, COLOR, TEXCOORD0, and TEXCOORD1 identifiers following the objectPosition, color, decalCoord, and lightMapCoord parameters are input semantics. They indicate how their parameters are initialized by per-vertex varying data. In OpenGL, glVertex commands feed the POSITION input semantic; glColor commands feed the COLOR semantic; glMultiTexCoord commands feed the TEXCOORDn semantics.
The sampler2D type corresponds to a 2D texture unit. The Cg standard library routine tex2Dproj performs a projective 2D texture lookup. The two tex2Dproj calls sample a decal and light map texture and assign the result to the local variables, d and lm, respectively.
1
vec1.xw = vec3; // vec1 = (3.0, -2.0, 5.0, 3.0) Use either .xyzw or .rgba suffixes swizzling and write masking.
The program multiplies the two textures results, the interpolated color, and the constant 2.0 together and returns this RGBA color. The program returns a float4 and the semantic for the return value is COLOR, the final color of the fragment.
The Cg standard library includes a large set of built-in functions for mathematics (abs, dot, log2, reflect, rsqrt, etc.) and texture access (texCUBE, tex3Dproj, etc.). The standard library makes extensive use of function overloading (similar to C++) to support different vector lengths and data types. There is no need to use #include to obtain prototypes for standard library routines as in C; Cg standard library routines are automatically prototyped.
The Cg compiler generates the following code for brightLightMapDecal when compiled with the arbfp1 multivendor OpenGL fragment profile: !!ARBfp1.0 PARAM c0 = {2, 2, 2, 2}; TEMP R0; TEMP R1; TEMP R2; TXP R0, fragment.texcoord[0], texture[0], 2D; TXP R1, fragment.texcoord[1], texture[1], 2D; MUL R2, c0.x, fragment.color.primary; MUL R0, R2, R0; MUL result.color, R0, R1; END This same program also compiles for the DirectX 8 and 9 profiles (ps_1_3 & ps_2_x) and NVIDIA-proprietary OpenGL extensions (fp20 & fp30).
In addition to the out modifier for call-by-result parameter passing, the inout modifier treats a parameter as both a call-byvalue input parameter and a call-by-result output parameter. The discard keyword is similar to return but aborts the processing without returning a transformed fragment.
2.3 Features Not Supported Cg has no support currently for pointers or bitwise operations (however, the necessary C operators and keywords are reserved for this purpose). Cg does not (currently) support unions and function variables.
2. Other Cg Functionality 2.1 Features from C
Cg lacks C++ features for “programming in the large” such as classes, templates, operator overloading, exception handling, and namespaces.
Cg provides structures and arrays, including multi-dimensional arrays. Cg provides all of C’s arithmetic operators (+, *, /, etc.). Cg provides a boolean type and boolean and relational operators (||, &&, !, etc.). Cg provides increment/decrement (++/--) operators, the conditional expression operator (?:), assignment expressions (+=, etc.), and even the C comma operator.
The Cg standard library lacks routines for functionality such as string processing, file input/output, and memory allocation, which is beyond the specialized scope of Cg. However, Cg reserves all C and C++ keywords so that features from these languages could be incorporated into future implementations of Cg as warranted.
Cg provides user-defined functions (in addition to pre-defined standard library functions), but recursive functions are not allowed. Cg provides a subset of C’s control flow constructs (do, while, for, if, break, continue); other constructs such as goto and switch are not supported in current the current Cg implementation but the necessary keywords are reserved.
3. Profile Dependencies When you compile a C or C++ program, you expect it to compile without regard to how big (within reason) the program is or what the program does. With Cg, a syntactically and semantically correct program may still not compile due to limitations of the profile for which you are compiling the program.
Like C, Cg does not mandate the precision and range of its data types. In practice, the profile chosen for compilation determines the concrete representation for each data type. float, half, and double are meant to represent continuous values, ideally in floating-point, but this can depend on the profile. half is intended for a 16-bit half-precision floating-point data type. (NVIDIA’s CineFX architecture provides such a data type.) int is an integer data type, usually used for looping and indexing. fixed is an additional data type intended to represent a fixed-point continuous data type that may not be floating-point.
For example, it is currently an error to access a texture when compiling with a vertex profile. Future vertex profiles may well allow texture accesses, but existing vertex profiles do not. Other errors are more inherent. For example, a fragment profile should not output a parameter with a TEXCOORD0 semantic. Other errors may be due to exceeding a capacity limit of current GPUs such as the maximum number of instructions or the number of texture units available.
Cg provides #include, #define, #ifdef, etc. matching the C preprocessor. Cg supports C and C++ comments.
Understand that these profile dependent errors do not reflect limitations of the Cg language, but rather limitations of the current implementation of Cg or the underlying hardware limitations of your target GPU.
2.2 Additional Features Not in C Cg provides built-in constructors (similar to C++ but not userdefined) for vector data types: float4 vec1 = float4(4.0, -2.0, 5.0, 3.0); Swizzling is a way of rearranging components of vector values and constructing shorter or longer vectors. Example: float2 vec2 = vec1.yx; // vec2 = (-2.0, 4.0) float scalar = vec1.w; // scalar = 3.0 float3 vec3 = scalar.xxx; // vec3 = (3.0, 3.0, 3.0) More complicated swizzling syntax is available for matrices. Vector and matrix elements can also be accessed with standard array indexing syntax as well. Write masking restricts components. Example:
vector
assignments
to
4. Compatibility and Portability NVIDIA's Cg implementation and Microsoft's High Level Shader Language (HLSL) are very similar as they were co-developed. HLSL is integrated with DirectX 9 and the Windows operating system. Cg provides support for multiple APIs (OpenGL, Direct X 8, and Direct X 9) and multiple operating systems (Windows, Linux, and Mac OS X). Because Cg interfaces to multi-vendor APIs, Cg runs on GPUs from multiple vendors.
5. More Information
indicated
In March 2003, look for The Cg Tutorial: The Definitive Guide to Programmable Real-Time Graphics (ISBN 0321194969) published by Addison-Wesley. 2