Laden...

DirectX: Tutorial

Erstellt von hauptmann vor 19 Jahren Letzter Beitrag vor 18 Jahren 62.101 Views
Thema geschlossen
H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren
DirectX: Tutorial

Ab sofort ist die neueste Version des Tutorials hier zu erreichen.

Hi!

weil hier immer wieder mal nach DirectX Tutorials gefragt wird, hab ich einfach mich mal dazu entschlossen eins zu schreiben. Um das hier zu verstehen braucht man keine besondere Erfahrung mit C#. Aber man sollte schon sicher mit C# umgehen können und auch mit WindowsForms sollte man schon ein wenig gearbeitet haben. Feedback etc. sind natürlich erwünscht. Der Sourcecode dieses Tutorials ist am Schluss als Zip angehängt.

** DirectX: Eine Einführung **

DirectX wurde 1995 erstmals von Microsoft unter dem Namen The Games SDK(oder so...) vorgestellt. Davor war(zu DOS Zeiten) musste man umständlich auf Assembler zurückgreifen, wenn man irgendwas mit Grafik machen wollte. DirectX vereinfachte dies extrem und wurde von Version zu Version immer weiter entwickelt und auch immer besser. Derzeit stehen wir bei Version DirectX 9. DirectX ist eigentlich eine Sammlung von mehreren APIs. Diese sind DirectInput um Daten von Tastaturen,Mäuse,Joysticks etc. zu empfangen und verarbeiten, DirectSound/Music um Sounds/Musik zu spielen, DirectPlay um Netzwerkprogramme wie Multiplayer Spiele zu schreiben, DirectShow um Videos abzuspielen und zu guter Letzt Direct3D um auf dem Bildschirm zu zeichnen. Daneben gibt es noch DirectDraw, mit dem man ausschließlich 2D Objekte zeichnen kann(mit Direct3D kann man sowohl 3D als auch 2D Objekte zeichnen) und DirectSetup, ein Setup Generationsprogramm spez. für DirectX Applikationen.
Dieses Tutorial befasst sich also nicht mit dem gesamten DirectX Multimedia Packet, sondern "nur" mit Direct3D. Es muss natürlich gesagt werden, dass jeder Unterbereich von DirectX schon allein ziemlich groß ist. So, nun wollen wir mal sehen, welche Möglichkeiten uns Direct3D bietet. Ebenso werden die nächsten Kapitel ziemlich Theorie beinhalten, mit nur relativ wenig Code. Aber durchhalten. Wir werden bald zu unseren ersten Zeichenoperationen kommen...

** Direct3D: A closer look **

Voll DirectX 9 kompatibel.
Damit wird seit längerer Zeit von vielen Grafikkartenherstellern geworben. Die Grafikkartenhersteller meinen damit natürlich Direct3D.(DirectInput auf der Grafikkarte? ^^) Aber was macht Direct3D eigentlich genau? Direct3D bietet uns die Möglichkeit auf dem Bildschirm Objekte zu zeichnen. Denkt einfach mal an ein Spiel wie Far Cry. Der ganze tolle Dschungel dort wird mit Direct3D gezeichnet.(achja, wir haben noch einen langen Weg vor uns, bis wir sowas wie Far Cry machen können). Aber wie sind diese Objekte jetzt aufgebaut? Die Antwort ist einfach: Aus Dreiecken. Denn aus Dreiecken kann man jedes beliebige Objekt zusammenstellen. Nehmt einfach einen Zettel und malt ein Rechteck drauf. Wie ihr vielleicht sehen könnt, besteht ein Rechteck aus zwei sich gegenüberliegenen rechtwinkeligen Dreiecken. Eine solche Aufteilung in Dreiecken lässt sich praktisch bei jedem Objekt machen. D.h. wir können also Dreiecke mit Direct3D zeichen.(man kann aber auch Punkte und Linien zeichnen, aber am öftesten verwendet man Dreieckte). Aber Direct3D kann noch mehr. Es kann diese Dreiecke nun texturieren. Normalerweise kann man einem Dreieck nur eine Farbe zuweisen(blau,gelb,rot....), aber das ist natürlich langweilig. Deshalb erfand man eine Technik namens Texturieren. D.h. das man einfach ein Bild über das Dreieck legt. Damit sehen Objekte viel realitätsnäher aus. Desweiteren kann man mit Direct3D auch Licht erzeugen.(Lights) Das ist allerdings ziemlich komplex, daher werden wir das erst später besprechen. Zum Schluss kann Direct3D Dreiecke noch verschieben,vergrößern und verkleinern. Aber das werden wir auch noch später genauer kennen lernen.
so, jetzt haben wir uns mal einen groben Überblick über Direct3D verschafft. Jetzt können wir näher auf die einzelnen Bereiche eingehen. Zuerst sehen wir mal, wie wir Direct3D dazu bringen, dass es uns erlaubt irgendwas zu zeichnen.

** Direct3D: Erste Schritte **

Überlegen wir uns erstmal, was wir in diesem Kapitel erreichen wollen. Wir wollen ganz einfach ein Fenster erzeugen, das Blau(oder in irgendeiner anderen Farbe) gefärbt ist. Das ist sozusagen das "Hello World" von DirectX.
Was braucht man dazu? Ich sage mal ganz einfach, dass man mit einer IDE arbeiten sollte. Warum? Weil es das Leben vereinfacht. Ich selbst nutze VS.net 2003. Als nächstes braucht man halt die Standardtools, um mit C# zu programmieren. Zu guter Letzt braucht man noch das so genannte DirectX SDK. Das beinhaltet alles, was man braucht um mit DirectX zu programmieren. Nachdem das SDK installiert wurde, können wir weitermachen.(irgendwie logisch...).
Zunächst referenzieren wir auf folgende DLLs: System.dll,System.Drawing.dll,System.Windows.Forms.dll,Microsoft.DirectX.dll,Microsoft.DirectX.Direct3D.dll Microsoft.DirectX.Direct3DX.dll.
Nun binden wir folgende Namespaces ein, die eigentlich selbsterklärend sind:


using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

Für unsere erste DirectX Anwendung brauchen wir nur eine einzige Membervariable. Das DirectX Device(Microsoft.DirectX.Direct3D.Device m_Device). Ein Device ist die elementare Grundverbindung zwischen unserer Anwendung und Direct3D. Mit einem Device können wir alle oben genannten Dinge machen. Nun implementieren wir eine Funktion InitGfx(), die uns das Device erstellt.
Die ersten Zeilen dieser Funktion sollten kein Problem darstellen:


public void InitGfx()
{
	try
	{
		this.ClientSize = new Size(1024,768);
		this.Text = "Direct3D Tutorial 1";

		this.KeyPress += new KeyPressEventHandler(OnKeyPress);

Nun kommen wir zum interessanten Teil: Die PresentParameter. Diese werden wir später zum erstellen des Devices benötige. Zuerst mal der Code:


PresentParameters pp = new PresentParameters();

pp.Windowed = true;
pp.SwapEffect = SwapEffect.Copy;

Die PresentParamter(Präsentations Parameter) bestimmen also wie sich das Device grundlegend verhalten soll.(es gibt noch mehr, aber in unserem Fall reichen diese aus). Für unsere Applikation brauchen wir zunächst nur diese beiden Parameter. Der erste Paramter zeigt an, das wir eine Fensteranwendung haben wollen. Der Zweite gibt an, wie wir die Bilder auf den Bildschirm bringen wollen. Copy bedeutet einfach, dass wir immer den gesamten Bildschirm neuzeichnen wollen.
Nun geht daran, das Device zu erstellen:


m_Device = new Device(Manager.Adapters.Default.Adapter,DeviceType.Hardware,this,CreateFlags.HardwareVertexProcessing,
										pp);

ok. Gehen wir Parameter für Parameter durch:
Manager.Adapters.Default.Adapter: Gibt an, auf welchem Adapter(=Bildschirm) wir zeichnen wollen. Normalerweise haben die meisten Systeme einen Bildschirm. Und diesen Bildschirm repräsentiert Manager.Adapters.Default.Adapter.
DeviceType.Hardware: Das bedeutet, das für alle möglichen Operationen von Direct3D die Grafikhardware verwendet wird. Man kann auch bestimmen, das das die CPU(Software) machen soll, was aber ziemlich langsam ist.
this: Zeigt einfach an, das das Devie einfach dieses Fenster zum zeichnen verwenden soll.
CreateFlags.HardwareVertexProcessing: Besagt ebenfalls grundsätzlich das selbe wie DeviceType.Hardware, aber ist trotzdem ein wenig anders, außerdem gibts da noch ein paar andere Paramter, die aber für uns(derzeit) nicht interessant sind. Ich werde später noch genauer darauf eingehen.
pp: Zeigt auf die ausgefüllte PresentParameters Struktur.

Und jetzt noch der Abschluss der Funktion:


}
			catch(DirectXException e)
			{
				MessageBox.Show(e.Message);
			}
		}

Gut. Nun kommen wir zum Zeichnen. Das wird als Rendern bezeichnet. Deshalb implementieren wir einfach mal eine Funktion namens Render.


public void Render()
		{
			m_Device.Clear(ClearFlags.Target ,Color.Blue,0.0f,0);
			m_Device.BeginScene();
                             // rendern
			m_Device.EndScene();
			m_Device.Present();

		}

Der erste Funktionsaufruf ist nicht sonderlich interessant(derzeit...) und besagt, dass das Fenster(Target) mit einem blauen Hintergrund gerendert werden soll.
Danach kommen wir zum eigentlichen rendern. Das rendern geschieht immer zwischen einem BeginScene und einem EndScene. Doch das werden wir später auch noch genauer besprechen. Das Present veranlsst Direct3D nun, alles auch wirkich zu rendern.
Nun brauchen wir noch eine Funktion, die Direct3D herunterfährt und noch die Main Funktion. Schließlich implementieren wir noch die OnKeyPress Funktion...


public void Shutdown()
		{
			m_Device.Dispose();
		}

		

	
		/// <summary>
		/// Der Haupteinstiegspunkt für die Anwendung.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			MDXSampleApp example = new MDXSampleApp();
			example.InitGfx();
			example.Show();

			while(example.Created)
			{
				example.Render();
				Application.DoEvents();
			}
			example.Shutdown();

		}

		private void OnKeyPress(object sender, KeyPressEventArgs e)
		{
			if((int)e.KeyChar == (int)Keys.Escape)	
				this.Close();
		}

Hier jetzt der gesamte Source: (insgesamt rund 100 Zeilen)


