www.rolandk.de
- Aktuelle Themen zu .Net -
Achtung: Hier handelt es sich um meine alte Seite.
Die aktuelle ist unter folgendem Link erreichbar: www.rolandk.de/wp/
Home Tutorials DirectX 11 Basics Chapter 2: Das erste Dreieck




















































Chapter 2: Das erste Dreieck
Sonntag, den 28. Oktober 2012 um 08:25 Uhr

 

 

Rendern in Direct3D

Im letzten Artikel haben wir in einem Windowsfenster eine einfache 3D-Ansicht mittels Direct3D 11 initialisiert. In diesem Kapitel werden wir unser erstes Objekt rendern, allerdings wird es noch kein echtes 3D-Objekt sein. Doch was bedeutet "Objekte rendern" überhaupt in Direct3D? Generell ist Direct3D eine Ebene näher an der Hardware wie in .Net übliche Grafik-APIs. Beispielsweise hat man mit System.Drawing schon viele fertige Methoden wie etwa DrawLine, DrawRect, DrawImage, usw., also alles was man braucht. In Direct3D sieht das anders aus, hier gibt es lediglich Befehle zum Rendern von primitiven Objekten. Primitive Objekte sind Punkte, Linien und Dreiecke. Was bedeutet das jetzt für uns? Ganz einfach, wir müssen alle unsere 3D-Modelle mittels Primitiven zusammenbauen. In der Praxis sieht das dann so aus:

 

 

Rechts sehen wir das eigentliche Spiel. Dabei handelt es sich bloß um ein vom Spieler gesteuertes Flugzeug, das durch die Gegend fliegt, also nichts Bahnbrechendes. Links davon sehen wir das gleiche Bild im Wireframe-Modus. Wireframe bedeutet, dass lediglich die Kanten der Dreiecke gezeichnet werden. Wie wir hier ziemlich gut sehen, kann so ziemlich jedes Objekt aus Dreiecken aufgebaut werden. So zum Beispiel auch die Berge im Hintergrund. Aus diesem Grund reicht das Rendern von Dreiecken (der geläufige Begriff ist in der 3D-Technik Polygon oder Triangle) auch für komplexe Modelle vollkommen aus. 

Ein anderer Begriff, den man im Bezug auf Direct3D auch kennen sollte, ist Shader. Ein Shader ist ein Programm auf der Grafikkarte, mit dem man den Weg vom Drahtgittermodell zur tatsächlichen Ausgabe maßgeblich beeinflussen kann (und auch muss). In Direct3D 11 gibt es 3 verschiedene Shader-Arten:

  • VertexShader: Hier müssen normalerweise die Eckpunkte des primitiven Objekts in Bildschirmkoordinaten umgerechnet werden, natürlich kann man auch andere Sachen machen...
  • GeometryShader: Ich selber habe diesen Shader bis jetzt noch nicht verwendet. Er erlaubt das dynamische Hinzufügen von neuer Geometrie in das gerenderte Model.
  • PixelShader: Hier werden schließlich die Pixel geschrieben.
  • ComputeShader: Solche Shader können unabhängig vom Renderung zur Berechnung komplexer Aufgaben und damit zur Entlastung der CPU genutzt werden.
  • HullShader: Shader zur Umsetzung des Features Tessellation.
  • DomainShader: Shader zur Umsetzung des Features Tessellation.

Was bedeutet das alles für uns? Damit wir beispielsweise eine Liste von Dreiecken rendern können, benötigen wir einen Shader. Aber keine Sorge, diese Programme werden in der C-ähnlichen Sprache HLSL (High Level Shading Language) erstellt und sind zumindest jetzt am Anfang auch nicht wirklich komplex.

 

Der erste Shader

