Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Форум / Рендер как в Minecraft-е - кто нибудь знает основные "секреты"? (4 стр)

Рендер как в Minecraft-е - кто нибудь знает основные "секреты"? (4 стр)

Страницы: 13 4 5 6101 Следующая »
alexzzzzПостоялецwww30 мая 20120:10#45
SCat
> > Нужно сделать его циклическим
> т.е  когда вышли за пределы, выгружаем предыдущий и загружаем новый (где
> позиция [0, 8]) ?

Хочешь записать новый блок в позицию с мировыми координатами [512, 520], вызываешь SetBlock(512, 520, block), и блок сохраняется в циклическом массиве в позицию [0, 8].

Хочешь прочитать блок из позиции с мировыми координатами [512, 520], вызываешь GetBlock(512, 520), и функция читает блок из циклического массива из позиции [0, 8].

Т.е. внешне всё выглядит как-будто у тебя бесконечный массив. Если игрок идёт, например, на север, то перед тем как загружать в массив данные нового северного чанка, надо не забыть выгрузить из массива данные противоположного ему южного чанка. Иначе новый северный перезатрёт данные старого южного.

> > https://skydrive.live.com/redir?resid=D42713A2C49773E7!195
> служба не работает (

Скопируй ссылку целиком вместе с !195

Правка: 30 мая 2012 0:21

alexzzzzПостоялецwww30 мая 20121:24#46
WISHMASTER35
>> для хранения блоков использовать отдельный массив блоков для каждого чанка, то
>> при расчёте освещения будут проблемы с обработкой пограничных блоков. Будет
>> много дополнительных проверок на выход за пределы чанка.

> Эти проверки ничего не отнимут. Ибо крайних блоков не много.

Кажется, что не много. В чанке размером 32x32x32 всего 32к блоков, из них крайних больше 5к (грубо 15%). В чанке 16x128x16 тоже 32к блоков, из них крайних около 8к (грубо 25%).

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

Одно дело для каждой из шести граней блока писать что-то типа:

// North
if (z + 1 < Settings.CHUNK_SIZE)
{
  neighbour = blocks[x, wy, z + 1];
}
else
{
  var neighbourN = parentMapChunk.NeighbourN;
  neighbour = neighbourN.blocks[x, wy, 0];
}
и умноженное в 6 раз крутить это 30 тысяч итераций на чанк.

И совсем другое дело писать так:

neighbour = GetBlock(wx, wy, wz + 1);

Я изучал профайлером, на что уходят ресурсы процессора. Оказалось, практически по-барабану как впоследствии обрабатываются видимые грани (которых оооочень мало), потому что бóльшая часть времени тратится на определение самогó факта их видимости (это приходится делать для каждой грани каждого блока в чанке). Каждая лишняя инструкция заметно влияет на общее время вычислений.

Для ориентира: создание списков треугольников, вершин, их цветов, нормалей и uv-координат для чанка 32х32х32 у меня занимает около 2,5 мс; заливка чанка 32x128x32 солнечным светом ― около 1,7 мс (C#, i2500K @4ГГц).

Но даже если не гнаться за скоростью, код стало намного легче поддерживать. Глядя на него, я снова стал понимать, что он делает. :)

Правка: 30 мая 2012 2:27

alexzzzzПостоялецwww30 мая 20122:12#47
WISHMASTER35
> 2) когда строишь крайний чанк, то у него по бокам строятся стены. А когда рядом
> достраиваешь еще соседний чанк, то эта стена скрывается и получается, что ты ее
> зря построил.
> Вроде и не так страшно, но этих не видимых фейсов получается больше, чем
> видимых фейсов. А ведь на них тратиться время и память( И получается, что
> строительство новых чанков - дело медленное.

Решение ― не строить чанку меш, пока все его соседние чанки не будут заполнены блоками и пока для них не будет расчитано освещение. Иначе получается то, что у тебя получается.

Вот твой мир, вид сверху, игрок где-то посередине:

+ Показать

Серое ― это далёкие области, хранящиеся не в памяти, а на диске или вообще пока несуществующие.
Красные квадраты ― это чанки, для которых имеется пока только ландшафт (блоки).
Жёлтые ― чанки с ландшафтом, для которых уже было расчитано освещение.
Зелёные ― чанки с ландшафтом, расчитанным освещением, для которых уже были построены меши. Т.е. это те чанки, которые игрок может видеть.

Предположим, игрок идёт на север. Ряд южных чанков сохраняем на диск и убираем, сверху добавляем новые пока пустые чанки:

+ Показать

Загружаем или генерируем на лету для них ландшафт:

+ Показать

Теперь получилось, что во втором ряду у нас есть чанки без освещения, у которых все соседи имеют ландшафт.
Значит, для этих чанков уже можно рассчитать освещение:

+ Показать

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

+ Показать

Теперь всё готово. Если соблюдать такой порядок расчётов, то никакие лишние грани и никакие артефакты освещения возникать не будут.

Правка: 30 мая 2012 22:03

HotDogПостоялецwww30 мая 201212:34#48
alexzzzz, ну ты отличный хостинг для картинок выбрал. 5 раз капчу вводить чтобы твои картинки посмотреть?
alexzzzzПостоялецwww30 мая 201213:08#49
HotDog
> 5 раз капчу вводить чтобы твои картинки посмотреть?

Пародон. Несколько лет пользуюсь, и такого никогда раньше не было.

Правка: 30 мая 2012 13:09

WISHMASTER35Участникwww30 мая 201213:38#50
В чанке размером 32x32x32 всего 32к блоков, из них крайних больше 5к

Тем не менее все равно далеко не для каждого. Хотя я профайлером не успел это глянуть(
Вот мое злокодерстов
public static void BuildCube(int x, int y, int z, Chunk chunk, List<Vector3> vertices, List<Vector2> uv, List<Vector3> normals, List<int> indices) {
    Block block = chunk.GetBlock(x, y, z);
    if(block == null) return;
    
    Vector3 pos = new Vector3(x, y, z);
    
    if(IsFree(chunk, x-1, y, z)) CubeBuilder.BuildFace(BlockFace.Left, block, pos, vertices, uv, normals, indices);
    if(IsFree(chunk, x+1, y, z)) CubeBuilder.BuildFace(BlockFace.Right, block, pos, vertices, uv, normals, indices);
          
    if(IsFree(chunk, x, y-1, z)) CubeBuilder.BuildFace(BlockFace.Bottom, block, pos, vertices, uv, normals, indices);
    if(IsFree(chunk, x, y+1, z)) CubeBuilder.BuildFace(BlockFace.Top, block, pos, vertices, uv, normals, indices);
          
    if(IsFree(chunk, x, y, z-1)) CubeBuilder.BuildFace(BlockFace.Back, block, pos, vertices, uv, normals, indices);
    if(IsFree(chunk, x, y, z+1)) CubeBuilder.BuildFace(BlockFace.Front, block, pos, vertices, uv, normals, indices);
  }
  
  private static bool IsFree(Chunk chunk, int x, int y, int z) {
    if(x>=0 && y>=0 && z>=0 && x<ChunkBuilder.CHUNK_SIZE && y<ChunkBuilder.CHUNK_SIZE && z<ChunkBuilder.CHUNK_SIZE) {
      return chunk.GetBlock(x, y, z) == null;
    }
    
    ChunkID id = chunk.GetID();
    if(x < 0) {
      x += ChunkBuilder.CHUNK_SIZE;
      id.x--;
    }
    if(y < 0) {
      y += ChunkBuilder.CHUNK_SIZE;
      id.y--;
    }
    if(z < 0) {
      z += ChunkBuilder.CHUNK_SIZE;
      id.z--;
    }
    
    if(x >= ChunkBuilder.CHUNK_SIZE) {
      x -= ChunkBuilder.CHUNK_SIZE;
      id.x++;
    }
    if(y >= ChunkBuilder.CHUNK_SIZE) {
      y -= ChunkBuilder.CHUNK_SIZE;
      id.y++;
    }
    if(z >= ChunkBuilder.CHUNK_SIZE) {
      z -= ChunkBuilder.CHUNK_SIZE;
      id.z++;
    }
    
    chunk = chunk.GetMap().GetChunk(id);
    if(chunk == null) return true;
    return chunk.GetBlock(x, y, z) == null;
  }
Решение ― не строить чанку меш, пока все его соседние чанки не будут заполнены блоками

Наверно самое простое и нормальное решение.
Спасибо за такое объяснение и картинки)
WISHMASTER35Участникwww30 мая 201213:49#51
Вот что у меня сейчас имеется http://www.youtube.com/watch?v=up4MMG_qL_k&feature=youtu.be
Генерация действительно медленная и вызывает подтормаживания.
Мир генерируется шумом перлина и пока получается очень скушный.
А шум перлина должен возвращать результат в каком диапазоне? У меня диапазон где-то [0.3, 0.6] Из-за этого нет впаден для воды.
Вот мой алгоритм. Может у кого есть более правильный?
private const int GradientSizeTable = 256;
    private float[] gradients = new float[GradientSizeTable * 3];
  
  private short[] perm = new short[] {
        225, 155, 210, 108, 175, 199, 221, 144, 203, 116, 70, 213, 69, 158, 33, 252,
        5, 82, 173, 133, 222, 139, 174, 27, 9, 71, 90, 246, 75, 130, 91, 191,
        169, 138, 2, 151, 194, 235, 81, 7, 25, 113, 228, 159, 205, 253, 134, 142,
        248, 65, 224, 217, 22, 121, 229, 63, 89, 103, 96, 104, 156, 17, 201, 129,
        36, 8, 165, 110, 237, 117, 231, 56, 132, 211, 152, 20, 181, 111, 239, 218,
        170, 163, 51, 172, 157, 47, 80, 212, 176, 250, 87, 49, 99, 242, 136, 189,
        162, 115, 44, 43, 124, 94, 150, 16, 141, 247, 32, 10, 198, 223, 255, 72,
        53, 131, 84, 57, 220, 197, 58, 50, 208, 11, 241, 28, 3, 192, 62, 202,
        18, 215, 153, 24, 76, 41, 15, 179, 39, 46, 55, 6, 128, 167, 23, 188,
        106, 34, 187, 140, 164, 73, 112, 182, 244, 195, 227, 13, 35, 77, 196, 185,
        26, 200, 226, 119, 31, 123, 168, 125, 249, 68, 183, 230, 177, 135, 160, 180,
        12, 1, 243, 148, 102, 166, 38, 238, 251, 37, 240, 126, 64, 74, 161, 40,
        184, 149, 171, 178, 101, 66, 29, 59, 146, 61, 254, 107, 42, 86, 154, 4,
        236, 232, 120, 21, 233, 209, 45, 98, 193, 114, 78, 19, 206, 14, 118, 127,
        48, 79, 147, 85, 30, 207, 219, 54, 88, 234, 190, 122, 95, 67, 143, 109,
        137, 214, 145, 93, 92, 100, 245, 0, 216, 186, 60, 83, 105, 97, 204, 52
    };
  
  public PerlinNoise() {
        System.Random random = new System.Random();
        
        for (int i = 0; i < GradientSizeTable; i++) {
            float z = 1f - 2f * (float)random.NextDouble();
            float r = Mathf.Sqrt(1 - z * z);
            float theta = 2 * Mathf.PI * (float)random.NextDouble();
            gradients[i * 3] = r * Mathf.Cos(theta);
            gradients[i * 3 + 1] = r * Mathf.Sin(theta);
            gradients[i * 3 + 2] = z;
        }
    }
  
  public float Noise(float x, float y) {
        //return (Noise(2 * x, 2 * y, -0.5f) + 1) / 2 * 0.7f + 
        //       (Noise(4 * x, 4 * y, 0) + 1) / 2 * 0.2f + 
        //       (Noise(8 * x, 8 * y, +0.5f) + 1) / 2 * 0.1f;
    
    float noise = (Noise(2 * x, 2 * y, -0.5f) + 1) / 2 * 0.7f;
    noise += (Noise(4 * x, 4 * y, 0) + 1) / 2 * 0.2f;
    noise += (Noise(8 * x, 8 * y, +0.5f) + 1) / 2 * 0.1f;
    return noise;
    }

    public float Noise(float x, float y, float z) {
        int ix = (int) Mathf.Floor(x);
        float fx0 = x - ix;
        float fx1 = fx0 - 1;
        float wx = Smooth(fx0);

        int iy = (int) Mathf.Floor(y);
        float fy0 = y - iy;
        float fy1 = fy0 - 1;
        float wy = Smooth(fy0);

        int iz = (int) Mathf.Floor(z);
        float fz0 = z - iz;
        float fz1 = fz0 - 1;
        float wz = Smooth(fz0);

        float vx0 = Lattice(ix, iy, iz, fx0, fy0, fz0);
        float vx1 = Lattice(ix + 1, iy, iz, fx1, fy0, fz0);
        float vy0 = Lerp(wx, vx0, vx1);

        vx0 = Lattice(ix, iy + 1, iz, fx0, fy1, fz0);
        vx1 = Lattice(ix + 1, iy + 1, iz, fx1, fy1, fz0);
        float vy1 = Lerp(wx, vx0, vx1);

        float vz0 = Lerp(wy, vy0, vy1);

        vx0 = Lattice(ix, iy, iz + 1, fx0, fy0, fz1);
        vx1 = Lattice(ix + 1, iy, iz + 1, fx1, fy0, fz1);
        vy0 = Lerp(wx, vx0, vx1);

        vx0 = Lattice(ix, iy + 1, iz + 1, fx0, fy1, fz1);
        vx1 = Lattice(ix + 1, iy + 1, iz + 1, fx1, fy1, fz1);
        vy1 = Lerp(wx, vx0, vx1);

        float vz1 = Lerp(wy, vy0, vy1);
        return Lerp(wz, vz0, vz1);
    }


    private int Permutate(int x) {
        int mask = GradientSizeTable - 1;
        return perm[x & mask];
    }

    private int Index(int ix, int iy, int iz) {
        // Turn an XYZ triplet into a single gradient table index.
        return Permutate(ix + Permutate(iy + Permutate(iz)));
    }

    private float Lattice(int ix, int iy, int iz, float fx, float fy, float fz) {
        // Look up a random gradient at [ix,iy,iz] and dot it with the [fx,fy,fz] vector.
        int index = Index(ix, iy, iz);
        int g = index * 3;
        return gradients[g] * fx + gradients[g + 1] * fy + gradients[g + 2] * fz;
    }

    private float Lerp(float t, float value0, float value1) {
        return value0 + t * (value1 - value0);
    }

    private float Smooth(float x) {
        return x * x * (3 - 2 * x);
    }

А вообще хочу сделать с эффектами как здесь http://www.youtube.com/watch?v=13ha669K88I
И сглаживать кубы, чтобы получилось как в Worms 3D.
И не обязательно бесконечный мир, если это позволит сделать более интересные уровни, как тут писалось http://www.gamedev.ru/code/forum/?id=161884&page=3#m34
Только мне еще далеко до этого всего)

