Wie kann ich zickzackförmige UV-Mapping-Artefakte auf einem generierten Netz beheben, das sich verjüngt?

10

Ich erstelle ein prozedurales Netz basierend auf einer Kurve und die Größe des Netzes wird in der gesamten Kurve kleiner, wie Sie unten sehen.

Geben Sie hier die Bildbeschreibung ein

Das Problem ist, dass UV-Strahlen im Zick-Zack-Modus auftreten, wenn sich die Größe ändert (dies funktioniert perfekt, wenn die Größe des Netzes in der gesamten Kurve gleich ist).

Geben Sie hier die Bildbeschreibung ein

 Vertices.Add(R);
 Vertices.Add(L);
 UVs.Add(new Vector2(0f, (float)id / count));
 UVs.Add(new Vector2(1f, (float)id / count));
 start = Vertices.Count - 4;
          Triangles.Add(start + 0);
                 Triangles.Add(start + 2);
                 Triangles.Add(start + 1);
                 Triangles.Add(start + 1);
                 Triangles.Add(start + 2);
                 Triangles.Add(start + 3);

 mesh.vertices = Vertices.ToArray();
         //mesh.normals = normales;
         mesh.uv = UVs.ToArray();
         mesh.triangles = Triangles.ToArray();

Wie kann ich das beheben?

fkkcloud
quelle
Was sind idund count? Was macht UVs.ToArray()das Wie lade ich die Eckpunkte und Texturkoordinaten auf die Karte hoch?
user1118321
@ user1118321 aus dem Kontext idist der Index des aktuellen Segments aller Querbalken entlang des Streifens, von denen insgesamt vorhanden sind count. List<T>.ToArray()Gibt ein typisiertes Array aller Einträge in der Liste zurück (also hier a Vector2[]). Diese Datenpuffer werden dem Rendering-System zur Verfügung gestellt, indem sie in den letzten Zeilen der MeshInstanz meshzugewiesen werden. Aber nichts davon ist der besonders interessante Teil des Codes: wie die Scheitelpunkte ausgewählt werden. Diese Details wären nützlicher zu sehen. ;)
DMGregory
Das war meine Annahme, aber ich bat um Klarstellung, weil manchmal Annahmen falsch sind. Ich habe mir in der Vergangenheit so etwas in meinem eigenen Code angesehen, nur um zu erkennen, dass dies counttatsächlich Eckpunkte anstelle von Polygonen bedeutete oder umgekehrt. Nur sicherzustellen, dass alles, was wir denken, wirklich los ist.
user1118321

Antworten:

13

Typisches UV-Mapping ist eine sogenannte affine Transformation . Das bedeutet, dass die Zuordnung jedes Dreiecks zwischen 3D-Raum und Texturraum Rotation, Translation, Skalierung / Squash und Skew umfassen kann (dh alles, was wir mit einer homogenen Matrixmultiplikation tun können).

Die Sache mit affinen Transformationen ist, dass sie über ihre gesamte Domäne hinweg einheitlich sind - die Rotation, Translation, Skalierung und der Versatz, die wir auf die Textur in der Nähe von Scheitelpunkt A anwenden, sind die gleichen wie die, die wir in der Nähe von Scheitelpunkt B innerhalb eines Dreiecks anwenden. Linien, die in einem Raum parallel sind, werden parallelen Linien im anderen zugeordnet, ohne zu konvergieren / divergieren.

Die allmähliche Verjüngung, die Sie anwenden möchten, ist jedoch nicht einheitlich. Sie ordnet parallele Linien in der Textur konvergierenden Linien im Netz zu. Das bedeutet, dass sich die Skala der über das Band gemessenen Textur kontinuierlich ändert, wenn wir uns den Streifen entlang bewegen. Das ist mehr, als die affinen Transformationen der 2D-UV-Abbildung genau darstellen können: Durch Interpolation von 2D-UV-Koordinaten zwischen benachbarten Scheitelpunkten wird eine konsistente Skalierung entlang der gesamten Kante erhalten, selbst die diagonale Kante, deren Skalierung schrumpfen sollte, wenn sie sich entlang des Streifens bewegt. Diese Nichtübereinstimmung schafft diesen trälelnden Zickzack.

Dieses Problem tritt immer dann auf, wenn wir ein Rechteck einem Trapez zuordnen möchten - parallele Seiten zu konvergierenden Seiten: Es gibt einfach keine affine Transformation, die dies tut, daher müssen wir es stückweise approximieren, was zu sichtbaren Nähten führt.

In den meisten Fällen können Sie den Effekt minimieren, indem Sie mehr Geometrie hinzufügen. Durch Erhöhen der Anzahl der Unterteilungen entlang der Länge des Streifens und Aufteilen des Streifens in zwei oder mehr Segmente entlang seiner Breite, wobei die Diagonalen der Dreiecke in einem Fischgrätenmuster angeordnet sind, kann der Effekt viel weniger auffällig werden. Es wird immer bis zu einem gewissen Grad vorhanden sein, solange wir affine Transformationen verwenden.

Aber es gibt einen Weg, um das zu umgehen. Wir können denselben Trick verwenden, den wir für das 3D-Rendering verwenden, um Trapezoide bei rechteckigen Wänden und Böden perspektivisch zu zeichnen: Wir verwenden projektive Koordinaten !