using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace MDXTutorial01
{
	/// <summary>
	/// Zusammenfassung für Form1.
	/// </summary>
	public class MDXSampleApp : System.Windows.Forms.Form
	{
		private Device m_Device;


		// ctor
		// do nothing...
		public MDXSampleApp()
		{
			
		}

		// dtor
		// shutdown D3D
		 ~MDXSampleApp()
		{
			
			 
		}

		public void InitGfx()
		{
			try
			{
				this.ClientSize = new Size(1024,768);
				this.Text = "Direct3D Tutorial 1";

				this.KeyPress += new KeyPressEventHandler(OnKeyPress);

				PresentParameters pp = new PresentParameters();

				pp.Windowed = true;
				pp.SwapEffect = SwapEffect.Copy;
				
				m_Device = new Device(Manager.Adapters.Default.Adapter,DeviceType.Hardware,this,CreateFlags.HardwareVertexProcessing,
										pp);

			}
			catch(DirectXException e)
			{
				MessageBox.Show(e.Message);
			}
		}
		public void Render()
		{
			m_Device.Clear(ClearFlags.Target ,Color.Blue,0.0f,0);
			m_Device.BeginScene();
			m_Device.EndScene();
			m_Device.Present();

		}

		public void Shutdown()
		{
			m_Device.Dispose();
		}

		

	
		/// <summary>
		/// Der Haupteinstiegspunkt für die Anwendung.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			MDXSampleApp example = new MDXSampleApp();
			example.InitGfx();
			example.Show();

			while(example.Created)
			{
				example.Render();
				Application.DoEvents();
			}
			example.Shutdown();

		}

		private void OnKeyPress(object sender, KeyPressEventArgs e)
		{
			if((int)e.KeyChar == (int)Keys.Escape)	
				this.Close();
		}
	}
}

So, das wars. Im nächsten Teil werden wir genauer aufs Rendern und auch auf den Fullscreen Modus zum sprechen kommen.

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
R
139 Beiträge seit 2004
vor 19 Jahren

super idee danke 😉

vielleicht kann ich ja was dazu lernen g

wieivele parts planst du?

posted by the real prince of persia

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

Original von r00t
super idee danke 😉

vielleicht kann ich ja was dazu lernen g

wieivele parts planst du?

danke.

ich denke mal das ich die Basics mache und dann entscheide, ob ich dann noch mehr mache oder nicht.
Der nächste Teil kommt entweder heute oder morgen...

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
R
139 Beiträge seit 2004
vor 19 Jahren

gehst du auch auf surfaces ein?

posted by the real prince of persia

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

Original von r00t
gehst du auch auf surfaces ein?

lass dich mal überachen 😁
im nächsten Teil geht es um 2D Zeichenoperationen(bevor wir zum 3D kommen...)

EDIT: am Wochenende wird es weitergehen. Vorraussichtlicher Termin ist entweder Samstag/Sonntag(unter der Woche hab ich keine Zeit was zu schreiben sry)

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

** Rendern von Dreiecken **

Gut, weiter gehts. Dieses Kapitel wird viele grundlegende Dinge beinhalten, also gut aufpassen. Zunächst wollen wir einmal sehen, wie ein Dreieck aufgebaut ist: Ein Dreieck besteht aus 3 Eckpunkten,die miteinander verbunden werden. Und für jedes Dreieck, dass wir zeichnen wollen, müssen wir diese 3 Eckpunkte definieren. Aber wie müssen wir diese 3 Eckpunkte definieren, damit DirectX sie zeichnen kann? Nun, dass ist relativ einfach: Für Direct3D ist der Bildschirm nichts weiter als ein Koordinatensystem. Wir müssen nur die Koordinaten für die einzelnen Eckpunkte an Direct3D schicken und Direct3D zeichnet uns dann ein Dreieck. Wie ist dieses Koordinatensystem nun aufgebaut? Ich denke mal, dass jeder weis, was ein (karthesisches)Koordinatensystem ist. Für Direct3D exestieren 3 Achsen. Die x Achse, die y Achse und die z Achse. Die x Achse verläuft auf der horizontalen, die y auf der vertikalen. Die z Achse geht in den Bildschirm hinein. Da wir ja jetzt erstmal nur 2D Objekte rendern wollen, brauchen wir uns darum nicht zu kümmern. Zu sagen bleibt hierzu eigentlich nur noch, dass die englische Bezeichnung für Eckpunkt Vertex lautet, im Plural Vertices.
Um solche Vertices zu speichern, bietet uns Direct3D eine ganze Reihe von Strukturen. Diese sind alle im Namespace Microsoft.DirectX.Direct3D.CustomVertex zusammengefasst. In diesem Namespace sind ziemlich viele unterschiedliche Strukturen zum Speichern von Vertices, für uns ist jedoch nur die Struktur CustomVertex.TransformedColored (erstmal) interessant. Das Transformed bedeutet das wir 2D Koordinaten angeben wollen und das Color das wir jeden Eckpunkt eine Farbe zuweisen wollen. Wenn wir jetzt jeden Eckpunkt eine eigene Farbe zuweisen, wird der unmittelbare Bereich zu diesem Eckpunkt mit der Farbe gefärbt. An den Übergängen zwischen den einzelnen Farben, werden die Farben interpoliert. Beim interpolieren wird einfach ein Mittelwert zwischen den einzelnen Farben berechnet und dadurch kommt ein Farbverlauf zwischen den Farben zustande.
Jetzt müssen wir noch wissen, wie wir die Vertices speichern. Dazu müssen wir zunächst mal ein Array von CustomVertex.TransformedColored erstellen und mit unseren Daten befüllen. Dazu fügen wir unserer Klasse aus dem 1. Kapitel eine weitere private Variable verts hinzu:


private CustomVertex.TransformedColored[] verts;

Nun, diese Vertices liegen jetzt aber im Hauptspeicher(dem RAM). Wenn wir nun unser Dreieck rendern wollen, muss Direct3D diese erst über den AG Port(bzw. PCI Port) zur Grafikkarte transportieren. Bei ein paar Vertices, wird sich das sicher nicht bemerkbar machen, aber wenn wir jetzt mehrere tausend Vertices haben? Deshalb wäre es ja von Vorteil, wenn die Vertices direkt auf dem Speicher der Grafikkarte liegen würden.(Video RAM) Der Vorteil des Video RAM ist, dass er eine extrem kurze Zugriffszeit hat und das wir nicht bei jedem Rendervorgang, die Daten über den AG Port transportieren müssen. Ein Nachteil ist jedoch, dass der Video RAM von der Größe her relativ begrenzt ist und teuer ist. Aber darum müssen wir uns nicht kümmern. Also wir speichern wir jetzt unsere Vertices im Video RAM? Ganz einfach: Mit einem VertexBuffer. Ein VertexBuffer ist nichts anderes als ein Speicherbereich im Video RAM, der unsere Vertices hält. Ein VertexBuffer wird durch die Klasse Direct3D.VertexBuffer repräsentiert.


private VertexBuffer m_VertexBuffer;

Nun müssen wir unserer Vertices noch definieren und nebenbei den VertexBuffer mit diesen Daten befüllen. Zuerst definieren wir die Vertices:


verts = new CustomVertex.TransformedColored[3];

			verts[0].X=150;verts[0].Y=50;verts[0].Z=0.5f; verts[0].Rhw=1;verts[0].Color = Color.Yellow.ToArgb();
			verts[1].X=250;verts[1].Y=250;verts[1].Z=0.5f; verts[1].Rhw=1;verts[1].Color = Color.Bisque.ToArgb();
			verts[2].X=50;verts[2].Y=250;verts[2].Z=0.5f; verts[2].Rhw=1; ;verts[2].Color = Color.White.ToArgb();

Wir sehen also, dass wir jedem Vertex eine X Koordinate, eine Y Koordinate, eine Z Koordinate und eine Farbe zuweisen. Wie gesagt, müssen wir uns eigentlich nicht um die Z Koordinate kümmern, jedoch habe ich sie auf 0.5 gesetzt, da ein 0.0 bei manchen(älteren) Grafikkarten zu Problemen führen könnte.
Nun erstellen wir den VertexBuffer und zwar mit den folgenden Konstruktor:

public VertexBuffer(
Type typeVertexType,
int numVerts,
Device device,
Usage usage,
VertexFormats vertexFormat,
Pool pool
);

Der erste Parameter bestimmt, welchen VertexType wir für unseren VertexBuffer verwenden wollen. Hier setzen wir einfach typeof(CustomVertex.TransformedColored) ein.
Der zweite Parameter bestimmt die Anzahl der Vertices, die wir in unserem VertexBuffer speichern wollen. Da wir nur ein Dreieck speichern wollen, setzen wir hier eine 3 ein.
Der vierte Parameter bestimmt das Device, in welchem Kontext der VertexBuffer erstellt werden soll: m_Device.
Der fünfte Parameter bietet uns einen Satz von Konstanten an, die bestimmen, wie der VertexBuffer intern aufgebaut ist. Hier setzen wir Usage.WriteOnly ein, das besagt, das wir nur in den VertexBuffer schreiben wollen, ihn jedoch nicht auslesen(das kann zu kleineren Performancegewinnen führen)
Der vorletze Parameter ist das VertexFormat: CustomVertex.TransformedColored.Format
Der letzte Parameter bestimmt schließlich, wo wir unseren VertexBuffer speichern wollen. Hier können wir Pool.Default einsetzen, was besagt, dass Direct3D die Vertices einfach in den Video RAM plaziert. Pool.Managed übergibt die Kontrolle des VertexBuffers an den Grafikkartentreiber. Der kann dann z.B., wenn der Platz auf dem Video RAM knapp wird, unsere Vertices wieder in den RAM verschieben. Pool.SystemMemory besagt schlieslich, dass wir unsere Vertices im System RAM plazieren wollen, was die Vorteile eines VertexBuffers jedoch wieder zunichte macht.
In der Praxis sieht das nun so aus:


m_VertexBuffer = new VertexBuffer(typeof(CustomVertex.TransformedColored), 3, m_Device, Usage.WriteOnly, CustomVertex.TransformedColored.Format, Pool.Default);

Nun müssen wir unsere Vertices noch in den VertexBuffer hineinschreiben. Dazu müssen wir ihn zuerst sperren(locken), um sicherzustellen, dass während unseres Schreibvorgangs niemand anders auf den Buffer zugreift. Dann schreiben wir unsere Daten in den VertexBuffer und schlieslich entsperren wir ihne wieder(unlocken).Das ganze sieht so aus:


GraphicsStream stream = m_VertexBuffer.Lock(0,0,0);
			
			stream.Write(verts);
			m_VertexBuffer.Unlock();