Правка: 30 мая 2012 13:50

alexzzzzПостоялецwww30 мая 201220:53#52
WISHMASTER35
Block block = chunk.GetBlock(x, y, z);
if(block == null) return;

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

Как-нибудь так:

public enum BlockMaterial : byte
{
    Empty,
    Sand,
    Stone,
    ...
}

public struct Block
{
    public readonly BlockMaterial material;

    ...
}

WISHMASTER35
> Вот что у меня сейчас имеется
> http://www.youtube.com/watch?v=up4MMG_qL_k&feature=youtu.be
> Генерация действительно медленная и вызывает подтормаживания.

Действительно медленно. У меня вначале было не быстрее. Сейчас примерно так: https://vimeo.com/34705458

Правка: 30 мая 2012 21:09

alexzzzzПостоялецwww30 мая 201222:25#53
icelex
> alexzzzz даш поиграться в скомпилирваную версию?) б.з вроде нашол:)

Новые видео периодически выкладываю тут.
Последние публичные бинарники лежат и будут лежать здесь.

Ты какую версию нашёл?

> есть микро лаги, что сбивают фпс в 30- (хтя на обжем фоне они падают с 200 до
> 100-110), или фруструм тупит, или какойто жосткий процениг чевото, раз в пару сек.

Лаги только в движении или даже стоя на одном месте?

Если в движении, то это работа с диском тупит. Давно заметил, но пока подробно не смотрел. Судя по счётчикам производительности (которые по клавише 'I'), скорее всего проблема в подгрузке новых чанков. Я не стал делать предварительное кэширование чанков, думал отдать это на откуп ОС, но она, похоже, не оправдывает надежд.

Если провалы fps возникают, даже стоя на месте, то у меня таких нет. Единственное подозрение ― сборка мусора. Глянь, не совпадают ли лаги по времени с увеличением счётчика "collections occured". Хотя я сборщик мусора вроде утихомирил, у меня он делает своё грязное дело за ~10 мс и запускается не так чтобы очень часто.

> по поводу кода в топике : кидайте блять его в спойлер всю страницу засрали =) и
> картинки тоже^^

Общение на англоязычном форуме успокаивает и реально расслабляет. Картинки убрал.

> ААААААААААААа я прошол Osu битмап

Боюсь даже выяснять, что это такое. :)

Правка: 30 мая 2012 22:55

WISHMASTER35Участникwww30 мая 201222:50#54
alexzzzz, у меня объекты блоков пока только по одному создаются т.к. нету в блоке параметров типа освещенности или угла.
Так медленно у меня потому, что чанки создаются только каждый 20-й кадр. И то подвисание заметно.
Надо в отдельном низко-приоритетном потоке их генерировать. У тебя наверно тоже в отдельном потоке или как-то по другому?
alexzzzz, хороший уровень. Что кроме шума перлина использовал? Ты тоже на юнити делаешь)
Зачем чанки на диск сохранять? Чтобы не хватило памяти уровень должен быть невероятно большой. Можно у чанков просто удалить меши и хватит.

Правка: 30 мая 2012 22:54

alexzzzzПостоялецwww30 мая 201223:49#55
WISHMASTER35
> у меня объекты блоков пока только по одному создаются т.к. нету в блоке
> параметров типа освещенности или угла.

Смутно (не)понимаю, о чём речь.

У тебя chunk.GetBlock(x, y, z) откуда вынимает данные о блоке, из массива блоков, принадлежащих чанку?
Этот массив хранит блоки как экземпляры класса Block или как-то иначе (enums, просто байты)?

Если как экземпляры класса, то надо переделать Block в структуру. Разница тут принципиальна.

> Надо в отдельном низко-приоритетном потоке их генерировать. У тебя наверно тоже
> в отдельном потоке или как-то по другому?

У меня есть очередь фоновых заданий. Кидаю туда задания, а несколько рабочих потоков их разбирают и выполняют. В фоне делается всё ресурсоёмкое: загрузка или генерирование ландшафта, расчёт освещения, расчёт всей геометрии.

Ещё есть очередь заданий главного потока. В ней приготовленная ранее геометрия тупо конвертируется в объект Mesh и выкидывается на сцену.

> Что кроме шума перлина использовал?
Пока только его. Просто смешал 2D карту высот с 3D горами.

> Зачем чанки на диск сохранять? Чтобы не хватило памяти уровень должен быть
> невероятно большой.

Считаем, на что нужна память:
1. Собственно блоки: каждый блок ― структура размером 1 байт.
2. Освещение: для каждого блока 1 байт на освещение от солнца и 3 байта на цветное RGB освещение от искусственных источников. Итого 4 байта на свет. Можно было бы запихать всё в два байта, но пока это неприоритетно, есть более интересные занятия.
3. Количество/давление воды: упаковано в 2 байта на блок. Хочу сделать продвинутую реализацию воды. Кое-то получается (https://vimeo.com/34789201), но пока не доделано.

В сумме у меня сейчас выходит 7 байт на блок. Бесконечный мир я делать точно не буду, но рассчитываю на хотя бы 2048x128x2048 ― это будет 3,5 гигабайта. И я ещё не уверен с высотой. Когда займусь генератором ландшафта более плотно, то может быть, пересмотрю высоту.

PS
Ещё была идея расчитывать и хранить напряжение материала, чтобы при попытке построить «звезду смерти или циклопический член» не по законам физики, эта конструкция рушилась. Или если удаляешь несущую колонну в доме, потолок рушится тебе на голову. Но дальше идеи и робких попыток нарисовать на бумажке, как всё должно работать, дело пока не ушло.

Правка: 31 мая 2012 0:05

susagePПостоялецwww30 мая 201223:55#56
тупой вопрос.. но как можно описать положение персонажа в бесконечном мире?
alexzzzzПостоялецwww30 мая 201223:58#57
Как плачевное или смещением относительно начала координат :)

