Создание карты нормалей из карты высот? - PullRequest
33 голосов
/ 12 марта 2011

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

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

enter image description here

Я думаю, что каждый пиксель изображения представляет координату решетки в сетке 256 x 256 (следовательно, почему существует высота 257 x 257). Это будет означать, что нормаль в координате (i, j) определяется высотами в (i, j), (i, j + 1), (i + 1, j) и (i + 1, j + 1) ) (назовите эти A, B, C и D соответственно).

Итак, учитывая трехмерные координаты A, B, C и D, имеет ли смысл:

  1. разделить четыре на два треугольника: ABC и BCD
  2. вычислить нормали этих двух граней через кросс-произведение
  3. разделен на два треугольника: ACD и ABD
  4. вычислить нормали этих двух граней
  5. в среднем четыре нормали

... или есть куда более простой метод, который мне не хватает?

Ответы [ 4 ]

41 голосов
/ 12 марта 2011

Пример кода GLSL от моего шейдера рендеринга поверхности воды:

#version 130
uniform sampler2D unit_wave
noperspective in vec2 tex_coord;
const vec2 size = vec2(2.0,0.0);
const ivec3 off = ivec3(-1,0,1);

    vec4 wave = texture(unit_wave, tex_coord);
    float s11 = wave.x;
    float s01 = textureOffset(unit_wave, tex_coord, off.xy).x;
    float s21 = textureOffset(unit_wave, tex_coord, off.zy).x;
    float s10 = textureOffset(unit_wave, tex_coord, off.yx).x;
    float s12 = textureOffset(unit_wave, tex_coord, off.yz).x;
    vec3 va = normalize(vec3(size.xy,s21-s01));
    vec3 vb = normalize(vec3(size.yx,s12-s10));
    vec4 bump = vec4( cross(va,vb), s11 );

Результатом является вектор удара: xyz = normal, a = height

15 голосов
/ 12 марта 2011

Я думаю, что каждый пиксель изображения представляет координату решетки в сетке 256 x 256 (следовательно, почему существует высота 257 x 257). Это будет означать, что нормаль в координате (i, j) определяется высотами в (i, j), (i, j + 1), (i + 1, j) и (i + 1, j + 1) ) (назовите эти A, B, C и D соответственно).

Нет. Каждый пиксель изображения представляет вершину сетки, поэтому интуитивно, исходя из симметрии, ее нормаль определяется высотой соседних пикселей (i-1, j), (i + 1, j), (i, j-1) , (i, j + 1).

Для функции f: 100 2 → ℝ, которая описывает поверхность в ℝ 3 , единица, нормальная в (x, y), определяется как

v = (−∂f / ∂x, −∂f / ∂y, 1) и n = v / | v |.

Можно доказать, что наилучшее приближение к ∂f / ∂x двумя выборками архивируется:

∂f / ∂x (x, y) = (f (x + ε, y) - f (x-ε, y)) / (2ε)

Чтобы получить лучшее приближение, вам нужно использовать как минимум четыре точки, поэтому добавление третьей точки (т.е. (x, y)) не улучшит результат.

Ваше hightmap является выборкой некоторой функции f на регулярной сетке. Взяв ε = 1, вы получите:

2v = (f (x − 1, y) - f (x + 1, y), f (x, y − 1) - f (x, y + 1), 2)

10 голосов
/ 14 октября 2014

Распространенным методом является использование фильтра Sobel для взвешенной / гладкой производной в каждом направлении.

Начните с выборки области высот 3x3 вокруг каждого текселя (здесь [4] - это пиксель, для которого мы хотим нормаль).

[6][7][8]
[3][4][5]
[0][1][2]

Тогда

//float s[9] contains above samples
vec3 n;
n.x = scale * -(s[2]-s[0]+2*(s[5]-s[3])+s[8]-s[6]);
n.y = scale * -(s[6]-s[0]+2*(s[7]-s[1])+s[8]-s[2]);
n.z = 1.0;
n = normalize(n);

Где scale можно настроить для соответствия глубине реального мира карты высот относительно ее размера.

9 голосов
/ 12 марта 2011

Если вы рассматриваете каждый пиксель как вершину, а не грань, вы можете создать простую треугольную сетку.

+--+--+
|\ |\ |
| \| \|
+--+--+
|\ |\ |
| \| \|
+--+--+

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

Вам нужен нормальный в каждой вершине .

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

Если у вас есть треугольник с вершинами v0, v1, v2, тогда вы можете использовать векторное перекрестное произведение (из двух векторов, которые лежат на двух сторонах треугольника), чтобы вычислить вектор в направлении нормали и масштабировать пропорционально площади треугольника.

Vector3 contribution = Cross(v1 - v0, v2 - v1);

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

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

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

for each interior vertex V {
  Vector3 sum(0.0, 0.0, 0.0);
  for each of the six triangles T that share V {
    const Vector3 side1 = T.v1 - T.v0;
    const Vector3 side2 = T.v2 - T.v1;
    const Vector3 contribution = Cross(side1, side2);
    sum += contribution;
  }
  sum.Normalize();
  V.normal = sum;
}

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...