Der Code bedarf eigentlich keiner großartigen Erklärungen, außer das die Parameter von Lock angeben, dass wir den ganzen VertexBuffer sperren wollen.
Nun kommen wir zum rendern.
Bevor wir unser Dreieck renden können, müssen wir unserem Device noch sagen, in welchem Format(CustomVertex.xxx) unsere Vertices gespeichert sind:


m_Device.VertexFormat = CustomVertex.TransformedColored.Format;

Jetzt haben wir zwei Möglichkeiten. Entweder wir rendern direkt aus dem SystemMemory heraus(unser Array verts liegt ja immer noch da drinnen) oder wir rendern vom VertexBuffer heraus. Ich zeig zuerst mal die Variante aus dem Systemmory, dazu nutzen wir die Funktion Device.


public void DrawUserPrimitives(
    PrimitiveType primitiveType,
    int primitiveCount,
    object vertexStreamZeroData
);

Der erste Parameter bestimmt den Primitive Type. Hier können wir entweder Punkte, Linien oder Dreiecke einsetzen.(Ein Primitiv ist nichts anderes als ein Punkt,Linie oder Dreieck) Wir setzen natürlich PrimitiveType.TriangleStrip ein. Es gibt mehrere Varianten von jedem Primitiv, TriangleSrip besagt, das wir, falls wir evtl. mehrere Dreiecke haben, die Dreiecke in einem Band miteinander verbunden gerendert werden.
Der nächste Parameter bestimmt die Anzahl der Primitive, die gezeichnet werden soll. Wir können unser verts Array z.B. mit 6 Elementen befüllen, um dann zwei Dreiecke zu zeichnen. Da wir aber nur ein Dreieck zeichnen wollen, setzen wir hier eine 1 ein.
Der letze Parameter bestimmt nun das Array, in dem unsere Vertices liegen: verts
Also hier der Code:


m_Device.DrawUserPrimitives(PrimitiveType.TriangleStrip,1,verts);

Nun kommen wir zur Methode mit den VertexBuffer. Bevor wir aus einem VertexBuffer heraus rendern können, müssen wir unserem Device sagen, aus welchem wir überhaupt rendern wollen(ja, wir können mehrere VertexBuffer haben...). Dazu nutzen wir die Funktion Device.SetStreamSource


m_Device.SetStreamSource(0,m_VertexBuffer,0);

Der erste Parameter bestimmt welchen Kanal(Vertex Stream) wir nutzen wollen. Einfach 0 einsetzen, denn das ist der 1. Vertex Kanal, auf dem die Vertices fließen.
Der zweite Parameter bestimmt jenden VertexBuffer, von dem die Daten kommen sollen.
Der letzte Parameter gibt den Offset vom Beginn des Streams bis zu den ersten Vertex Daten an. Hier setzen wir einfach wieder 0 ein.
Nun steht uns nichts mehr im Wege, aus dem VertexBuffer zu rendern. Und das machen wir via Device.DrawPrimitives.


m_Device.DrawPrimitives(PrimitiveType.TriangleStrip,0,1);

Die Parameter unterscheiden sich nicht allzu sehr von den Parametern von DrawUserPrimitives, bis auf 2., der den Start Vertex angibt, also den Vertex, ab dem gerendert werden soll(einfach 0). Der Letzte gibt wieder an, wie viele Primitive wir zeichnen wollen.

Dieses Kapitel war jetzt schon länger als das vorangehende. Zum Schluss nochmal der gesamte Code, wie immer gibts auch die Visual Studio .net 2003 Projektdateien zum Download:


using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace MDXTutorial02
{
	
	public class MDXSampleApp : System.Windows.Forms.Form
	{
		private Device m_Device;
		private VertexBuffer m_VertexBuffer;
		private CustomVertex.TransformedColored[] verts;
		

		// ctor
		// do nothing...
		public MDXSampleApp()
		{
		}

		// dtor
		~MDXSampleApp()
		{
		}

		public void InitGfx()
		{
			try
			{
				this.ClientSize = new Size(800,600);
				this.Text = "Direct3D Tutorial 2: Rendering colored Vertices";

				this.KeyPress += new KeyPressEventHandler(OnKeyPress);

				PresentParameters pp = new PresentParameters();

				pp.Windowed = true;
				pp.SwapEffect = SwapEffect.Copy;
				
				
				m_Device = new Device(Manager.Adapters.Default.Adapter,DeviceType.Hardware,this,CreateFlags.	HardwareVertexProcessing,
					pp);
				CreateVertexBuffer();

				
				
			
			}
			catch(DirectXException e)
			{
				MessageBox.Show(e.Message);
				
			}
		}

		private void CreateVertexBuffer()
		{
		
			verts = new CustomVertex.TransformedColored[3];

			verts[0].X=150;verts[0].Y=50;verts[0].Z=0.5f; verts[0].Rhw=1;verts[0].Color = Color.Yellow.ToArgb();
			verts[1].X=250;verts[1].Y=250;verts[1].Z=0.5f; verts[1].Rhw=1;verts[1].Color = Color.Bisque.ToArgb();
			verts[2].X=50;verts[2].Y=250;verts[2].Z=0.5f; verts[2].Rhw=1; ;verts[2].Color = Color.White.ToArgb();

			m_VertexBuffer = new VertexBuffer(typeof(CustomVertex.TransformedColored), 3, m_Device, Usage.WriteOnly, CustomVertex.TransformedColored.Format, Pool.Default);

			GraphicsStream stream = m_VertexBuffer.Lock(0,0,0);
			
			stream.Write(verts);
			m_VertexBuffer.Unlock();
		}

	
		public void Render()
		{
			

			m_Device.Clear(ClearFlags.Target ,Color.Blue,0.0f,0);
			
			m_Device.BeginScene();
			
			m_Device.VertexFormat = CustomVertex.TransformedColored.Format;

			m_Device.SetStreamSource(0,m_VertexBuffer,0);
			m_Device.DrawPrimitives(PrimitiveType.TriangleStrip,0,1);
		    //m_Device.DrawUserPrimitives(PrimitiveType.TriangleStrip,1,verts);
			
			m_Device.EndScene();
			m_Device.Present();

		}

		public void Shutdown()
		{
			m_Device.Dispose();
		}

		

	
		/// <summary>
		/// Der Haupteinstiegspunkt für die Anwendung.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			MDXSampleApp example = new MDXSampleApp();
			example.InitGfx();
			example.Show();

			while(example.Created)
			{
				example.Render();
				Application.DoEvents();
			}
			example.Shutdown();

		}

		private void OnKeyPress(object sender, KeyPressEventArgs e)
		{
			if((int)e.KeyChar == (int)Keys.Escape)	
				this.Close();
			
		}
	}
}

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

So, weiter gehts mit den....

** Renderstates **

Was ist ein Renderstate? Nun, ein Renderstate beschreibt, wie Direct3D unsere Dreiecke zeichnen soll. Es gibt sehr viele verschiedene Renderstates, daher kann ich jetzt nicht auf alle eingehen(abgesehen davon, könnten wir viele Renderstates mit unserem bisherigen Wissen ohnehin nicht verstehen). Daher will ich mal das grundlegende Konzept der RSs zeigen, damit wir später keine Probleme haben.
Also zuerst mal, beeinflusst ein Renderstate wie ein Dreieck gezeichnet wird. Ein einfacher Renderstate wäre z.B. der FillMode. Der sagt Direct3D, ob unser Dreieck, entweder solid(was der Standard Wert ist und mit dem haben wir auch in den bisherigen Anwendungen gearbeitet), ob nur die Eckpunkte oder nur die Linien gezeichnet werden sollen. Wir können disen RS vor dem Rendern unseres Dreiecks einsetzen:


....
m_Device.RenderState.FillMode = FillMode.WireFrame;

			m_Device.SetStreamSource(0,m_VertexBuffer,0);
			m_Device.DrawPrimitives(PrimitiveType.TriangleStrip,0,1);
....

Mit dem WireFrame Modus werden von unserem Dreieck nur die Linien gezeichnet. Daneben gibt es noch FillMode.Solid, der der Standardwert ist. Und dann gibts noch FillMode.Point, der nur die Eckpunkte zeichnet.(Bemerkung: Wenn wir den DrawPrimitive Aufruf auf m_Device.DrawPrimitives(PrimitiveType.PointList,0,3); abändern, hätte wir den selben Effekt wir mit dem Renderstate FillMode auf Point. )

Natürlich gibt es noch viel mehr RSs als nur FillMode, daher will ich hier noch ein paar vorstellen.

Der nächste ist DitherEnable. Beim Dithering wird da, wo mehrere verschiedene Farben aufeinandertreffen, ein Mittelwert aus diesen berechnet und dieser dann eingesetzt. Auf heutigen Grafikkarten macht es fast keinen Unterschied, ob Dithering eingeschalter ist oder nicht, daher schalten wir es ein:


m_Device.RenderState.DitherEnable = true;

Und noch einer ist das Culling. Das Culling entscheided, welche Objekte sichtbar sind und deshalb gezeichnet werden und welche nicht sichtbar sind und nicht gezeichnet werden.
Direct3D unterstüztt bereits von Haus aus 2 einfache Arten von Culling, die in unseren Beispielanwendungen vollkommen ausreichen werden. Bei Spielen/Grafikdemos würde man natürlich zum Standard Culling noch sein eigenes implementieren.
Direct3D bietet uns das clockwise(CW) culling und das counterclockwise(CCW) culling. Wenn wir unsere Vertices im Uhrzeigersinn anordnen, brauchen wir CCW culling, damit die "unsichtbaren" Vertices wegfallen, wenn unsere Vertices gegen der Uhrzeigersinn angeordnet sind, müssen CW culling verwenden.
Der Standard Wert dieses RS ist CCW. Natürlich können wir das Culling auch komplett abschalten:


m_Device.RenderState.CullMode = Cull.CounterClockwise; // Standard. Unser Dreieck aus dem vorigen Beispiel würde gezeichnet werden
m_Device.RenderState.CullMode = Cull.Clockwise; // CW Culling. Um unser Dreieck jetzt zu zeichnen müssten wir die Elemente 0 und 2 austauschen
m_Device.RenderState.CullMode = Cull.None; // Alles wird gezeichnet. Es ist egal wie wir unsere Vertices anordnen

Gut, das waren mal 3 Renderstate, wir werden jedoch im Laufe des Tutorials noch viele mehr kennen lernen...

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
K
25 Beiträge seit 2004
vor 19 Jahren

Hallo hauptmann,

ich finde das Tutorial richtig gut und verständlich.
Machst du das noch weiter oder hast du keine Lust/Zeit mehr?
wäre schade 🙁

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

