Подтвердить что ты не робот

Создание нормальной карты с карты высоты?

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

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

enter image description here

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

Поэтому, учитывая 3D-координаты A, B, C и D, имеет смысл:

  • разделите четыре на два треугольника: ABC и BCD
  • вычислить нормали этих двух граней через кросс-произведение
  • разбивается на два треугольника: ACD и ABD
  • вычислить нормали этих двух граней
  • в среднем четыре нормали

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

4b9b3361

Ответ 1

Пример кода 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

Ответ 2

Я считаю, что каждый пиксель изображения представляет собой координату решетки в сетке 256 x 256 (следовательно, высота 257 x 257). Это означало бы, что нормаль в координате (i, j) определяется высотами at (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: ℝ 2 → ℝ, описывающей поверхность в ℝ 3 единичная нормаль at (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)

Ответ 3

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

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

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

Что вам нужно, это нормальная в каждой вершине.

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

Если у вас есть треугольник с вершинами 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;
}

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

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

Ответ 4

Обычный метод использует фильтр 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 можно настроить в соответствии с высотой реального мира по высоте относительно его размера.