Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
DirectX: Tutorial
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

DirectX: Tutorial

beantworten | zitieren | melden

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.
Attachments
private Nachricht | Beiträge des Benutzers
r00t
myCSharp.de - Member



Dabei seit:
Beiträge: 139
Herkunft: Schwäbisch Gmünd

beantworten | zitieren | melden

super idee danke ;-)

vielleicht kann ich ja was dazu lernen *g*

wieivele parts planst du?
posted by the real prince of persia
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

Zitat
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...
private Nachricht | Beiträge des Benutzers
r00t
myCSharp.de - Member



Dabei seit:
Beiträge: 139
Herkunft: Schwäbisch Gmünd

beantworten | zitieren | melden

gehst du auch auf surfaces ein?
posted by the real prince of persia
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

Zitat
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)
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

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:
Zitat
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();
			
		}
	}
}
Attachments
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

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...
private Nachricht | Beiträge des Benutzers
kaybe
myCSharp.de - Member



Dabei seit:
Beiträge: 25

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

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)
private Nachricht | Beiträge des Benutzers
kaybe
myCSharp.de - Member



Dabei seit:
Beiträge: 25

beantworten | zitieren | melden

na dann warte ich mal gespannt und wünsch dir noch einen
schönen Urlaub hier in D ( die Sonnenbrille nicht vergessen 8) )
private Nachricht | Beiträge des Benutzers
user453
myCSharp.de - Member



Dabei seit:
Beiträge: 19

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

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 ^^
Attachments
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

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;
private Nachricht | Beiträge des Benutzers
cdr
myCSharp.de - Member



Dabei seit:
Beiträge: 980
Herkunft: Zürich

beantworten | zitieren | melden

Zitat
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...)
private Nachricht | Beiträge des Benutzers
mcs
myCSharp.de - Member



Dabei seit:
Beiträge: 8

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

spiel mal ein wenig mit den Parametern rum. Also z.B. das Vertex Processing mal auf Software stellen oder.
private Nachricht | Beiträge des Benutzers
SexyEnemy
myCSharp.de - Member



Dabei seit:
Beiträge: 23
Herkunft: D - Berlin

beantworten | zitieren | melden

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!
_________________________________
private Nachricht | Beiträge des Benutzers
maxE
myCSharp.de - Member



Dabei seit:
Beiträge: 456
Herkunft: Sachsen

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
SexyEnemy
myCSharp.de - Member



Dabei seit:
Beiträge: 23
Herkunft: D - Berlin

beantworten | zitieren | melden

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!
_________________________________
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

Zitat
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
private Nachricht | Beiträge des Benutzers
invisible
myCSharp.de - Member



Dabei seit:
Beiträge: 48

beantworten | zitieren | melden

*gespannt auf die fortsetzung des tutorial sein*
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

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.
Attachments
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

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 ^^
Attachments
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

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.
Attachments
private Nachricht | Beiträge des Benutzers
Fab96
myCSharp.de - Member



Dabei seit:
Beiträge: 66

beantworten | zitieren | melden

Mehr, ich will mehr :o)

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
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

Zitat
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 ^^
private Nachricht | Beiträge des Benutzers
x-sharp
myCSharp.de - Member



Dabei seit:
Beiträge: 133
Herkunft: NRW

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
hauptmann
myCSharp.de - Member



Dabei seit:
Beiträge: 704
Herkunft: Österreich/Kärnten

Themenstarter:

beantworten | zitieren | melden

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?
private Nachricht | Beiträge des Benutzers
S.H.-Teichhof
myCSharp.de - Member

Avatar #avatar-2460.jpg


Dabei seit:
Beiträge: 1.549
Herkunft: Sindringen

beantworten | zitieren | melden

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
private Nachricht | Beiträge des Benutzers
cynos
myCSharp.de - Member



Dabei seit:
Beiträge: 7

beantworten | zitieren | melden

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. ^^
private Nachricht | Beiträge des Benutzers