Как вычислить нормали поверхности для сгенерированной геометрии

12

У меня есть класс, который генерирует трехмерную фигуру на основе входных данных из вызывающего кода. Входными данными являются такие вещи, как длина, глубина, дуга и т. Д. Мой код прекрасно генерирует геометрию, однако у меня возникают проблемы при вычислении нормалей поверхности. Когда горит, моя форма имеет очень странную окраску / текстуру от неправильных нормалей поверхности, которые рассчитываются. Из всех моих исследований я считаю, что моя математика верна, кажется, что что-то не так с моей техникой или методом.

На высоком уровне, как можно программно рассчитать нормали поверхности для сгенерированной фигуры? Я использую Swift / SceneKit на iOS для своего кода, но общий ответ в порядке.

У меня есть два массива, которые представляют мою форму. Одним из них является массив трехмерных точек, представляющих вершины, которые составляют форму. Другой массив представляет собой список индексов первого массива, которые отображают вершины в треугольники. Мне нужно взять эти данные и сгенерировать 3-й массив, который представляет собой набор нормалей поверхности, которые помогают в освещении фигуры. (см. SCNGeometrySourceSemanticNormalв SceneKit` )

Список вершин и индексов всегда различается в зависимости от входных данных для класса, поэтому я не могу предварительно рассчитать или жестко закодировать нормали поверхности.

macinjosh
источник
Нужно больше контекста. Вы пытаетесь вычислить аналитические нормали для параметрической поверхности? Неявная поверхность? Или вы хотите вычислить нормали из общей треугольной сетки? Или что-то другое?
Натан Рид
Спасибо, я добавил больше деталей. Чтобы ответить на ваш вопрос, мне нужно вычислить нормали из общей треугольной сетки. Хотя должно быть ясно, что меш отличается в зависимости от входных данных. Моя форма - это трехмерная стрелка, в качестве примера здесь приведен скриншот с двумя разными формами (то есть радиальной и линейной). Класс изменяет ширину, глубину, длину, дугу и радиус сетки в соответствии с запросом. cl.ly/image/3O0P3X3N3d1d Вы можете увидеть странное освещение, которое я получаю от моих неудачных попыток решить эту проблему .
Macinjosh
3
Краткая версия: вычислите каждую нормаль вершины как нормализованную сумму нормалей всех треугольников, которые ее касаются. Тем не менее, это сделает все гладким, что может быть не то, что вы хотите для этой формы. Я постараюсь раскрыть полный ответ позже.
Натан Рид
Гладкий - то, к чему я иду!
Macinjosh
4
В большинстве случаев, если вы вычисляете позиции вершин аналитически, вы также можете вычислять нормали аналитически. Для параметрической поверхности нормали являются перекрестным произведением двух градиентных векторов. Вычисление среднего значения нормалей треугольника является лишь приблизительным и часто приводит к визуально гораздо худшему качеству. Я бы опубликовал ответ, но я уже разместил подробный пример на SO ( stackoverflow.com/questions/27233820/… ), и я не уверен, что нам нужен здесь реплицируемый контент.
Рето Коради

Ответы:

10

Вы просто не хотите полностью гладких результатов. В то время как прокомментированный метод Натана Рида: «Рассчитать каждую вершину, чтобы найти нормаль, сложить их, нормализовать сумму», обычно работает, но иногда это не удается. Но это не имеет значения, мы можем использовать этот метод, добавив к нему предложение отклонения.

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

Изображение мы после

Изображение 1 : Результат, который вы хотите.

По сути, вы хотите усреднить только вершины искривленной области, все остальные могут использовать нормали, которые они получают из своего треугольника. Таким образом, вам лучше думать о сетке как о 9 отдельных областях, которые обрабатываются без других.

Отображение сетки и нормалей]

Изображение 2 : Изображение, показывающее структуру сетки и нормали.

Вы можете, конечно, автоматически сделать это, не включая нормали, которые находятся за пределами определенного угла от первичных вершин нормали. псевдокод:

For vertex in faceVertex:
    normal = vertex.normal
    For adjVertex in adjacentVertices:
        if anglebetween(vertex.normal, adjVertex.normal )  < treshold:
            normal += adjVertex.normal
    normal = normalize(normal)

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

joojaa
источник
10

Я вижу в основном три способа вычисления нормалей для сгенерированной фигуры.

Аналитические нормы

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

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

Нормали вершин

Перекрестное произведение двух векторов дает вектор, перпендикулярный плоскости, к которой они принадлежат. Таким образом, получить нормаль треугольника просто:

vec3 computeNormal(vec3 a, vec3 b, vec3 c)
{
    return normalize(crossProduct(b - a, c - a));
}

Более того, в приведенном выше примере длина поперечного произведения пропорциональна площади внутри abc . Таким образом, сглаженная нормаль в вершине, разделяемой несколькими треугольниками, может быть вычислена путем суммирования перекрестных произведений и нормализации в качестве последнего шага, таким образом взвешивая каждый треугольник по его площади.

vec3 computeNormal(vertex a)
{
    vec3 sum = vec3(0, 0, 0);
    list<vertex> adjacentVertices = getAdjacentVertices(a);
    for (int i = 1; i < adjacentVertices; ++i)
    {
        vec3 b = adjacentVertices[i - 1];
        vec3 c = adjacentVertices[i];
        sum += crossProduct(b - a, c - a);
    }
    if (norm(sum) == 0)
    {
        // Degenerate case
        return sum;
    }
    return normalize(sum);
}

Если вы работаете с квадраторами, есть хороший прием, который вы можете использовать: для квадроциклов abcd используйте, crossProduct(c - a, d - b)и он отлично справится со случаями, когда квад на самом деле является треугольником.

Иньго Квилес написал несколько коротких статей на эту тему: умная нормализация сетки , нормаль и площадь n-сторонних многоугольников .

Нормалы из частных производных

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

Жюльен Геро
источник
1
Существует четвертый способ, художник поставил нормали;)
joojaa
@joojaa: я полагаю, вы имеете в виду карты нормалей? В противном случае я никогда не слышал о созданных вручную нормалях.
Жюльен Геро,
1
Нет, нормали, созданные вручную. Иногда случается, что ваш художник знает больше о том, как должны вести себя нормали, чем модели программистов. Иногда механизмам вычислений немного проблематично, если они предполагают, что нормали исходят из базовых вычислений. Но, конечно, это происходит, и вы экономите много времени на математическом моделировании.
Джуджаа
1
Их иногда называют «явными нормалями» (термины 3ds max и maya).
Душан Бошняк, «поручитель»