Генерация процедурной структуры

У меня сейчас в разработке игра на основе вокселей, и я пока генерирую свой мир, используя Simplex Noise. Теперь я хочу сгенерировать некоторые другие структуры, такие как реки, города и другие вещи, которые нелегко создать, потому что я разделил свой мир (который практически бесконечен) на куски размером 64x128x64. Я уже сгенерировал деревья (листья могут вырасти на соседние куски), сгенерировав деревья для куска, а также деревья для 8 кусков, окружающих его, чтобы листья не пропадали. Но если я перейду в более высокие измерения, это может стать трудным, когда мне нужно будет вычислить один кусок, учитывая фрагменты в радиусе 16 других фрагментов.

Есть ли способ сделать это лучше?


person user3088126    schedule 12.09.2015    source источник
comment
Если я хорошо понял, когда вы генерируете дерево (например), вы должны проверять 8 чанков соседей на случай, если дерево выросло внутри чанка соседей, не так ли? чего я до сих пор не понимаю, так это о том, что «если я пойду в более высокие измерения, это может стать трудным ...», не могли бы вы уточнить? может вы имели в виду объекты более высокого измерения?   -  person alesegdia    schedule 13.09.2015
comment
Ну, когда я, например, создаю город, который может расширяться кусками 8x8, мне пришлось бы вычислить 64 куска, чтобы сгенерировать один кусок. И мне нужно вычислить 64 из них, что означает, что я бы вычислил 4096 фрагментов, чтобы сгенерировать 64 (если бы я делал это так же, как деревья).   -  person user3088126    schedule 13.09.2015


Ответы (3)


В зависимости от желаемой сложности сгенерированной структуры вам может быть полезно сначала сгенерировать ее в отдельном массиве, возможно, даже в карте (словарь местоположения к содержимому, полезный в случае высокой разреженности), а затем передать структуру к миру?

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

person Thehx    schedule 12.09.2015
comment
Я предполагаю, что имея смещение объекта в мире и размер блока, вы можете легко нарезать объект, а затем обновить воксели в движке с помощью нарезанных частей, верно? - person alesegdia; 13.09.2015
comment
Кажется, хороший подход. Я также смотрел на фракталы, но они, похоже, не работают, поскольку мой мир практически бесконечно велик. У меня остается один вопрос: как насчет длинных рек? - person user3088126; 13.09.2015
comment
Сама река может быть очень хорошо определена фрактально, т. Е. Проведите прямую линию от одного озера (A) к другому (B), добавьте несколько точек между ними, повторите, пока не будет достигнут желаемый уровень детализации, не так ли? - person Thehx; 13.09.2015
comment
И совсем не обязательно, чтобы весь бесконечный мир сохранял фрактальную структуру во всех масштабах. Просто некоторые объекты выглядят как фракталы, когда они находятся далеко (береговая линия, если смотреть сверху). Но нет необходимости делать все фрактальным в большом масштабе (и, очевидно, нет необходимости (или возможности) сохранять фрактальную структуру в самом маленьком масштабе; в конце концов, реальные береговые линии гладкие, если внимательно наблюдать). - person Thehx; 13.09.2015

Я читал что-то об этом в книге, и то, что они сделали в этих случаях, было сделать более тонкое разделение кусков в зависимости от приложения, то есть: если вы собираетесь выращивать очень большие объекты, может быть полезно иметь другое разделенное логическое разделение например, 128x128x128 только для этого конкретного приложения.

По сути, данные находятся в одном и том же месте, вы просто используете разные логические подразделения.

Честно говоря, никогда не делал никаких вокселей, так что не воспринимайте мой ответ слишком серьезно, просто подбрасывайте идеи. Кстати, книга - game engine gems 1, у них там есть гем на воксельные движки.

Что касается рек, не могли бы вы просто установить уровень воды и позволить рекам автоматически образовываться в горных лестницах? Чтобы избежать попадания воды в горные пещеры, вы можете выполнить raycast вверх, чтобы проверить, свободна ли она на N блоков.

person alesegdia    schedule 12.09.2015
comment
Что ж, возможно, это подход, но я вижу предел, основанный на том, насколько большой может быть структура. Если структура имеет радиус 8 блоков, я должен был бы проверить перед созданием блока, есть ли поблизости структуры, которые необходимо было бы создать. Я понял, что делать с такими городами, но как насчет длинных рек? - person user3088126; 13.09.2015

Я знаю, что эта ветка устарела, и я не умею объяснять, но я поделюсь своим подходом.

Так, например, деревья 5x5x5. Вы хотите, чтобы ваша функция шума возвращала одно и то же значение для области размером 5x5 блоков, так что даже за пределами блока вы все равно можете проверить, следует ли вам сгенерировать дерево или нет.

// Here the returned value is different for every block
float value = simplexNoise(x * frequency, z * frequency) * amplitude; 

// Here it will return the same value for an area of blocks (you should use floorDiv instead of dividing, or you it will get negative coordinates wrong (-3 / 5 should be -1, not 0 like in normal division))
float value = simplexNoise(Math.floorDiv(x, 5) * frequency, Math.floorDiv(z, 5) * frequency) * amplitude;

А теперь посадим дерево. Для этого нам нужно проверить, в какой позиции x y z находится этот текущий блок относительно начальной позиции дерева, чтобы мы могли знать, в какой части дерева находится этот блок.

if(value > 0.8) { // A certain threshold (checking if tree should be generated at this area)
    int startX = Math.floorDiv(x, 5) * 5; // flooring the x value to every 5 units to get the start position
    int startZ = Math.floorDiv(z, 5) * 5; // flooring the z value to every 5 units to get the start position
    // Getting the starting height of the trunk (middle of the tree , that's why I'm adding 2 to the starting x and starting z), which is 1 block over the grass surface
    int startY = height(startX + 2, startZ + 2) + 1;

    int relx = x - startX; // block pos relative to starting position
    int relz = z - startZ;

    for(int j = startY; j < startY + 5; j++) {
        int rely = j - startY;
        byte tile = tree[relx][rely][relz]; // Get the needing block at this part of the tree
        tiles[i][j][k] = tile;
    }
}

Трехмерный массив tree здесь почти как «префаб» дерева, который вы можете использовать, чтобы узнать, какой блок установить в позицию относительно начальной точки. (Боже, я не знаю, как это объяснить, и то, что мой пятый язык - английский, мне тоже не помогает; -; не стесняйтесь улучшить свой ответ или придумать новый). Я реализовал это в своем движке, и он полностью работает. Структуры могут быть сколь угодно большими без предварительной загрузки фрагментов. Единственная проблема с этим методом заключается в том, что деревья или структуры будут создаваться почти внутри сетки, но это можно легко решить с помощью нескольких октав с разными смещениями.

Итак, резюмируем

for (int i = 0; i < 64; i++) {
    for (int k = 0; k < 64; k++) {
        int x = chunkPosToWorldPosX(i); // Get world position
        int z = chunkPosToWorldPosZ(k);

        // Here the returned value is different for every block
        // float value = simplexNoise(x * frequency, z * frequency) * amplitude; 

        // Here it will return the same value for an area of blocks (you should use floorDiv instead of dividing, or you it will get negative coordinates wrong (-3 / 5 should be -1, not 0 like in normal division))
        float value = simplexNoise(Math.floorDiv(x, 5) * frequency, Math.floorDiv(z, 5) * frequency) * amplitude;

        if(value > 0.8) { // A certain threshold (checking if tree should be generated at this area)
            int startX = Math.floorDiv(x, 5) * 5; // flooring the x value to every 5 units to get the start position
            int startZ = Math.floorDiv(z, 5) * 5; // flooring the z value to every 5 units to get the start position
            // Getting the starting height of the trunk (middle of the tree , that's why I'm adding 2 to the starting x and starting z), which is 1 block over the grass surface
            int startY = height(startX + 2, startZ + 2) + 1;

            int relx = x - startX; // block pos relative to starting position
            int relz = z - startZ;

            for(int j = startY; j < startY + 5; j++) {
                int rely = j - startY;
                byte tile = tree[relx][rely][relz]; // Get the needing block at this part of the tree
                tiles[i][j][k] = tile;
            }
        }
    }
}

Итак, «i» и «k» зацикливаются внутри блока, а «j» зацикливается внутри структуры. Вот как это должно работать.

Что касается рек, я лично еще этого не делал, и я не уверен, почему вам нужно устанавливать блоки вокруг чанка при их генерации (вы можете просто использовать червей Perlin, и это решит проблему), но это довольно почти такая же идея, и для ваших городов тоже.

person Adam    schedule 03.02.2020