Hi!

ja, ich werde es schon noch weitermachen, allerdings hatte ich jetzt in letzter zeit nur wenig Zeit(was für ein satz :ugly🙂
Aber nächste Woche hab ich wieder mehr Zeit, dann gehts weiter...
(Ich fahr nämlich übermorgen auf Urlaub nach D)

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
K
25 Beiträge seit 2004
vor 19 Jahren

na dann warte ich mal gespannt und wünsch dir noch einen
schönen Urlaub hier in D ( die Sonnenbrille nicht vergessen 8) )

U
19 Beiträge seit 2004
vor 19 Jahren

Hi

Ich muss deine Arbeit sehr loben 👍, ist wirklich alles verständlich auch für einen Anfänger, aber..........

...wan wird es denn weitergehen mt deinem super Tutorial??? infoaufsaug

m.f.g
Felix

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

Heute geht es weiter ^^

**Transformationen **

Bisher waren unsere Programme eher langweilig. Jetzt wird sich das aber ändern, da wir jetzt Bewegung in die ganze Sache bringen. Und dazu brauchen wir jedoch ein wenig Mathematik. Wie wir bereits gesehen haben haben wir 3 Werte um einen Punkt in unserer Szene zu beschreiben. Diese sind die x Koordinate, die y Koordinate und die z Koordinate. Währe es nun nicht gut, wenn wir diese Werte zusammenfassen könnten und mit ihnen rechnen? Nun, das geht natürlich und das ganze nennt sich einen _ Vektor_. Wir verwenden einen 3D Vektor, da wir ja 3 Werte verwenden. Wie man damit rechnet will ich hier mal ausklammern, da es bereits auf Wikipedia einen ausgezeichneten Artikel zu diesem Thema gibt.
Das nächste, was wir wissen müssen, ist der Begriff der Matrix. Eine Matrix ist eine rechteckige Anordnung von Zahlen. Wir werden gleich sehen, wozu wir eine Matrix brauchen.(Für die Rechenregeln verweise ich wieder auf Wikipedia).
Nun kommen wir erstmal zu einer wichtigen Frage: Wie können wir usnere Szene nun animieren bzw. in Bewegung versetzen.
Um das zu programmieren, müssen wir erstmal verstehen, wie unsere Vertices aus unseren VertexBuffern auf der Grafikkarte behandelt werden. Und zwar wird jeder Vertex duch eine sogenannte Fixed Function Geometry Pipeline. Dort werden die Vertices(welche als Vektoren vorliegen) mit 3 verschiedenen Matrizen transformiert. Anschließend werden die Vertices geclippt(= Culling) und ihre Koordinaten werden auf den Monitor skaliert d.h. die Koordinaten werden so angepasst, das sie auf bzw. für den Monitor passen.
Für uns sind nun die ersten 3 Schritte dieser Pipeline wichtig. Diese 3 Transformationen sind: World Transformation, View Transformation und Projection Transformation.
Sehen wir uns diese 3 einmal genauer an:
World Transformation:
Normalerweise sind alle Vertices relativ zum Ursprung des Objekts(also in einem lokalen Koordinatensystem). Jedoch müssen wir, um das Objekt auch anzeigen zu können, dieses lokale Koordinatensystem zu einem sogenannten world space transformieren. Der world space ist nichts anderes, als ein Koordinatensystem, in dem alle Koordinaten relativ zu einem gemeinsamen Ursprung sind. Welche genauen mathematischen Vorgänge das nun sind, dies ist für uns eher uninteressant. Für uns ist es jetzt wichtig zu wissen, das wir mit dieser World Transformation unser Dreieck aus dem vorigen Beispiel bewegen können. Und diese World Transformation wird durch eine Matrix dargestellt, die World Matrix. Wir können nämlich auf die World Matrix 3 verschiedenen Operationen anwenden: Translieren, Rotieren und Skalieren.
Translieren bedeutet einfach ein Objekt zu verschieben. Man kann es nach oben,unten und links,rechts verschieben. Rotieren ist wohl klar. Man rotiert das Objekt entweder um die x-Achse oder um die y-Achse. Und beim Skalieren kann man ein Objekt um einen bestimmten Faktor vergrößern oder verkleinern.
Ich habe jetzt von Objekt gesprochen, nun warum rede ich hier von Objekt und nicht von Matrix? Nun, alle diese Operationen werden auf die World Matrix angewendet und diese World Matrix wird ja wiederrum auf alle Vertices eines Objektes angewendet(wir können die World Matrix während der Laufzeit jederzeit verändern). DirectX bietet uns natürlich mehrere Funktionen, damit wir uns nicht mit den genauen Rechnungen herumschlagen müssen. Wichtig ist außerdem, dass wir skalieren, translieren und rotieren nicht einfach in beliebiger Reiehenfolge durchführen dürfen(bzw, können schon, jedoch kommen dann unlogische Verschiebungen zum Vorschein...) Die richtige Reihenfolge lautet: Skalieren und danach zuerst rotieren und zum Schluss translieren. Man kann auch zuerst translieren und dann rotieren(skalieren kommt immer als erstes.). Der unterschied ist einfach der, das ja um den Koordinatenursprung rotiert wird. Wenn man nun zuerst transliert und dann rotiert, dann wird das Objekt zuerst irgendwohin in der Szene gesetzt und dann nochmal um die Rotierung verschoben. Um diesen Effekt zu vermeiden, rotieren wir zuerst und translieren erst später.
_View Transformation _
Mit der View Transformation können wir eine Kamera oder auch Viewport erzeugen. Damit ist einfach gemeint, wohin der Betrachter einer Szene schaut. Eine View Matrix wird aus 3 Vektoren gebildet. Diese sind der eye point Vektor, der look-at Vektor und der world's up Vektor. Der eye point Vektor beschreibt, wo der Augenpunkt(Also der Ausgangspunkt) des Betrachters ist. Der look-at Vektor gibt an, wohin(also zu welchem Punkt) der Betrachter schaut. Und der world's up Vektor beschreibt die Ausrichtig auf der y Achse.(Dieser Vektor ist normalerweise [ 0,1,0] außer man will die Kamera um ihre eigene z Achse drehen).
Um eine View Matrix zu bilden, bietet uns DirectX wie immer die Möglichkeit dies mit mehreren vorgefertigten Funktionen zu tun.
_Projection Transformation _
Die Projection Transformation(beschrieben durch die Projection Matrix) ist dafür verantwortlich, das wir auf dem 2D Monitor eine 3D Szene darstellen und auch sehen können. Und zwar sorgt sie dafür, dass Objekte, die weiter weg sind, kleiner sind und Objekte, die näher sind, größer sind(also wie, als wenn man aus dem Fenster schaut.)

Nun, jetzt wissen wir die Theorie, jedoch noch nicht, wie man das in der Praxis anwendet. In der Praxis ist das aber äußerst einfach: Man berechnet einfach die jeweiligen Matrizen und gibt sie dann dem Device bekannt. Alle Objekte, die wir danach rendern, werden mit den derzeit gesetzten Matrizen transformiert. Jedes Objekt wird immer nur einmal durch die Pipeline geschleust, außer man render es nochmal im selben Frame.

Aber wir müssen noch etwas bedenken. Unsere Initalisierung wird von nun an länger sein. Deshalb werden wir ab jetzt auch eine neue Struktur des Quellcodes haben(der dem aus dem Tutorial sehr ähnlich ist ^^).

Kommen wir nun endgültig zur Praxis. Jedoch vorher müssen wir noch den Typ unserer Vertices ändern. Bisher hatten wir sie vom Typ TransformedColored, dies müssen wir jetzt ändern. Das Transformed bedeutet nämlich, das wir uns nicht um die einzelnen Transformationen kümmern wollen und diese DirectX überlassen wollen(was bei statischen Objekten ja auch durchaus sinnvoll sein kann). Wir benötigen jetzt jedoch Vertices vom Typ PositionColored.

Die einzelnen Matrizen setzen wir über uns device. D.h.
device.Transform.View = View Matrix
device.Transform.World = World Matrix
device.Transform.Projection = Projection Matrix

Jetzt wird es aber ernst: Die Render Funktion. Hier setzen wir zuerst die Matrizen und rendern dann aus unseren Vertex Buffer ein Dreieck. Die Besonderheit: Es dreht sich um die y Achse. Sehen wir uns zuerst die World Matrix an:


int iTime = Environment.TickCount % 1000;
float fAngle = iTime * (2.0f * (float)Math.PI) / 1000.0f;
device.Transform.World = Matrix.RotationY(fAngle);

Zunächst berechnen wir den Winkel, in dem sich das Dreieck rotieren soll. Danach setzen wir die World Matrix einfach über die Funktion Matrix.RotationY, welche uns eine Roation um die Y Achse mit dem angegbenen Winkel(fAngle) berechnet.

Nun die View Matrix:


device.Transform.View = Matrix.LookAtLH( new Vector3( 0.0f, 3.0f,-5.0f ), new Vector3( 0.0f, 0.0f, 0.0f ), new Vector3( 0.0f, 1.0f, 0.0f ) );

Die View Matrix ist nichts besonderes. Zuerst wird der eye Point definiert, dann der look-at Vektor und dann noch world's up.


device.Transform.Projection = Matrix.PerspectiveFovLH( (float)Math.PI / 4, 1.0f, 1.0f, 100.0f );

Hier die die Projection berechnet. Ebenfalls nichts besonderes.

In den nächsten Programmen werden wie die Projections Matrix und die World Matrix eher in den Schatten stellen und uns dafür ausgiebig mit der World Matrix beschäftigen.

Etwas, das wir noch klären müssen, ist warum wir das Culling ausschalten. Wir rotieren ja das Dreieck. Und beim rotieren sind die Vertices ja einmal gegen den Uhrzeigersinn angeordnet und einmal im Uhrzeigersinn. Wenn wir jetzt das Culling einschalten würden, würden wir je nach gesetzen Wert eine Seite des Dreiecks nicht sehen(einfach mal ausprobieren).
Ansonsten gibt es nicht mehr viel zu sagen. Der Code ist halt vom Microsoft DirectX Tutorial übernommen, aber ich hoffe mal, das das kein Problem darstellt.
Der gesamte Sourcecode ist wie immer als Visual Studio .net 2003 Projekt(gepackt als zip) angehängt.

EDIT: wow, is ja schon spät geworden, als ich das letzte mal auf die Uhr schaute war erst halb 11 ^^

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

** Die World Matrix **

