Як я можу реалізувати освітлення на основі вокселів із оклюзією в грі Minecraft?


13

Я використовую C # і XNA. Мій поточний алгоритм освітлення - це рекурсивний метод. Однак це дорого , до того моменту, коли один шматок 8x128x8 розраховувався кожні 5 секунд.

  • Чи існують інші методи освітлення, які дозволять зробити тіні зі змінною темрявою?
  • Або рекурсивний метод хороший, і, можливо, я просто роблю це неправильно?

Це здається, що рекурсивний матеріал є принципово дорогим (змушений пройти близько 25 тис. Блоків за шматок). Я думав про використання методу, подібного до трасування променів, але не маю уявлення, як це буде працювати. Інша річ, яку я спробував, - це зберігання джерел світла в списку, і для кожного блоку отримувати відстань до кожного джерела світла, і використовувати це для його освітлення до потрібного рівня, але тоді освітлення проходитиме через стіни.

Мій поточний код рекурсії нижче. Це називається з будь-якого місця в шматку, що не має рівня нульового рівня, після очищення та повторного додавання сонячного світла та факела.

world.get___atце функція, яка може отримувати блоки поза цим фрагментом (це всередині класу фрагмента). Locationце моя власна структура, яка є як Vector3, але використовує цілі числа замість значень з плаваючою комою. light[,,]- це світлова карта для шматка.

    private void recursiveLight(int x, int y, int z, byte lightLevel)
    {
        Location loc = new Location(x + chunkx * 8, y, z + chunky * 8);
        if (world.getBlockAt(loc).BlockData.isSolid)
            return;
        lightLevel--;
        if (world.getLightAt(loc) >= lightLevel || lightLevel <= 0)
            return;
        if (y < 0 || y > 127 || x < -8 || x > 16 || z < -8 || z > 16)
            return;
        if (x >= 0 && x < 8 && z >= 0 && z < 8)
            light[x, y, z] = lightLevel;

        recursiveLight(x + 1, y, z, lightLevel);
        recursiveLight(x - 1, y, z, lightLevel);
        recursiveLight(x, y + 1, z, lightLevel);
        recursiveLight(x, y - 1, z, lightLevel);
        recursiveLight(x, y, z + 1, lightLevel);
        recursiveLight(x, y, z - 1, lightLevel);
    }

1
Щось страшенно неправильно, якщо ви робите 2 мільйони блоків за шматок - тим більше, що в 8 * 128 * 8 шматках насправді є лише 8,192 блоків . Що ти можеш зробити, коли ти перебираєш кожен блок ~ 244 рази? (може це було 255?)
doppelgreener

1
Я неправильно зробив математику. Вибачте: P. Зміна. Але причина, чому вам потрібно йти, хоча стільки, - вам доведеться "вибухати" з кожного блоку, поки ви не потрапите на рівень світла, більший за встановлений. Це означає, що кожен блок може бути перезаписаний 5-10 разів, перш ніж він досягне фактичного рівня освітленості. 8x8x128x5 = багато

2
Як ви зберігаєте свої вокселі? Це важливо для скорочення часу переходу.
Самаурса

1
Чи можете ви розмістити свій алгоритм освітлення? (ви запитуєте, чи погано ви це робите, ми не маємо уявлення)
doppelgreener

Я зберігаю їх у масиві "Блоки", і Блок складається з перерахунку на матеріал, плюс байта метаданих для подальшого використання.

Відповіді:


6
  1. Кожен світло має точне положення ( з плаваючою точкою), і що обмежує сферу , яка визначається скалярним значенням радіусу світла, LR.
  2. Кожен воксель має точне (плаваюча точка) положення в його центрі, яке ви можете легко обчислити з його положення в сітці.
  3. Пропустіть лише один з 8192 вокселів лише один раз, і для кожного подивіться, чи не потрапляє він у сферичний обмежуючий обсяг кожного з N вогнів, перевіривши |VP - LP| < LR, де VP - вектор положення вокселя відносно походження та чи LPє вектор положення світла відносно походження. Для кожного джерела світла, радіус якого струм вокселей виявляється в, це збільшення світловий фактор на відстані від світлового центру, |VP - LP|. Якщо ви нормалізуєте цей вектор, а потім отримаєте його величину, це буде в діапазоні 0,0-> 1,0. Максимальний рівень освітлення, який може досягти воксель, - 1,0.

