Как я могу исправить зигзагообразные артефакты UV-картирования на сгенерированной сетке, которая сужается?

10

Я создаю процедурную сетку на основе кривой, и размер сетки уменьшается по всей кривой, как вы видите ниже.

введите описание изображения здесь

И проблема в том, что UV изменяется зигзагообразно при изменении размера (он отлично работает, когда размер сетки одинаков по всей кривой).

введите описание изображения здесь

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

Как я могу это исправить?

fkkcloud
источник
Какие есть idи count? Что делает UVs.ToArray()? Как вы загружаете вершины и координаты текстуры на карту?
user1118321
@ user1118321 из контекста, idэто индекс текущего сегмента из всех перемычек вдоль полосы, где их countобщее количество. List<T>.ToArray()возвращает типизированный массив всех записей в списке (поэтому здесь, а Vector2[]). Эти буферы данных становятся доступными для системы рендеринга, назначая их Meshэкземпляру meshв последних нескольких строках. Но ничто из этого не является особенно интересной частью кода: как выбираются точки вершин. Эти детали было бы более полезно увидеть. ;)
DMGregory
Это было мое предположение, но я попросил разъяснений, потому что иногда предположения неверны. Я смотрел на что-то подобное в своем собственном коде в прошлом только для того, чтобы понять, что на countсамом деле это вершины, а не полигоны или наоборот. Просто убедиться, что все, что мы думаем, происходит на самом деле.
user1118321

Ответы:

13

Типичное УФ-картирование - это то, что называется аффинным преобразованием . Это означает, что отображение каждого треугольника между трехмерным пространством и текстурным пространством может включать в себя вращение, перемещение, масштабирование / сдвиг и наклон (т. Е. Все, что мы можем сделать с однородным умножением матриц)

Суть аффинных преобразований в том, что они однородны по всей своей области - вращение, перемещение, масштабирование и перекос, которые мы применяем к текстуре около вершины A, совпадает с тем, что мы применяем около вершины B внутри любого одного треугольника. Линии, параллельные в одном пространстве, будут отображаться на параллельные линии в другом, никогда не сходясь / не расходясь.

Но постепенное сужение, которое вы пытаетесь применить, не является равномерным - это сопоставление параллельных линий в текстуре со сходящимися линиями в сетке. Это означает, что масштаб текстуры, измеряемой по всей полосе, непрерывно меняется при движении вниз по полосе. Это больше, чем могут точно представить аффинные преобразования 2D UV-картирования: интерполяция 2D UV-координат между смежными вершинами даст один согласованный масштаб по всему краю, даже по диагональному краю, который должен уменьшаться в масштабе при перемещении вниз по полосе. Это несоответствие - то, что создает этот трогательный зигзаг.

Эта проблема возникает всякий раз, когда мы хотим отобразить прямоугольник на трапецию - параллельные стороны к сходящимся сторонам: просто нет аффинного преобразования, которое делает это, поэтому мы должны приблизить его кусочно, приводя к видимым швам.

Для большинства целей вы можете минимизировать эффект, добавив больше геометрии. Увеличение количества делений по длине полосы и разделение полосы на два или более сегмента вдоль ее ширины с диагоналями треугольников, расположенных в виде рисунка "елочкой", может сделать эффект гораздо менее заметным. Это всегда будет присутствовать до некоторой степени, пока мы используем аффинные преобразования.

Но есть способ обойти это. Мы можем использовать тот же трюк, который мы используем для 3D-рендеринга, чтобы рисовать трапеции в перспективе при заданных прямоугольных стенах и полах: мы используем проективные координаты !

Аффинное текстурирование: Пример нормального аффинного текстурирования с зигзагообразными артефактами

Проективное текстурирование: Пример проективного ввода текста, исправляющего артефакты

Для этого нам нужно добавить третью координату uv (uvw) и изменить наши шейдеры.

Учитывая масштабный коэффициент в каждой точке (скажем, равный ширине вашей полосы в этом месте), вы можете построить 3D-проективную координату uvw из вашей обычной 2D-координаты uv следующим образом:

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

Чтобы применить эти 3D-координаты uvw к вашей сетке, вам нужно использовать Mesh.SetUVs перегрузки Vector3 (int channel, List uvs)

И обязательно измените структуру ввода вашего шейдера так, чтобы он ожидал трехмерную текстурную координату (показанную здесь с помощью неосвещенного шейдера по умолчанию):

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.

Вам также необходимо вырезать макрос TRANSFORM_TEX в вершинном шейдере, так как он ожидает 2D uv:

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

Наконец, чтобы вернуться к 2D-координате текстуры для выборки текстуры, вы просто делите на третью координату в своем фрагментном шейдере :

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

Поскольку мы сделали эту трехмерную координату uvw из нашей желаемой 2D-координаты умножением на одно и то же число, две операции отменяются, и мы возвращаемся к нашей первоначальной желаемой 2D-координате, но теперь с нелинейной интерполяцией между вершинами. : D

Важно делать это деление на фрагмент, а не на вершинный шейдер. Если это сделано для каждой вершины, то мы вернулись к линейной интерполяции результирующих координат вдоль каждого ребра, и мы потеряли нелинейность, которую мы пытались ввести с проективной координатой!

ДМГригорий
источник
Привет, я уже давно борюсь с той же проблемой, и, похоже, именно это и происходит со мной. Разница лишь в том, что я работаю в три раза, а не в единстве. Нам удалось решить эту проблему, увеличив количество треугольников, но мы все же хотели бы попробовать проективное текстурирование. У вас есть идеи, как вообще начать реализовывать это в ThreeJS? Спасибо!
Дживо Елич
1
Примерно так же. Вам нужна третья координата текстуры для хранения делителя, затем вы выполняете деление в фрагментном шейдере. Если у вас возникли проблемы с применением этого в вашем случае, то, задав новый вопрос, вы сможете включить пример кода, показывающий, как вы рисуете свою сетку, на которые могут опираться ответы.
DMGregory
Нужно ли хранить делитель в качестве третьей координаты (и из-за этого изменить структуру и вырезать макрос TRANSFORM_TEX, который я не знаю, как сделать в ThreeJS) или я могу просто передать делитель в качестве атрибута вершинного шейдера а потом оттуда как вариации на фрагмент шейдера, где его можно было бы использовать для деления?
Дживо Елич,
TRANSFORM_TEX - это макрос Unity. У вас нет этого в three.js, поэтому вырезать нечего. Пока интерполированные ультрафиолетовые координаты и делители достигают фрагмента шейдера, на самом деле не имеет значения, как вы их там получили. Вы сталкивались с какими-либо проблемами, предоставляя это как атрибут / изменяющийся до сих пор?
DMGregory
Я еще не пробовал, так как не хотел терять время, пока не убедился, что это возможно. Еще один последний вопрос, делитель также будет интерполирован, как только он достигнет фрагмента шейдера, правильно? Имеется в виду, что на пикселе между двумя вершинами это будет какое-то интерполированное значение, а не исходное значение? Мне трудно обдумать, почему умножение ультрафиолета, а затем деление его помогает, но я поверю на слово и попробую. : small_smiling_face: Большое спасибо за помощь!
Дживо Елич