Генерация карты нормалей из карты высот?
Я работаю над процедурным генерированием пятен грязи, используя рандомизированные фракталы для видеоигры. Я уже сгенерировал карту высот, используя алгоритм смещения средней точки, и сохранил ее в текстуре. У меня есть некоторые идеи о том, как превратить это в текстуру нормалей, но некоторые отзывы будут высоко оценены.
Моя текстура высоты в настоящее время является изображением серой шкалы 257 x 257 (значения высоты масштабируются для целей видимости):
Я думаю, что каждый пиксель изображения представляет координату решетки в сетке 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, имеет ли смысл:
- разделить четыре на два треугольника: ABC и BCD
- рассчитать нормали этих двух граней через кросс-произведение
- разделить на два треугольника: ACD и ABD
- рассчитать нормали этих двух граней
- в среднем четыре нормали
... или есть намного более простой метод, который я пропускаю?
4 ответа
Пример кода 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
Я думаю, что каждый пиксель изображения представляет координату решетки в сетке 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: →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)
Распространенным методом является использование фильтра Собеля для взвешенной / гладкой производной в каждом направлении.
Начните с выборки 3х3 высот вокруг каждого текселя (здесь [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
можно отрегулировать в соответствии с высотой реального мира карты высот относительно ее размера.
Если вы думаете о каждом пикселе как о вершине, а не как о лице, вы можете создать простой треугольный меш.
+--+--+
|\ |\ |
| \| \|
+--+--+
|\ |\ |
| \| \|
+--+--+
Каждая вершина имеет координаты 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;
}
Если вам нужна нормаль в конкретной точке треугольника (отличной от одной из вершин), вы можете интерполировать, взвешивая нормали трех вершин по барицентрическим координатам вашей точки. Вот как графические растеризаторы воспринимают обычное для затенения. Это позволяет треугольной сетке выглядеть как гладкая криволинейная поверхность, а не как куча смежных плоских треугольников.
Совет. Для первого теста используйте идеально ровную сетку и убедитесь, что все вычисленные нормали направлены прямо вверх.