Am besten fangen wir auch gleich mit dem Shader an. Wie schon geschrieben wird dieser nicht in C#, sondern in HLSL codiert. Die Frage ist jetzt bloß mit welcher IDE? Ich selbst nutze am liebsten den FX Composer von NVidia (zu finden im Developer Center unter www.nvidia.com). Für den Anfang ist dieses Tool aber sehr komplex, daher gehe ich hier auch nicht näher auf die Funktionsweise dieses Tools ein. Das Einfachste für dieses Tutorial ist, den Shader direkt in Visual Studio als normale Textdatei anzulegen. Ab Visual Studio 2012 gibt es übrigens SyntaxHighlighting für die Sprache Hlsl. Was wir jetzt brauchen ist also eine neue Datei in unserem Projekt, ich nenn Sie mal SimpleRendering.fx. Fx ist die Dateiendung für eine Effekt-Datei für DirectX. Eine Effekt Datei bietet zusätzlich zum Shader-Code noch einige andere Kleinigkeiten, die uns die Arbeit mit Shadern erleichtern, aber dazu später mehr. 

So, jetzt haben wir also eine leere fx Datei vor uns. Zuerst müssen wir der Grafikkarte darin sagen, welche Daten sie überhaupt von uns bekommt:

  1. struct VS_IN
  2. {
  3. float4 pos : POSITION;
  4. float4 col : COLOR;
  5. };
  6.  

Sieht doch schon mal fasst wie eine ganz normale C#-Struktur aus. Der einzige wirkliche Unterschied ist dieses : in jeder Zeile (z. B. ": POSITION"). Im Unterschied zu C müssen wir im Shader der Grafikkarte sagen, welchen Sinn eine Variable hat. In unserem Fall haben wir zwei Felder jeweils vom gleichen Typ (float4 = Vektor mit 4 Komponenten), aber mit unterschiedlicher Bedeutung (Position und Farbe). Diese Dinger werden im DirectX Sprachgebrauch als Semantics bezeichnet. Es gibt für jede Shader-Art (Vertex-, Geometry-, und Pixelshader) jeweils andere Semantics. Welche es gibt und welche Bedeutung sie haben findet man in der Dokumentation im DirectX SDK. 

Jetzt weiß die Grafikkarte also, was wir zu ihr runter schicken wollen. In diesem Beispiel werden wir den Vertex Shader und den PixelShader verwenden. Aus diesem Grund brauchen wir noch eine Struktur, welche für den Datenaustausch zwischen diesen beiden Shader verantwortlich ist:

  1. struct PS_IN
  2. {
  3. float4 pos : SV_POSITION;
  4. float4 col : COLOR;
  5. };
  6.  

Hat irgendwie Ähnlichkeit mit der anderen Struktur vorher, oder? Die einzigen Unterschiede sind zum Einen der Name und zum Anderen das Semantic SV_POSITION statt POSITION. Letzteres liegt an den anderen Semantics, die wir für den PixelShader brauchen. Wie schon geschrieben, nähere Infos dazu in der Doku zum DirectX SDK. 

Jetzt wird es aber Zeit, die Shaderfunktionen zu codieren. Generell unterscheidet sich ein Shader-Programm nicht sonderlich zu einem normalen C-Programm. Man muss nur Aufpassen: Der Funktionsumfang und die mögliche Länge des Programms sind in jeder DirectX Version etwas unterschiedlich. Wir programmieren mit dem Shader Model 5.0 (welches mit Direct3D 11 eingeführt wurde) und haben damit vor allem was die Programmlänge angeht völlige Freiheit. Beginnen wir also mit dem VertexShader:

  1. PS_IN VS( VS_IN input )
  2. {
  3. PS_IN output = (PS_IN)0;
  4.  
  5. output.pos = input.pos;
  6. output.col = input.col;
  7.  
  8. return output;
  9. }
  10.  