Sehen wir uns nun die World Matrix einmal genauer an. Im letzten Beispiel haben wir ja das Dreieck rotiert, was ist aber, wenn wir es still stehen lassen wollen. Dann müssen wir die World Matrix auf die Identitätmatrix setzen(was mit 1 oder 0 verglichen wird. Also eine Matrix + der Identitätsmatrix ergibt wieder die Matrix selbst). Das machen wir so:


device.Transform.World = Matrix.Identity;

Damit steht unser Dreieck jetzt still. Kommen wir jetzt zum Rotieren. Wir können das Objekt um die x Achse, die y Achse und um die z Achse rotieren. Die Parameter beschreiben immer den Winkel, um den rotiert werden soll:


int  iTime  = Environment.TickCount % 1000;
float fAngle = iTime * (2.0f * (float)Math.PI) / 1000.0f;
device.Transform.World = Matrix.RotationX(fAngle);

Hier wird unser Objekt jetzt eine Rotation um die X Achse vollziehen. Es gibt noch die Funktionen Matrix.RotationY(für die Y Achse) und Matrix.RotationZ(für die Z Achse)

Nun zum Translieren oder verschieben. Erstmal ein Beisiel:


device.Transform.World = Matrix.Translation(1.0f,0.0f,0.0f);

Wie unschwer zu erkennen, haben wir hier unser Dreieck auf der x-Achse verschoben(Vektorangaben sind immer x,y,z).
Und zum Schluss das skalieren. Das skalieren bedeutet ja vergrößern oder verkleiner.


device.Transform.World = Matrix.Scaling(1.5f,1.0f,1.0f);

Auch hier können wir auf den verschiedenen Achsen skalieren. Wichtig zu wissen ist, dass der Wert 1 bedeutet, das das Objekt seine ursprüngliche Größe behalten soll. Jeder Wert kleiner 1 bedeutet eine Verkleinerung, jeder Wert größer 1(so wie hier), bedeutet eine Vergrößerung.

In einer echten Anwendung wollen wir jedoch nicht einfach nur mal rotieren und mal translieren, sondern diese Operationen auch kombinieren. Das ist eigentlich ganz einfach: Wir müssen einfach eine Matrix bilden und diese dann als World Matrix setzen. Wie wir bereits wissen, müssen wir zuerst skalieren, dann rotieren und dann translieren. Wichtig ist, das man die einzelnen Ergebnisse(Skalieren,rotieren,translieren) miteinander mulitplizieren muss.(Eine Matrix Multiplikation ist jedoch nicht assoziativ d.h. Marix M * Matrix B ist ungleich Matrix B * Matrix M)


Matrix world;
			
// skalieren
world = Matrix.Scaling(1.0f,1.0f,1.0f);
// rotieren
world *= Matrix.RotationY(1.0f);
// translieren
world *= Matrix.Translation(1.0f,0.0f,0.0f);
			
device.Transform.World = world;

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
C
980 Beiträge seit 2003
vor 19 Jahren

Original von hauptmann
Eine Matrix + der Identitätsmatrix ergibt wieder die Matrix selbst

Kleine Korrektur am Rande: Mathematisch korrekt wäre:

  • Eine Matrix * die Identitätsmatrix ergibt wieder die Matrix selbst
  • Eine Matrix + die Nullmatrix ergibt wieder die Matrix selbst

(aus dem kontext geht schon heraus was du sagen wolltest, aber das + Zeichen impliziert Addition was du offensichtlich nicht wolltest...)

M
8 Beiträge seit 2004
vor 19 Jahren

Hi,

echt super das Tutorial 👍, habe lange nach etwas in dieser Art gesucht.
Leider habe ich ein kleines Problem beim nachvollziehen, vielleicht kann mir jemand helfen.

Beim Erstellen des Device fall ich ins catch

m_Device = new Device(Manager.Adapters.Default.Adapter,DeviceType.Hardware,this,CreateFlags.HardwareVertexProcessing,pp);

Microsoft.DirectX.Direct3D.InvalidCallException

ErrorString: D3DERR_INVALIDCALL
ErrorCode: -2005530516

StackTrace
" at Microsoft.DirectX.Direct3D.Device..ctor(Int32 adapter, DeviceType deviceType, Control renderWindow, CreateFlags behaviorFlags, PresentParameters[] presentationParameters)

Hab mal rumgegoogelt, aber diesen Fehler nur im Zusammenhang mit Shadern und zu alten Grafikkarten gefunden, sollte hoffentlich bei mir kein Problem sein. Die neueste Shaderversion unterstützt meine Graka zwar nicht, aber um n blaues Fenster anzuzeigen sollte es wohl noch reichen 🙂

Ich arbeite mit Visual Studio 02 und hab ne Mobility Readeon 7500

TIA

fastest code is no code

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

spiel mal ein wenig mit den Parametern rum. Also z.B. das Vertex Processing mal auf Software stellen oder.

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
S
23 Beiträge seit 2004
vor 19 Jahren

hab da mal ne frage zum code am anfang:

ich habe es gelernt, dass man das programm mit Application.Run() ausführt?

worin liegt der unterschied, da es in deisem code nicht vorkommt?


Mess with the best die like the rest!


M
456 Beiträge seit 2004
vor 19 Jahren

Mit Application.Run musst du nicht zwangsläufig deine Form Anwendungen starten.
Sie macht ja nichts weiter, als in die Windows Message Loop einzutreten. Intern, glaube ich, dass Appliaction.Run nichts weiter macht als

while(form.Created)
   Application.DoEvents();

Hauptmann hat die Message Loop explizit angegeben, da er mit example.Render(); für jeden Frame das Fenster neu zeichnen will.
Also normale Windowsanwendungen warten auf Nachrichten die sie von Windows bekommen und verarbeiten sie dann. Bei Spielen macht man das oft etwas anders. Dort muss zusätzlich, zu den Nachrichten der Bildschirm ständig neu berechnet werden, deswegen auch die while() Schleife mit dem Aufruf der Render Funktion.

I am Jack's smirking revenge.
I am Jack's raging bile duct.
I am Jack's cold sweat.
I am Jack's complete lack of surprise.
I am Jack's broken heart.
I am Jack's wasted life.

S
23 Beiträge seit 2004
vor 19 Jahren

habe mal verscht aus dem dreieck ein viereck zu machen, indem ich einen weiteren punkt eingefügt habe...
ich habe auch die anzhal der punkte verändert, aber er zeichnet kein viereck..
WARUM?

und wie würde ein Kreis aussehen, oder besser gesagt eine Kugel in 3D?

Wäre lieb wenn mir einer helfen könnte

danke


Mess with the best die like the rest!


H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

Original von SexyEnemy
habe mal verscht aus dem dreieck ein viereck zu machen, indem ich einen weiteren punkt eingefügt habe...
ich habe auch die anzhal der punkte verändert, aber er zeichnet kein viereck..
WARUM?

gib mal den Code vom Vertex Buffer erstellen und vom Rendern. Dann ist es leichter einen Fehler auszumachen 😉

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
I
48 Beiträge seit 2004
vor 19 Jahren

*gespannt auf die fortsetzung des tutorial sein* 😉

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

** 3D Objekte **

Bisher waren unsere Programme eher langweilig. Klar gibt es heute noch 2D Spiele und und 2D war mal das Maß aller Dinge, aber die richtige Action gibt's heute mit 3D. Und deshalb werden wir uns jetzt mal ansehen, wie man einfache 3D Objekte mit Direct3D erstellt und diese anzeigt.
Zunächst jedoch ein wenig Theorie, um überhaupt zu verstehen, wie 3D Objekte gespeichert werden: Wie unschwer zu erkennen ist der Monitor vor euch eine Fläche. Dh das er 2 Koordinaten hat. Die x und die y Koordinate(wie man es beim karthesischen Koordinatensystem in der Schule lernt ^^). Durch ein Koordinatenpaar x|y können wir nun jeden beliebigen Punkt auf diesem Monitor angeben. Aber wie kann man dann 3D Objekte, wie man sie in jedem Spiel hat, abbilden? Wie ist es möglich, dass man eine 3D Szene auf ein 2D Koordinatensystem abbildet? Die Antwort ist simpel: Man führt einfach noch eine Koordinate ein(die z Koordinate), die einfach mit den beiden vorhanden Koordinaten verknüpft wird. Nun, das klingt ein wenig schwer verständlich, jedoch ist es im Endeffekt für uns Programmierer sehr einfach: Wir geben einfach bei Koordinaten eine z Koordinate zusätzlich an, die Tiefeninformationen speichert. Eine positive z Koordinate zeigt dabei an, dass wir "in den Bildschirm hinein" wollen, eine negative "aus dem Bildschirm heraus". Mathematisch ausgedrückt müssen wir nun also eine Formel finden, die uns nun aus einem Koordinaten Tripel x|y|z ein Koordinatenpaar x|y macht. Dazu können wir nun auf etwas, das wir jeden Tag erleben: Wenn wir uns von einem Haus entfernen, dann erscheint es mit zunehmender Entfernung kleiner. Nähern wir uns dem Haus, wird es größer. Dazu dividieren wir x und y einfach durch z. Die Koordinaten, die wir dadurch erhalten nennt man projezierte Koordinaten. Die Formel lautet nun:
x' (x projeziert) = x / z
y' (y projeziert) = y / z

Wir müssen uns diese Formel nicht merken und uns um diese Umrechnung auch keine Sorgen machen: Direct3D bzw. die Grafikhardware macht dies automatisch.(dazu stecken wir ja mehrere hundert € in Grafikkarten(zumindest ich ^^))

Jetzt können wir uns wieder Direct3D zuwenden. Normalerweise würde hier nun ein Beispiel kommen, in dem man einen Vertex Buffer und einen Würfel oder eine Pyramide erzeugt, jedoch will ich jetzt mal nicht diesen Weg gehen. Und zwar nutzen wir einfach eine vorgefertige Funktion, die uns bereits eine "Box" aus 3 Parametern berechnet und die wir dann leicht anzeigen können.
Zunächst deklarieren wir eine neue Variable box vom Typ Mesh. Mesh ist eine Klasse, in der man genau solche Vertex Informationen leicht speichern und anzeigen kann(bisher haben wir uns ja um die Erstellung eines Vertex Buffers gekümmert und um dessen anzeigen ...).


Mesh box;

Nun verwenden wir die statische Funktion Box der Klasse Mesh, die uns aus 3 Parametern eine Box berechnet und uns eine Variable vom Typ Mesh zurückgibt.


public void OnCreateDevice(object sender, EventArgs e)
{
			Device dev = (Device)sender;

			box = Mesh.Box(dev,2.0f,1.0f,2.0f);
}