Правка: 30 мая 2012 23:59

WISHMASTER35Участникwww31 мая 20120:34#58
Смутно (не)понимаю, о чём речь.

Класс - ссылочный тип. Т.е. на один объект может быть сколько угодно ссылок. Вот и получается, что у меня всего пару объектов на которые ссылаются все элементы массива. Но конечно сама ссылка может занимать 4 или 8 байт.
2048x128x2048

Да, при таком уровне памяти не хватит.

Вообще у меня блоки хранятся в префабах, а сам класс блока наследован от ScriptableObject. И в нужные поля скрипта-генератора в инспекторе записываю ссылки на эти скрипты. Думаю это намного удобнее, чем хранить в структуре тип блока, а сам блок черт знает где.
Здесь http://www.youtube.com/watch?v=TC3dv7IrHs8&feature=channel&list=UL я показывал редактирование блока)

Правка: 31 мая 2012 0:36

alexzzzzПостоялецwww31 мая 20120:39#59
icelex
> про воду думаю знаешь =) (она никакая)
Ага.

> у тебя как понимаю встроенная прелесть юнити,

Не, всё своё, ручное, полностью процедурное (http://vimeo.com/album/1857240). Встроенные деревья Unity ― процедурные, но только в редакторе. В игре в реальном времени их никак нельзя модифицировать. Про рост деревьев я сразу не подумал, а потом уже было поздно, так что фича откладывается на неопределённое время. В последней публичной демке мои деревья можно только ставить (клавишей T) и пока больше ничего. Допишу сейчас несколько фич (рубка деревьев в их числе) и выложу на неделе или на следующей новую демку.

> также хочу предупредить про проблемы поика пути, =) и боюсь юниту тут не может,
Совсем недавно писал поиск путей между всеми вершинами направленного взвешенного графа с оптимизацией по скорости (два ролика на vimeo тоже валяются), думаю справлюсь. Я от Unity беру в общем-то только встроенные звук и физику, графический API, готовую математику и удобный workflow (йа нэ знат как эта будет поруски); а всякая там навигация ― хрен бы с ней. Надо будет, сам напишу под себя. К тому же встроенная навигация работает, кажется, только в платной версии.

> побей её на функцыи так найдеш кто лагает
Да я найду, проблем нет, просто на это надо выделить время.

Правка: 31 мая 2012 0:43

Страницы: 13 4 5 6101 Следующая »

/ Форум / Программирование игр / Графика

2001—2018 © GameDev.ru — Разработка игр