Auch hier erkennt man die gute alte C-Syntax sofort wieder. Unser VertexShader ist von der C-Betrachtungsweise aus lediglich eine Funktion (hier mit Namen VS). Die Übergabeparameter entsprechen dem, was wir von der jeweils letzten Ebene (In diesem Fall unser C# Programm) bekommen, und die Rückgabewerte dem, was wir der nächsten Ebene (In diesem Fall der PixelShader) weitergeben. Innerhalb des Shaders machen wir nicht wirklich viel. Wir nehmen uns im Prinzip bloß die Werte die wir kriegen und reichen diese weiter. Es findet keinerlei Berechnung statt. Was bedeutet das jetzt? Oben hab ich doch mal geschrieben, dass im VertexShader die 3D-Koordinaten aus dem Programm in Bildschirmkoordinaten umgerechnet werden müssen. Ja, währe normalerweise auch so, aber nur wenn ich im Programm auch wirklich 3D-Koordinaten zur Grafikkarte schicke. In diesem kleinen Programmbeispiel ist es erst mal gar nicht nötig und wir schicken gleich die 2D-Bildschirmkoordinaten zur Grafikkarte (Wir rendern ja auch nur ein Dreieck). 

Ähnlich einfach ist auch unser PixelShader aufgebaut. Dessen einzige Aufgabe besteht darin, die ihm übergebene Farbe auf den Bildschirm zu schreiben:

  1. float4 PS( PS_IN input ) : SV_Target
  2. {
  3. return input.col;
  4. }
  5.  

Anders als der VertexShader hat der PixelShader hier keine Struktur als Rückgabewert, sondern lediglich einen einfachen Vektor. Dieser Vektor beinhaltet die Farbe des Pixels, der gerade gezeichnet wird. Das Semantic für diesen Rückgabewert kann man einfach an den Funktionskopf anfügen. Aber halt, die Farbe eines Pixels in einem Vektor mit 4 Fließkommazahlen? Farbe wird doch normalerweise mit 3 (oder 4) Ganzzahlen gespeichert, und zwar Rot, Grün, Blau und eventuell Transparenz mit jeweils einen Wert von 0 bis 255. In der Grafikkarte ist das etwas anders, dort werden Farben als Vektoren dargestellt und die Farbkomponenten reichen auch nicht von 0 bis 255, sonder von 0.0 bis 1.0. 

Damit hätten wir auch schon den Shader selbst codiert, nur eines müssen wir noch in die fx-Datei schreiben: Wir müssen noch festlegen, welche Funktion überhaupt unser VertexShader und welche unser PixelShader ist. Kurz gesagt funktioniert das so:

  1. technique10 Render
  2. {
  3. pass P0
  4. {
  5. SetGeometryShader( 0 );
  6. SetVertexShader( CompileShader( vs_4_0, VS() ) );
  7. SetPixelShader( CompileShader( ps_4_0, PS() ) );
  8. }
  9. }
  10.  

Was ist denn das jetzt alles? Wir wollten doch nur sagen welche Funktion was ist und dann kommt da so ein seltsamer Code raus. Der Grund daran liegt darin, dass man in einer fx-Datei auch mehrere Kombinationen von Shadern deklarieren kann. Zunächst gibt es die Techniques, diese Bilden einen Shader Effekt. So kann es eine Technique für eine Wasserdastellung geben, eine für den Himmel, usw.. Die nächste Ebene bilden die Passes. Eine Technique kann manchmal mehrere Rendervorgänge benötigen. Warum? Bei einem Cartoon Effekt beispielsweise kann in einem ersten Pass das 3D-Objekt selbst und in einem zweiten dessen Konturen (Schwarze Ränder) gezeichnet werden. Die ShaderFunktionen werden dann innerhalb eines Pass zugeordnet. Wie wir in dem Code-Ausschnitt sehen, legen wir lediglich eine Technique (Name: Render) und einen Pass (Name: P0), mehr brauchen wir ja auch noch nicht. Dem Pass werden schließlich unsere Shader zugeordnet, mit Ausnahme des GeometryShaders. Dieser bekommt 0, da wir garkeinen haben. 

Zusammenfassend hier nocheinmal die vollständige fx-Datei:

  1. struct VS_IN
  2. {
  3. float4 pos : POSITION;
  4. float4 col : COLOR;
  5. };
  6.  
  7. struct PS_IN
  8. {
  9. float4 pos : SV_POSITION;
  10. float4 col : COLOR;
  11. };
  12.  
  13. PS_IN VS( VS_IN input )
  14. {
  15. PS_IN output = (PS_IN)0;
  16.  
  17. output.pos = input.pos;
  18. output.col = input.col;
  19.  
  20. return output;
  21. }
  22.  
  23. float4 PS( PS_IN input ) : SV_Target
  24. {
  25. return input.col;
  26. }
  27.  
  28. technique10 Render
  29. {
  30. pass P0
  31. {
  32. SetGeometryShader( 0 );
  33. SetVertexShader( CompileShader( vs_4_0, VS() ) );
  34. SetPixelShader( CompileShader( ps_4_0, PS() ) );
  35. }
  36. }
  37.  

 

Zurück zu C#

Da wir unseren Shader programmiert haben, können wir uns jetzt wieder um den C#-Code kümmern. Wir erinnern uns, am Anfang unseres Shaders haben wir eine Struktur angelegt, welche angibt, welche Daten von der C# Anwendung zum Shader geschickt werden. Jetzt müssen wir die selbe Struktur nochmal in C# anlegen, damit wir das auch tun können:

  1. [StructLayout(LayoutKind.Sequential)]
  2. public struct Vertex
  3. {
  4. public Vector4 Position;
  5. public int Color;
  6.  
  7. public Vertex(Vector4 position, int color)
  8. {
  9. this.Position = position;
  10. this.Color = color;
  11. }
  12. }

Sieht doch schon mal fast genauso aus. Der einzige Unterschied ist, dass wir hier die Farbe in einem Integer statt in einem Vektor speichern. Grund dafür ist die leichtere Handhabung im C# Programm. Beim Übertragen der Daten zur Grafikkarte werden die Daten automatisch konvertiert. 

Jetzt benötigen wir natürlich noch das Wichtigste: Unser 3D-Objekt. Dafür erstellen wir eine neue Klasse SimpleTriangle. Was müssen wir darin tun? Schauen wir uns dazu doch mal die Membervariablen unserer neuen Klasse an:

  1. private DX11.Buffer m_vertexBuffer;
  2. private DX11.InputLayout m_vertexLayout;
  3. private DX11.Effect m_effect;
  4. private DX11.EffectTechnique m_effectTechnique;
  5. private DX11.EffectPass m_effectPass;

Die erste Variable, der VertexBuffer, speichert alle Vertices unseres Objekts (zu Deutsch: Alle Eckpunkte). Das InputLayout sagt der Grafikkarte, wie genau die Vertices im VertexBuffer aufgebaut sind. Darin werden beispielsweise auch die Semantics auf C#-Seite zugeordnet. Die letzten 3 benötigen wir für unseren Shader. Was hier auch sofort auffällt ist, dass die Bezeichnungen aus dem Shader (Technique, Pass) hier wieder auftauchen. Was müssen wir also nun in dieser Klasse tun? Ganz einfach. Zum Einen müssen wir das 3D-Objekt und den Shader laden, und zum Anderen müssen wir natürlich auch Rendern. Diese Aufgaben erledigen wir am besten in 2 getrennten Methoden:

  1. public void LoadResources(DX11.Device device)
  2. {
  3. //TODO: Hier wird das Objekt geladen
  4. }
  5.  
  6. public void Render(DX11.Device device)
  7. {
  8. //TODO: Hier wird das Objekt gezeichnet
  9. }

Mehr brauchen wir in der neuen Klasse auch nicht. Nur wie schaut es jetzt mit dem Inhalt dieser Methoden aus? Fangen wir am besten mit der LoadResources Methode an. Die erste Aufgabe dort ist es, die Eckpunkte unseres Dreiecks zu erzeugen. Dafür erzeugen wir uns eine Vertex-Array und füllen diese mit entsprechend:

  1. Vertex[] vertices = new Vertex[]
  2. {
  3. new Vertex(new Vector4(-0.8f, -0.8f, 0f, 1f), Color.Red.ToArgb()),
  4. new Vertex(new Vector4(0f, 0.8f, 0f, 1f), Color.Green.ToArgb()),
  5. new Vertex(new Vector4(0.8f, -0.8f, 0f, 1f), Color.Blue.ToArgb())
  6. };

Jetzt haben wir uns unsere Vertices allerdings nur so erzeugt, dass C# was damit anfangen kann. Jetzt müssen wir das ganze mittels der Klasse DataStream in ein Format bringen, welches auch DirectX versteht:

  1. DataStream outStream = new DataStream(3 * Marshal.SizeOf(typeof(Vertex)), true, true);
  2. outStream.WriteRange(vertices);
  3. outStream.Position = 0;

Dem Konstruktor der DataStream-Klasse müssen wir sagen, wie groß der Speicher wird, in dem unser 3D-Objekt seine Daten ablegt. Da wir 3 Vertices für unser Dreieck benötigen, brauchen wir auch genau 3x die Größe eines Vertex in Bytes. Anschließend schreiben wir unsere Array in den Stream. Achtung: Man darf nicht vergessen, die Position im Stream wieder auf 0 zu setzen. Denn DirectX ließt später von der hier eingestellten Position an und diese sollte 0 sein ;). 

