Perspective Correct Texture Mapping

7

Ich versuche also, meine Texturen in meinem Software-Renderer mithilfe der u, v-Koordinaten korrekt zuzuordnen, aber ich kann es scheinbar nicht zum Laufen bringen. Ich habe affine Textur-Mapping funktioniert. Das kann ich damit produzieren:

Geben Sie hier die Bildbeschreibung ein

Sie können an der "Neigung" in der Textur erkennen, dass es nicht ganz richtig ist. Wenn ich dem Netz mehr Dreiecke hinzufüge, wird es korrekter. Nach meinem Verständnis sind dies keine perspektivisch korrekten Texturen. Aber diese Perspektiventeilung verwirrt mich. Ich habe gelesen, dass ich die u, v-Koordinaten durch die w-Komponente teilen muss (perspektivische Teilung). Ich vermute, dass dies die w-Komponente vom Scheitelpunkt ist, nachdem sie mit der Projektionsmatrix multipliziert wurde. Aber ich konnte das auch nicht zum Laufen bringen. Also habe ich mich ein bisschen mehr mit dem Problem befasst. Zuerst passiert Folgendes mit einem Scheitelpunkt meiner Dreiecke.

float4 pt1 = worldViewProj * mesh->vertices[mesh->indices[i]];
float2 uv1 = mesh->texCoords[mesh->indices[i]];
triangle.p1 = float3((pt1.x / pt1.w + 0.5f) * screen->GetScreenWidth(), (pt1.y / pt1.w + 0.5f) * screen->GetScreenHeight(), pt1.w);

Ich multipliziere es mit dem wVP und teile dann meine Perspektive mit x und y. Das + 0.5f dient dazu, es im homogenen Raum zu zentrieren und dann mit den Bildschirmabmessungen zu multiplizieren, um es im Bildschirmraum zu erhalten. Ich fülle dann die w-Komponente des Scheitelpunkts als z-Wert ein, damit sie an die Dreieckszeichnungsfunktion gesendet wird und für Dinge wie den Tiefenpuffer verwendet werden kann.

Nach meinem Verständnis wird der z-Wert vom Scheitelpunkt nach Multiplikation mit der Projektionsmatrix in w gesetzt. Dies ist sinnvoll, da in meiner Projektionsmatrix m32 = 1 ist. Also nach Multiplikation mit der Projektionsmatrix w = z. Ich habe zuerst versucht, die Texturkoordinaten pro Scheitelpunkt durch w zu teilen. Wie so:

 float2 uv1 = mesh->texCoords[mesh->indices[i]] / w;

Dann habe ich in der Dreieckszeichnungsfunktion die zentrischen Koordinaten verwendet, um über diese die UV-Koordinaten zu interpolieren. Das hat nicht funktioniert. Da w = z. Wenn die Textur weit von der Kamera entfernt ist (dh z ist ein großer Wert. Wenn meine Weltübersetzungen auf z = ~ 700 angewendet werden), schrumpfen die Texturkoordinaten stark und bei jedem Rendern erhalte ich nur den (0, 0) -Wert meiner Textur .

Aber ich habe dies stattdessen auch in meiner Dreieckszeichnungsfunktion versucht:

float2 texCoord = (texCoord1 * u + texCoord2 * v + texCoord3 * w) / z;

(Hier sind u, v und w die zentrischen Koordinaten. Jeder texCoord ist die Texturkoordinate an diesem Scheitelpunkt.) Der Unterschied besteht darin, dass der interpolierte z-Wert an jedem Pixel geteilt wird, anstatt an den Scheitelpunkten. So oder so kann ich es nicht zu gut zum Laufen bringen. Wieder ist der z-Wert zu groß und ich bekomme am Ende (0, 0).

