Недавно я начал работать над игрой, которая происходит в процедурной солнечной системе. После небольшой кривой обучения (ранее я не работал ни с Scala, ни с OpenGL 2 ES, ни с Libgdx), у меня есть демонстрация базовой технологии, где вы вращаетесь вокруг одной процедурно текстурированной планеты:
Проблема, с которой я сталкиваюсь - это производительность генерации текстур. Краткий обзор того, что я делаю: планета - это куб, деформированный в сферу. К каждой стороне применяется текстура с тревогой (например, 256 x 256), которая объединяется в одну текстуру 8n xn, которая отправляется фрагментному шейдеру. Последние два пробела не используются, они используются только для того, чтобы убедиться, что ширина равна степени 2. Текстура в настоящее время генерируется на процессоре с использованием обновленной версии алгоритма симплексного шума 2012 года, ссылка на которую приведена в статье «Симплекс». шум демистифицирован, Сцена, которую я использую для проверки алгоритма, содержит две сферы: планету и фон. Оба используют текстуру в оттенках серого, состоящую из шести октав трехмерного симплексного шума, поэтому, например, если мы выберем 128x128 в качестве размера текстуры, то будет 128 x 128 x 6 x 2 x 6 = около 1,2 миллиона вызовов функции шума.
Ближе всего вы попадете на планету о том, что показано на скриншоте, и поскольку целевое разрешение игры составляет 1280x720, это означает, что я предпочел бы использовать текстуры 512x512. Объедините это с фактом, что фактические текстуры, конечно, будут более сложными, чем базовые шумы. (В шейдере фрагментов, основанном на солнечном свете, будет смешана дневная и ночная текстура. Мне нужен шум для континентов, изменение цвета местности. , облака, огни города и т. д.) и мы смотрим на что-то вроде 512 x 512 x 6 x 3 x 15 = 70 миллионов шумовых вызовов только для планеты. В финальной игре будут происходить действия при путешествии между планетами, поэтому было бы приемлемо подождать 5 или 10 секунд, возможно, 20, так как я могу вычислить текстуру фона во время путешествия, хотя, очевидно, чем быстрее, тем лучше.
Возвращаясь к нашей тестовой сцене, производительность на моем ПК не слишком ужасна, хотя все еще слишком медленна, учитывая, что конечный результат будет примерно в 60 раз хуже:
128x128 : 0.1s
256x256 : 0.4s
512x512 : 1.7s
Это после того, как я переместил весь критичный к производительности код на Java, поскольку попытка сделать это в Scala была намного хуже. Запуск этого на моем телефоне (Samsung Galaxy S3), однако, дает более проблемный результат:
128x128 : 2s
256x256 : 7s
512x512 : 29s
Уже слишком долго, и это даже не учитывает тот факт, что в финальной версии это будут минуты, а не секунды. Очевидно, что-то должно быть сделано. Лично я вижу несколько потенциальных путей, хотя я пока не особо заинтересован в любом из них:
- Не пересчитывайте текстуры, но позвольте фрагментному шейдеру вычислить все. Вероятно, это невозможно, потому что в какой-то момент у меня был фон в виде полноэкранного четырехугольника с пиксельным шейдером, и я получил около 1 кадра в секунду на своем телефоне.
- Используйте графический процессор для визуализации текстуры один раз, сохраните ее и используйте сохраненную текстуру с этого момента. Наверху: может быть быстрее, чем делать это на процессоре, поскольку предполагается, что графический процессор будет быстрее при вычислениях с плавающей запятой. Недостаток: эффекты, которые нельзя (легко) выразить как функции симплексного шума (например, вихри газовой планеты, кратеры луны и т. Д.), Гораздо сложнее кодировать в GLSL, чем в Scala / Java.
- Рассчитайте большое количество шумовых текстур и отправьте их вместе с приложением. Я хотел бы избежать этого, если это вообще возможно.
- Уменьшите разрешение. Покупает 4-кратное увеличение производительности, которого на самом деле недостаточно, и я теряю много качества.
- Найти более быстрый алгоритм шума. Если у кого-то есть один, у меня все уши, но симплекс уже должен быть быстрее, чем perlin.
- Принять стиль пиксельной графики, позволяющий использовать текстуры с более низким разрешением и меньше шумовых октав. Хотя я изначально представлял игру в этом стиле, я предпочел реалистичный подход.
- Я делаю что-то не так, и производительность уже должна быть на один-два порядка лучше. Если это так, пожалуйста, дайте мне знать.
Если у кого-то есть какие-либо предложения, советы, обходные пути или другие комментарии по этой проблеме, я бы хотел их услышать.
В ответ на Layoric вот код, который я использую:
//The function that generates the simplex noise texture
public static Texture simplex(int size) {
byte[] data = new byte[size * size * columns * 4];
int offset = 0;
for (int y = 0; y < size; y++) {
for (int s = 0; s < columns; s++) {
for (int x = 0; x < size; x++) {
//Scale x and y to [-1,1] range
double tx = ((double)x / (size - 1)) * 2 - 1;
double ty = 1 - ((double)y / (size - 1)) * 2;
//Determine point on cube in worldspace
double cx = 0, cy = 0, cz = 0;
if (s == 0) { cx = 1; cy = tx; cz = ty; }
else if (s == 1) { cx = -tx; cy = 1; cz = ty; }
else if (s == 2) { cx = - 1; cy = -tx; cz = ty; }
else if (s == 3) { cx = tx; cy = - 1; cz = ty; }
else if (s == 4) { cx = -ty; cy = tx; cz = 1; }
else if (s == 5) { cx = ty; cy = tx; cz = - 1; }
//Determine point on sphere in worldspace
double sx = cx * Math.sqrt(1 - cy*cy/2 - cz*cz/2 + cy*cy*cz*cz/3);
double sy = cy * Math.sqrt(1 - cz*cz/2 - cx*cx/2 + cz*cz*cx*cx/3);
double sz = cz * Math.sqrt(1 - cx*cx/2 - cy*cy/2 + cx*cx*cy*cy/3);
//Generate 6 octaves of noise
float gray = (float)(SimplexNoise.fbm(6, sx, sy, sz, 8) / 2 + 0.5);
//Set components of the current pixel
data[offset ] = (byte)(gray * 255);
data[offset + 1] = (byte)(gray * 255);
data[offset + 2] = (byte)(gray * 255);
data[offset + 3] = (byte)(255);
//Move to the next pixel
offset += 4;
}
}
}
Pixmap pixmap = new Pixmap(columns * size, size, Pixmap.Format.RGBA8888);
pixmap.getPixels().put(data).position(0);
Texture texture = new Texture(pixmap, true);
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
return texture;
}
//SimplexNoise.fbm
//The noise function is the same one found in http://webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java
//the only modification being that I replaced the 32 in the last line with 16 in order to end up with
//noise in the range [-0.5, 0.5] instead of [-1,1]
public static double fbm(int octaves, double x, double y, double z, double frequency) {
double value = 0;
double f = frequency;
double amp = 1;
for (int i = 0; i < octaves; i++) {
value += noise(x*f, y*f, z*f) * amp;
f *= 2;
amp /= 2;
}
return value;
}