[3D] Clipping UV-Probleme

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

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von jvbsl.

    [3D] Clipping UV-Probleme

    Hallo,

    ich entwickle im Moment eine GDI+ 3D-Engine.
    Funktioniert _relativ gut, auch Clipping et cetera wurden bereits implementiert.

    Der Ansatz für Clipping war folgender:

    I.) Prüfe ob Vertex hinter der Kamera ist ( einfache Planar-Rechnungen, á la, wenn lambda negativ ist dann ja)
    II.) wenn ja => ziehe von sichtbaren Vertizes eine Gerade zum unsichtbaren Vertex, bis Kollision mit Ebene.
    wenn kollidiert, merke neue Vertex-Position, projiziere auf Bildschirm. Es entsteht nun ein Quadrangel, ( was logisch ist, schneidet man ein Dreieck an einer Ecke ab, erhält man ein Trapez)
    II.) Wenn nein, alle drei Vertizes direkt projizieren.


    Das Resultat ist dann exakt dasselbe wie bei MonoGame, je näher man ans Dreieck heranrückt, desto größer werden die Winkel zwischen den jeweiligen Vertizes.

    Problem nun:
    Die Engine soll _ Texturen auf Dreiecke mappen können.
    Die Dreiecke, respektive die Polygone( man darf Sonderfall Trapez nicht vergessen), liegen projiziert vor.
    Um Texturen auf Dreiecke mappen zu können greift man natürlich auf baryzentrische Koordinate zurück; geht auch sicherlich gut, bis eben der Spieler
    zu nah an eine Ecke rückt, sodass ein Trapez zurückgegeben wird.

    Für diesen Fall hab ich mir überlegt, beim Sonderfall Trapez einfach erneut zwei Geraden zu generieren( aber diesmal eben nicht im Raum, sondern Bildschirm-Koordinaten), und sie gleichzusetzen ( einfache lineare Algebra).

    Code:

    C#-Quellcode

    1. public float Slope { get; private set; }
    2. public float YIntersection { get; private set; }
    3. public AlgebraicRay(Vector2 a, Vector2 b)
    4. {
    5. float dx = b.X - a.X;
    6. float dy = b.Y - a.Y;
    7. Slope = dy / dx;
    8. YIntersection = a.Y - Slope * a.X;
    9. }
    10. public float f(float x)
    11. {
    12. return Slope * x + YIntersection;
    13. }
    14. public Vector2 Intersects(AlgebraicRay a)
    15. {
    16. /*
    17. *
    18. * mx+b=mx+b
    19. */
    20. float m0 = Slope;
    21. float m1 = a.Slope;
    22. float b0 = YIntersection;
    23. float b1 = a.YIntersection;
    24. float rM = m1 - m0;
    25. float rB = b0 - b1;
    26. float x = rB / rM;
    27. return new Vector2(x, f(x));
    28. }


    Funktioniert soweit gut bis obige Konsequenz ( je näher man ans Dreieck heranrückt, desto größer werden die Winkel zwischen den jeweiligen Vertizes) eintritt.
    Wenn nämlich die Winkel zu groß werden, gehen die Geraden auseinander ( siehe Bilder unten), und schneiden sich dann im falschen ( eigentlich richtigen) Punkt.

    Korrekte Darstellung ( das gefüllte Polygon ist das Trapez, die beiden Linien das fortzuführende Dreieck):


    Inkorrekte Darstellung:


    Besser erklärt durch folgende Situation:


    Die Winkel sind einfach so groß, das quasi die Vorzeichen sich ändern, und sich (bildschirmkoordinaten-technisch) im Negativen Bereich treffen.

    Habe nach 3D-Baryzentrischen Koordinaten gesucht, aber nicht fündig geworden.
    Wüsstet ihr eine Lösung?


    Lieben Dank!

    _
    Und Gott alleine weiß alles am allerbesten und besser.

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

    ich seh nicht ganz das Problem für baryzentrische Koordinaten?
    Das einzige was ich mir vorstellen kann ist, dass es bei dir Perspektivisch noch nicht passt :P
    scratchapixel.com/lessons/3d-b…olation-vertex-attributes

    Du behandelst es trotzdem im projezierten Zustand, musst dir jedoch die ursprünglichen Koordinaten natürlich merken.

    Für das Clipping ist ein Trapez etwas ungeschickt->Unterteile ein Trapez in zwei Dreiecke und berechne an den Eckpunkten(da wo geclippt wird) dieser neuen Dreiecke interpolierte Koordinaten über perspektivisch korrekte baryzentrische Interpolation ausgehend vom ungeclipptem Dreieck.
    Am Ende musst du damit beim rasterisieren keinerlei Sonderbehandlungen machen und kannst ganz normal mit Dreiecken weiter machen...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    NearPlane ist auf 5.0, deswegen sieht das so komisch aus, desweiteren ist FOV PI/2 .
    Ansonsten: Gute Idee, schaue mir das _mal an.

    Edit:
    "Unterteile ein Trapez in zwei Dreiecke und berechne an den Eckpunkten(da wo geclippt wird) dieser neuen Dreiecke interpolierte Koordinaten über perspektivisch korrekte baryzentrische Interpolation ausgehend vom ungeclipptem Dreieck." Wie denn?
    Das geclippte Dreieck ist deswegen geclippt, weil eine oder zwei Vertizes außerhalb des Frustum liegen. ( andernfalls wird falsch projiziert, stattdessen werden einfach die neuen Eckpunkte die innerhalb des Frustums liegen projiziert).

    Ich kenne_ bereits Formeln zur Berechnung der Gewichte, sind aber alle auf 2D Vertizes Koordinaten ausgelegt.
    Wenn ich diese fehlende Ecke( und Ecken) in Bildschirmkoordinaten überführen könnte, wäre das ja gut, Problem: Sie liegen hinter der Kamera und deswegen
    werden sie falsch projiziert.
    Apropos projiziert, ich nutze MonoGame-Klassen( Matrix, Vector, Maths, Viewport)



    Lieben Dank ^^
    Und Gott alleine weiß alles am allerbesten und besser.

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

    Nunja direkt im clipping Prozess hast du doch alle Vertices des Dreiecks, also auch die, die geclippt werden. Und die Koordinaten des dadurch entstehenden Trapezes hast du ja auch

    von v1/v2/v3 hast du UV Koordinaten, die Screen-Space koordinaten(mit auf jeden fall auch w).

    v4 und v5 lassen sich dann ganz einfach berechnen, das gilt für u/v und w gleichermaßen.

    Ich weiß nicht ob ich das alles noch richtig im Kopf hab, aber du projezierst und hast am Ende z als Depth, mit welchem du clippst, richtig?

    Du stellst ja anscheinend auch schon fest, was geclippt wird. Weißt also, dass v3 geclippt wird v1/v2 aber nicht.
    Jetzt müssen wir v4/v5 berechnen:
    Wir erzeugen erstmal nen Ray:

    Quellcode

    1. r1 = v3+(v1-v3)*t

    Und wir müssen das so auflösen, dass gilt:

    Quellcode

    1. r1 = clippingplane
    2. clippingplane.z = 5//dein wert von 5
    3. t = (clippingplane.z-v3.z)/(v1.z-v3.z)

    das clippingplane ist ja fest gegeben, deshalb sollte das kein Problem sein

    danach ist es ganz einfach um an die neuen Werte von v4 zu kommen:

    Quellcode

    1. v4 =v3 + (v1-v3)*t

    Das gilt außerdem nicht nur für die 3D-Koordinaten(Achtung w nicht vergessen, w ist für projektivisch korrektes TextureMapping sehr wichtig), sondern auch für die UV Koorrdinaten.

    Kleine Bemerkung: Das sind auch quasi baryzentrishe Koordinaten, aber da jeweils eine Koordinate immer eine Gewichtung von 0 bekommt, ist es eine einfache linear Interpolation zwischen zwei Punkten ;)

    Dasselbe kannst du für v2/v3 machen um an v5 zu kommen. Damit hast du von v4/v5 alle nötigen Dinge berechnet um das Dreieck(v1/v2/v5) und (v1/v4/v5) zu erstellen.
    Erstmal würde ich die Triangulation auf diese Art statisch machen, später könnte man das dynamisch abhängig von den Trapezwinkeln machen, braucht man glaub ich um Rundundgsfehler zu vermeiden(aber erstmal nicht so wichtig)...


    Hier kommt dann der ganze Part, der für alle Dreiecke gilt, sollte also auch auf die Dreiecke oben Anwendbar sein ;)

    Sollten baryzentrische Koordinaten in 3D nicht so lösbar sein?

    Quellcode

    1. v1.x*a1 + v2.x*a2 + v3.x*a3 = p.x
    2. v1.y*a1 + v2.y*a2 + v3.y*a3 = p.y
    3. v1.z*a1 + v2.z*a2 + v3.z*a3 = p.z
    4. a1 + a2 + a3 = 1

    Also einfach nach a1/a2/a3 auflösen. Ist ja ein überbestimmtes lineares Gleichungssystem.(Keine Anhung ob das überhaupt so funktioniert)

    Aber ich meine mich zu erinnern, dass du die Interpolation einfach in 2D Screenspace machen kannst unter der Berücksichtigung von w.

    D.h. du homogenisierst erst deine Koordinaten:

    Quellcode

    1. p'.x = p.x / p.w
    2. p'.y = p.y / p.w
    3. p'.z = p.z / p.w
    4. p'.w = 1 / p.w // Achtung nicht p.w/p.w(==1), das hier wird verwendet um später wieder zu "dehomogenisieren"(?/enthomogenisieren? kp^^)
    5. p'.u = p.u / p.w
    6. p'.v = p.v / p.w

    Dann berechnest du jetzt die baryzentrischen Koordinaten:

    Quellcode

    1. v1.x*a1 + v2.x*a2 + v3.x*a3 = p.x
    2. v1.y*a1 + v2.y*a2 + v3.y*a3 = p.y
    3. a1 + a2 + a3 = 1

    müsstest dir halt einmal auflösen nach a1/a2/a3 und im Code verewgien.

    Und nun wird das ganze wieder dehomogenisiert, wobei wir im Prinzip dasselbe machen, wie beim homogenisieren(Merke p.w ist hier ausgehend von den ursprünglichen Koordinaten 1/w, somit ist das neue w==1/(1/w) und somit wieder das ursprüngliche):

    Quellcode

    1. p'.x = p.x / p.w
    2. p'.y = p.y / p.w
    3. p'.z = p.z / p.w
    4. p'.w = 1 / p.w
    5. p'.u = p.u / p.w
    6. p'.v = p.v / p.w

    das dürfte hoffentlich eine perspektivisch korrekte baryzentrische Interpolation ergeben

    Noch eine Anmerkung:
    Falls du außerdem kein w hast, dann hast du etwas falsch gemacht, denn eigentlich arbeitet man intern mit Vector4:
    Wenn du einen Vector3 mit einer Matrix4x4 transformieren möchtest machst du ja folgendes:

    Quellcode

    1. transformed = matrix*vector3 // das hier geht nicht
    2. vector4 = (vector3.x,vector3.y,vector3.z, 1)
    3. transformed = matrix*vector4

    transformed ist somit auch ein vector4 und hat ein w, welches du wie oben gezeigt verwendest.

    Dieser Beitrag ist zusammengefischt und geschustert aus gefährlichem Halbwissen, an welches ich mich noch erinnern kann, sowie etwas Google und eigenen Überlegungen. Deshalb nicht alles für bare Münze nehmen. Sollte sich jedoch herausstellen, dass das ganze irendwie richtig ist, dann finde ich wäre es fast Wert diesen Beitrag irgendwo anzupinnen, weil mMn findet man zu den Sachen sogar recht wenig in Englisch^^(Zumindest so, dass es nicht nur rein Wissenschaftlich ist)
    Bilder
    • clipping.png

      7,42 kB, 640×400, 661 mal angesehen
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    V4 und V5 werden bereits erfolgreich berechnet.
    Das Clipping ist ja kein Problem.

    Was ein Problem ist, ist, dass wenn ein Vertex geclippt wird, weil es hinter der Kamera ist, es nicht mehr möglich ist, seine Bildschirm-Koordinate zu bestimmen.
    Denn um die Bildschirm-Koordinaten eines Vertex' zu bestimmen muss sie projiziert werden, wenn sie hinter der Kamera ist, wird sie falsch projiziert.

    In der Tat krieg ich bei der Projektion den Z-Depth, der interessiert für das Clipping aber nicht, denn ich gehe folgendermaßen vor:

    Die Kamera ist statisch (Position immer 0,0,0), außer dem Richtungsvektor ändert sich an der Kamera nichts.
    Wenn der Spieler sich bewegt, werden stattdessen alle Dreiecke quasi zu ihm zu bewegt.

    Ich generiere _ nun über die Inverte der Sicht-Matrix die Senkrechte der Ebene, ( sie liegt also in Koordinatenform vor).
    Nun werden die Vertizes transformiert und geprüft : Liegen sie hinter der Ebene - die den Bildschirm quasi repräsentiert - oder vor ?
    Wenn hinter, dann wird ein Ray von sichtbaren Vertizes zu unsichtbaren gezogen, und geprüft: Wann kollidiert es mit der Ebene ( dem Bildschirm).
    Die Punkte werden gespeichert und diese werden projiziert: Ihre Z-Werte sind definitiv > 1, weil eben vor der Kamera.

    Die Idee funktioniert, aber : Macht man das *auch* so? :D

    Alles jut, Problem aber:
    Trapez.

    Das Trapez sieht man ja auch in _deiner schönen Illustration, sie sind aber im Grunde Punkte der Geraden zwischen V1V3 und V2V3.
    Bei mir liegen sie bereits projiziert vor, ich brauche ja aber für baryzentrische Koordinaten die drei Eckpunkte des Dreiecks: Nun das Problem: V3 kann nicht projiziert werden, somit kann
    das Dreieck nicht vollständig abgebildet werden ( deswegen ja clipping), und es fehlt Punkt B(V3) in folgender Gleichung :
    computergraphics.stackexchange.com/a/1867

    Ein w hab ich nicht, das verlangt auch nicht Viewport.Project ^^

    "Hier kommt dann der ganze Part, der für alle Dreiecke gilt, sollte also auch auf die Dreiecke oben Anwendbar sein"
    Stimmt, Dreieck bleibt Dreieck ...


    Hier der Code für Ebene:

    C#-Quellcode

    1. public struct Plane
    2. {
    3. public Vector3 Base { get; set; }
    4. public Vector3 Direction0 { get; set; }
    5. public Vector3 Direction1 { get; set; }
    6. public Vector3 Perpendicular { get; set; }
    7. public Plane(Vector3 a, Vector3 b, Vector3 c)
    8. : this()
    9. {
    10. Base = a;
    11. Direction0 = b - a;
    12. Direction1 = c - a;
    13. Perpendicular = Direction0.Cross(Direction1);
    14. }
    15. public bool Intersects(Ray a, out Ray p)
    16. {
    17. if (a.Direction * Perpendicular == 0)
    18. {
    19. p = new Ray();
    20. return false;
    21. }
    22. float d = Perpendicular * (-Base);
    23. float c0, c1, c2;
    24. float a0, a1, a2;
    25. float n0, n1, n2;
    26. c0 = Perpendicular.X;
    27. c1 = Perpendicular.Y;
    28. c2 = Perpendicular.Z;
    29. a0 = a.Base.X;
    30. a1 = a.Base.Y;
    31. a2 = a.Base.Z;
    32. n0 = a.Direction.X;
    33. n1 = a.Direction.Y;
    34. n2 = a.Direction.Z;
    35. float r = -(-d + c0 * a0 + c1 * a1 + c2 * a2) / (n0 * c0 + n1 * c1 + n2 * c2);
    36. a.Lambda = r;
    37. p = a;
    38. return true;
    39. }
    40. public bool IsVectorBehind(Vector3 p, Func<float, bool> f)
    41. {
    42. Ray ray = new Ray(p, Perpendicular);
    43. Intersects(ray, out ray);
    44. return f(ray.Lambda);
    45. }
    46. }


    Hier für Clipping:

    C#-Quellcode

    1. private List<TriangleProjected> clipTriangle()
    2. {
    3. Vector3 clipA = new Vector3(0, 1, 0);
    4. Vector3 clipB = new Vector3(1, 0, 0);
    5. Vector3 clipC = new Vector3(0, 0, 0);
    6. Matrix m = Matrix.CreateTranslation(0, 0, Surface.Screen.Viewport.NearPlane) * currentEffect.View.Invert();
    7. planeClipping = new Plane(Vector3.Transform(clipC, m), new Vector3(), new Vector3());
    8. planeClipping.Perpendicular = Vector3.Transform(clipA.Cross(clipB), m);
    9. int n = 0;
    10. List<TriangleProjected> triangles = new List<TriangleProjected>();
    11. foreach (var vertices in currentVertexCollector.VerticesCollection)
    12. {
    13. TriangleProjected t0 = new TriangleProjected();
    14. triangles.Add(t0);
    15. for (int i = 0; i < vertices.Length; i++)
    16. {
    17. vertices[i].Position = currentEffect.ManipulateVertex(vertices[i].Position);
    18. }
    19. Vector3 a = Vector3.Transform(vertices[0].Position, currentEffect.World);
    20. Vector3 b = Vector3.Transform(vertices[1].Position, currentEffect.World);
    21. Vector3 c = Vector3.Transform(vertices[2].Position, currentEffect.World);
    22. bool ca, cb, cc;
    23. ca = planeClipping.IsVectorBehind(a, r => r < 0);
    24. cb = planeClipping.IsVectorBehind(b, r => r < 0);
    25. cc = planeClipping.IsVectorBehind(c, r => r < 0);
    26. if (ca && cb && cc)
    27. continue;
    28. Dictionary<short, Vector3> clippingPoints = new Dictionary<short, Vector3>();
    29. short index = 0;
    30. if (ca)
    31. {
    32. if (!cb)
    33. {
    34. Ray ray = new Ray(b, b - a);
    35. planeClipping.Intersects(ray, out ray);
    36. clippingPoints.Add(index++, ray.Position);
    37. }
    38. if (!cc)
    39. {
    40. Ray ray = new Ray(c, c - a);
    41. planeClipping.Intersects(ray, out ray);
    42. clippingPoints.Add(index++, ray.Position);
    43. }
    44. }
    45. else
    46. {
    47. var p0 = currentEffect.Stream(a);
    48. triangles[n].Colors.Add(vertices[0].Color);
    49. triangles[n].Points.Add(new PointF(p0.X, p0.Y));
    50. }
    51. if (cb)
    52. {
    53. if (!ca)
    54. {
    55. Ray ray = new Ray(a, a - b);
    56. planeClipping.Intersects(ray, out ray);
    57. clippingPoints.Add(index++, ray.Position);
    58. }
    59. if (!cc)
    60. {
    61. Ray ray = new Ray(c, c - b);
    62. planeClipping.Intersects(ray, out ray);
    63. clippingPoints.Add(index++, ray.Position);
    64. }
    65. }
    66. else
    67. {
    68. var p1 = currentEffect.Stream(b);
    69. triangles[n].Colors.Add(vertices[1].Color);
    70. triangles[n].Points.Add(new PointF(p1.X, p1.Y));
    71. }
    72. if (cc)
    73. {
    74. if (!ca)
    75. {
    76. Ray ray = new Ray(a, a - c);
    77. planeClipping.Intersects(ray, out ray);
    78. clippingPoints.Add(index++, ray.Position);
    79. }
    80. if (!cb)
    81. {
    82. Ray ray = new Ray(b, b - c);
    83. planeClipping.Intersects(ray, out ray);
    84. clippingPoints.Add(index++, ray.Position);
    85. }
    86. }
    87. else
    88. {
    89. var p2 = currentEffect.Stream(c);
    90. triangles[n].Colors.Add(vertices[2].Color);
    91. triangles[n].Points.Add(new PointF(p2.X, p2.Y));
    92. }
    93. clippingPoints = clippingPoints.OrderByDescending(p => p.Key).ToDictionary(x => x.Key, y => y.Value);
    94. for (int i = 0; i < clippingPoints.Count; i++)
    95. {
    96. Vector3 projected = currentEffect.Stream(clippingPoints.Values.ElementAt(i));
    97. triangles[n].Points.Add(new PointF(projected.X, projected.Y));
    98. }
    99. triangles[n].Score = (a + b + c).Length();
    100. clippingPoints.Clear();
    101. n++;
    102. }
    103. return triangles.OrderByDescending(p => p.Score).ToList();
    104. }


    Die Viematrix :

    C#-Quellcode

    1. ​ ViewMatrix = Matrix.CreateLookAt(new Vector3(), Direction, new Vector3(0, 1, 0));


    Einfach mal Code posten, damit man versteht was ich meine.
    Aber auf jeden Fall hilft dein Beitrag, vor allem "Hier kommt dann der ganze Part, der für alle Dreiecke gilt, sollte also auch auf die Dreiecke oben Anwendbar sein"
    Danke, und liebe Grüße.
    Und Gott alleine weiß alles am allerbesten und besser.
    Also die Idee über baryzentrische Koordinaten funktioniert_, war auch schon zu erwarten:



    Hier wird aber eben das projizierte Dreieck bereits bearbeitet.
    Wenn man es vor der Projizierung macht, müsste man ja die Operation zwei mal durchführen.
    Kann man das vermeiden..? Die Rede ist schließlich von GDI+ :D


    Hier der Code von SetPixel:

    C#-Quellcode

    1. public bool SetPixel(int x, int y, Effect effect, Vector2 a, Vector2 b, Vector2 c)
    2. {
    3. if (x < 0)
    4. return false;
    5. if (x >= 800)
    6. return false;
    7. if (y < 0)
    8. return false;
    9. if (y >= 600)
    10. return false;
    11. Maths.Barycentric(new Vector2(x, y), a, b, c, out float u, out float v, out float w);
    12. byte cr = (byte)((float)a.R * u + (float)b.R * v + (float)c.R * w);
    13. byte cg = (byte)((float)a.G * u + (float)b.G * v + (float)c.G * w);
    14. byte cb = (byte)((float)a.B * u + (float)b.B * v + (float)c.B * w);
    15. directBits.SetPixel(x, y, cr, cg, cb);
    16. return true;
    17. }


    mit

    C#-Quellcode

    1. public static void Barycentric(Vector2 p, Vector2 a, Vector2 b, Vector2 c, out float u, out float v, out float w)
    2. {
    3. Vector2 v0 = b - a, v1 = c - a, v2 = p - a;
    4. float d00 = Dot(v0, v0);
    5. float d01 = Dot(v0, v1);
    6. float d11 = Dot(v1, v1);
    7. float d20 = Dot(v2, v0);
    8. float d21 = Dot(v2, v1);
    9. float denom = d00 * d11 - d01 * d01;
    10. v = (d11 * d20 - d01 * d21) / denom;
    11. w = (d00 * d21 - d01 * d20) / denom;
    12. u = 1.0f - v - w;
    13. }
    14. public static float Dot(Vector2 a, Vector2 b)
    15. {
    16. return a.X * b.X + a.Y * b.Y;
    17. }
    entnommen aus gamedev.stackexchange.com/a/23745/99830
    Und Gott alleine weiß alles am allerbesten und besser.

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

    u,v,w passt mir ja mal ganz und gar nicht für die Variablennamen bei baryzentrischen Koordinaten mMn etwas irreführend(ich kenn das ja als alpha, beta, gamma), da es so nach Texturkoordinaten klingt, was es ja irwie nicht sind^^

    Also ich glaube nicht, dass es die GPU auf diese Art macht, ich denke das läuft in etwa so ab:

    C#-Quellcode

    1. //ich hab hier auch eine Projection-Matrix
    2. Matrix world;//whatever
    3. Matrix view;//whatever
    4. Matrix projection;//whatever
    5. Matrix wvp = world*view*projection;
    6. List<TriangleStripProjected> triangles = new List<TriangleStripProjected>();
    7. //möglicherweise geht das ganze auch ohne zweimal das ganze zu durchlaufen kp
    8. foreach (var vertices in currentVertexCollector.VerticesCollection)
    9. {
    10. var strip = new TriangleStripProjected();
    11. foreach (var vertex in vertices)
    12. {
    13. vertex.Transformed = wvp*new Vector4(vertex.Position,1.0f);//* sollte rein Mathematisch gesehen implementiert sein, ansonsten halt Vector4.Transform
    14. strip.Add(vertex);
    15. }
    16. triangles.Add(strip);
    17. }
    18. for (int i=0;i<triangles.Count;i++)
    19. {
    20. var strip = triangles[i];
    21. var newStrip = new TriangleStripProjected();
    22. for (int c=0;c<3;c++)//x,y,z
    23. {
    24. clipTriangleStrip(strip,newStrip,c);
    25. triangles[i] = newStrip;
    26. newStrip = strip;
    27. newStrip.Clear();//Vereinfachtes Memory Pooling indem wir den alten strip leeren und wiederverwenden als den neuen
    28. }
    29. }
    30. void clipTriangleStrip(TriangleStripProjected unclipped,TriangleStripProjected clipped,int c)
    31. {
    32. for (int clipDir = -1;i <= 1;i+=2)
    33. {
    34. var previousVertex = strip[0];
    35. bool previousVisible = visibleIndex(curTransformed,clipDir,c);
    36. for (int i=1;i<strip.Count;i++)//dürfte nur funktionieren, wenn vertices ein TriangleStrip ist, bzw. einzelne Dreiecke?
    37. {
    38. var curVertex = vertices[i];
    39. bool curVisible = visibleIndex(curTransformed,clipDir,c);
    40. if (curVisible ^ previousVisible)//einer von beiden nicht Sichtbar
    41. {
    42. float pT = (previousTransformed.w - clipDir * getComponent(previousTransformed,c));//random var name
    43. float t = pT / (pT - curTransformed.w - clipDir getComponent(curTransformed,c));
    44. clipped.Add(Vertex.Lerp(previousvertex,curVertex,t));
    45. }
    46. if (curVisible)
    47. {
    48. clipped.Add(curVertex);
    49. }
    50. previousVertex = curVertex;
    51. previousVisible = curVisible;
    52. }
    53. }
    54. }
    55. Vertex Lerp(Vertex v1,Vertex v2,float t)//teil von Vertex
    56. {
    57. return new Vertex(Vector3.Lerp(v1.Position,v2.Position,t),
    58. Vector4.Lerp(v1.Transformed,v2.Transformed,t),
    59. Vector2.Lerp(v1.TextureCoords,v2.TextureCoords,t),
    60. Color.Lerp(v1.Color,v2.Color,t));//kp wie dein vertex aussieht
    61. }
    62. //https://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe/
    63. [MethodImpl(MethodImplOptions.AggressiveInlining)]
    64. int unsafe getComponent(Vector4 v,int index)
    65. {
    66. return *(((float*)Unsafe.AsPointer(v)) + index);//irgendwie so?!^^
    67. }
    68. [MethodImpl(MethodImplOptions.AggressiveInlining)]
    69. bool visibleIndex(Vector4 v,int clipDir,int index)
    70. {
    71. return visible(getComponent(v,index),clipDir * v.w);
    72. }
    73. bool visbile(float c,float w)
    74. {
    75. return c <= w;
    76. }


    So ist meine Grundvorstellung, doch da das ausm Kopf geschrieben ist, wird der Code nie von Grundauf funktionieren.
    Für die baryzentrischen Koordinaten gilt dann das ganze was ich oben bereits geschrieben hab(inklusive dem teilen durch w)...

    Um das ganze funktionierend zu bekommen und zu Wissen ob nicht doch ein paar Fälle fehlen(z.b. wenn in unterschiedlichen Planes geclippt wird, oder ob der Strip so überhaupt korrekt erzeugt wird) müsste ich das ganze mal selbst implementieren und einfach rumprobieren :D
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Hallo,
    lieben Dank für deinen Ansatz!

    Soweit also funktioniert es folgendergestalt:
    Drei Vertizes ( mit Position als Vektor4 repräsentiert) werden definiert.

    Zuvor wurden World, View und Projektion-Matrizen multipliziert.
    Nun wird Position des Vertizes N mit w*v*p transformiert.
    Weil Position des Vertizes N nun mal eine Position ist, ist w = 1.( w=0 ist eine Richtung, soweit ich das richtig las).

    Nun erhalten wir den projizierten Vertex mit Komponenten Position(x,y,z,w).
    Jetzt wird auch häufig in dem Zusammenhang eine Clipping-Matrix genannt.
    Macht das DirectX auch, und wie sieht die aus?

    Nach dieser Prozedur wird geguckt: Sind die Punkte außerhalb des Bereichs w- < xyz <= w
    Wenn ja, dann wird vom sichtbaren Punkt zum Punkt außerhalb interpoliert, die neuen Koordinaten gemerkt.
    Dann wird das ganze zu Bildschirmkoordinaten verschoben und ganz am Ende durch W geteilt.
    Der Grund wieso vor der Division mit W geclippt ist der, dass man so umgeht durch 0 zu teilen.

    Nun gibt es aber statt 3 Punkten, plötzlich mehr.
    Das Polygon wird trianguliert und gezeichnet.

    Meine Frage nun: Was sind die drei zwei-dimensionalen Eckpunkte (A, B, C) für die baryzentrische Berechnungen?
    Weil wenn exemplarisch C außerhalb des Rechteck liegt, werden schließlich die neuen Punkte A B D E weiter bearbeitet.
    Immer noch fehlt das letzte Glied C für die baryzentrische Berechnung.. das ist so das einzige was ich glaub ich bezüglich Clipping noch nachvollziehen muss.
    _

    Lieben Dank für deine Mühe
    Und Gott alleine weiß alles am allerbesten und besser.
    Clipping Matrix dürfte zumindest laut meinem Verständnis einfach der Teil der Projection-Matrix sein, der die ClippingPlanes definiert(Könnte sogar die ganze Matrix benötigen keine Ahnung ehrlich gesagt, denke aber schon).

    Die 2D Punkte sollten doch ganz einfach xy sein nach meinem Verständnis? Schließlich projeziert man es ja auf diese Ebene. Also ist es auch nicht großartig schwer die baryzentrischen Koordinaten zu berechnen, denn der Algorithmus für die Texturierung bleibt derselbe wie bei normalen Dreiecken auch, schließlich haben wir keine Unterscheidung zwischen Polygonen und Dreiecken, es gibt einfach nur Dreiecke. Und das teilen durch w braucht man um die baryzentrischen Koordinaten perspektivisch korrekt zu berechnen.

    Aber wie gesagt bei mir ist es auch nur gefählriches Halbwissen und ich hab nie so weit an einem Renderer gearbeitet^^ Edit: außer einfaches RayTracing halt auf GPU :D
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    ^^ tut mir leid, falls ich dich nerve, hat mir aber sehr weit geholfen, danke!
    Dann habe ich nur noch eine letzte Frage_:
    Bei deiner Illustration oben mit Clipping-Plane und den 5 Vertizes: Ist das nach, oder vor Multiplikation mit Projektions-Matrix?
    Und falls nach: Nach oder vor

    C#-Quellcode

    1. result.X = (result.X + 1f) * 0.5f * (float)this.width + (float)this.x;
    2. result.Y = (-result.Y + 1f) * 0.5f * (float)this.height + (float)this.y;
    3. result.Z = result.Z * (this.maxDepth - this.minDepth) + this.minDepth;

    ?

    Also gemeint ist, die Überführung in Bildschirmkoordinaten.
    Weil projiziert man ein Vertex, dass hinter der Kamera ist, wird falsch projiziert.
    Deswegen ja diese ganzen Fragen haha
    Und Gott alleine weiß alles am allerbesten und besser.
    Also sollte nach dem multiplizieren mit der Projektionsmatrix sein, den Code den du jedoch da geschrieben hast, ist das nicht eigt. Teil der Projektionsmatrix? Naja außer vlt. der Teil für X/Y, welchen man dann anwenden muss mehr oder minder unabhängig von der Projektionsmatrix, dass man am Ende auf nen Bitmap malen kann?
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Das hab ich nicht selber geschrieben, das ist die Source für [Monogame] Viewport.Project.

    Da wird eben world*view*projection, und dann eine gewisse Zahl berechnet, alle Komponenten durch diese dividiert und dann
    eben X Y Z in Bildschirmkoordinaten übersetzt:

    C#-Quellcode

    1. public Vector3 Project(Vector3 source, Matrix projection, Matrix view, Matrix world)
    2. {
    3. Matrix matrix = Matrix.Multiply(Matrix.Multiply(world, view), projection);
    4. Vector3 result = Vector3.Transform(source, matrix);
    5. float num = source.X * matrix.M14 + source.Y * matrix.M24 + source.Z * matrix.M34 + matrix.M44;
    6. if (!Viewport.WithinEpsilon(num, 1f))
    7. {
    8. result.X /= num;
    9. result.Y /= num;
    10. result.Z /= num;
    11. }
    12. result.X = (result.X + 1f) * 0.5f * (float)this.width + (float)this.x;
    13. result.Y = (-result.Y + 1f) * 0.5f * (float)this.height + (float)this.y;
    14. result.Z = result.Z * (this.maxDepth - this.minDepth) + this.minDepth;
    15. return result;
    16. }


    Das "float num = source.X * matrix.M14 + source.Y * matrix.M24 + source.Z * matrix.M34 + matrix.M44;" ist meines Erachten die W Komponente!
    Also statt das, könnte man auch:

    C#-Quellcode

    1. public Vector4 Project(Vector3 source, Microsoft.Xna.Framework.Matrix projection, Microsoft.Xna.Framework.Matrix view, Microsoft.Xna.Framework.Matrix world)
    2. {
    3. Microsoft.Xna.Framework.Matrix matrix = Microsoft.Xna.Framework.Matrix.Multiply(Microsoft.Xna.Framework.Matrix.Multiply(world, view), projection);
    4. Vector4 result = Vector4.Transform(new Vector4(source, 1.0f), matrix);
    5. result.X /= result.W;
    6. result.Y /= result.W;
    7. result.Z /= result.W;
    8. result.X = (result.X + 1f) * 0.5f * (float)GDIDevice.Viewport.Width + (float).0f;
    9. result.Y = (-result.Y + 1f) * 0.5f * (float)GDIDevice.Viewport.Height + (float).0f;
    10. result.Z = result.Z * (1.0f - .0f) + .0f;
    11. return result;
    12. }

    berechnen.
    Ist - zumindest vom Ergebnis her auf die Schnelle geguckt - dasselbe.

    Nun ist meine Frage: Wo würdest du clippen?
    Ab "Vector4 result = Vector4.Transform(new Vector4(source, 1.0f), matrix);", oder ab "return result;"?
    _
    Und Gott alleine weiß alles am allerbesten und besser.
    jop in Zeile 5.

    Hm das homogenisieren verwirrt mich dann doch etwas, bin mir da ehrlich gesagt Total unsicher, ob das da so Sinn ergibt. Ich hatte das so verstanden, dass man homogenisiert aber vor dem eigentlichen Zeichnen wieder dehomogenisiert.
    deshalb speichert man in result.W = 1/result.W, damit man zum dehomogenisieren 6-8 wiederholt.

    Aber bei der Clippingstelle bin ich mir eigt. sicher^^
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Du dividierst also im ganzen Prozess zwei mal durch W?
    Ah deswegen W' = 1/W denn,

    W = 1/W' also 1/(1/W) = W

    Gibt es da irgendwelche Papers zu?
    Finde nur Forum-Artikel die auch zum Teil aus Fragmenten bestehen.
    Hab das jetzt eigentlich auch nur intuitiv programmiert, funktioniert_ schon, ist aber eben trotzdem nicht richtig, weil mein Clipping-Algorithmus VOR der eigentlichen
    Projizierung stattfindet und so wichtige Informationen einfach verloren gehen.

    Was oben imho mir keinen Sinn macht ist folgende Zeile, du schreibst:
    "v1.x*a1 + v2.x*a2 + v3.x*a3 = p.x
    v1.y*a1 + v2.y*a2 + v3.y*a3 = p.y
    a1 + a2 + a3 = 1"..

    p.X und p.Y ist ja der Punkt AUF dem projizierten Dreieck.
    Muss ich also nicht mit (v1/v2/v3).xyz die Berechnung durchführen:
    "p'.x = p.x / p.w
    p'.y = p.y / p.w
    p'.z = p.z / p.w
    p'.w = 1 / p.w // Achtung nicht p.w/p.w(==1), das hier wird verwendet um später wieder zu "dehomogenisieren"(?/enthomogenisieren? kp^^)
    p'.u = p.u / p.w
    p'.v = p.v / p.w"

    Oder ist das hier einfach nur derselbe Punkt-Name, aber unterschiedliche Bedeutung?
    Und Gott alleine weiß alles am allerbesten und besser.
    Mit homogenisieren/dehomogenisieren hast ja anscheinend verstanden ;)

    Das Paper meiner Uni ist leider nicht öffentlich zugänglich, will das also nicht einfach hier reinkopieren.

    φConst schrieb:

    Oder ist das hier einfach nur derselbe Punkt-Name, aber unterschiedliche Bedeutung?

    Ühm ja sorry hier ist das eine p nicht wie das andere p. Oben ists der Punkt auf dem Dreieck, der interpoliert werden soll(also 2D und bereits projeziert), wobei v1/v2/v3 die 3 projezierten Punkte des Dreieckss sind(homogenisiert, so wie ich das verstanden hab).
    Das p unten jedoch sind die punkte der Vertices, wobei v1/v2/v3 p' entsprechen. p ist dabei vor der Projektion. Also ja hier ist p nicht gleich p, hab ich etwas ungeschickt gewählt...
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---