Also untersuche ich, was ich brauche, um u und v durch zu teilen. Offensichtlich mache ich etwas falsch. Ist es wirklich die w-Komponente des Scheitelpunkts nach Multiplikation mit der Projektionsmatrix? Ist mein w-Wert falsch? Könnte die Projektionsmatrix dies verursachen? Ich habe jetzt eine Weile gegoogelt ... x / w und y / w sind [-1, 1] (homogen), aber z ist nicht [-1, 1]. Soll es sein Meine Perspektiventeilung für x und y funktioniert offensichtlich.

So habe ich meine Projektionsmatrix für den Fall erstellt, dass sie falsch ist?:

float tanHalfFov = tanf(fov * 0.5f * 3.14f / 180.0f);
float s = 1.0f / (tanHalfFov * aspectRatio);
float zRange = farZ - nearZ;

// Set the projection matrix
proj.m00 = s;    proj.m01 = 0.0f;            proj.m02 = 0.0f;          proj.m03 = 0.0f;

proj.m10 = 0.0f; proj.m11 = aspectRatio * s; proj.m12 = 0.0f;          proj.m13 = 0.0f;

proj.m20 = 0.0f; proj.m21 = 0.0f;            proj.m22 = farZ / zRange; proj.m23 = farZ * nearZ / zRange;

proj.m30 = 0.0f; proj.m31 = 0.0f;            proj.m32 = 1.0f;          proj.m33 = 0.0f;
Andrew Wilson
quelle
2
Die Projektionsmatrix wandelt Punkte in den Clip-Bereich um. Hierbei handelt es sich um einen 2x2x2-Würfel, der am Ursprung zentriert ist und nicht als "Bildschirmbereich" bezeichnet wird. Homogene Koordinaten bedeutet, dass Sie wzusätzlich zu Ihren Raumkoordinaten (x, y und möglicherweise z) ein haben. Es hat nichts mit Normalisierung zu tun. Sie dividieren nie durch Z - immer W. Der Punkt von W ist, dass Sie die Perspektive perspektivisch teilen und die Tiefe beibehalten können (wieder im Clip-Bereich) und die Übersetzung in Ihre Matrix einfügen können, anstatt einen separaten Schritt zu haben. Sie vermischen viele verschiedene Konzepte.
3Dave

Antworten:

6

Sie sind auf dem richtigen Weg, müssen jedoch u / w und v / w sowie 1 / w für jeden Scheitelpunkt berechnen, den Sie in Ihrem Rasterizer linear im Bildschirmbereich interpolieren. Dann teilen Sie für jedes Pixel die interpolierten U / W- und V / W-Koordinaten mit den interpolierten 1 / W-Koordinaten, um perspektivisch korrekte UV-Koordinaten für das Pixel zu erhalten.

Gleiches gilt für alle Scheitelpunktattribute. Wenn Sie beispielsweise eine perspektivisch korrekte Farbe oder Position der Scheitelpunkte pro Pixel benötigen, interpolieren Sie c / w und p / w und dividieren diese Werte durch 1 / w pro Pixel. Selbst wenn Sie keinen Software-Rasterizer implementieren, kann dies beispielsweise beim Ray-Marching der Implementierung der Bildschirmraumreflexion sehr nützlich sein. Ich habe Leute gesehen, die den Strahl für SSR im Weltraum marschierten, weil sie nicht wissen, wie man die Position im Bildschirmraum richtig interpoliert.

Alte Software-Rasterizer haben diese 1 / (1 / w) -Division nur alle X Pixel durchgeführt und dazwischen Werte linear interpoliert, da die Division eine relativ teure Operation war, um jedes Pixel durchzuführen.

JarkkoL
quelle
Ist 1 / (1 / w) ein Tippfehler oder etwas, das ich missverstanden habe?
Trichoplax
2
Kein Tippfehler. Sie interpolieren 1 / w linear über den Bildschirm und berechnen 1 / (1 / w) pro Pixel. Dann multiplizieren Sie linear interpolierte u / w und v / w mit dem 1 / (1 / w) -Wert pro Pixel. Nur eine triviale Optimierung, um Divs in Mul zu verwandeln, um mehrere Divisionen pro Pixel zu vermeiden.
JarkkoL