Würfel darstellen mit verschiedenen Perspektiven

  • C#

SSL ist deaktiviert! Aktivieren Sie SSL für diese Sitzung, um eine sichere Verbindung herzustellen.

Es gibt 4 Antworten in diesem Thema. Der letzte Beitrag () ist von Niko Ortner.

    Würfel darstellen mit verschiedenen Perspektiven

    Hey Community,

    ich brauche etwas Hilfe bei der 3D-Programmierung.
    Ich möchte einen einfachen Würfel (später mehrere) darstellen, aber richtig. Das heißt: Ich habe eine AnimationLoop die mit 30fps rendert und updatet. Bevor ich aber zum Würfel komme, wollte ich einfach die drei Koordinatenachsen darstellen (siehe Axes3D.cs).
    Diese Klasse wird einmalig instantiiert (mit nem Scaling von (20, 20, 20)) und der AnimationLoop hinzugefügt, sodass halt die Render- und Update-Methode aufgerufen wird.
    Interessant ist jetzt in Axes3D.cs die erste Zeile in der Render-Methode, da wird von alle Ecken (Vertices, Typ ist Point3D) eine Methode ToProjectionSpace aufgerufen, die soll halt 3D in 2D umwandeln, unter Berücksichtigung der Zielposition, Skalierung und Rotation der Achsen und des Weiteren der definierten Kamera und Dingen wie FOV und sowas.
    Mein Problem ist, dass das ganze vllt richtig aussieht, aber bei Variation der Werte für Rotation und Position falsche Dinge rauskommen. Ich habs mal versucht darzustellen, da sollte sich eig alles um die X-Achse drehen, siehe Anhang.
    Gerichtet hab ich mich dabei nach dem MVP Konzept (Model-View-Projection), seit bitte nachsichtig, ich habe wahrscheinlich irgendwas vergessen anzuhängen, fragt dann einfach.


    Axes3D.cs

    C#-Quellcode

    1. public class Axes3D : RenderEntity
    2. {
    3. public Axes3D()
    4. {
    5. Vertices = new[] {new Point3D(0, 0, 0), new Point3D(1, 0, 0), new Point3D(0, 1, 0), new Point3D(0, 0, 1)};
    6. Axes = new[] {new[] {0, 1}, new[] {0, 2}, new[] {0, 3}};
    7. }
    8. public Point3D[] Vertices { get; set; }
    9. public int[][] Axes { get; }
    10. public Point3D TargetPosition
    11. { get; set; } = Point3D.Origin;
    12. public Vector3D Scaling
    13. { get; set; } = Vector3D.One;
    14. public Vector3D Rotation
    15. { get; set; } = Vector3D.Zero;
    16. public override void Update()
    17. {
    18. }
    19. public override void Render(Graphics g)
    20. {
    21. //Convert 3D Vertices to ProjectedPoints
    22. var ver = Vertices.Select(v => v.ToProjectionSpace(TargetPosition, Scaling, Rotation));
    23. //Converts ProjectedPoints to (positioned) 2DPoints
    24. var pt2D = ver.Select(v => new Point2D(v.X + ProjectionSettings.CanvasSize.Width / 2f, v.Y + ProjectionSettings.CanvasSize.Height / 2f)).ToArray();
    25. //EDGES
    26. var edges = Axes.Select(e => e.Select(i => pt2D[i])).ToArray();
    27. var pens = new [] {new Pen(Color.Red, 2f), new Pen(Color.Blue, 2f), new Pen(Color.Green, 2f)};
    28. for(var i = 0; i < edges.Count(); i++)
    29. g.DrawLine(pens[i], (PointF)edges[i].ToArray()[0], (PointF)edges[i].ToArray()[1]);
    30. g.FillEllipse(Brushes.Black, pt2D[0].X - 3, pt2D[0].Y - 3, 6, 6);
    31. }
    32. }

    Camera.cs

    C#-Quellcode

    1. public static class Camera
    2. {
    3. public static Point3D Position
    4. { get; set; } = new Point3D(1, 0, 0); //wird zu new Point3D(2, 2, 2) geändert
    5. public static Point3D Target
    6. { get; set; } = Point3D.Origin;
    7. public static Vector3D UpVector
    8. { get; set; } = new Vector3D(0, 1, 0);
    9. }

    ProjectionSettings.cs

    C#-Quellcode

    1. public static class ProjectionSettings
    2. {
    3. public static Size CanvasSize
    4. { get; set; } = new Size(100, 100); //wird zu pictureBox1.Size geändert (iwie 600 * 400)
    5. public static float FieldOfView
    6. { get; set; } = MathHelper.Deg2Rad(90);
    7. public static float AspectRatio
    8. { get; set; } = 1f;
    9. public static float NearPlane
    10. { get; set; } = 1f;
    11. public static float FarPlane
    12. { get; set; } = 100f;
    13. public static float Depth => FarPlane - NearPlane;
    14. }

    ToProjectionSpace

    C#-Quellcode

    1. /// <summary>
    2. /// Turns a 3DPoint into a 2DPoint based on all projection factors
    3. /// </summary>
    4. /// <param name="p">The 3DPoint in model space</param>
    5. /// <param name="dest">The destination point in world space</param>
    6. /// <param name="scale">The scaling</param>
    7. /// <param name="rot">The rotation</param>
    8. /// <returns></returns>
    9. public static ProjectedPoint ToProjectionSpace(this Point3D p, Point3D dest, Vector3D scale, Vector3D rot)
    10. {
    11. var model = GetModelMatrix(dest, scale, rot);
    12. var view = GetViewMatrix();
    13. var proj = GetProjectionMatrix();
    14. var mvp = proj * view * model;
    15. var res = mvp * p;
    16. return new ProjectedPoint(res.X, res.Y, res.Z);
    17. }
    18. /// <summary>
    19. /// Creates a model matrix based on the model vertices the entity and all transformations to be done
    20. /// </summary>
    21. /// <returns>The model matrix.</returns>
    22. private static Matrix4X4 GetModelMatrix(Point3D destPoint, Vector3D scaling, Vector3D rot)
    23. {
    24. //Model matrix
    25. var translation = Matrix4X4.CreateTranslation(destPoint.X, destPoint.Y, destPoint.Z);
    26. var scale = Matrix4X4.CreateScale(scaling.X, scaling.Y, scaling.Z);
    27. var rotation = Matrix4X4.CreateRotationX(rot.X) * Matrix4X4.CreateRotationY(rot.Y) * Matrix4X4.CreateRotationZ(rot.Z);
    28. var matrix = translation * scale * rotation;
    29. return matrix;
    30. }
    31. /// <summary>
    32. /// Creates a view (camera) matrix based on the camera position and target and the definition of "up".
    33. /// </summary>
    34. /// <returns>The view matrix.</returns>
    35. private static Matrix4X4 GetViewMatrix()
    36. {
    37. var zaxis = Point3D.Connect(Camera.Target, Camera.Position).Normalize();
    38. var xaxis = (Vector3D.Cross(Camera.UpVector, zaxis)).Normalize();
    39. var yaxis = Vector3D.Cross(zaxis, xaxis);
    40. var result = Matrix4X4.Identity;
    41. result.M11 = xaxis.X;
    42. result.M12 = yaxis.X;
    43. result.M13 = zaxis.X;
    44. result.M14 = 0.0f;
    45. result.M21 = xaxis.Y;
    46. result.M22 = yaxis.Y;
    47. result.M23 = zaxis.Y;
    48. result.M24 = 0.0f;
    49. result.M31 = xaxis.Z;
    50. result.M32 = yaxis.Z;
    51. result.M33 = zaxis.Z;
    52. result.M34 = 0.0f;
    53. result.M41 = -Vector3D.Dot(xaxis, (Vector3D)Camera.Position);
    54. result.M42 = -Vector3D.Dot(yaxis, (Vector3D)Camera.Position);
    55. result.M43 = -Vector3D.Dot(zaxis, (Vector3D)Camera.Position);
    56. result.M44 = 1.0f;
    57. return result;
    58. }
    59. /// <summary>
    60. /// Creates a perspective projection matrix based on a field of view, aspect ratio, and near and far view plane distances.
    61. /// </summary>
    62. /// <returns>The perspective projection matrix.</returns>
    63. private static Matrix4X4 GetProjectionMatrix()
    64. {
    65. var yScale = 1.0f / (float)Math.Tan(ProjectionSettings.FieldOfView * 0.5f);
    66. var xScale = yScale / ProjectionSettings.AspectRatio;
    67. var result = Matrix4X4.Identity;
    68. result.M11 = xScale;
    69. result.M12 = result.M13 = result.M14 = 0.0f;
    70. result.M22 = yScale;
    71. result.M21 = result.M23 = result.M24 = 0.0f;
    72. result.M31 = result.M32 = 0.0f;
    73. result.M33 = ProjectionSettings.FarPlane / -ProjectionSettings.Depth;
    74. result.M34 = -1.0f;
    75. result.M41 = result.M42 = result.M44 = 0.0f;
    76. result.M43 = ProjectionSettings.NearPlane * ProjectionSettings.FarPlane / -ProjectionSettings.Depth;
    77. return result;
    78. }
    Bilder
    • Problem.gif

      492,56 kB, 756×399, 101 mal angesehen
    Grundlage für die Mathematik ist diese Seite, soweit ich mich errinnern kann :whistling:
    rodrigo-silveira.com/3d-progra…ormation-matrix-tutorial/

    Die Matrix-Rechnungen an sich sollten stimmen, die Matrix4x4-Klasse stammt aus dem Framework selbst, wurde von mir nur angepasst.
    Sowas hab ich schon mal von Hand gebastelt, aber nur relativ einfach.
    Ich würde Dir da WPF empfehlen. Das macht es gaaanz einfach:

    XML-Quellcode

    1. <Border
    2. Background="#01FFFFFF"
    3. MouseDown="Viewport3D_MouseDown"
    4. MouseUp="Viewport3D_MouseUp"
    5. MouseMove="Viewport3D_MouseMove"
    6. MouseWheel="Viewport3D_MouseWheel"
    7. >
    8. <Viewport3D>
    9. <Viewport3D.Camera>
    10. <PerspectiveCamera
    11. x:Name="ViewportCamera"
    12. FarPlaneDistance="200"
    13. LookDirection="-2 2 -2"
    14. UpDirection="0,0,1"
    15. NearPlaneDistance="0.1"
    16. Position="2 -2 2"
    17. FieldOfView="70"
    18. />
    19. </Viewport3D.Camera>
    20. <ModelVisual3D>
    21. <ModelVisual3D.Content>
    22. <Model3DGroup>
    23. <!--<DirectionalLight
    24. Color="White"
    25. Direction="0,0,-1"
    26. />-->
    27. <AmbientLight Color="White"/>
    28. </Model3DGroup>
    29. </ModelVisual3D.Content>
    30. </ModelVisual3D>
    31. <!-- Hier Objekte einfügen. Z.B. einen Würfel: -->
    32. <ModelVisual3D>
    33. <ModelVisual3D.Content>
    34. <Model3DGroup>
    35. <!-- Seite Unten: -->
    36. <GeometryModel3D>
    37. <GeometryModel3D.Geometry>
    38. <MeshGeometry3D>
    39. <MeshGeometry3D.Positions>
    40. <!-- Das sind die Ecken des Würfels -->
    41. <Point3D X="0" Y="0" Z="0"/>
    42. <Point3D X="1" Y="0" Z="0"/>
    43. <Point3D X="0" Y="1" Z="0"/>
    44. <Point3D X="1" Y="1" Z="0"/>
    45. <Point3D X="0" Y="0" Z="1"/>
    46. <Point3D X="1" Y="0" Z="1"/>
    47. <Point3D X="0" Y="1" Z="1"/>
    48. <Point3D X="1" Y="1" Z="1"/>
    49. </MeshGeometry3D.Positions>
    50. <MeshGeometry3D.TriangleIndices>
    51. <!--
    52. Das sind die Indices der Punkte oben. Man muss Dreiecke bilden.
    53. Die Reihenfolge der einzelnen Indices pro Dreieck müssen stimmen, sonst sieht man die Fläche "von innen". In welcher Reihenfolge die Dreiecke stehen ist egal.
    54. -->
    55. 0 2 1, 1 2 3
    56. </MeshGeometry3D.TriangleIndices>
    57. </MeshGeometry3D>
    58. </GeometryModel3D.Geometry>
    59. <GeometryModel3D.Material>
    60. <MaterialGroup>
    61. <!--
    62. Das ist das Material dieser Seite. Diffuse Material ist das einfachste. Hat eine ganz einfache Farbe.
    63. MaterialGroup kann man sich bei einem einzigen Material sparen, aber so kann man mehrere Materialien kombinieren (z.B. SpecularMaterial).
    64. -->
    65. <DiffuseMaterial Brush="Red"/>
    66. </MaterialGroup>
    67. </GeometryModel3D.Material>
    68. </GeometryModel3D>
    69. <!--
    70. Hier weitere GeometryModel3D für die übrigen Seiten einfügen. Die Ecken sind die selben, aber die Indices sind anders:
    71. 0 1 4, 4 1 5
    72. 1 3 5, 5 3 7
    73. 3 2 7, 7 2 6
    74. 2 0 4, 2 4 6
    75. und
    76. 4 5 6, 6 5 7
    77. Natürlich macht es nur Sinn, die einzelnen Flächen in mehrere GeometryModel3D aufzuteilen, wenn man unterschiedliche Materialien haben möchte. Wenn alles ein Material ist, kann man alle Dreiecke in eine Liste packen.
    78. -->
    79. </Model3DGroup>
    80. </ModelVisual3D.Content>
    81. </ModelVisual3D>
    82. </Viewport3D>
    83. </Border>

    Und für die Kamerasteuerung noch ein bisschen Code:

    VB.NET-Quellcode

    1. Imports System.Windows.Media.Media3D
    2. Class MainWindow 'Das Fenster halt, in dem Du arbeitest.
    3. Dim HorizontalAngle As Double = (45 - 90) / 180 * Math.PI
    4. Dim VerticalAngle As Double = 45 / 180 * Math.PI
    5. Dim Zoom As Double = 5
    6. Dim MouseIsDown As Boolean = False
    7. Dim LastMousePosition As Point
    8. Public Sub New()
    9. InitializeComponent()
    10. ActualizeCamera()
    11. End Sub
    12. Private Sub Viewport3D_MouseDown(sender As System.Object, e As System.Windows.Input.MouseButtonEventArgs)
    13. MouseIsDown = True
    14. LastMousePosition = e.GetPosition(Me)
    15. End Sub
    16. Private Sub Viewport3D_MouseUp(sender As System.Object, e As System.Windows.Input.MouseButtonEventArgs)
    17. MouseIsDown = False
    18. End Sub
    19. Private Sub Viewport3D_MouseMove(sender As System.Object, e As System.Windows.Input.MouseEventArgs)
    20. If MouseIsDown Then
    21. Dim CurrentPosition = e.GetPosition(Me)
    22. Dim Delta = CurrentPosition - LastMousePosition
    23. HorizontalAngle -= Delta.X / 100
    24. VerticalAngle = Clamp(VerticalAngle + Delta.Y / 100, -Math.PI / 2, Math.PI / 2)
    25. LastMousePosition = CurrentPosition
    26. ActualizeCamera()
    27. End If
    28. End Sub
    29. Private Function Clamp(Target As Double, Min As Double, Max As Double) As Double
    30. If Target < Min Then
    31. Return Min
    32. End If
    33. If Target > Max Then
    34. Return Max
    35. End If
    36. Return Target
    37. End Function
    38. Private Sub Viewport3D_MouseWheel(sender As System.Object, e As System.Windows.Input.MouseWheelEventArgs)
    39. Dim Delta = e.Delta
    40. If Delta > 0 Then
    41. Do While Delta > 0
    42. Zoom /= 1.05
    43. Delta -= 120
    44. Loop
    45. ElseIf Delta < 0 Then
    46. Do While Delta < 0
    47. Zoom *= 1.05
    48. Delta += 120
    49. Loop
    50. End If
    51. ActualizeCamera()
    52. End Sub
    53. Private Sub ActualizeCamera()
    54. Dim Z = Math.Sin(VerticalAngle)
    55. Dim ZCos = Math.Cos(VerticalAngle)
    56. Dim X = Math.Cos(HorizontalAngle) * ZCos
    57. Dim Y = Math.Sin(HorizontalAngle) * ZCos
    58. ViewportCamera.Position = New Point3D(X * Zoom, Y * Zoom, Z * Zoom)
    59. ViewportCamera.LookDirection = New Vector3D(-X, -Y, -Z)
    60. End Sub
    61. End Class
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Okay, ich danke dir @Niko Ortner , dann werde ich mich wohl mal mit WPF befassen (ist eh schon längst überfällig).

    Magst du mir den Gefallen tun und ein Bildchen anhängen bevor ich alles bei mir anpasse, und mir erklären woher die Werte aus der ActualizeCamera-Methode kommen.
    Achja, bis jetzt habe ich ein ganzes Projekt was für die Darstellung gedacht war - sprich die AnimationLoop, sämtliche Klassen für die Mathematik (Matrix4x4, Point und Vector in 2D und 3D) etc., ist das damit alles überfällig ?
    Gerne doch:

    Im Visual Studio Designer sieht man die Standardansicht. Im Fenster hab ichs dann ca. 180° gedreht.
    Zum besseren Verständnis hab ich die Achsen eingezeichnet.

    Mit WPF fällt fast die ganze Mathematik weg, denn WPF verwendet ja die Grafikkarte, und die kann selbst 3D rendern ;)
    Du musst nur noch selbst berechnen, wo sich die Kamera befindet (Position-Property), wo sie hin schaut (LookDirection-Property) und wo bei der Kamera oben ist (UpDirection-Property), denn man kann die Kamera auch z.B. kopfüber oder schräg halten.
    Bei UpDirection hab ich ein bisschen gecheatet. Da ich festgelegt habe, dass Z positiv immer nach oben zeigen soll (man kann den Würfel nicht kopfüber anzeigen), reicht es, 0,0,1 anzugeben.
    Der Rest ist simple Trigonometrie. Horizontale Mausbewegungen verändern den horizontalen Winkel (HorizontalAngle), vertikale den vertikalen Winkel (VerticalAngle). Mit dem Mausrad wird eingestellt, wie weit die Kamera vom Mittelpunkt weg ist (Zoom). Die Berechnung in der ActualizeCamera-Methode ist eine simple Berechnung mit Kugelkorrdinaten. Ist etwas schwer in Worten zu erklären. Vielleicht hilft das: de.wikipedia.org/wiki/Kugelkoordinaten
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils