Создание нормальной карты с карты высоты?
Я работаю над процедурной генерации пятен грязи, используя рандомизированные фракталы для видеоигры. Я уже создал карту высот, используя алгоритм смещения средней точки и сохранил ее в текстуре. У меня есть некоторые идеи о том, как превратить это в текстуру нормалей, но некоторые отзывы будут высоко оценены.
Моя текстура высоты в настоящее время представляет собой изображение серого масштаба 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
- вычислить нормали этих двух граней
- в среднем четыре нормали
... или есть гораздо более простой метод, который мне не хватает?
Ответы
Ответ 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
можно настроить в соответствии с высотой реального мира по высоте относительно его размера.