DirectX projekt i Visual Studio 2005

Anvendelse af Mesh

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:

  1. Tepotten igen
  2. Indlæsning af mesh fra fil

Kommende udfordringer og udvidelser

1. Tepotten igen

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.

2. Indlæsning af mesh fra fil

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.

Kommende udfordringer og udvidelser

Placering af lyskilder

Tilføjelse af tekstur


Sidst opdateret: 21. februar 2007

Index