[3D] 3 dimensionalen Punkt auf ein Bildschirm projizieren

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von φConst.

    [3D] 3 dimensionalen Punkt auf ein Bildschirm projizieren

    Guten Tag liebes Forum,

    ich programmiere gerade eine Mathematik Klasse das fundamentale Methoden zur Verfügung stellt, und will nun die Projektion eines 3D Punktes auf eine Ebene(Bildschirm) implementieren_

    Ich fand diesen gelungenen Wikipedia Artikel,
    en.wikipedia.org/wiki/3D_projection
    das ich auch anfangs implementiert hatte...

    Nur gab es eben Probleme mit der World-Matrix weshalb ich hernach beschloss einfach die Projektion aus XNA zu übernehmen ( Viewport.Project).
    Nun habe ich bemerkt, dass die Methode .Project ein 3-dimensionalen Vektor zurückgibt(um vermutlich die Tiefe zu speichern).

    Relevant sind ja eigentlich die X und Y Komponente des Vektors.
    Wie repräsentiert denn nun XNA 3d in 2d?

    Muss ich etwa die X und Y Komponente des Vektors durch die Z Komponente teilen ?


    C#-Quellcode

    1. public static Vector3<T> Project(Vector3<T> point, Matrix4x4<T> projection, Matrix4x4<T> view, Matrix4x4<T> world, int width, int height, int x, int y)
    2. {
    3. Matrix4x4<T> result = (world * view) * projection;
    4. Vector3<T> transformed = result * point;
    5. return new Vector3<T>((transformed.X.ParseToNumeric<T>() + 1.0) * 0.5 * width + x, (-transformed.Y.ParseToNumeric<T>() + 1.0) * 0.5 * height + y, transformed.Z.ParseToNumeric<T>() * (1.0 - 0.0) + 0.0);
    6. }


    Die X und Y Komponente liegen im Bereich -1 und 1 und werden deswegen zwischen Ursprung und Width, respektive Height verschoben .

    Wie projiziert XNA ein Punkt im Raum auf ein Bildschirm?
    So?

    Lieben Dank!

    EDIT:
    Hier ein Beispiel:


    C#-Quellcode

    1. Vector3<double> origin = new Vector3<double>(0.1, 0.1, 50.0);
    2. Matrix4x4<double> view = Matrix4x4<double>.CreateLook(new Vector3<double>(0, 0, 5), new Vector3<double>(0, 0, 1), new Vector3<double>(0, 1, 0));
    3. Matrix4x4<double> projection = Matrix4x4<double>.CreateProjection(Math.PI / 4.0, panel1.Width / panel1.Height, 1.0, 500.0);
    4. Vector3<double> projected = Matrix4x4<double>.Project(origin, projection, view, Matrix4x4<double>.Identity(), panel1.Width, panel1.Height, 0, 0);
    5. pt0 = new PointF((float)projected.X, (float)projected.Y);


    Origin ist bei (0.1, 0.1, 250.0)
    dann wird Origin verschoben auf
    (0.1, 0.1, 500) Resultat: Identische Bildschirmkoordinaten.
    Wie kann die Tiefe projiziert werden?
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „φConst“ ()

    Hi
    die von dir gepostete Project-Methode macht folgendes:
    Der Punkt P soll projeziert werden
    - (transformed.X|Y.ParseToNumeric<T>() + 1.0) * 0.5: Die Eingabe wird im (sichtbaren) Intervall von [-1, 1] auf das Intervall von [0, 1] gemappt
    - * width bzw. * height: Skaliere auf die angegebenen Bildschirmabmessungen
    - + x bzw. + y: Verschiebe noch um die x- und y-Koordinate
    Bei Z geschieht Quatsch, weil warum sollte man (1.0 - 0.0) multiplizieren (ist ja 1.0) und 0.0 aufaddieren, aber im Prinzip wird die Z-Koordinate nur übernommen.

    Es handelt sich insofern lediglich um eine simple Orthogonalprojektion, die idR. insbesondere der perspektivischen Projektion gegenübersteht. Schau' dir mal diesen Link an. Dort wird das mathematisch gut beschrieben. Bei der Orthogonalprojektion wird die Z-Koordinate einfach abgeschnitten, d.h. die Projektion von (x, y, z) ergibt (x, y). Bei der perspektivischen Projektion wird dann noch ein Z-Anteil eingerechnet.

    Häufig verzichtet man übrigens auf die Matrizendarstellung für Transformationen und greift gleich auf Quaternionen zur Rotation und Vektoren zur Translation bzw. Skalierung zurück.

    Viele Grüße
    ~blaze~
    das Z *(1.0 - 0.0) + 0.0 entspricht Z * (maxDepth - minDepth) + minDepth aus der dekompilierten Viewport Klasse des MonogameFrameworks.

    Ist dein Link äquivalent zu en.wikipedia.org/wiki/3D_projection#Perspective_projection ?
    Macht nämlich Sinn.

    EDIT:

    Ich habe nun den Wikipedia Eintrag folgendergestalt implementiert:

    C#-Quellcode

    1. public static Vector2<T> Project(Vector3<T> a, Vector3<T> c, Vector3<T> o, Vector3<T> e)
    2. {
    3. Vector3<T> d = (Matrix4x4<T>.CreateRotationX(o.X.ParseToNumeric<T>()) *
    4. Matrix4x4<T>.CreateRotationY(o.Y.ParseToNumeric<T>()) *
    5. Matrix4x4<T>.CreateRotationZ(o.Z.ParseToNumeric<T>())) * (a - c);
    6. var eX = e.X.ParseToNumeric<T>();
    7. var eY = e.Y.ParseToNumeric<T>();
    8. var eZ = e.Z.ParseToNumeric<T>();
    9. var dX = d.X.ParseToNumeric<T>();
    10. var dY = d.Y.ParseToNumeric<T>();
    11. var dZ = d.Z.ParseToNumeric<T>();
    12. Vector4<T> f = new Matrix4x4<T>(1.0, 0, -(eX / eZ), 0,
    13. 0, 1.0, -(eY / eZ), 0,
    14. 0, 0, 1.0, 0,
    15. 0, 0, 1.0 / eX, 0) * new Vector4<T>(dX, dY, dZ, 1.0);
    16. var fX = f.X.ParseToNumeric<T>();
    17. var fY = f.Y.ParseToNumeric<T>();
    18. var fW = f.W.ParseToNumeric<T>();
    19. var bX = fX / fW;
    20. var bY = fY / fW;
    21. return new Vector2<T>(bX, bY);
    22. }


    Erhalte jedoch für den Vektor (2,1, 0) NaN was Sinn macht, denn die Z-Komponente ist ja 0 und durch diese wird dividiert.
    Wie kommen die auf (2,1)?!
    Ahso, haha, ich muss prüfen.
    Falls c = o = 0 dann einfach a.xy = b

    Wie projiziert denn XNA?
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „φConst“ ()

    Dein im 1. Posting geposteter Code projiziert ebenfalls über perspektivische Projektion. Das lässt sich über Near/Far plane und Field of View festmachen. Der Wikipediaartikel zeigt das ebenfalls, jep.
    Die Matrix brauchst du übrigens gar nicht, die macht das Verfahren höchstens ineffizient. Rechne es am besten einfach direkt. Wenn du die World-View-Projection-Matrix hast, ist das was anderes.

    Hast du btw. in der Matrix nicht einen Fehler? Sollte es nicht 1.0 / eZ sein, statt 1.0 / eX?

    Btw. warum hast du eigentlich die ParseToNumeric-Methode eingebaut?
    Das mit der Rotation ist mir auch etwas suspekt. ;)

    Viele Grüße
    ~blaze~
    Hi, danke vielmals für deine Antworten.

    Also,
    die Position wird nun direkt berechnet:

    C#-Quellcode

    1. public static Vector2<T> Project(Vector3<T> a,
    2. Vector3<T> c,
    3. Vector3<T> o,
    4. Vector3<T> e,
    5. int width,
    6. int height)
    7. {
    8. if (c == new Vector3<T>(0, 0, 0) && o == new Vector3<T>(0, 0, 0))
    9. return new Vector2<T>(a.X, a.Y);
    10. a = new Vector3<T>(-a.X.ParseToNumeric<T>() / width, a.Y.ParseToNumeric<T>() / height, a.Z);
    11. Vector3<T> d = (Matrix4x4<T>.CreateRotationX(o.X.ParseToNumeric<T>()) *
    12. Matrix4x4<T>.CreateRotationY(o.Y.ParseToNumeric<T>()) *
    13. Matrix4x4<T>.CreateRotationZ(o.Z.ParseToNumeric<T>())) * (a - c);
    14. var eX = e.X.ParseToNumeric<T>();
    15. var eY = e.Y.ParseToNumeric<T>();
    16. var eZ = e.Z.ParseToNumeric<T>();
    17. var dX = d.X.ParseToNumeric<T>();
    18. var dY = d.Y.ParseToNumeric<T>();
    19. var dZ = d.Z.ParseToNumeric<T>();
    20. //Vector4<T> f = new Matrix4x4<T>(1.0, 0, -(eX / eZ), 0,
    21. // 0, 1.0, -(eY / eZ), 0,
    22. // 0, 0, 1.0, 0,
    23. // 0, 0, 1.0 / eZ, 0) * new Vector4<T>(dX, dY, dZ, 1.0);
    24. //var fX = f.X.ParseToNumeric<T>();
    25. //var fY = f.Y.ParseToNumeric<T>();
    26. //var fW = f.W.ParseToNumeric<T>();
    27. //var bX = fX / fW;
    28. //var bY = fY / fW;
    29. var bX = (eZ / dZ) * dX - eX;
    30. var bY = (eZ / dZ) * dY - eY;
    31. return new Vector2<T>((bX + 2.0) * 0.5 * width, (bY + 2.0) * 0.5 * height);
    32. }


    Wenn ich nun eine ZRotation auf einen Punkt anwenden und diese dann projiziere klappt alles wunderbar.
    Sobald jedoch eine XRotation, oder YRotation appliziert wird sieht das sehr ominös aus....


    Hier die Methoden für die Rotation:

    C#-Quellcode

    1. public static Matrix4x4<T> CreateRotationX(double angle)
    2. {
    3. return new Matrix4x4<T>
    4. (
    5. 1.0, 0.0, 0.0, 0.0,
    6. 0.0, Math.Cos(angle), Math.Sin(angle), 0.0,
    7. 0.0, -Math.Sin(angle), Math.Cos(angle), 0.0,
    8. 0.0, 0.0, 0.0, 1.0
    9. );
    10. }
    11. public static Matrix4x4<T> CreateRotationY(double angle)
    12. {
    13. return new Matrix4x4<T>
    14. (
    15. Math.Cos(angle), 0.0, -Math.Sin(angle), 0.0,
    16. 0.0, 1.0, 0.0, 0.0,
    17. Math.Sin(angle), 0.0, Math.Cos(angle), 0.0,
    18. 0.0, 0.0, 0.0, 1.0
    19. );
    20. }
    21. public static Matrix4x4<T> CreateRotationZ(double angle)
    22. {
    23. return new Matrix4x4<T>
    24. (
    25. Math.Cos(angle), Math.Sin(angle), 0.0, 0.0,
    26. -Math.Sin(angle), Math.Cos(angle), 0.0, 0.0,
    27. 0.0, 0.0, 1.0, 0.0,
    28. 0.0, 0.0, 0.0, 1.0
    29. );
    30. }


    ParseToNumeric<T>() erlaubt es mit T mathematische Operationen durchzuführen.
    Das Projekt im Anhang ( nicht kompiliert).

    Interessant ist die Methode

    C#-Quellcode

    1. float i = 0.0f;
    2. private IEnumerable<PointF> ProjectToPointF(List<Vector3<float>> point3d)
    3. {
    4. i += 0.01f;
    5. foreach(var pt in point3d)
    6. {
    7. // statt CreateRotationZ(i) CreateRotationX oder CreateRotationY generiert sehr suspekte Rotation
    8. var ptn = Matrix4x4<float>.CreateRotationZ(i) * pt;
    9. Vector2<float> a = Matrix4x4<float>.Project(ptn, new Vector3<float>(0, 0, 1), new Vector3<float>(0, 0.1f, 0), new Vector3<float>(1, 1, 1), panel1.Width, panel1.Height);
    10. yield return new PointF(a.X, a.Y);
    11. }
    12. }
    in TestFrm.cs

    Wird CreateRotationZ durch CreateRotationX oder CreateRotationY ersetzt, erscheint die Rotation sehr falsch.
    Wie projiziert denn XNA ein Vertex auf den Bildschirm?

    EDIT:
    Folgendes Resultat wenn um X Achse rotiert wird:
    Von
    zu
    Dateien
    Und Gott alleine weiß alles am allerbesten und besser.

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „φConst“ ()

    Ist vielleicht noch ein Skalierungsfaktor erforderlich, um die unterschiedlichen, und damit nicht streckengleichen, vertikalen und horizontalen Auflösungen des Bildschirms auszugleichen?
    Naja, in XNA ist die maximale Länge - wenn um X oder Y rotiert wird - Eins ( das dann beliebig skaliert wird).
    Anders als bei meiner Implementierung: Die maximale Länge variiert zwischen width und height.
    Und Gott alleine weiß alles am allerbesten und besser.
    Die maximale Länge von was? Von der Ausgabe? Und von was in der Ausgabe?
    Wenn du die Multiplikation mit width und height rausnimmst, liegt der Wert zwischen 0 und 1. Das ist dann aber keine typische Viewport-Projektion.

    Mit eigenen Shadern hast du die Möglichkeit, ein eigenes Konzept aufzuziehen und bist nicht mehr an die World-View-Projection-Geschichte gebunden, wenn du auf sie verzichten möchtest. Du kannst das lösen, wie es dir beliebt, nur solltest du halt korrekte Resultate erzielen. Im Vertexshader würdest du bspw. eben die WVP-Matrix auf jeden Vektor multiplizieren. Der Viewport muss das eben dann genauso implementieren, wie es in den Shadern gemacht wird, sonst ist das Ergebnis wohl nicht das gewünschte. Im Viewport wird außerdem noch eine Skalierung auf width und height durchgeführt und vmtl. die Verschiebung um X und Y, da sonst das Ergebnis eben nicht bildschirmspezifisch vorliegt.
    Ich verstehe aber auch nicht, wo genau dein Problem liegt.

    Viele Grüße
    ~blaze~
    @~blaze~
    Das Problem ist folgendes:

    Gegeben seien drei Punkte:
    0,0,2
    1,0,2
    0,1,2

    Und folgende Matrizen:
    View: Matrix.CreateLookAt(new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 1, 0))
    Projection: Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 0.1f, 1000.0f)
    World: Matrix.Identity

    Werden diese nun gezeichnet resultieren folgende Szenen:
    Meine Implementierung:




    Wie es auszusehen hat:


    Deswegen wundere ich mich...
    Welche Skalations Matrix wird denn da nur angewandt?
    Und wenn die Punkte außerhalb der Kamera liegen, werden diese bei XNA nicht gezeichnet,
    bei meiner Implementierung wird einfach die Figur gespiegelt.

    Muss etwa manuell geprüft werden, ob ein Punkt innerhalb der Kamera liegt?

    Lieben dank für deine Beiträge ( =
    Und Gott alleine weiß alles am allerbesten und besser.
    Also an sich clippt DirectX die unsichtbaren Teile von Faces von sich aus. An sich musst aber du die zu zeichnenden Elemente einschränken, d.h. all jene Elemente möglichst effizient möglichst stark entfernen, die nicht sichtbar sind. Bspw. sind Binary Space Partitioning oder kd-Trees Algorithmen, die ganz gut dafür sind. Eine einfache Methode, sowas zu implementieren, sind bspw. Octrees, die sind aber eher weniger effizient für allgemeine Szenarien (BSP ist eine Verallgemeinerung von kd-Trees, kd-Trees kann man als Verallgemeinerung von Octrees verstehen). Wenn du BSP implementiert hast, kannst du's außerdem auch gleich für Kollisionen verwenden.

    Es ist bisschen schwer, das zu beurteilen, aber ich denke, dass bei deiner Implementierung die Multiplikation mit width und height noch fehlt (bzw. width/2 und height/2)? Du dividierst am Anfang mal durch die beiden, aber der Raum an sich sollte eigentlich auf 1 normiert sein, oder?

    Viele Grüße
    ~blaze~
    Ne, ist einkalkuliert:

    C#-Quellcode

    1. public static Vector2<T> Project(Vector3<T> source, Matrix4x4<T> world, Matrix4x4<T> view, Matrix4x4<T> projection, int width, int height)
    2. {
    3. Vector4<T> worldPosition = world * new Vector4<T>(source.X.ParseToNumeric<T>(), source.Y.ParseToNumeric<T>(), source.Z.ParseToNumeric<T>(), 1);
    4. Vector4<T> viewPosition = view * worldPosition;
    5. Vector4<T> position = projection * viewPosition;
    6. var X = position.X.ParseToNumeric<T>();
    7. var Y = position.Y.ParseToNumeric<T>();
    8. var Z = position.Z.ParseToNumeric<T>();
    9. return new Vector2<T>(((X / Z) * width / 2) + width / 2, ((Y / Z) * height / 2) + height / 2);
    10. }
    Und Gott alleine weiß alles am allerbesten und besser.
    Ggf. solltest du übrigens angle zu radians umbenennen oder umrechnen.

    Was mich irritiert sind die folgenden Dinge:

    VB.NET-Quellcode

    1. a = new Vector3<T>(-a.X.ParseToNumeric<T>() / width, a.Y.ParseToNumeric<T>() / height, a.Z);

    Soll das so sein?
    Außerdem

    VB.NET-Quellcode

    1. return new Vector2<T>((bX + 2.0) * 0.5 * width, (bY + 2.0) * 0.5 * height);

    Müsste es nicht 1.0 statt 2.0 sein, sodass das in die Mitte wandert? Es wäre sonst doch ganz rechts, oder?

    VB.NET-Quellcode

    1. var bX = (eZ / dZ) * dX - eX;
    2. var bY = (eZ / dZ) * dY - eY;

    Warum genau hast du da eigentlich * -1 gerechnet? Sollte es nicht eX - (eZ / dZ) * dX, usw. lauten? Ich kann gerade leider nur kurz drüberschauen.

    Viele Grüße
    ~blaze~
    Hey Leute vielen Dank,
    Wieso irritiert es dich?

    Jede Struktur verlangt ein Typ-Parameter.
    Um mit den Typ-Parameter zu rechnen, wird der Typ-Parameter in sein jeweiligen Typ konvertiert( double, float, integer et cetera) und mittels dynamic Rückgabewert dann dynamisch in ein Datentyp konvertiert.
    "Müsste es nicht 1.0 statt 2.0 sein, sodass das in die Mitte wandert? Es wäre sonst doch ganz rechts, oder?"
    Der Wert liegt im Intervall -1 und 1.
    Wenn ich um 1.0 addieren liegt es zwischen 0 und 2.
    Problem: Wenn nun (0,0,0) projiziert werden soll, wird der Vektor (0,0) statt (width/2, height/2) zurückgegeben.

    "Warum genau hast du da eigentlich * -1 gerechnet? Sollte es nicht eX - (eZ / dZ) * dX, usw. lauten? Ich kann gerade leider nur kurz drüberschauen."
    Weil
    Und Gott alleine weiß alles am allerbesten und besser.