Процедурно генерировать бесшовные текстуры фрактального шума

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

В коде генерируется таблица из 64×64 случайных значений, а значения в текстуре являются результатом интерполяции между этими точками на различных «частотах» и «длинах волн» с использованием функции сглаживания, а затем объединяются для формирования окончательной текстуры шума ; и, наконец, значения в текстуре делятся на ее наибольшее значение, чтобы эффективно их нормализовать. Когда игрок находится на границе и создается новая текстура, создаваемая таблица случайных чисел повторно использует значения из соответствующего края предыдущей текстуры (например, если новая текстура предназначена для участка земли, который находится на сторона + X предыдущего, последнее значение в каждой строке предыдущей текстуры используется как первое значение в каждой строке случайных чисел в следующей.)

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

Вот часть кода, которая возвращает значение, интерполированное между точками таблицы случайных чисел, заданной точкой P:

float MainApp::assessVal(glm::vec2 P){
//Integer component of P
int xi = (int)P.x;
int yi = (int)P.y;

//Decimal component ofP
float xr = P.x - xi;
float yr = P.y - yi;

//Find the grid square P lies inside of
int x0 = xi % randX;
int x1 = (xi + 1) % randX;
int y0 = yi % randY;
int y1 = (yi + 1) % randY;//Get random values for the 4 nodes
float r00 = randNodes->randNodes[y0][x0];
float r10 = randNodes->randNodes[y0][x1];
float r01 = randNodes->randNodes[y1][x0];
float r11 = randNodes->randNodes[y1][x1];

//Smoother interpolation so
//texture appears less blocky
float sx = smoothstep(xr);
float sy = smoothstep(yr);

//Find the weighted value of the 4
//random values. This will be the
//final value in the noise texture
float sx0 = mix(r00, r10, sx);
float sx1 = mix(r01, r11, sx);

return mix(sx0, sx1, sy);
}

Где randNodes — это двумерный массив, содержащий случайные значения.

А вот код, который принимает все значения, возвращенные из вышеуказанной функции, и создает данные текстуры:

 int layers = 5;
float wavelength = 1, frequency = 1;

for (int k = 0; k < layers; k++) {
for (int i = 0; i < stepsY; i++) {
for(int j = 0; j < stepsX; j++){
//Compute value for (stepsX * stepsY) interpolation points
//across the grid of random numbers
glm::vec2 P = glm::vec2((float)j/stepsX * randX, (float)i/stepsY * randY);
buf[i * stepsY + j] += assessVal(P * wavelength) * frequency;
}
}
//repeat (layers) times with different signals
wavelength *= 0.5;
frequency *= 2;
}

for(int i = 0; i < buf.size(); i++){
//divide all data by the largest value.
//this normalises the data to avoid saturation
buf[i] /= largestVal;
}

Наконец, вот пример двух текстур, сгенерированных этими функциями, которые должен быть цельным, но не:

введите описание изображения здесь введите описание изображения здесь

2 изображения, расположенные рядом, как сейчас, явно не совпадают.

1

Решение

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

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

0

Другие решения

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

Для текстуры T размера stepX чтобы быть повторяемым (давайте рассмотрим 1-й случай для простоты), вы должны иметь

T(0) == T(stepX)

Или в вашем случае (заменить j = 0 а также j = stepX):

assessVal(0) == assessVal(randX * wavelength)

Когда k >= 1 это явно не так в вашем коде, потому что

(randX / pow(2, k)) % randX != 0

Одним из решений является уменьшение randX а также randY пока вы идете вверх по частотам.

Но мой типичный подход скорее будет начинаться с 2x2 случайная текстура, увеличить ее до 4x4 с GL_REPEATдобавить немного шума на пиксель, продолжить масштабирование до 8x8 и т.д .. пока я не доберусь до нужного размера.

0

По вопросам рекламы [email protected]