Як я можу досягти плавного двовимірного освітлення?


18

Я роблю 2D гру на основі плитки в XNA.

В даний час моя блискавка виглядає це .

Як я можу змусити його виглядати це ?

Замість того, щоб кожен блок мав свій відтінок, він має плавне накладення.

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

Моє поточне освітлення обчислює світло, а потім передає його на a SpriteBatchі малює за допомогою параметра відтінку. Кожна плитка має Colorобчислювану величину перед тим, як намалювати в моєму алгоритмі освітлення, який використовується для відтінку.

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

введіть тут опис зображення

Тож насправді дістати та намалювати світло поки що не проблема !

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

Тож для перегляду

  • Розрахунок освітлення (Готово)
  • Намалюйте плитку (Готово, я знаю, що мені потрібно буде змінити її для шейдера)
  • Тіньові плитки (Як передавати значення та застосовувати "градієнт")

Відповіді:


6

Як щодо чогось подібного?

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

Це дасть саме те, що ви маєте зараз. Це вам не допомагає, тож давайте трохи змінимо це.

Змініть розміри цілі відображення світлової карти так, щоб кожна плитка була представлена ​​одним пікселем , а не прямокутною областю. Складаючи кінцеву сцену, використовуйте стан пробовідбору з лінійною фільтрацією. Інакше залиште все інше те саме.

Припускаючи, що ви правильно написали свій шейдер, світлова карта повинна бути ефективно «розширена» під час композиції. Це дозволить отримати хороший градієнтний ефект безкоштовно через пробник текстури графічного пристрою.

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

ОНОВЛЕННЯ

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

Як я запропонував, ви можете досягти точно такого ж ефекту за допомогою спеціального BlendState. Зокрема, це користувацька BlendState:

BlendState Multiply = new BlendState()
{
    AlphaSourceBlend = Blend.DestinationAlpha,
    AlphaDestinationBlend = Blend.Zero,
    AlphaBlendFunction = BlendFunction.Add,
    ColorSourceBlend = Blend.DestinationColor,
    ColorDestinationBlend = Blend.Zero,
    ColorBlendFunction = BlendFunction.Add
}; 

Рівняння змішування є

result = (source * sourceBlendFactor) blendFunction (dest * destBlendFactor)

Так що з нашим користувацьким BlendState це стає

result = (lightmapColor * destinationColor) + (0)

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

Щоб застосувати це на практиці, спочатку зробіть все, що вам потрібно зробити, щоб створити свою світлу карту:

var lightmap = GetLightmapRenderTarget();

Тоді просто намалюйте свою неосвітлену сцену прямо до резервного буфера, як зазвичай:

spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
/* draw the world here */
spriteBatch.End();

Потім намалюйте світлу карту за допомогою спеціального BlendState:

var offsetX = 0; // you'll need to set these values to whatever offset is necessary
var offsetY = 0; // to align the lightmap with the map tiles currently being drawn
var width = lightmapWidthInTiles * tileWidth;
var height = lightmapHeightInTiles * tileHeight;

spriteBatch.Begin(SpriteSortMode.Immediate, Multiply);
spriteBatch.Draw(lightmap, new Rectangle(offsetX, offsetY, width, height), Color.White);
spriteBatch.End();

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


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

Після встановлення цілі візуалізації на пристрої зателефонуйте device.Clear (Color.Transparent);
Коул Кемпбелл

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

Я думаю, що те, що я спробував раніше, але це було все ще фіолетовим кольором, добре Погляньте на це завтра, коли у мене є час на роботу над цим.
Cyral

Практично все це випрацювалося, але воно вицвітає від чорного до білого, а не від чорного до прозорого. Шейдерна частина - це те, про що я плутаюсь, як написати одну, щоб примружити оригінальну неосвітлену сцену до нової.
Cyral

10

Добре, ось один дуже простий метод створити просту і гладку 2D блискавку, візуалізувати в три проходи:

  • Прохід 1: ігровий світ без блискавки.
  • Прохід 2: Блискавка без світу
  • Поєднання проходу 1. і 2., який відображається на екрані.

У проході 1 ви малюєте всі спрайти та місцевість.

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

У пропуску 3 ви поєднуєте два попередні проходи. Існує кілька різних способів їх поєднання. Але найпростіший і найменш мистецький метод - це поєднання їх між собою через множення. Це виглядатиме так:
введіть тут опис зображення


Справа в тому, що у мене вже є система освітлення, і точкові світильники тут не працюють через те, що для деяких об'єктів потрібно світло, а деякі ні.
Кірал

2
Ну це більш хитро. Вам потрібно буде зробити знімки, щоб отримати тіні. Терарія використовує великі блоки для своєї місцевості, так що там немає жодних проблем. Ви можете використовувати неточний простір, але це буде виглядати не краще, ніж терарія, і може підійти ще менше, якщо ви не блокуєте графіку. Для передової та гарної техніки виглядає ця стаття: gamedev.net/page/resources/_/technical/…
API-Beast

2

Друге посилання, яке ви опублікували, схоже на тумани війни , тут є ще кілька питань

Мені це здається текстурою , де ви "стираєте" біти чорного накладення (встановивши альфа цих пікселів на 0), коли гравець рухається вперед.

Я б сказав, що людина, мабуть, використовувала пензлик типу "стирання", де гравець дослідив.


1
Це не туман війни, він обчислює блискавку кожного кадру. Гра, яку я вказав, схожа на Terraria.
Кірал

Я мав на увазі, що це може бути реалізовано схоже на туман війни
bobobobo

2

Легкі вершини (кути між плитками) замість плиток. Змішайте освітлення по всій плитці на основі чотирьох вершин.

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

Під час візуалізації плитки надсилайте значення світла для кожної вершини в GPU. Ви можете легко використовувати шейдер фрагмента, щоб взяти інтерпольоване значення світла на кожному фрагменті в спрайті плитки, щоб плавно освітлити кожен піксель. З часу, коли я торкнувся GLSL, я не відчував себе комфортно, даючи реальний зразок коду, але в псевдокоді це було б так просто, як:

texture t_Sprite
vec2 in_UV
vec4 in_Light // coudl be one float; assuming you might want colored lights though

fragment shader() {
  output = sample2d(t_Sprite, in_UV) * in_Light;
}

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

Ви отримаєте ще більш плавне освітлення з більшою кількістю вершин (скажімо, якщо кожна плитка представлена ​​як чотири підкладки), але це, можливо, не варто докладати зусиль, залежно від якості, для якої ви збираєтеся. Спробуйте спочатку простішим способом.


1
line-of sight tests to each vertex? Це буде дорого.
ashes999

Ви можете оптимізувати з деякою кмітливістю. Для 2D-гри ви можете досить швидко зробити дивовижну кількість променевих тестів проти суворої сітки. Замість прямого тесту на ЛОС ви могли б "протікати" (я не можу в житті мені придумати правильний термін прямо зараз; пивна ніч) значення світла над вершинами, а не робити тести на проміння.
Шон Міддлічч

Використання тестів зору ще більше ускладнить це, я вже зрозумів освітлення. Тепер, коли я надсилаю 4 вершини до шейдера, як це зробити? Я знаю, що вам не комфортно давати реальний зразок коду, але якби я міг отримати трохи більше інформації, це було б непогано. Я також відредагував питання з додатковою інформацією.
Cyral
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.