Jetzt müssen wir diese Daten mittels der Klasse VertexBuffer in den Grafikspeicher schreiben. Wie der Name schon sagt, handelt es sich dabei um einen Buffer, welcher für die Speicherung von Vertex-Daten genutzt wird. Bevor wir das VertexBuffer Objekt anlegen können, benötigen wir aber noch eine Beschreibung des Buffers in Form einer BufferDescription. Das ganze sieht dann so aus:

  1. DX11.BufferDescription bufferDescription = new DX11.BufferDescription();
  2. bufferDescription.BindFlags = DX11.BindFlags.VertexBuffer;
  3. bufferDescription.CpuAccessFlags = DX11.CpuAccessFlags.None;
  4. bufferDescription.OptionFlags = DX11.ResourceOptionFlags.None;
  5. bufferDescription.SizeInBytes = 3 * Marshal.SizeOf(typeof(Vertex));
  6. bufferDescription.Usage = DX11.ResourceUsage.Default;
  7.  
  8. m_vertexBuffer = new DX11.Buffer(device, outStream, bufferDescription);
  9. outStream.Close();

In der BufferDescription müssen wir mithilfe der Eigenschaft BindFalgs zuerst sagen, dass es überhaupt ein VertexBuffer ist. Die CpuAccessFlags regeln den Zugriff der CPU auf den Speicherbereich. Wir schränken diesen Wert hier absichtlich mittels CpuAccessFlags.None ein, da so die Grafikkarte den VertexBuffer dort platzieren kann, wo sie am schnellsten die Daten abrufen kann. Wenn die CPU auch einen Zugriff auf diese Resource benötigt, ist die Verwendung von der Grafikkarte aus daher meist langsamer. Die Eigenschaft OptionFlags hat hier keine besondere Bedeutung, wir setzen sie einfach auf None. Die Größe des Buffers in Bytes entspricht dem Wert, den wir bereits an den DataStream übergeben haben. Der letzte Wert Usage sagt DirectX, wie wir diese Resource verwenden wollen. Default bedeutet hier, dass auf den VertexBuffer lediglich von der Grafikkarte aus zugegriffen wird. Nachdem wir die Beschreibung gefüllt haben, können wir unseren VertexBuffer erzeugen. 