Час автономної роботи O(s^3 * n), де sдовжина сторони (128) Вашого вокселі регіону і nє кількістю джерел світла. Якщо ваші джерела світла статичні, це не проблема. Якщо ваші джерела світла рухаються в режимі реального часу, ви можете працювати виключно на дельтах, а не перераховувати цілий шебанг за кожне оновлення.

Ви навіть можете зберігати вокселі, на які впливає кожне світло, як посилання всередині цього світла. Таким чином, коли світло рухається або руйнується, ви можете пройти саме цей список, відповідно регулюючи значення світла, а не знову проходити всю кубічну сітку.


Якщо я правильно зрозумів його алгоритм, він намагається зробити якусь псевдорадіацію, дозволяючи світлу діставатися до далеких місць, навіть якщо це означає, що він повинен "обходити" куточки. Або, інакше кажучи, алгоритм заповнення «порожнього» (нетвердого) простору з обмеженою максимальною відстані від джерела (джерела світла) та відстані (а від нього - ослаблення світла), що обчислюється відповідно до найкоротший шлях до походження. Отже - не зовсім те, що ви зараз пропонуєте.
Мартін Сойка

Дякую за деталі @MartinSojka. Так, це більше нагадує інтелектуальну заливку. При будь-якій спробі глобального освітлення витрати, як правило, високі навіть при розумних оптимізаціях. Таким чином, добре спробувати ці проблеми спочатку в 2D, і якщо вони навіть віддалені дорого, знайте, що у вас виникне певна проблема на руках у 3D.
Інженер

4

Сам Minecraft не робить сонячного світла таким чином.

Ви просто заповнюєте сонячне світло зверху вниз, кожен шар збирає світло з сусідніх вокселів в попередньому шарі з ослабленням. Дуже швидко - один прохід, відсутні списки, без структури даних, без рекурсії.

Ви повинні додати факели та інші незатоплені вогні в подальшому пропуску.

Існує так багато інших способів зробити це, включаючи химерне спрямоване поширення світла і т. Д., Але вони, очевидно, повільніше, і ви повинні розібратися, чи хочете ви вкласти гроші в додатковий реалізм, враховуючи ці штрафи.


Зачекайте, так як саме це робить Minecraft? Я не міг зрозуміти, що ти говорив ... Що означає "кожен шар збирає світло з сусідніх вокселів попереднього шару з ослабленням"?

2
Почніть з верхнього шару (зріз постійної висоти). Наповніть його сонячним світлом. Потім перейдіть до шару внизу, і кожен воксель там отримує своє освітлення від найближчих вокселів попереднього шару (над ним). Покладіть нульове світло у тверді вокселі. У вас є декілька способів вирішити "ядро", ваги внесків з вищезгаданих вокселів, Minecraft використовує максимальне знайдене значення, але зменшує його на 1, якщо поширення не відбувається прямо. Це бічне ослаблення, тому розблоковані вертикальні стовпці вокселів отримають повне розповсюдження сонячного світла та легкі вигини по кутах.
Бйорн Весен

1
Зверніть увагу, що цей метод жодним чином не базується на реальній фізиці :) Основна проблема полягає в тому, що ви, по суті, намагаєтеся наблизити ненапрямне світло (атмосферне розсіювання) І відскакувати радіовипромінювання простою евристикою. Це виглядає досить добре.
Bjorn Wesen

3
Що з «губою», що нависає, як світло піднімається там? Як світло рухається у напрямку вгору? Якщо ви йдете лише зверху вниз, ви не можете повернутися вгору, щоб заповнити нависання. Також смолоскипи / інші джерела світла. Як би ти це зробив? (вони могли зійти лише!)

1
@Felheart: Дещо зараз я дивився на це, але по суті є мінімальний рівень освітлення навколишнього середовища, якого, як правило, достатньо для підвісів, щоб вони не були повністю чорними. Коли я реалізував це самостійно, я додав другий пропуск знизу вгору, але я не побачив великих естетичних удосконалень порівняно з методом навколишнього середовища. Факели / точкові світильники потрібно обробляти окремо - я думаю, ви можете побачити схему поширення, яка використовується в MC, якщо поставити факел на середину стіни і трохи експериментувати. У своїх тестах я їх розповсюджую в окремому світлому полі, а потім додаю.
Бйорн Весен

