Jak mogę naprawić zygzakowate artefakty mapowania UV na wygenerowanej siatce, która zwęża się?

10

Tworzę siatkę proceduralną na podstawie krzywej, a rozmiar siatki zmniejsza się na całej krzywej, jak widać poniżej.

wprowadź opis zdjęcia tutaj

Problem polega na tym, że UV zmienia się w zygzakowate, gdy zmienia się rozmiar (działa idealnie, gdy rozmiar siatki jest taki sam na całej krzywej).

wprowadź opis zdjęcia tutaj

 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();

Jak mogę to naprawić?

fkkcloud
źródło
Jakie są idi count? Co ma UVs.ToArray()zrobić? Jak przesyłasz na kartę wierzchołki i współrzędne tekstury?
user1118321,
@ user1118321 z kontekstu, idjest indeksem bieżącego segmentu ze wszystkich poprzeczek wzdłuż paska, gdzie countłącznie są. List<T>.ToArray()zwraca tablicę wszystkich wpisów na liście (więc tutaj, a Vector2[]). Te bufory danych są udostępniane systemowi renderującemu, przypisując je do Meshinstancji meshw kilku ostatnich wierszach. Ale nic z tego nie jest szczególnie interesującą częścią kodu: jak wybierane są punkty wierzchołków. Te szczegóły byłyby bardziej przydatne do zobaczenia. ;)
DMGregory
Takie było moje założenie, ale poprosiłem o wyjaśnienie, ponieważ czasami założenia są błędne. W przeszłości patrzyłem na coś takiego w swoim własnym kodzie, aby zdać sobie sprawę, że countfaktycznie oznacza to wierzchołki zamiast wielokątów lub odwrotnie. Tylko upewnienie się, że wszystko, co naszym zdaniem się dzieje, naprawdę się dzieje.
user1118321,

Odpowiedzi:

13

Typowe mapowanie UV to tak zwana transformacja afiniczna . Oznacza to, że mapowanie każdego trójkąta między przestrzenią 3D a przestrzenią tekstury może obejmować obrót, translację, skalowanie / squash i pochylenie (tj. Wszystko, co możemy zrobić z jednorodnym zwielokrotnieniem macierzy)

Rzecz w transformacjach afinicznych polega na tym, że są one jednolite w całej swojej dziedzinie - obrót, translacja, skala i pochylenie, które stosujemy do tekstury w pobliżu wierzchołka A, jest takie samo, jak to, co stosujemy w pobliżu wierzchołka B, w obrębie dowolnego trójkąta. Linie, które są równoległe w jednej przestrzeni, zostaną odwzorowane na linie równoległe w drugiej, nigdy nie będą zbieżne / rozbieżne.

Ale stopniowe zwężanie, które próbujesz zastosować, nie jest jednolite - odwzorowuje równoległe linie w teksturze na zbieżne linie na siatce. Oznacza to, że skala tekstury mierzona w paśmie zmienia się ciągle, gdy przesuwa się w dół paska. To więcej, niż mogą dokładnie reprezentować afiniczne przekształcenia 2D UV mapping: interpolacja współrzędnych UV uv między sąsiadującymi wierzchołkami uzyska jedną spójną skalę na całej krawędzi, nawet krawędź ukośną, która powinna się zmniejszać w miarę przesuwania się w dół paska. To niedopasowanie jest tym, co tworzy ten warczący zygzak.

Ten problem pojawia się za każdym razem, gdy chcemy odwzorować prostokąt na trapezoid - równoległe boki do zbieżnych boków: po prostu nie ma transformacji afinicznej, która to robi, więc musimy to przybliżać fragmentarycznie, co prowadzi do widocznych szwów.

W większości przypadków można zminimalizować efekt, dodając więcej geometrii. Zwiększenie liczby podziałów wzdłuż długości paska i podzielenie paska na dwa lub więcej segmentów wzdłuż jego szerokości, przy przekątnych trójkątów ułożonych w wzór w jodełkę, może sprawić, że efekt będzie znacznie mniej zauważalny. Zawsze będzie jednak obecny do pewnego stopnia, dopóki używamy transformacji afinicznych.

Ale jest na to sposób. Możemy użyć tej samej sztuczki, której używamy do renderowania 3D, aby narysować trapezoidy w perspektywie, biorąc pod uwagę prostokątne ściany i podłogi: używamy współrzędnych projekcyjnych !

Teksturowanie afiniczne: Przykład normalnego teksturowania afinicznego z artefaktami zygzakowatymi