Da unser 3D-Objekt jetzt auf der Grafikkarte bereitsteht, müssen wir noch unseren Shader laden. Dieser Vorgang ist in DirectX recht einfach und beschränkt sich in diesem Beispiel auf ein paar wenige Methodenaufrufe:

  1. D3DCompiler.ShaderBytecode shaderByteCode =
  2. D3DCompiler.ShaderBytecode.CompileFromFile(
  3. "Shaders/SimpleRendering.fx", "fx_5_0",
  4. D3DCompiler.ShaderFlags.None, D3DCompiler.EffectFlags.None);
  5. m_effect = new DX11.Effect(
  6. device, shaderByteCode);
  7. m_effectTechnique = m_effect.GetTechniqueByIndex(0);
  8. m_effectPass = m_effectTechnique.GetPassByIndex(0);

Dazu brauch ich jetzt eigentlich nicht viel schreiben. Im Prinzip wird in der ersten Zeile der Shader aus der Datei SimpleRendering.fx geladen und in ein ShaderBytecode Objekt übersetzt. Der zweite Parameter (fx_5_0) gibt das verwendete Shadermodel an. In der nächsten Zeile wird das Effect Objekt erzeugt und danach hohlen wir uns die Referenzen auf die Technique und dem Pass, welche wir uns im Shader angelegt haben. 