3

Хтось сказав, щоб відповісти на ваше власне питання, якщо ви це зрозуміли, так, так. З’ясував метод.

Що я роблю, це так: по-перше, створіть 3d булевий масив "вже змінених блоків", накладених на шматок. Потім заповніть сонячне світло, факел тощо (просто засвітіть блок, який увімкнено, поки заливки не заливаються). Якщо ви щось змінили, натисніть на "Змінені блоки" в цьому місці на true. Також перейдіть і змініть кожен суцільний блок (і, отже, не потрібно обчислювати освітлення для) на "вже змінений".

Тепер про важкі речі: перейдіть весь фрагмент з 16 проходами (для кожного рівня світла), і якщо його "вже змінилося", просто продовжуйте. Потім отримайте рівень освітлення для блоків навколо себе. Отримайте найвищий рівень освітлення з них. Якщо рівень світла дорівнює рівню освітленості поточного проходу, встановіть блок, який ви перебуваєте на поточному рівні, і встановіть значення "вже змінено" для цього місця на істинне. Продовжуйте.

Я знаю його складний вид, я намагався пояснити все можливе. Але важливим фактом є те, що воно працює і швидко.


2

Я б запропонував алгоритм, який поєднує ваше багатопропускне рішення з оригінальним рекурсивним методом, і, швидше за все, досить швидкий, ніж будь-який з них.

Вам знадобиться 16 списків (або будь-яких колекцій) блоків, по одному для кожного рівня світла. (Насправді, існують способи оптимізації цього алгоритму для використання меншої кількості списків, але цей спосіб найлегше описати.)

Спочатку очистіть списки і встановіть рівень освітлення всіх блоків на нуль, а потім ініціалізуйте джерела світла, як це робите в поточному рішенні. Після (або під час) цього додайте будь-які блоки з ненульовим рівнем освітлення до відповідного списку.

Тепер перегляньте список блоків із рівнем освітлення 16. Якщо будь-який із сусідніх з ними блоків має рівень світла менше 15, встановіть рівень їх освітленості на 15 та додайте їх до відповідного списку. (Якщо вони вже були в іншому списку, ви можете видалити їх із нього, але це не принесе шкоди, навіть якщо ви цього не зробите.)

Потім повторіть те саме для всіх інших списків, у порядку зменшення яскравості. Якщо ви виявите, що блок у списку вже має більший рівень освітленості, ніж він повинен бути у цьому списку, ви можете припустити, що він уже оброблений і навіть не турбуватися перевіряти своїх сусідів. (Знову ж таки, може бути швидше просто перевірити сусідів - це залежить від того, як часто це відбувається. Напевно, ви повинні спробувати це в обох напрямках і побачити, який шлях швидший.)

Ви можете зауважити, що я не вказав, як слід зберігати списки; насправді майже будь-яка розумна реалізація повинна це зробити, доки вставлення заданого блоку та витяг довільного блоку - це швидкі операції. Зв'язаний список повинен працювати, але так би і будь-яка на півдорозі гідна реалізація масивів змінної довжини. Просто використовуйте все, що найкраще підходить для вас.


Додаток: Якщо більшість ваших світильників не рухаються дуже часто (а також стіни), можливо, навіть швидше буде зберігати для кожного освітленого блоку вказівник на джерело світла, який визначає його рівень освітлення (або на один із їх, якщо кілька прив’язаних). Таким чином, ви можете уникнути глобальних оновлень освітлення майже повністю: якщо додано нове джерело світла (або наявне освітлене), вам потрібно зробити лише один рекурсивний освітлювальний прохід для блоків навколо нього, а якщо його видалити (або затемнено), вам потрібно лише оновити ті блоки, які вказують на нього.

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

(Якщо відразу відбувається декілька змін освітлення - наприклад, якщо світло переміщений, що вважається видаленням і доповненням, - слід об'єднати оновлення в одне, використовуючи алгоритм, наведений вище. В основному, ви нівелюєте рівень освітлення всіх блоки, що вказують на видалені джерела світла, додайте будь-які освітлені блоки, що їх оточують, а також будь-які нові джерела світла (або наявні джерела світла в нульових зонах) у відповідні списки та запустіть оновлення, як зазначено вище.)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.