Ускорение процедурной генерации текстур

14

Недавно я начал работать над игрой, которая происходит в процедурной солнечной системе. После небольшой кривой обучения (ранее я не работал ни с 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; 
}
FalconNL
источник
Не могли бы вы опубликовать то, что у вас есть в настоящее время в Java для функции шума? Не говоря уже о том, что от этого можно добиться какого-то прироста производительности, но кто-то может заметить что-то, что даст вам прирост.
Даррен Рейд
Я добавил код, который я использую, к оригинальному сообщению.
FalconNL
Не относится к вашему Q как таковому, но вы должны вызывать dispose () в вашем растровом изображении после того, как вы закончите с ним.
Junkdog

Ответы:

10

Вы можете комбинировать подходы (2) и (3) следующим образом:

  • Во-первых, используйте графический процессор для генерации нескольких текстур шума и сохранения их. Это будет ваш «кэш шума»; Вы можете сделать это только один раз при первом запуске.
  • Чтобы создать текстуру в игре, объедините несколько текстур из кэша - это должно быть очень быстро. Затем, если необходимо, добавьте специальные эффекты, такие как вихри.
  • Кроме того, вы также можете предварительно сгенерировать некоторые текстуры со «спецэффектами» и просто смешать их, чтобы получить окончательный результат.
Ничего
источник
+1 Я думаю, лучший способ сделать это - создать кучу текстур и объединить их с игрой для комбинирования или применения простых эффектов.
TheNickmaster21
2

Процедурное поколение текстуры абы * * из MoFo в терминах вычислительного времени. Что есть, то есть.

Лучшая реализация Simplex Noise, которую я нашел, - это Stefan Gustavson's .

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

Так что начинайте генерировать текстуры при запуске игры в фоновом потоке , пока пользователь все еще возится с опциями и меню или смотрит трейлер запуска уровня.

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

bobobobo
источник