Nun ist in der LoadResources Methode schon fast alles gemacht, allerdings fehlt noch was. Damit DirectX unsere Vertex-Struktur auch richtig verwenden kann, brauchen wir noch ein InputLayout. Ohne jetzt zu viel über das warum und weshalb zu schreiben, hier der Code:

  1. DX11.InputElement[] inputElements = new DX11.InputElement[]
  2. {
  3. new DX11.InputElement("POSITION",0,DXGI.Format.R32G32B32A32_Float,0,0),
  4. new DX11.InputElement("COLOR",0,DXGI.Format.R8G8B8A8_UNorm,16,0)
  5. };
  6.  
  7. m_vertexLayout = new DX11.InputLayout(
  8. device,
  9. inputElements,
  10. m_effectPass.Description.Signature);

Für jedes Element unserer Struktur erzeugen wir ein InputElement, welches ein paar zusätzliche Informationen speichert. So wird beispielsweise die Verwendung der Variable angegeben, aber auch die Position in Bytes, an welcher der Wert steht und wie lang er ist. 

Damit sind wir jetzt nicht mehr weit vom Ziel dieses Kapitels entfernt, unsere Lade-Routine ist fertig. Aber das Wichtigste müssen wir noch machen: Rendern. Im Gegensatz zum Laden ist das Rendern allerdings recht simpel. Wir müssen der Grafikkarte lediglich sagen, was gerendert werden soll:

  1. deviceContext.InputAssembler.InputLayout = m_vertexLayout;
  2. deviceContext.InputAssembler.PrimitiveTopology = DX11.PrimitiveTopology.TriangleList;
  3. deviceContext.InputAssembler.SetVertexBuffers(
  4. 0,
  5. new DX11.VertexBufferBinding(m_vertexBuffer, Marshal.SizeOf(typeof(Vertex)), 0));
  6.  
  7. m_effectPass.Apply(deviceContext);
  8. deviceContext.Draw(3, 0);

So, das war es auch schon. Zum besseren Überblick hier der komplette Inhalt der Klasse in einem Stück:

  1. private DX11.Buffer m_vertexBuffer;
  2. private DX11.InputLayout m_vertexLayout;
  3. private DX11.Effect m_effect;
  4. private DX11.EffectTechnique m_effectTechnique;
  5. private DX11.EffectPass m_effectPass;
  6.  
  7. public void LoadResources(DX11.Device device)
  8. {
  9. Vertex[] vertices = new Vertex[]
  10. {
  11. new Vertex(new Vector4(-0.8f, -0.8f, 0f, 1f), Color.Red.ToArgb()),
  12. new Vertex(new Vector4(0f, 0.8f, 0f, 1f), Color.Green.ToArgb()),
  13. new Vertex(new Vector4(0.8f, -0.8f, 0f, 1f), Color.Blue.ToArgb())
  14. };
  15.  
  16. DataStream outStream = new DataStream(3 * Marshal.SizeOf(typeof(Vertex)), true, true);
  17. outStream.WriteRange(vertices);
  18. outStream.Position = 0;
  19.  
  20. DX11.BufferDescription bufferDescription = new DX11.BufferDescription();
  21. bufferDescription.BindFlags = DX11.BindFlags.VertexBuffer;
  22. bufferDescription.CpuAccessFlags = DX11.CpuAccessFlags.None;
  23. bufferDescription.OptionFlags = DX11.ResourceOptionFlags.None;
  24. bufferDescription.SizeInBytes = 3 * Marshal.SizeOf(typeof(Vertex));
  25. bufferDescription.Usage = DX11.ResourceUsage.Default;
  26.  
  27. m_vertexBuffer = new DX11.Buffer(device, outStream, bufferDescription);
  28. outStream.Close();
  29.  
  30. D3DCompiler.ShaderBytecode shaderByteCode =
  31. D3DCompiler.ShaderBytecode.CompileFromFile(
  32. "Shaders/SimpleRendering.fx", "fx_5_0",
  33. D3DCompiler.ShaderFlags.None, D3DCompiler.EffectFlags.None);
  34. m_effect = new DX11.Effect(
  35. device, shaderByteCode);
  36. m_effectTechnique = m_effect.GetTechniqueByIndex(0);
  37. m_effectPass = m_effectTechnique.GetPassByIndex(0);
  38.  
  39. DX11.InputElement[] inputElements = new DX11.InputElement[]
  40. {
  41. new DX11.InputElement("POSITION",0,DXGI.Format.R32G32B32A32_Float,0,0),
  42. new DX11.InputElement("COLOR",0,DXGI.Format.R8G8B8A8_UNorm,16,0)
  43. };
  44.  
  45. m_vertexLayout = new DX11.InputLayout(
  46. device,
  47. inputElements,
  48. m_effectPass.Description.Signature);
  49. }
  50.  
  51. public void Render(DX11.Device device)
  52. {
  53.  
  54. DX11.DeviceContext deviceContext = device.ImmediateContext;
  55.  
  56. deviceContext.InputAssembler.InputLayout = m_vertexLayout;
  57. deviceContext.InputAssembler.PrimitiveTopology = DX11.PrimitiveTopology.TriangleList;
  58. deviceContext.InputAssembler.SetVertexBuffers(
  59. 0,
  60. new DX11.VertexBufferBinding(m_vertexBuffer, Marshal.SizeOf(typeof(Vertex)), 0));
  61.  
  62. m_effectPass.Apply(deviceContext);
  63. deviceContext.Draw(3, 0);
  64. }

 