Die Parameter sind simpel: Der Erste ist einfach das device. Der zweite beschreibt die Ausdehnung auf der x Achse. Der dritte auf der y Achse und letzte schließlich, trommelwirbel, auf der z Achse.
Diese Funktion berechnet uns nun aus diesen Parametern eine Box die wir in der der Variable box speichern. Einfach, nicht wahr?

Nun kommen wir zum anzeigen. Das geht ebenfalls entsprechend einfach. Wir springen in unsere Render Funktion und rufen die Funktion DrawSubset auf. Fertig.


box.DrawSubset(0);

Um den Parameter brauchen wir uns erstmal nicht kümmern.(erst später, wenn wir dann zB Vertex Daten von Dateien laden o.ä.)

Die Mesh Klasse bietet uns jedoch nicht nur eine Funktion, für die Erstellung einer Box, sondern wir können mir ihr auch noch Teekannen, Zylinder, Kugeln und Ringe erzeugen. Und natürlich auch beliebige 2D Polygone(ein Polygon ist einfach der Sammelbegriff für Dreiecke, Vierecke, Sechsecke usw.). Einfach mal ein wenig in der Klasse rumstöbern ^^

Einen Schönheitsfehler hat das ganze jedoch. Die Box erscheint weiß. Logisch, wir haben ja auch keine Farbe angegeben. Wie können wir der Box jetzt jedoch eine Farbe zuweisen? Nun, dazu müssen wir einfach die Vertex Daten ändern, doch dazu im nächsten Kapitel mehr.

Wie immer gibt es nun auch den Source als VS.net 2003 Projektfiles als Zip File.

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

** Der Box eine Farbe zuweisen **

Nun, wir haben jetzt zwar eine schöne Box, jedoch ist sie noch komplett weiß. Das bringt uns natürlich nicht viel,
da wir ja mal aufregende Spiele schreiben wollen. Wie bringen wir jetzt jedoch Direct3D dazu, die Box mit einer
Farbe zu rendern? Das ist eigentlich ganz einfach: Wir verändern einfach die Vertices der Box direkt. Und dazu
kommen wir wieder auf unsere Vertex Buffer zu sprechen, aber wir brauchen diesmal keinen selber erstellen, sondern
wir holen uns die Vertex Daten einfach aus der Mesh Klasse(die diese ja speichert).
Und das ist mit ein paar Handgriffen gemacht. Zuerst müssen wir unseren Mesh "klonen" und bei diesem Vorgang das
Vertex Format ändern. Dies ändern wir so ab, dass wir die Box ganz normal transformieren können, jedoch jedem Vertex noch
eine Farbe zuweisen können. Nachdem das getan ist, holen wir uns die Vertex Daten des Mesh und schreiben einfach unsere
gewünschte Farbe hinein und geben die Vertex Daten der Mesh Klasse wieder bekannt. Und hierbei verändern wir nur die
Farbkomponente, nicht die Koordinaten der Vertices, da diese ja bereits vorberechnet wurden.
Sehen wir uns nuneinmal an, wie wir unseren Mesh klonen und ihm ein neues Vertex Format zuweisen:


VertexFormats format = VertexFormats.PositionNormal | VertexFormats.Diffuse;
Mesh tempBox = box.Clone( box.Options.Value, format, dev );
box.Dispose();
box = tempBox;

Zuerst definieren wir eine Variable vom Typ VertexFormat das unser VertexFormat enthält. Das VertexFormat für uns ist
PositionNormal(das standardmäßig verwendet wird) und dazu fügen wir Diffuse hinzu. Diffuse beschreibt die diffuse Farbe
eines Vertex. Die diffuse Farbe können wir uns derzeit einfach als jene Farbe merken, die man direkt sieht. Wir werden
später, wenn wir unsere Szene beleuchten sehen, dass es noch andere Arten von Farben gibt bzw. Lichtfarben.
Danach verwenden wir die Funktion Clone um einen vorläufigen Mesh zu erstellen. Danach löschen wir den eigentlichen Mesh
wieder und weisem unserem Mesh den Vorläufigen zu.
Nun müssen wir an die Vertex Daten kommen. Das geht ebenfalls äußerst einfach: Wie im Kapitel über Vertex Buffer locken
wir ihn einfach und kommen so an seine Daten(Lock sperrt die Daten eines Vertex Buffers für weitere Zugriffe und gibt
uns eine Referenz aus diese zurück. Die Änderungen werden übernommen und der Vertex Buffer entsperrt, wenn wir Unlock
aufrufen)
Gespeichert werden sie natürlich in einem Array vom Typ PositionNormalColored


CustomVertex.PositionNormalColored[] verts = (CustomVertex.PositionNormalColored[])box.VertexBuffer.Lock( 0, 
															typeof( CustomVertex.PositionNormalColored ), 
															LockFlags.None, 
															box.NumberVertices );

Da uns Lock nur ein System.Array zurückgibt, müssen wir dies erst explizit auf CustomVertex.PositionNormalColored casten.
Der Erste Parameter bestimmt die Menge an Daten, die wir sperren wollen. Wenn wir 0 angeben, wird der gesamte Vertex
Buffer gesperrt. Beim zweiten Parameter müssen wir bestimmen, welchen Typ die zurückgegebenen Daten haben sollen.
Der Dritte bestimmt die Lock Flags, die wir hier jedoch vernachlässigen können. Der letzte Parameter bestimmt, wie viele
Vertices zurückgegeben werden sollen. Da wir ja jeden Vertex des Mesh verändern wollen, müssen wir auch alle angeben.
Der nächste Schritt ist, die Vertex Daten zu verändern. Dazu gehen wir jeden Vertex durch und ändern seine Farbe:


for(int i=0;i < verts.Length;++i)
{
	verts[i].Color = Color.Red.ToArgb();
}

Die Variable Color ist uns bereits im Kapitel über Vertex Buffer begegnet, sie bestimmt welche Farbe der Vertex haben soll.
Als Letztes müssen wir den Vertex Buffer wieder entsperren, um die Änderungen zu übernehmen.


box.VertexBuffer.Unlock();

Nun rotieren wir unseren Mesh noch:


Matrix world;
world = Matrix.Scaling(1.0f,2.0f,1.0f);
			
int iTime = Environment.TickCount % 1000;
float fAngle = iTime * (2.0f * (float)Math.PI) / 1000.0f;
			
world *= Matrix.RotationY(fAngle);
world *= Matrix.Translation(0.0f,0.0f,0.0f);

device.Transform.World = world;
box.DrawSubset(0);

Und das wars schon. Direct3D ist einfach, wenn man weis wie's geht ^^

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

** Mehr als 2 Objekte **

Bisher hatten wir immer nur 1 Objekt auf unserem Bildschirm. Das wird sich nun ändern, da wir nun zwei Objekte rendern
werden. Wie immer baut dieses Tutorial auf den anderen auf, sowohl im Wissen, als auch im Code. Doch nun zum
vergnüglichen Teil ^^
Zuerst brauchen wir natürlich zwei Objekte, dazu nehmen wir einfach die rote Box aus dem vorgen Teil und eine Teekanne.
Lassen wir die Teekanne einfach mal grün sein.
Dazu machen wir eigentlich dasselbe, was wir mit der Box getan haben: Eine Variable vom Typ Mesh erzeugen, in ihr eine
Teekanne speichern und danach die Farbe verändern. Zuerst definieren wir die Membervariable sphere:


Mesh teapot;

Danach weisen lassen wir uns wieder durch eine statische Funktion eine Kugel vorberechnen und speichern diese in der
Variable teapot:


teapot = Mesh.Teapot(dev);

Die Mesh Klasse ist wirklich toll ^^
Nun machen wir dasselbe, was wir für die Box getan haben. Wir verändern die Farbe der Teekanne. Da wir den Code gleich
nach dem erstellen der Box einfügen, müssen wir nicht wieder extra Variablen einfügen und können so das Vertex Format von
oben verwenden:


tempBox = teapot.Clone( teapot.Options.Value, format, dev );
			teapot.Dispose();
			teapot = tempBox;

			verts = (CustomVertex.PositionNormalColored[])teapot.VertexBuffer.Lock( 0, 
				typeof( CustomVertex.PositionNormalColored ), 
				LockFlags.None, 
				teapot.NumberVertices );
		

			for(int i=0;i < verts.Length;++i)
			{
				verts[i].Color = Color.Green.ToArgb();
			}
			teapot.VertexBuffer.Unlock();

Jetzt kommen wir zum interessanten Teil. Und zwar zum Rendern. Zuerst begnügen wir uns mal, einfach beide Objekte zu
rendern, ohne sie zu rotieren o.ä.
Dazu müssen wir für jedes Objet zuerst eine World Matrix setzen und es dann damit zeichnen.
Anders ausgedrückt:
Begin Scene
Projection Matrix setzen
View Matrix setzen

World Matrix für Objekt 1 setzen
Objekt 1 rendern

World Matrix(und evtl. auch Projection und View Matrix) für Objekt 2 setzen
Objekt 2 rendern
World Matrix für Objekt 1 setzen
Objekt 1 rendern

World Matrix(und evtl. auch Projection und View Matrix) für Objekt n setzen
Objekt n rendern
End Scene

Das ist natürlich stark vereinfacht ausgedrückt, da in echten Spielen natürlich noch haufenweise andere Sachen dazukommen(
wie z.B. Kollisionsabfrage, Benutzereingabe etc.). Wir werden später noch sehen, wie man eine einfache Benutzereingabe
verwirklichen kann.
Sehen wir uns nun einmal an, wie wir das rendern verwirklichen. Zuerst setzen wir die Projection und View Matrix, für unser
Beispiel reicht eine Projection und eine View Matrix.


device.Transform.View = Matrix.LookAtLH( new Vector3( 0.0f, 3.0f,-5.0f ), new Vector3( 0.0f, 0.0f, 0.0f ), 
											new Vector3( 0.0f, 1.0f, 0.0f ) );
device.Transform.Projection = Matrix.PerspectiveFovLH( (float)Math.PI / 4, 1.0f, 1.0f, 100.0f );

Danach setzen wir die World Matrix für das erste Objekt und zeichnen es:


Matrix world;
world = Matrix.Scaling(0.5f,0.5f,1.5f);
			
world *= Matrix.Translation(-1.0f,0.0f,0.0f);
device.Transform.World = world;

box.DrawSubset(0);

Zuerst verkleinern wir das Objekt und strecken es auf der z Achse "nach hinten". Danach verschieben wir das Objekt um
eine Einheit nach links(auf der x Achse, negative Koordinaten sind hierbei nach links gerichtet). Schließlich rendern wir
es.
Nun zum zweiten Objekt. Wir setzen unsere Variable world zuerst auf die Identitätsmatrix:


world = Matrix.Identity;

Nun verkleinern wir das Objekt ein wenig und rotieren seitlich. Danach verschieben wir es um eine Einheit nach rechts.


world = Matrix.Scaling(0.5f,0.5f,1.0f);
world *= Matrix.Translation(1.0f,0.0f,0.0f);
world *= Matrix.RotationX(380);

Nun setzen wir die neue World Matrix und rendern die Teekanne:


device.Transform.World = world;
teapot.DrawSubset(0);

Und das wars schon. Was ganz wichtig ist, man darf vor dem rendern nicht vergessen, eine neue World Matrix zu setzen, denn
ansonsten wir weiterhin die Alte verwendet. Das könnt ihr ja ausprobieren. Kommentiert einfach mal den device.Transform.World =
world; vor dem teapit.DrawSubset(0); aus. Ihr werdet sehen, das die Teekanne dann auf der Box liegt. Kein schöner
Anblick(ich weis, die derzeitige Szene ist auch nicht gerade "schön", aber das muss man einfach können. Wenn wir dann
Spiele programmieren wollen, müssen wir uns auf diese Grundlagen zurückerinnern ^^)

Im nächsten Teil sehen wir uns dann eine einfache Benutzereingabe an. Wie immer gibt es den Source zum Download und seit
dem 5. Tutorial lege der zip Datei noch das Tutorial als txt File bei.

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
F
66 Beiträge seit 2004
vor 19 Jahren

Mehr, ich will mehr 😮)

muss mir das meiste zwar noch durchlesen aber ich will mehr, das was ich las war jedenfalls erste sahne.

Wieso machste aus demtutorial nicht ne kleine hp oder so ?

kommt sicher klasse an und findet auch sicherlich mehr beachtung

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

Original von Fab96
Wieso machste aus demtutorial nicht ne kleine hp oder so ?

kommt sicher klasse an und findet auch sicherlich mehr beachtung

hmm ja, währe eine Idee. Mal sehen was sich da machen lässt... Aber schonmal danke für die Idee

Der nächste Teil kommt bestimmt, ist nur so das ich über die Weihnachtsferien halt auch lernen muss(BWL Test, Mathe Schularbeit und Rechnungswesen Test). Und tja, das sind halt alles so Gegenstände die ich nicht so mag. Und Rechnungswesen hab ich heute einen 5er auf den 2. Test, d.h. den 3. muss ich jetzt auf einen 1er schreiben ^^

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
X
133 Beiträge seit 2004
vor 19 Jahren

Ich finde man sollte Hauptmann auch mal ganz herzlich für seine Mühe danken.
Also ich finde deine Mühe hat sich bis jetzt auf alle fälle gelohnt.

Danke Hauptmann

Um was wirklich Neues zu erschaffen muss man das Rad neu erfinden

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

also ich werde jetzt alle Tutorials als pdf und als Threadpost veröffentlichen. Ich denke mal das sollte reichen ^^

Oder denkt ihr, dass eher eine Website her sollte?

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
1.549 Beiträge seit 2004
vor 19 Jahren

Meiner meinung nach wäre eine Seit vieleicht angebracht wenn das tut vertig ist dan knn man es besser finden

Wir Arbeiten eigendlich nicht wir nehmen nur das geld

C
7 Beiträge seit 2004
vor 19 Jahren

Klasse Tutorial! 👍
Ich hoffe deine nächsten Werke behandeln etwas wie man die Maus gescheit über DInput abfragt (klappt bei mir zwar schon ganz gut, aber hakt manchmal etwas 😕). Wäre super, aber hast ja schon erwähnt, dass Benutzereingabe vorkommen wird. ^^

K
355 Beiträge seit 2004
vor 19 Jahren

Super Tutorial Hauptmann echt supereinfach zum nachmachen

@cynos:
Im Prinzip genügt dazu eh eine Suche über google, da DirectInput zumindest unter C++ einige Male auftaucht. Und den Code von C++ nach C# zu "übersetzen" ist ja nicht wirklich schwer... 🙂

M
8 Beiträge seit 2004
vor 19 Jahren

Original von hauptmann
spiel mal ein wenig mit den Parametern rum. Also z.B. das Vertex Processing mal auf Software stellen oder.

Danke für Deine Hilfe Hauptmann, die Parameter hatte ich schon bei allen, bei welchen dies möglich war auf software gestellt, hatte damit aber leider keinen Erfolg. Nun habe ich aber vor kurzen Visual Studio 2003 installiert und siehe da, es läuft. Sonst hat sich meines Wissens nichts relevantes geändert kein neuer GraKa Treiber, keine Neuinstallation von DirectX überhaupt nichts, aber was solls Hauptsache es funktioniert. Nochmals danke, bin sehr gespannt auf die weiteren Teile deines Tutorials - immer weiter so.

mcs

fastest code is no code

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

** Input -> Output **

Nun, in unseren Anwendungen war jetzt zwar bereits etwas Bewegung. In Spielen kommt jedoch noch ein weiterer Faktor
hinzu: Der Userinput. Ohne diesen gäbe es eigentlich keine Spiele. Deshalb sehen wir uns einmal eine vereinfachte Form
an, die wir in unseren Anwendungen nutzen können. Unser Ziel ist einfach: Wir wollen anhand der Leertaste steuern
ob sich unser Objekt dreht oder nicht.
Dazu überschreiben wir einfach die Funktion ProcessCmdKeys der Basisklasse Form. Diese liefert uns im einem Parameter
die gerade gedrückte Taste.


protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
	return base.ProcessCmdKey( ref msg, keyData );
}

Nun können wir unseren bisherigen Eventhandler für Input löschen und fügen hier folgendes ein:


if(keyData == Keys.Escape)
		Application.Exit();

Nun kommen wir zum eigentlichen Teil: Zunächst reduzieren wir den Rendercode auf eine Box(und löschen nebenbei natürlich
überflüssigen Code für das zweite Objekt aus dem letzten Tutorial)


		private void Render()
		{
			if (device == null) 
				return;


			//Clear the backbuffer to a blue color 
			device.Clear(ClearFlags.Target, System.Drawing.Color.Blue, 1.0f, 0);
			//Begin the scene
			device.BeginScene();

			device.Transform.View = Matrix.LookAtLH( new Vector3( 0.0f, 3.0f,-5.0f ), new Vector3( 0.0f, 0.0f, 0.0f ), new Vector3( 0.0f, 1.0f, 0.0f ) );
			device.Transform.Projection = Matrix.PerspectiveFovLH( (float)Math.PI / 4, 1.0f, 1.0f, 100.0f );

			Matrix world;
			world = Matrix.Scaling(0.5f,0.5f,1.5f);
	
			device.Transform.World = world;

			box.DrawSubset(0);

			device.EndScene();
			device.Present();
		}

Nun müssen wir eine Variable vom Typ bool in unsere Klasse aufnehmen, die anzeigt ob das Objekt derzeit rotieren soll
oder nicht:


bool rotate = false;

Und schon kommen wir wieder zur ProcessCmdKey Funktion:


if(keyData == Keys.Space)
		rotate = !rotate;

Wir fragen ab ob die Leertaste(Space) gedrückt wurde, wenn ja dann wird roate invertiert(aus false wird true gemacht,
aus true false).

Nun müssen wir jedoch noch in die Render Funktion etwas ändern.
Vor dem device.Transform.World = world müssen wir noch abfragen ob rotate auf true ist, wenn ja dann müssen wir unser
Objekt drehen lassen.


	Matrix world;
	world = Matrix.Scaling(0.5f,0.5f,1.5f);

	if(rotate)
		world *= Matrix.RotationX(Environment.TickCount * (float)0.0025);
			
	device.Transform.World = world;

	box.DrawSubset(0);

Und damit ist es schon fertig. So einfach kann Benutzereingabe sein ^^
Wie immer ist das fertige Projekt im Visual Studio .net 2003 Format dabei. Außerdem ist in der Zip Datei das Tutorial als Txt enthalten ...

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
S
42 Beiträge seit 2005
vor 19 Jahren

geil das tut! Und dankeschön an Hauptmann!

Nur hat die Sache einen hacken: Ich hab mir das ganze Programm runtergeladen und in #develop (1.0.3a) aufgeführt. Es funktioniert nicht!

Hier ist die Fehlermeldung:
Die Anwendung hat einen Ausnahmefehler verursacht, der nicht verarbeitet werden konnte.

Prozess-ID=0xf28 (3880), Thread-ID=0xcfc (3324)

Klicken Sie auf 'OK', um die Anwendung abzubrechen,
oder auf 'Abbrechen', um sie zu debuggen.

Ich hab das Programm wie gesagt runtergeladen, nicht selbst abgeschrieben, und ausgeführt.

Bitte helft mir!

thx

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

** Texture Mapping **

Alle unsere Objekte hatten bisher immer nur eine einzige Farbe und die Oberfläche der Objekte war glatt und durchgehend.
Jedoch hat eine solche Oberfläche so gut wie kein Objekt der realen Welt. Wenn man sich umschaut sind solche regelmäßigen
Anordnungen sehr selten und Oberflächen sind auch nicht sehr oft so glatt und "perfekt" wie wir sie bisher hatten.
Deshalb greifen wir auf das Texture Mapping zurück: Wir weisen unserem Objekt nicht einfach eine Farbe zu, sondern eine
Textur. Was ist jetzt eine Textur? Eine Textur ist ganze einfach ein Bild das wir über das Objekt legen. Dadurch können
wir jetzt zB einen Ziegelstein nicht einfach nur rot färben, sondern ihn auch ein realistisches Aussehen verpassen.
Sehen wir uns nun einmal an wie wir unsere Box texturieren können. Zunächst brauchen wir eine neue Variable vom Typ
Texture. In ihr speichern wir unsere Textur:


Texture textur;

Nun müssen wir unsere Textur laden. Das geht ziemlich einfach, da uns Direct3D die Klasse TextureLoader zur Verfügung
stellt, in der bereits vorgefertigte Funktionen zum Laden von Texturen enthalten sind:


