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
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ГГц).
Но даже если не гнаться за скоростью, код стало намного легче поддерживать. Глядя на него, я снова стал понимать, что он делает. :)
WISHMASTER35
> 2) когда строишь крайний чанк, то у него по бокам строятся стены. А когда рядом
> достраиваешь еще соседний чанк, то эта стена скрывается и получается, что ты ее
> зря построил.
> Вроде и не так страшно, но этих не видимых фейсов получается больше, чем
> видимых фейсов. А ведь на них тратиться время и память( И получается, что
> строительство новых чанков - дело медленное.
Решение ― не строить чанку меш, пока все его соседние чанки не будут заполнены блоками и пока для них не будет расчитано освещение. Иначе получается то, что у тебя получается.
Вот твой мир, вид сверху, игрок где-то посередине:
Серое ― это далёкие области, хранящиеся не в памяти, а на диске или вообще пока несуществующие.
Красные квадраты ― это чанки, для которых имеется пока только ландшафт (блоки).
Жёлтые ― чанки с ландшафтом, для которых уже было расчитано освещение.
Зелёные ― чанки с ландшафтом, расчитанным освещением, для которых уже были построены меши. Т.е. это те чанки, которые игрок может видеть.
Предположим, игрок идёт на север. Ряд южных чанков сохраняем на диск и убираем, сверху добавляем новые пока пустые чанки:
Загружаем или генерируем на лету для них ландшафт:
Теперь получилось, что во втором ряду у нас есть чанки без освещения, у которых все соседи имеют ландшафт.
Значит, для этих чанков уже можно рассчитать освещение:
А теперь получилось, что в третьем ряду есть чанки без мешей, у которых все соседи имеют рассчитанное освещение.
Значит, этим чанкам уже можно создать геометрию:
Теперь всё готово. Если соблюдать такой порядок расчётов, то никакие лишние грани и никакие артефакты освещения возникать не будут.
alexzzzz, ну ты отличный хостинг для картинок выбрал. 5 раз капчу вводить чтобы твои картинки посмотреть?
HotDog
> 5 раз капчу вводить чтобы твои картинки посмотреть?
Пародон. Несколько лет пользуюсь, и такого никогда раньше не было.
В чанке размером 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; }
Решение ― не строить чанку меш, пока все его соседние чанки не будут заполнены блоками
Наверно самое простое и нормальное решение.
Спасибо за такое объяснение и картинки)
Вот что у меня сейчас имеется 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
Только мне еще далеко до этого всего)
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
icelex
> alexzzzz даш поиграться в скомпилирваную версию?) б.з вроде нашол:)
Новые видео периодически выкладываю тут.
Последние публичные бинарники лежат и будут лежать здесь.
Ты какую версию нашёл?
> есть микро лаги, что сбивают фпс в 30- (хтя на обжем фоне они падают с 200 до
> 100-110), или фруструм тупит, или какойто жосткий процениг чевото, раз в пару сек.
Лаги только в движении или даже стоя на одном месте?
Если в движении, то это работа с диском тупит. Давно заметил, но пока подробно не смотрел. Судя по счётчикам производительности (которые по клавише 'I'), скорее всего проблема в подгрузке новых чанков. Я не стал делать предварительное кэширование чанков, думал отдать это на откуп ОС, но она, похоже, не оправдывает надежд.
Если провалы fps возникают, даже стоя на месте, то у меня таких нет. Единственное подозрение ― сборка мусора. Глянь, не совпадают ли лаги по времени с увеличением счётчика "collections occured". Хотя я сборщик мусора вроде утихомирил, у меня он делает своё грязное дело за ~10 мс и запускается не так чтобы очень часто.
> по поводу кода в топике : кидайте блять его в спойлер всю страницу засрали =) и
> картинки тоже^^
Общение на англоязычном форуме успокаивает и реально расслабляет. Картинки убрал.
> ААААААААААААа я прошол Osu битмап
Боюсь даже выяснять, что это такое. :)
alexzzzz, у меня объекты блоков пока только по одному создаются т.к. нету в блоке параметров типа освещенности или угла.
Так медленно у меня потому, что чанки создаются только каждый 20-й кадр. И то подвисание заметно.
Надо в отдельном низко-приоритетном потоке их генерировать. У тебя наверно тоже в отдельном потоке или как-то по другому?
alexzzzz, хороший уровень. Что кроме шума перлина использовал? Ты тоже на юнити делаешь)
Зачем чанки на диск сохранять? Чтобы не хватило памяти уровень должен быть невероятно большой. Можно у чанков просто удалить меши и хватит.
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
Ещё была идея расчитывать и хранить напряжение материала, чтобы при попытке построить «звезду смерти или циклопический член» не по законам физики, эта конструкция рушилась. Или если удаляешь несущую колонну в доме, потолок рушится тебе на голову. Но дальше идеи и робких попыток нарисовать на бумажке, как всё должно работать, дело пока не ушло.
тупой вопрос.. но как можно описать положение персонажа в бесконечном мире?
Как плачевное или смещением относительно начала координат :)
Смутно (не)понимаю, о чём речь.
Класс - ссылочный тип. Т.е. на один объект может быть сколько угодно ссылок. Вот и получается, что у меня всего пару объектов на которые ссылаются все элементы массива. Но конечно сама ссылка может занимать 4 или 8 байт.
2048x128x2048
Да, при таком уровне памяти не хватит.
Вообще у меня блоки хранятся в префабах, а сам класс блока наследован от ScriptableObject. И в нужные поля скрипта-генератора в инспекторе записываю ссылки на эти скрипты. Думаю это намного удобнее, чем хранить в структуре тип блока, а сам блок черт знает где.
Здесь http://www.youtube.com/watch?v=TC3dv7IrHs8&feature=channel&list=UL я показывал редактирование блока)
icelex
> про воду думаю знаешь =) (она никакая)
Ага.
> у тебя как понимаю встроенная прелесть юнити,
Не, всё своё, ручное, полностью процедурное (http://vimeo.com/album/1857240). Встроенные деревья Unity ― процедурные, но только в редакторе. В игре в реальном времени их никак нельзя модифицировать. Про рост деревьев я сразу не подумал, а потом уже было поздно, так что фича откладывается на неопределённое время. В последней публичной демке мои деревья можно только ставить (клавишей T) и пока больше ничего. Допишу сейчас несколько фич (рубка деревьев в их числе) и выложу на неделе или на следующей новую демку.
> также хочу предупредить про проблемы поика пути, =) и боюсь юниту тут не может,
Совсем недавно писал поиск путей между всеми вершинами направленного взвешенного графа с оптимизацией по скорости (два ролика на vimeo тоже валяются), думаю справлюсь. Я от Unity беру в общем-то только встроенные звук и физику, графический API, готовую математику и удобный workflow (йа нэ знат как эта будет поруски); а всякая там навигация ― хрен бы с ней. Надо будет, сам напишу под себя. К тому же встроенная навигация работает, кажется, только в платной версии.
> побей её на функцыи так найдеш кто лагает
Да я найду, проблем нет, просто на это надо выделить время.
Тема в архиве.