Как получить эффект плавного 2D-освещения?

18

Я делаю 2D-игру на основе плитки в XNA.

В настоящее время моя молния выглядит это .

Как я могу заставить его выглядеть это ?

Вместо каждого блока, имеющего собственный оттенок, он имеет плавное наложение.

Я предполагаю какой-то шейдер и передаю значения освещения для окружающих плиток шейдеру, но я начинающий с шейдерами, поэтому я не уверен.

Мое текущее освещение вычисляет свет, а затем передает его в a SpriteBatchи рисует с параметром оттенка. У каждой плитки есть значение Color, которое вычисляется до рисования в моем алгоритме освещения, который используется для оттенка.

Вот пример того, как я в настоящее время вычисляю освещение (я делаю это также слева, справа и снизу, но я очень устал делать это кадр за кадром ...)

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

Так что на самом деле получить и нарисовать свет до сих пор не проблема !

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

Итак, для обзора

  • Рассчитать освещение (Готово)
  • Draw Tiles (Готово, я знаю, что мне нужно изменить его для шейдера)
  • Оттенок плитки (Как передать значения и применить «градиент»)
Cyral
источник

Ответы:

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 «прозрачным», у него было фиолетовое наложение, но я хотел, чтобы он просвечивал слой плитки actall)
Cyral
После установки цели визуализации на устройстве, вызовите device.Clear (Color.Transparent);
Коул Кэмпбелл
Хотя в этом случае стоит указать, что ваша карта освещения, вероятно, должна быть очищена либо в белый, либо в черный цвет, соответственно, полный свет и полная темнота.
Коул Кэмпбелл
Я думаю, это то, что я пробовал раньше, но он все еще был фиолетовым, хорошо, я посмотрю это завтра, когда у меня будет время поработать над этим.
Cyral
Почти все получилось, но оно исчезает с черного на белый, а не с черного на прозрачный. Шейдерная часть - это то, над чем я смущен: как написать ту, которая приглушает исходную неосвещенную сцену к новой.
Cyral
10

Хорошо, вот один очень простой метод для создания простой и плавной 2D-молнии, рендеринг за три прохода:

  • Проход 1: игровой мир без молний.
  • Проход 2: Молния без мира
  • Комбинация 1. и 2. прохода, который отображается на экране.

В проходе 1 вы рисуете все спрайты и местность.

В проходе 2 вы рисуете вторую группу спрайтов, которые являются источниками света. Они должны выглядеть примерно так:
введите описание изображения здесь
Инициализируйте цель рендеринга черным и нарисуйте эти спрайты с максимальным или аддитивным смешиванием.

В проходе 3 вы объединяете два предыдущих прохода. Есть несколько разных способов, как они могут быть объединены. Но самый простой и наименее вычурный метод - это смешать их вместе с помощью Multiply. Это будет выглядеть так:
введите описание изображения здесь

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

Вторая ссылка, которую вы разместили, выглядит как своего рода туман войны , есть еще пара вопросов на этот счет.

Это выглядит как текстура , где вы «стираете» биты черного наложения (устанавливая альфа этих пикселей в 0), когда игрок движется вперед.

Я бы сказал, что этот человек использовал кисть типа «стереть» там, где исследовал игрок.

bobobobo
источник
1
Это не туман войны, он подсчитывает молнии каждый кадр. Игра, которую я указал, похожа на Terraria.
Cyral
Я имел в виду, что это может быть реализовано подобно туману войны
бобобобо
2

Легкие вершины (углы между плитками) вместо плиток. Смешайте освещение каждой плитки на основе четырех вершин.

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

При рендеринге плитки отправьте значение освещения для каждой вершины в графический процессор. Вы можете легко использовать фрагментный шейдер, чтобы получить интерполированное значение освещения для каждого фрагмента в спрайте элемента мозаичного изображения для плавного освещения каждого пикселя. Прошло достаточно много времени с тех пор, как я прикоснулся к 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-игры вы можете довольно быстро провести удивительное количество тестов лучей на строгой сетке. Вместо прямого теста LOS вы могли бы «течь» (я не могу придумать правильный термин прямо сейчас; пивной вечер) значения света по вершинам, а не проводить лучевые тесты тоже.
Шон Мидлдич
Использование тестов прямой видимости усложнило бы это, я уже понял освещение. Теперь, когда я отправляю 4 вершины в шейдер, как я могу это сделать? Я знаю, что вам неудобно давать настоящий пример кода, но если бы я мог получить немного больше информации, это было бы неплохо. Я также отредактировал вопрос с дополнительной информацией.
Cyral