GDI3D - 3D Rendering in WinForms

    • Release
    • Open Source

    Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von TheFatTurtle.

      GDI3D - 3D Rendering in WinForms

      Name:
      GDI3D

      Beschreibung:
      Ich hatte mal irgendwann vor eine kleine Grafikkarte aus einem Mikrocontroller zu bauen (ist noch aber noch nichts geworden ^^ ). Dazu hatte ich in C++ einen einfachen Rasterizer implementiert und diesen jetzt nach C# portiert.
      Mit GDI3D könnt ihr, ähnlich wie mit OpenGL oder DirectX, Dreiecksnetze auf eine Bitmap zeichnen. Das alles ist softwarebasiert und absolut langsam, aber das Funktionsprinzip wird deutlich.
      Aktuell werden folgende Features unterstützt:
      • Eigene Vertex- & Pixelshader
      • Depth Test (Z Test)
      • Near- / Farplane clipping
      • Beliebig viele Vertex-Attribute
      • Einfacher Importer für PLY-Dateien
      Geplante Features:
      • Rasterizer mit Bresenham Algo (im Moment gibt's nur die baryzentrische Variante)
      • Stencilbuffer
      • Vielleicht Interpolationsoptionen für Vertexattribute
      Verwendete Programmiersprache(n) und IDE(s):
      C# und Visual Studio Commiunity

      Systemanforderungen:
      Framework 4.0

      Systemveränderungen:
      Keine

      Download(s):
      Im Anhang befindet sich die Library inkl. einer Demo mit ein paar 3D Modellen und Shadern. Dazu gibts die lauffähige Solution. (171,05kB)

      Lizenz/Weitergabe:
      Open Source

      Hier mal ein Beispiel um das berühmte erste Dreieck anzuzeigen:
      Spoiler anzeigen

      C#-Quellcode

      1. public partial class frmMain : Form
      2. {
      3. Viewport vp = null;
      4. public frmMain()
      5. {
      6. InitializeComponent();
      7. FormBorderStyle = FormBorderStyle.FixedSingle;
      8. MaximizeBox = false;
      9. }
      10. private void frmMain_Load(object sender, EventArgs e)
      11. {
      12. // Sensorgröße etc. sind erstmal weniger wichtig
      13. vp = new Viewport(new SizeF(1.5f, 1.5f), ClientSize, 35, 0.1f, 100);
      14. // Initialisiere die Vertices des Dreiecks
      15. Vertex v0 = new Vertex(new Vector4(-1, -1, 0));
      16. v0.Attribute.Add("COLOUR", new Vector3(1, 0, 0)); // Jedem Vertex eine Farbe geben
      17. Vertex v1 = new Vertex(new Vector4(1, -1, 0));
      18. v1.Attribute.Add("COLOUR", new Vector3(0, 1, 0));
      19. Vertex v2 = new Vertex(new Vector4(0, 1, 0));
      20. v2.Attribute.Add("COLOUR", new Vector3(0, 0, 1));
      21. vp.VertexBuffer = new Vertex[] { v0, v1, v2 }; // Setze Vertexbuffer
      22. vp.IndexBuffer = new uint[] { 0, 1, 2 };
      23. vp.VertexShader = BasicVertexShader.None; // Gibt einfach Input zurück (Vertices müssen im NDC space sein)
      24. vp.PixelShader = BasicPixelShader.VertexColour;
      25. // Buffer löschen
      26. vp.Clear(Color.CornflowerBlue.ToVector3());
      27. // Dreieck zeichnen
      28. vp.DrawIndexedTriangles(Matrix4.Identity);
      29. }
      30. private void frmMain_Paint(object sender, PaintEventArgs e)
      31. {
      32. vp.DrawBufferToDC(e.Graphics);
      33. }
      34. }
      Das Ergebnis sieht dann so aus:


      Im Anhang sind auch noch ein paar Screenshots der Demo Anwendung. Wenn irgendwo Fragen aufkommen dann immer her damit! Kritik und Verbesserungsvorschläge sind erwünscht.
      Bilder
      • ClassDiagram.png

        190,02 kB, 1.781×1.145, 71 mal angesehen
      • cube.png

        44,32 kB, 677×519, 75 mal angesehen
      • ball.png

        51,48 kB, 676×520, 69 mal angesehen
      • bear.png

        57,81 kB, 676×520, 69 mal angesehen
      Dateien
      • GDI3D.zip

        (171,05 kB, 15 mal heruntergeladen, zuletzt: )
      • GDI3D V1.1.zip

        (174,22 kB, 16 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Gonger96“ ()

      So, jetzt gibt's ein Update :thumbup: Habe Folgendes implementiert:
      • Alpha blending
      • Stencil testing
      • CullMode wurde erweitert und verbessert
      • FastInvSqrt wurde hinzugefügt (schnelle Approximation der inversen Quadratwurzel) inkl. der Funktion NormaliseF() und der Eigenschaft NormalisedF in Vector2, Vector3 und Vector4
      • Ein paar Optionen für den DepthTest wurden hinzugefügt (u.A. Disable, ReadOnly)
      • DepthBuffer, ColourBuffer und StencilBuffer sind nun seperat und auch durch ClearFrameBuffer() gemeinsam löschbar
      Hier ein Beispiel für's Alphablending:
      Spoiler anzeigen

      C#-Quellcode

      1. public partial class Form1 : Form
      2. {
      3. Viewport vp;
      4. Mesh ufo = Mesh.FromPly("");
      5. Mesh roof = Mesh.FromPly("");
      6. Texture2 texture = new Texture2("");
      7. private void Form1_Load(object sender, EventArgs e)
      8. {
      9. vp = new Viewport(new SizeF(1.5f, 1.5f), ClientSize, 35, 0.1f, 100);
      10. vp.VertexShader = (new ProjectiveVertexShader() { Viewport = vp }).Shader;
      11. vp.PixelShader = PixelShader; // Normaler shader
      12. vp.CullMode = CullMode.None; // Rendere alle Dreiecke
      13. Matrix4 m = Matrix4.Identity;
      14. m.Translate(0, 0, -15); // Bisschen die Sicht ändern
      15. vp.Transform = m;
      16. vp.VertexBuffer = ufo.Vertices;
      17. vp.IndexBuffer = ufo.Indices;
      18. vp.ClearFrameBuffer();
      19. Matrix4 world = Matrix4.Identity;
      20. world.RotateX(60 * MathHelper.DegToRad);
      21. world.RotateZ(10 * MathHelper.DegToRad); // Model etwas drehen zur besseren Sicht
      22. vp.DrawIndexedTriangles(world.Inverse); // Zeichne
      23. vp.CullMode = CullMode.AntiClockwise; // Verwerfe alle Backfaces
      24. vp.VertexBuffer = roof.Vertices;
      25. vp.IndexBuffer = roof.Indices;
      26. vp.PixelShader = AlphaShader;
      27. vp.BlendEnable = true; // Alphablending ein
      28. vp.DestBlendFunction = BlendFunction.OneMinusSrcAlpha; // Standard Equation
      29. vp.SrcBlendFunction = BlendFunction.SrcAlpha;
      30. vp.DrawIndexedTriangles(world.Inverse);
      31. }
      32. bool AlphaShader(Vertex input, out Vector4 c)
      33. {
      34. c = Vector4.Zero;
      35. Vector2 uv = input.Attribute.Get<Vector2>("TEXCOORD");
      36. var textureColour = texture.Sample(uv);
      37. var vertexColour = new Vector4(input.Attribute.Get<Vector3>("COLOUR"));
      38. if (uv == Vector2.Zero)
      39. c = vertexColour;
      40. else
      41. c = MathHelper.Clamp(textureColour * vertexColour * 2, Vector4.Zero, Vector4.One);
      42. // Die Berechnung der Farbe ist eher uninteressant
      43. c.A = 0.7f; // Hier wird Alpha auf 0.7 gesetzt
      44. return c.RGB != Vector3.Zero; // Discard black pixel
      45. }
      46. private void Form1_Paint(object sender, PaintEventArgs e)
      47. {
      48. vp.DrawBufferToDC(e.Graphics);
      49. }
      50. }

      Wie man sehen kann wird das Dach des UFOs schön transparent dargestellt.

      Und ein Beispiel für den StencilTest:
      Spoiler anzeigen

      C#-Quellcode

      1. public partial class Form1 : Form
      2. {
      3. Viewport vp;
      4. Mesh ufo = Mesh.FromPly("");
      5. Mesh roof = Mesh.FromPly("");
      6. Texture2 texture = new Texture2("");
      7. private void Form1_Load(object sender, EventArgs e)
      8. {
      9. vp = new Viewport(new SizeF(1.5f, 1.5f), ClientSize, 35, 0.1f, 100);
      10. vp.VertexShader = (new ProjectiveVertexShader() { Viewport = vp }).Shader;
      11. vp.PixelShader = PixelShader; // Normaler shader
      12. vp.CullMode = CullMode.None; // Rendere alle Dreiecke
      13. Matrix4 m = Matrix4.Identity;
      14. m.Translate(0, 0, -15); // Bisschen die Sicht ändern
      15. vp.Transform = m;
      16. vp.VertexBuffer = ufo.Vertices;
      17. vp.IndexBuffer = ufo.Indices;
      18. vp.StencilEnable = true; // Stencil ein
      19. vp.StencilFunction = Comparison.Always; // Stenciltest ist immer true
      20. vp.StencilFail = StencilOperation.Keep;
      21. vp.DepthFail = StencilOperation.Keep;
      22. vp.StencilDepthPass = StencilOperation.Replace; // Ersetze Stencil mit StencilReference
      23. vp.StencilFunctionMask = 0xFF; // Alle Bits ein
      24. vp.StencilMask = 0xFF; // Alle Bits ein
      25. vp.StencilReference = 1; // 1 = true
      26. vp.ClearFrameBuffer();
      27. Matrix4 world = Matrix4.Identity;
      28. world.RotateX(60 * MathHelper.DegToRad);
      29. world.RotateZ(10 * MathHelper.DegToRad); // Model etwas drehen zur besseren Sicht
      30. vp.DrawIndexedTriangles(world.Inverse); // Zeichne
      31. vp.StencilFunction = Comparison.NotEqual; // Stenciltest ist true bei ungleich
      32. vp.StencilMask = 0; // Schreiben in den Stencilbuffer = 0
      33. vp.DepthTest = DepthTestState.Disable; // Ignoriere Depthtest
      34. vp.PixelShader = BasicPixelShader.White; // Pixelshader der immer Weiß zurückgibt
      35. Matrix4 inverse = world.Inverse;
      36. inverse.Scale(1.02f, 1.02f, 1); // Skaliere das Ufo etwas größer
      37. vp.DrawIndexedTriangles(inverse); // Zeichne
      38. }
      39. private void Form1_Paint(object sender, PaintEventArgs e)
      40. {
      41. vp.DrawBufferToDC(e.Graphics);
      42. }
      43. }

      Damit zeichnet man eine etwas vergrößerte weiße Outline um ein Objekt. So sieht das Ergebnis dann aus:

      Bilder
      • ClassDiagram1.png

        132,85 kB, 1.339×1.177, 43 mal angesehen
      Den Shader Ansatz finde ich recht Witzig gelöst, cooles Projekt! Mich würde mal interessieren, wie "langsam" das ganze ist, wie viel fps bekommst du mit einen bear.ply example?

      Edit: Ich finde der Viewport als zentrales Objekt zum verwalten von resourcen ist etwas unglücklich gewählt.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ThuCommix“ ()

      Ein beeindruckendes Projekt! Der Name ist allerdings ein bisschen unglücklich gewählt, weil hier 2010 schon ein mal ein Projekt unter dem gleichen Namen vorgestellt wurde. ;)
      GDI3D - 3D auch ohne DirectX
      Die Bibliothek bietet bei weitem weniger Features, ist dafür aber auch einfacher gestrickt. Wer sich für das Thema interessiert, kann ja auch mal dort vorbeischauen.

      Deine Bibliothek sieht jedenfalls richtig gut aus :thumbup: Kannst du was zur Performance sagen?
      Erstmal Danke :)

      ThuCommix schrieb:

      Edit: Ich finde der Viewport als zentrales Objekt zum verwalten von resourcen ist etwas unglücklich gewählt.

      Fand ich eigentlich garnicht schlecht. Die Kamera nimmt ja die Fotos auf. Letzten Endes ist es aber eine Rendererklasse mit einem extra Konstruktor der gleichzeitig die Projektionsmatrix erstellt.

      fufu schrieb:

      Der Name ist allerdings ein bisschen unglücklich gewählt

      Mist 8o

      Also bei Bear.ply krieg ich 26,82 Frames/s (nur Draw() und Clear() 100s lang gerendert und Wiederholungen gezählt). Das UFO mit Alpha Blending kommt auf 9,47 Frames/s, sind auch 2 Draw Calls. Jeweils bei einer Auflösung von 600 x 380. 3 GHz Prozessor.
      Wobei ich sagen muss ich habe es so implementiert, dass es leicht nachvollziehbar ist und anzuwenden ist. Ich habe nicht wirklich Wert auf Geschwindigkeit gelegt. Besonders Vertex muss während des Renderns mehrmals kopiert werden weils ein Referencetype ist (wegen der AttributeList). Man könnte da also noch einiges rausholen.

      /Edit
      Ich bin zu dumm für Github ;) Hier gibt's die Repo github.com/Gonger96/GDI3D

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Gonger96“ ()

      Brauche Hilfe

      Ich arbeite derzeit an einer physik engine.
      Sie funktioniert soweit aber mit der Grafik...
      WPF eignet sich nich so gut( Durchsichtigkeit,Brushes und Geschwindigkeit)
      Ich bin daher auf der Suche nach einer guten 3d grafik engine. Es würde mich freuen, wenn ich diese hier nutzten kann