Affine Texturierung: Beispiel für eine normale affine Texturierung mit Zick-Zack-Artefakten

Projektive Texturierung: Beispiel für projektives Textring zur Korrektur der Artefakte

Dazu müssen wir eine dritte UV-Koordinate (UVW) hinzufügen und unsere Shader ändern.

Wenn Sie an jedem Punkt einen Skalierungsfaktor angeben (z. B. gleich der Breite Ihres Streifens an dieser Stelle), können Sie die projektive 3D-UVW-Koordinate aus Ihrer regulären 2D-UV-Koordinate folgendermaßen konstruieren:

Vector3 uv3 = ((Vector3)uv2) * scale;
uv3.z = scale;

Um diese 3D-UVW-Koordinaten auf Ihr Netz anzuwenden, müssen Sie die Vector3-Überlast Mesh.SetUVs (int channel, List uvs) verwenden.

Ändern Sie unbedingt die Eingabestruktur Ihres Shaders, um eine 3D-Texturkoordinate zu erwarten (hier mit dem standardmäßigen unbeleuchteten Shader dargestellt):

struct appdata
{
    float4 vertex : POSITION;
    float3 uv : TEXCOORD0; // Change float2 to float 3.
};
// Also do this for the uv sent from the vertex shader to the fragment shader.

Sie müssen auch das TRANSFORM_TEX-Makro im Vertex-Shader ausschneiden, da es eine 2D-UV erwartet:

// o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.uv = v.uv;
// If you define a float4 with the special name _MainTex_ST, 
// you can get the same effect the macro had by doing this:
o.uv.xy = o.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;

Um zu einer 2D-Texturkoordinate für die Texturabtastung zurückzukehren, dividieren Sie einfach durch die dritte Koordinate in Ihrem Fragment-Shader :

float2 uv = i.uv.xy / i.uv.z;

Da wir diese 3D-UVW-Koordinate aus unserer gewünschten 2D-Koordinate durch Multiplizieren mit derselben Zahl erstellt haben, werden die beiden Operationen aufgehoben und wir kehren zu unserer ursprünglichen gewünschten 2D-Koordinate zurück, jedoch jetzt mit nichtlinearer Interpolation zwischen den Scheitelpunkten. : D.

Es ist wichtig, diese Aufteilung pro Fragment und nicht im Vertex-Shader vorzunehmen. Wenn dies pro Scheitelpunkt erfolgt, werden die resultierenden Koordinaten wieder linear entlang jeder Kante interpoliert, und wir haben die Nichtlinearität verloren, die wir mit der projektiven Koordinate einführen wollten!

DMGregory
quelle
Hallo, ich habe seit einiger Zeit mit dem gleichen Problem zu kämpfen, und das scheint genau das zu sein, was mit mir passiert. Der einzige Unterschied ist, dass ich in drei Jahren arbeite und nicht in Einheit. Wir haben es geschafft, das Problem durch Erhöhen der Anzahl der Dreiecke zu lösen, möchten aber dennoch eine projektive Texturierung ausprobieren. Haben Sie eine Idee, wie Sie überhaupt damit beginnen können, dies in drei Sekunden zu implementieren? Danke!
Dživo Jelić
1
So ziemlich genauso. Sie benötigen eine dritte Texturkoordinate, um den Divisor zu speichern, und führen dann die Division im Fragment-Shader durch. Wenn Sie Probleme hatten, dies auf Ihren Fall anzuwenden, können Sie durch das Stellen einer neuen Frage ein Codebeispiel einfügen, das zeigt, wie Sie Ihr Netz bisher gezeichnet haben, auf dem die Antworten aufbauen können.
DMGregory
Muss ich den Divisor als dritte Koordinate speichern (und aufgrund dieser Änderung die Struktur ändern und das TRANSFORM_TEX-Makro ausschneiden, was ich nicht in drei Schritten tun kann) oder kann ich den Divisor einfach als Attribut an den Vertex-Shader übergeben? und dann von dort als Variationen zum Fragment-Shader, wo es an die Teilung gewöhnt sein könnte?
Dživo Jelić
TRANSFORM_TEX ist ein Unity-Makro. Sie haben das nicht in three.js, also gibt es nichts herauszuschneiden. Solange die interpolierten UV-Koordinaten und Teiler den Fragment-Shader erreichen, spielt es keine Rolle, wie Sie sie dorthin gebracht haben. Haben Sie Probleme damit, es als Attribut / Variation zu verwenden?
DMGregory
Ich habe es noch nicht versucht, da ich keine Zeit verschwenden wollte, bis ich überprüft habe, ob es möglich ist. Nur eine letzte Frage: Der Divisor wird auch interpoliert, sobald er den Fragment-Shader erreicht hat. Richtig? Bedeutet das, dass es sich bei einem Pixel zwischen zwei Eckpunkten um einen interpolierten Wert und nicht um einen ursprünglichen Wert handelt? Es fällt mir schwer, mich darum zu kümmern, warum es hilfreich ist, die UV-Strahlung zu multiplizieren und dann zu teilen, aber ich werde Ihr Wort dafür nehmen und es versuchen. : leicht_smiling_face: Vielen Dank für Ihre Hilfe!
Dživo Jelić