public void OnCreateDevice(object sender, EventArgs e)
{
	Device dev = (Device)sender;

	textur = TextureLoader.FromFile(dev,"iron04.jpg");

Wir verwenden die Funktion FromFile, die als ersten Parameter das verwende Device verlangt und als zweiten das Bild,
das wir in der Textur speichern wollen. Die FromFile Funktion ist ein wahres Multitalent, denn laut SDK unterstützt
es die folgenden Bildformate: .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, und .tga
In echten Spielen sollte man wenn möglich nicht jpg verwenden, so wie ich das hier tue, jedoch ist eine jpg schön klein
und als Beispiel sollte es reichen. Der Nachteil ist halt das ein jpg als Textur nicht wirklich toll aussieht.
Der Rückgabewert der Funktion ist ein Texture Objekt das die geladene Textur enthält.
Als nächstes müssen wir unsere Box anpassen. Bisher haben wir ja die Vertices der Box mit einer Farbkomponente erweitert,
jetzt müssen wir sie mit Texturkoordinaten erweitern.
Texturkoordinaten? Was sind Texturkoordinaten?
Wenn wir eine Textur auf ein Dreieck legen, woher soll Direct3D jetzt wissen wie es die Textur auf das Dreieck legen soll?
Deshalb gibt es die Texturkoordinaten, mit ihnen können wir bestimmen wie eine Textur auf ein Dreieck gelegt werden soll.
Als Anlehnung an Vertex werden diese Koordinaten auch Texel genannt. Ein Texel ist immer zwei dimensional(Bilder sind ja
auch nur zwei dimensional) und hat also 2 Achsen: Die u-Achse und die v-Achse. Die u-Achse ist eigentlich die x-Achse,
während die v-Achse die y-Achse ist. Der Koordinatenursprung einer Textur liegt jedoch in der linken oberen Ecke.
Eine weitere Besonderheit von Texel sind das sie in einem Intervall von [0,1] liegen. Dh das es bei allen Texturen nur
Texel zwischen 0 und 1 gibt unabhängig von der eigentlichen Größe der Textur. Der Texel 0,5 liegt daher immer in der Mitte
einer Achse.
Wir müssen nun also jedem Vertex einen Texel zuweisen, der anzeigt welcher Teil einer Textur auf diesen Vertex gelegt
werden soll.
Ein Beispiel: Nehmen wir an wir haben ein Rechteck mit 4 Vertices und eine Textur. Die Textur ist ebenfalls rechteckig
und wir wollen nun die Textur auf dieses Rechteck legen. Dazu müssen wir jetzt jedem Vertex des Rechteck einen Texel
der Textur zuweisen.
Sehen wir uns es jetzt den 1. Vertex an. Nehmen wir mal an das dieser in der linken oberen Ecke liegt. Wir können diesem
Vertex jetzt den Texel (0.0/0.0) zuweisen. Oder (1.0/1.0). Uns sind hierbei keine Grenzen gesetzt. Wir wollen aber die
Textur normal auf das Rechteck legen, also so wie ein Bildprogramm uns die Textur anzeigt, so wollen wir diese auf dem
Rechteck haben. Also weisem wir dem 1. Vertex den Texel (0/0) zu. Der 2. Vertex liegt nun in der rechten oberen Ecke.
Also weisen wir diesem Vertex den Texel (1/0) zu(wir müssen auf der u-Achse ganz nach rechts). Der 3. Vertex ist nun
die linke untere Ecke. Also brauchen wir hier den Texel (0/1). Und für den letzten Vertex brauchen wir schließlich den
Texel (1/1).
Ich hoffe mit diesem Beispiel versteht man die Texturkoordinaten recht gut. Am Besten ist es hier eine Zeichnung zu machen.
Kommen wir nun zum praktischen:


box = Mesh.Box(dev,2.0f,1.0f,2.0f);
			VertexFormats format = VertexFormats.PositionNormal | VertexFormats.Texture1;
			Mesh tempBox = box.Clone( box.Options.Value, format, dev );
			box.Dispose();
			box = tempBox;

			CustomVertex.PositionNormalTextured[] verts = (CustomVertex.PositionNormalTextured[])box.VertexBuffer.Lock( 0, 
				typeof( CustomVertex.PositionNormalTextured ), 
				LockFlags.None, 
				box.NumberVertices );

Wir wir sehen ist der Code nicht allzu schwer. Das VertexFormat der Box müssen wir auf PositionNormal und Texture1 setzen.
Texture1 beschreibt, das wir pro Vertex 1 Texel haben.
Danach müssen wir noch die neuen Vertices die wir schreiben wollen erstellen. Hierbei müssen wir PositionNormalTextured
verwenden, anstatt PositionNormalColored.
Nun kommen wir zum zuweisen der Texel, was nicht sonderlich schwer ist:


for(int i=0;i < verts.Length;++i)
	{
			verts[i].Tu = verts[i].X * 0.8f;
			verts[i].Tv = verts[i].Y * 0.8f;
	}
box.VertexBuffer.Unlock();
}

Sehen wir uns jetzt das Rendern an. Bevor unsere Box normal rendern können, müssen wir noch die Textur setzen. Dh wir
müssen Direct3D sagen das es jetzt für die nächsten Operationen wo es Texturen braucht die Textur nehmen soll, die wir
gesetz haben.


device.SetTexture(0,textur);

Was hat die 0 hier zu bedeuten? Wie wir später sehen werden gibt es verschiedene Texture Stages. Die untereste dieser
Schichten ist die Stufe 0. Mit diesen Texturstages werden wir später zwei oder mehr Texturen miteinander verbinden können
und auf unser Objekt legen. Dieses Verfahren nennt man dann Multi-Texturing.
Nun können wir unser Objekt mit der Textur rendern:


box.DrawSubset(0);

Und das wars schon. Mit Texturen kann man natürlich noch viel mehr anfangen, doch dazu mehr in späteren Teilen 😉
Anbei ist natürlich wieder das Visual Studio .net 2003 Projekt sowie die Datei iron04.jpg

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
K
355 Beiträge seit 2004
vor 19 Jahren

Hi Hauptmann

Hab grad noch ne Frage zum Texturing. Wieso ist die Textur da so unscharf? Im jpg ist sie doch scharf, vergrößert Direct3D die so?

Hab Anisotropisches Filtering aktiviert, damit sieht es ein wenig besser aus. Nur wie bekommt man die Textur schärfer?

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

Original von KRambo
Hi Hauptmann

Hab grad noch ne Frage zum Texturing. Wieso ist die Textur da so unscharf? Im jpg ist sie doch scharf, vergrößert Direct3D die so?

Hab Anisotropisches Filtering aktiviert, damit sieht es ein wenig besser aus. Nur wie bekommt man die Textur schärfer?

das hängt vorallem am jpeg 😉

Sieh dir mal das Beispiel im DirectX SDK zum Thema Texturen an, da wird auch eine jpeg geladen und die ist auch sehr unscharf.

Ich werde aber bald zeigen wie man die Texturqualität erhöhen kann ^^

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
K
355 Beiträge seit 2004
vor 19 Jahren

Naja ich sollte in ein paar Tagen ne lauffähige Version haben, wo die Tex nich so unscharf iss. Mit Filtering wirkt sie nicht so pixelig, aber sonst nützt das auch nix bei der Schärfe...hoffe bis dahin hast du wieder was. Find ich auf jeden Fall super und leicht verständlich das Ganze 🙂

S
42 Beiträge seit 2005
vor 19 Jahren

Kann mir da denn jetzt jemand helfen?
Ich würde auch sehr gerne mit DirectX programmieren!
Und das Geld für Visual Studio hab ich nich.
Hier nochmal das Problem:
Ich hab mir die .zip Datei heruntergeladen und in #develop laufen lassen (natürlich nach dem importieren)
Aber es tut nicht!

Weis da jemand weiter?

H
hauptmann Themenstarter:in
704 Beiträge seit 2003
vor 19 Jahren

hmm, kann viele Ursachen haben. Fehlermeldungen?

[last.fm](http://www.last.fm/user/hauptmanAlpha/)
S
42 Beiträge seit 2005
vor 19 Jahren

Hier ist die Fehlermeldung:

Die Anwendung hat einen Ausnahmefehler verursacht, der nicht verarbeitet werden konnte.

Prozess-ID=0xf28 (3880), Thread-ID=0xcfc (3324)

Klicken Sie auf 'OK', um die Anwendung abzubrechen,
oder auf 'Abbrechen', um sie zu debuggen.

Wär nett wenn ihr mir helfen könntet.
Danke schonmal im voraus.

F
66 Beiträge seit 2004
vor 19 Jahren

Wenn du debuggen lässt kommt dann eine FileNotFound Exception ?

wenn ja ist als references sicherlich irgendwas drin was nichtintalliert ist. jedenfalls war das mal bei mir so.

K
355 Beiträge seit 2004
vor 19 Jahren

Die Meldung hatte ich auch mal. War weil das Managed DirectX nicht "richtig" installiert war auf dem Rechner, danach gings...

S
42 Beiträge seit 2005
vor 19 Jahren

was meinst du mit "nicht richtig installiert"?

was hast du denn gemacht damit es wieder weg ging?

K
355 Beiträge seit 2004
vor 19 Jahren

Im Ordner wo das Setup liegt, gibts nen eigenen Ordner, wo eine extra Setup-Datei für Managed liegt. Die hab ich installiert, dann gings auch. Die andere installiert glaub ich nur das "normale" DirectX...

Musste nur ein bischen Suchen da in den Ordnern die Bezeichnungen sind eh selbsterklärend. Weiss den Ordnernamen nicht auswendig...

F
66 Beiträge seit 2004
vor 19 Jahren

Die MDXREDIS.msi ? sprich manadged DirectX redistutable oderwie man das schreibt.

S
42 Beiträge seit 2005
vor 19 Jahren

hm... meinst du mit dem Ordner in dem das Setup ist den ornder C:\Programme\directx oder so ähnlich?
oder bin ich völlig im falschen film?
In dem Ordner is bei mir nämlich gar nichts auser der Ordner setup und der is auch leer.

S
42 Beiträge seit 2005
vor 19 Jahren

hat sich jetzt halbwegs erledigt. Ich habe mir VC# 2005 Express beta runtergeladen, jetzt tut es.
Wär aber trotzdem nett wenn ihr mir sagen könntet warum es in #develop nicht tat.

K
355 Beiträge seit 2004
vor 19 Jahren

Hm wenn du DirectX installierst und die eine Setup hast, dann doppelklickst du und er fragt nach nem Ordner. Da wird dann alles entpackt. Dann hast du da mehrere Unterordner und das Setup wird automatisch gestartet. Nur bin ich draufgekommen, dass die Installation nich immer ganz funktioniert, und dann muss man das 2te Setup in dem betreffenden Ordner auch ausführen, dann gehts...ich glaub die Struktur geht so irgendwie \DirectX\Managed DirectX\Debug und da iss ne msi drin....

S
42 Beiträge seit 2005
vor 19 Jahren

aber dann müste es dann doch auch mit VC# 2005 nicht funktionieren oder?
Ich mein, wenn es an DirectX liegt, dann sollte es doch jetzt auch nicht funktionieren.

Thema geschlossen