Start med at gennemføre de tre første punkter i Eksempel 1, eller anvend Copy & paste.
I de foregående eksempler har vi brugt simple figurer bestående af nogle få trekanter. I disse tilfælde har det været muligt at definere figurerne med punkter, som er blevet indlagt i en simpel vertex tabel. På tilsvarende måde er rendering af figurerne foretaget med den simple funktion DrawUserPrimitive. Det turde være indlysende, at denne metode bliver for vanskelig, når kompleksiteten af den tegnede figur overstiger et vist antal trekanter. Så må man have nogle mere abstrakte metoder at arbejde med, som kan håndtere i hundredvis af trekanter med nogle få kommandoer.
Her kommer Mesh klassen ind i billedet. Et mesh definerer et prædefineret sæt af hjørner, samt et sæt af texturer og materialer, som kan dække figuren. Vi slipper for at holde styr på alle de små trekanter, som en detaljeret figur er opbygget af.
Udviklingen af applikationen kan inddeles i en række naturlige trin. Efter hvert trin bør man have en applikation, der kan oversættes og køres, også selvom den måske ikke kan udføre noget meningsfyldt endnu. Her er en oversigt over trinene i udviklingen af applikationen:
Kommende udfordringer og udvidelser
At lave et lille program, der kan få en tepotte til at dreje rundt om sig selv, er en klassisk øvelse indenfor grafik programmering - måske kan man ligefrem sige, at det er grafik programmeringens "Hello World" program. Lad os derfor ikke prøve at undvige det uundgåelige - en applikation med en tepotte.
Klassen bygges op efter samme principper og med samme metoder som alle de foregående eksempler:
Metode | Vigtigste indhold |
Constructor | Opsætning af skærmbilledets dimensioner og caption |
InitializeGraphics |
Opsætning af device Opsætning af view matrix - kameraets placering og synsretning Opsætning af world matrix - objektets placering og rotation Kald af RestoreDevice Initiering af tepotte meshet |
RestoreDevice | Opsætning af parametre til håndtering af dybdebuffer
og lys Opsætning af lyskilder Opsætning af projektionsmatrix |
Render | Blankstilling af Backbuffer med ønskede baggrundsfarve Dernæst hentes world transformations matricen View og projektionsmatricerne hentes Tepottens sider tilsættes farve eller struktur Tepotten tegnes |
UpdateScene | Opdatering af scenen |
Mesh klassen indeholder heldigvis en metode, som kan generere de nødvendige data til en tepotte mesh. Vi vil derfor starte med at definere nogle variabler, som kan rumme disse data og applikationens øvrige data:
// Global variables for this project private Device device = null; // Rendering device private Mesh teapotMesh; private Material teapotMaterial;
private Matrix world; // positions the teapot in the scene private Matrix view; // positions the camera in the scene private Matrix projection; // controls scene perspective private float angle = 0.01F;
Metoden InitializeGraphics ses herunder:
public void InitializeGraphics() { // Device til DirectX Rendering PresentParameters presentParams = new PresentParameters(); presentParams.Windowed = true; presentParams.SwapEffect = SwapEffect.Discard; presentParams.AutoDepthStencilFormat = DepthFormat.D16; presentParams.EnableAutoDepthStencil = true;
device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams); // Initial values for the view and world transforms view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 5), new Vector3(0, 1, 0)); world = Matrix.Translation(0, 0, 5); RestoreDevice(); // Create the teapot mesh and material teapotMesh = Mesh.Teapot(device); teapotMaterial = new Material(); teapotMaterial.AmbientColor = new ColorValue(Color.Red.R, Color.Red.G, Color.Red.B); teapotMaterial.DiffuseColor = new ColorValue(Color.Red.R, Color.Red.G, Color.Red.B); }
I forbindelse med initialisering af device er der tilføjet en parameter, AutoDepthStencilFormat, som forbereder systemet på behandling af fladerne i figuren, hvor det kun er meningen, at man skal se forsiden af fladerne.
I dette eksempel gemmes matricerne til world transformationen og view transformationen i variabler. Herfra hentes de, når de skal bruges i Render metoden. Jeg placerer objektet (tepotten) i punktet (0, 0, 5) - det gøres ved at udføre en translation, som gemmes i world matricen. Kameraet placeres i punktet (0, 0, -5) og det sættes til at pege på objektet.
Den sidste del af metoden indeholder kode til indlæsning af tepottens data i variablen teapotMesh.
Metoden RestoreDevice, som ses herunder, indeholder opsætning af lys og projektionsmatrix.
private void RestoreDevice() { device.RenderState.ZBufferEnable = true; // Enable z-buffering device.RenderState.ZBufferWriteEnable = true;
device.RenderState.Lighting = true; // Enable lighting device.RenderState.Ambient = Color.Gray; // Set the ambient color
// configure a directional light device.Lights[0].Type = LightType.Directional; device.Lights[0].Direction = new Vector3(10, 3, 0); device.Lights[0].Diffuse = Color.White; device.Lights[0].Enabled = true;
// projection transform projection = Matrix.PerspectiveFovLH((float)(Math.PI / 4.0f), (float)(this.Width / this.Height), 1.0f, 100.0f); }
Lyskilden defineres med hensyn til: typen af lys, retning for lyskilden og lysets farve.
Projektionsmatricen er sat op som sædvanlig med en vinkel på 45 grader, aspekt ratio (forholdet mellem bredde og højde), near- og far-plane.
Herunder er Render metoden vist:
private void Render() { //Clear the backbuffer to a blue color device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, System.Drawing.Color.DarkBlue, 1.0f, 0); device.BeginScene(); //Begin the scene device.Transform.World = world; device.Transform.View = view; device.Transform.Projection = projection; device.Material = teapotMaterial; device.SetTexture(0, null); teapotMesh.DrawSubset(0); device.EndScene(); //End the scene device.Present(); }
Render metoden bruges til at tegne figuren - faktisk tegnes figuren igen og igen, fordi kaldet af Render metoden ligger inde i det loop i Main metoden, som udgør programmets puls.
private void UpdateScene() { world = world * Matrix.Translation(0, 0, -5) * Matrix.RotationY(angle) * Matrix.Translation(0, 0, 5); }
I dette program har jeg udskilt opdateringen af modellen fra Render metoden. De ændringer, der skal udføres i hvert trin er indsat i en Update metode, som kaldes fra Main metodens puls loop.
Koden kan hentes her.
I videreudviklingen af eksemplet vil vi i stedet for tepotten vise en figur, som er gemt i et mesh, der indlæses fra en fil. Filer af denne type har normalt extension ".x", og de indeholder alle de informationer, der er nødvendige for korrekt rendering af objektet. Der kan være henvisninger til textures, som er grafiske data, som man bruger til at beklæde objektets flader med. I dette eksempel indlæses et objekt (earth.x), som forestiller jordkloden. Denne vil vi sætte op med en langsom rotation i urets retning.
De oprindelige variabler til Mesh og materials suppleres med en variabel til opbevaring af textures:
private Mesh mesh; private Material[] meshMaterials; // Materials for our mesh private Texture[] meshTextures; // Textures for our mesh
I metoden InitializeGraphics ændres koden for opsætning af view og world således:
view = Matrix.LookAtLH(new Vector3(0, 0, -10), new Vector3(0, 0, 0), new Vector3(0, 1, 0)); world = Matrix.Identity; device.RenderState.Ambient = Color.White;
device.RenderState.CullMode = Cull.None;
RestoreDevice();
CreateMesh();
Objektet placeres uskaleret i koordinatsystemets nulpunkt. Kameraet placeres i en afstand af -10 på Z-aksen. Som det også fremgår af kodestumpen er indlæsningen af data i meshet flyttet til metoden CreateMesh:
public void CreateMesh() { ExtendedMaterial[] materials = null; String path = @"..\..\"; mesh = Mesh.FromFile(path + "earth.x", MeshFlags.SystemMemory, device, out materials); if (meshTextures == null) { // Egenskaber for materialer og texture udtrækkes meshTextures = new Texture[materials.Length]; meshMaterials = new Material[materials.Length]; for (int i = 0; i < materials.Length; i++) { meshMaterials[i] = materials[i].Material3D; // Set the ambient color for the material (D3DX does not do this) meshMaterials[i].Ambient = meshMaterials[i].Diffuse; if (materials[i].TextureFilename != null) { // Create the texture meshTextures[i] = TextureLoader.FromFile(device, path + materials[i].TextureFilename); } } } }
Variablen path indeholder en henvisning til den mappe, hvor mesh-filen er placeret - i dette tilfælde er den placeret sammen med projektets kildefiler - stien anviser vejen hertil fra det sted, hvor det oversatte program er placeret.
Den følgende kodestump viser opsætning af lyskilden og projektionsmatricen. Aspect ratio er her sat til 1.0, fordi størrelsen på vinduet er fastsat til 600x600.
// Configure a directional light device.Lights[0].Type = LightType.Directional; device.Lights[0].Direction = new Vector3(1000, 0, -50); device.Lights[0].Diffuse = Color.White; device.Lights[0].Enabled = true;
// projection transform float aspectRatio = this.Width / this.Height; projection = Matrix.PerspectiveFovLH((float)(Math.PI / 4.0f), 1.0f, 1.0f, 100.0f);
Herunder følger Render metoden og UpdateScene metoden:
private void Render() { //Clear the backbuffer to a blue color device.Clear(ClearFlags.ZBuffer | ClearFlags.Target, System.Drawing.Color.DarkBlue, 1.0f, 0); device.BeginScene(); //Begin the scene device.Transform.World = world; device.Transform.View = view; device.Transform.Projection = projection; for (int i = 0; i < meshMaterials.Length; i++) { // Set the material and texture for this subset device.Material = meshMaterials[i]; device.SetTexture(0, meshTextures[i]);
// Draw the mesh subset mesh.DrawSubset(i); } device.EndScene(); //End the scene device.Present(); }
private void UpdateScene() { world = world * Matrix.RotationY(angle); }
Et snapshot fra en afprøvning af programmet:
Koden til dette eksempel, der indlæser et mesh fra en .x fil kan hentes her. Den tilhørende fil, earth.x, indgår også i pakken. Hvis man i stedet har lyst til at eksperimentere med en roterende tiger, indeholder pakken også en mesh-fil med navnet tiger.x.
Placering af lyskilder
Tilføjelse af tekstur