Anzeigen des Objekts

Zum Abschluss dieses Programmbeispiels müssen wir die Klasse SimpleTriangle natürlich noch in unser Hauptfenster einbinden. Dazu erzeugen wir uns innerhalb der Initialize3D-Methode ein neues Objekt davon. Direkt danach rufen wir die LoadResources Methode auf, aber Achtung: Das Device muss schon vorher erzeugt worden sein, sonst klappt es nicht. Im OnPaint Ereignis schließlich können wir die Render Methode ausführen, und zwar genau zwischen Device.Clear und SwapChain.Present.

Sieht doch schon mal fast genauso aus. Der einzige Unterschied ist, dass wir hier die Farbe in einem Integer statt in einem Vektor speichern. Grund dafür ist die leichtere Handhabung im C# Programm. Beim Übertragen der Daten zur Grafikkarte werden die Daten automatisch konvertiert.

 

Kommentar hinzufügen

Ihr Name:
Kommentar:

Kommentare (2)

2. Marcel W.
Dienstag, den 19. März 2013 um 18:13 Uhr
Habe den Fehler gefunden: Bei der Triangle-Klasse in der Render-Methode habe ich

int vertexSize = 3 * System.Runtime.InteropServices.Marshal.SizeOf(typeof(Vertex));
context.InputAssembler.SetVertexBuffers(0, new SlimDX.Direct3D11.VertexBufferBinding(buffer, vertexSize, 0));

obwohl ich die Größe des Vertex garnicht * 3 nehmen musste..

1. Marcel W.
Dienstag, den 19. März 2013 um 12:41 Uhr
Hallo,
Als erstes möchte ich mich für dein gut verständliches Tutorial bedanken. Es hat mir sehr dabei geholfen, einen ersten einblick in die Shader und deren Struktur zu werfen. Ich stehe jedoch nun vor folgenden Problem: Ich habe mittels den SlimDX-Tutorial (auf der offiziellen Seite) eine Renderform erstellt und dein Tutorial mit eingebunden. Beim Debuggen des Projektes wird die Triangle-Klasse erstellt und die Rendermethode wie es soll durchlaufen. Problem jedoch ist, dass ich kein Dreieck sehe (obwohl alles erstellt und durchlaufen wird). Ich habe schon selbstständig versucht das Problem zu lösen, jedoch leider ohne erfolg. Mir bleibt daher nur die Hoffnung mein Projekt in deine Obhut zu schicken und zu hoffen, dass du mir erklären kannst, welchen entscheiden Denkfehler ich in meinen Quellcode habe. Meine E-Mail-Adresse lautet: blaick.corp@gmail.com. Außerdem habe ich das Projekt unter mega.co.nz hochgeladen (https://mega.co.nz/#!Ah0wTbQb!DbE8cg-ClnPue6UsZ1Zzlg5J_touMg97771aoGPxe3Y). Ich hoffe, dass du mir bei meinen Problem weiterhelfen kannst. Ich stehe gerne bei weiteren fragen unter die oben genannte Adresse zu Verfügung.