Teksturowanie projekcyjne: Przykład rzutowania tekstowego korygującego artefakty

Aby to zrobić, musimy dodać trzecią współrzędną UV (UVW) i zmodyfikować nasze shadery.

Biorąc pod uwagę współczynnik skali w każdym punkcie (powiedzmy, równy szerokości paska w tym miejscu), możesz zbudować współrzędną projekcyjną 3D uvw ze swojej zwykłej współrzędnej 2D UV w ten sposób:

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

Aby zastosować te współrzędne 3D UVV do swojej siatki, musisz użyć siatki przeciążeniowej Vector3.SetUVs (kanał wewnętrzny, lista UV)

I pamiętaj, aby zmienić strukturę wejściową modułu cieniującego, aby oczekiwać współrzędnej tekstury 3D (pokazanej tutaj przy użyciu domyślnego nieoświetlonego modułu cieniującego):

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.

Konieczne będzie również wycięcie makra TRANSFORM_TEX w module cieniującym wierzchołki, ponieważ oczekuje uv 2D:

// 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;

Wreszcie, aby powrócić do współrzędnej tekstury 2D do próbkowania tekstury, wystarczy podzielić przez trzecią współrzędną w module cieniującym fragmenty :

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

Ponieważ wykonaliśmy tę współrzędną 3D uvw z pożądanej współrzędnej 2D przez pomnożenie przez tę samą liczbę, dwie operacje anulują się i wracamy do naszej pierwotnej pożądanej współrzędnej 2D, ale teraz z interpolacją nieliniową między wierzchołkami. :RE

Ważne jest, aby wykonać ten podział na fragment, a nie w module cieniującym wierzchołki. Jeśli zostanie to zrobione w odniesieniu do wierzchołka, wrócimy do interpolacji wynikowych współrzędnych liniowo wzdłuż każdej krawędzi i utraciliśmy nieliniowość, którą próbowaliśmy wprowadzić za pomocą współrzędnej rzutowej!

DMGregory
źródło
Cześć, od dłuższego czasu zmagam się z tym samym problemem i wydaje mi się, że dokładnie to się ze mną dzieje. Jedyna różnica polega na tym, że pracuję w threejs, a nie w jedności. Udało nam się rozwiązać problem, zwiększając liczbę trójkątów, ale nadal chcielibyśmy spróbować teksturowania rzutowego. Czy masz jakiś pomysł, jak zacząć wdrażać to w Threejs? Dziękuję Ci!
Dživo Jelić,
1
Prawie w ten sam sposób. Potrzebujesz trzeciej współrzędnej tekstury do przechowywania dzielnika, a następnie wykonasz podział w module cieniującym fragmenty. Jeśli miałeś problem z zastosowaniem tego do swojej sprawy, zadanie nowego pytania pozwoli Ci dołączyć próbkę kodu, pokazującą, jak rysujesz siatkę do tej pory, na której można budować odpowiedzi.
DMGregory
Czy muszę przechowywać dzielnik jako trzecią współrzędną (i z powodu tej zmiany struct i wyciąć makro TRANSFORM_TEX, czego nie wiem jak to zrobić w trzech sekcjach) czy mogę po prostu przekazać dzielnik jako atrybut shadera wierzchołków a następnie stamtąd jako odmiany shadera fragmentów, gdzie można go wykorzystać do podziału?
Dživo Jelić,
TRANSFORM_TEX to makro Unity. Nie masz tego w Three.js, więc nie ma nic do wycięcia. Dopóki interpolowane współrzędne UV i dzielniki docierają do modułu cieniującego fragmenty, tak naprawdę nie ma znaczenia, jak je tam dostałeś. Czy masz jakikolwiek problem z dostarczeniem go jako atrybutu / zmiennego do tej pory?
DMGregory
Nie próbowałem jeszcze, ponieważ nie chciałem tracić czasu, dopóki nie sprawdzę, czy jest to możliwe. Jeszcze jedno pytanie, dzielnik będzie również interpolowany, gdy osiągnie moduł cieniujący fragmenty, prawda? Czy na pikselach między dwoma wierzchołkami będzie to pewna wartość interpolowana, a nie oryginalna? Trudno mi się owinąć głową, dlaczego pomnożenie UV, a następnie podzielenie go pomaga, ale uwierzę ci na słowo i spróbuję. : nieznacznie uśmiechając się: Dziękuję bardzo za pomoc!
Dživo Jelić