Unity3D: как узнать степень освещения точки сцены?

в 16:40, , рубрики: camera, rendertexture, unity3d, освещение, разработка игр

Приветствую!

Я знаю, и вы в глубине души знаете, чего не хватает вашим карточным играм или играм «три в ряд». Системы скрытности!

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

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

Способ 1: коллайдеры

Простой и не особо ресурсоёмкий способ.

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

Остальное — ясно, как тень. Пишем простенький скрипт, где в OnTriggerEnter() размещаем активацию расчета освещенности (чтобы источники света не работали «вхолостую», когда игрока рядом нет).

Сам расчет освещенности будет расположен в Update(). По сути, это обычный Physics.Raycast(). Если попадает в игрока — игрок в зоне света. Если не попадает — значит, игрок за препятствиями и, значит, в тени.

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

Пример

Unity3D: как узнать степень освещения точки сцены? - 1
В точке 1 освещенность близка к максимальной. В точке 2 освещенность минимальна — между светом и точкой препятствие. В точке 3 освещенность средняя.

И это не всё! Можно добавить триггер-коллайдеров в различные «зоны теней», где игрок должен прятаться. В лучших традициях Manhunt. Аналогичным образом можно помечать коллайдером светлые зоны, имитируя, например, свет прожектора.

Преимущества:

  • Легко настроить Point light'ы.
  • Довольно экономен в плане ресурсов, если не спамить источники света.

Недостатки:

  • Тяжело настраиваются Spot light и Directional light. Если для первого достаточно отпозиционировать коллайдер в области света (чтобы повышать видимость игрока при входе), то второй представляется настоящим ужасом. Нужно либо размещать коллайдеры у каждой тени (чтобы снижать видимость игрока при входе), либо постоянно проверять с помощью Physics.Raycast() между игроком и «солнцем» — находится тот под лучами или в тени.
  • Большое количество коллайдеров захламляет сцену, усложняя физику.
  • Необходимо аккуратно работать с пересекающимися источниками света.
  • Динамичный свет (перемещающийся или меняющий интенсивность) нужно отдельно дописывать через скрипты.

Способ 2: RenderTexture

Что мы тут делаем? По сути — получаем «скриншот» с камеры, причем необязательно с камеры основной. А затем анализируем цвет скриншота, чтобы узнать, насколько яркий свет падает на предмет.

Для начала нам нужен объект, с которого мы будем «читать» свет. Создаем обычную сферу или плоскость, делаем маленькой (scale 0.1), размещаем вплотную к полу, делаем белой, убираем коллайдер:

Скрытый текст

Unity3D: как узнать степень освещения точки сцены? - 2

Добавляем камеру (обязательно убираем audio listener и проверяем, что не стоит таг MainCamera). Привязываем её к нашему объекту. Ставим её чуть выше, направляем вниз. Выставляем в настройках не основной дисплей. Делать ли её ортографической — это на ваш вкус.

Под конец позиционируем её так, чтобы она смотрела на наш объект и только на него.

Скрытый текст

Unity3D: как узнать степень освещения точки сцены? - 3

Под конец настраиваем Culling mask основной и вторичных камер, чтобы основная не отображала наши «световые» объекты, а вторичные видели только их, не захламляясь ничем прочим.

И тут начинается самое интересное. Привязываем к камере скрипт:

public Camera cam; // наша камера
RenderTexture tex;
Texture2D _tex;

void Start () {
	// Создаем изображение для "скриншота". 
	// Да, он всего в один пиксель размером - больше не надо.
	// Depth лучше на 0 не ставить - появляются различные баги.
	tex = new RenderTexture (1, 1, 8); 
	// RenderTexture "читать" нельзя, 
	// поэтому создаем текстуру, в которую его переводим.
	_tex = new Texture2D (1, 1, TextureFormat.RGB24, false);
}

void Update () {
	// назначаем текстуру "скриншота" камере
	cam.targetTexture = tex; 
	cam.Render ();
	// делаем полученный скриншот активным
	RenderTexture.active = tex;
	// записываем в Texture2D
	_tex.ReadPixels (new Rect (0, 0, 1, 1), 0, 0); 
	_tex.Apply ();
	Color col = _tex.GetPixel (0, 0);
	float vis = (col.r + col.g + col.b) / 3;
}

На выходе получаем float vis, который, по сути, является числовой репрезентацией уровня освещения, падающего на наш объект. Если источник близко — предмет белый — vis равен 1. Если темно — предмет черный — vis равен ~0.

Нам не нужно проделывать вышеуказанную операцию каждый фрейм, так что встраиваем небольшой секундный таймер:

float interval = 0;
void Update ()
{
	interval += Time.deltaTime;
	if (interval < 1)
		return;
	interval = 0;
	// наш код
}

Далее мы всю нашу систему привязываем к игроку, чтобы она передвигалась вместе с ним. И наша переменная vis автоматически выдает освещенность вокруг игрока!

Данную систему можно использовать не только в связке с игроком. Можно разместить её где угодно и как угодно, создавая своеобразные датчики света. Как правило для их реализации имеются более эффективные пути, но всегда приятно иметь альтернативы?

Преимущества очевидны, поговорим о недостатках.

Недостатки

  • Каждый «детектор света» (если их больше одного) требует отдельной камеры.
  • Texture2D.ReadPixels() — ну крайне медленная. Даже если проделывать её раз в секунду, а не каждый фрейм, даже если разбить функции записи и чтения текстур на разные фреймы, все равно бывают пролагивания в 40-110ms.
  • Эта система не учитывает некоторые редкие случаи. Например, на персонажа светят фонариком. Персонаж хорошо освещен, но свет падает на него и за ним, а не вниз, соответственно, наш детектор света показывает низкий уровень освещенности. Решить проблему можно, например, размещая детектор не у пола, а на уровне груди персонажа. Тогда нужно ставить две камеры с противоположных сторон, чтобы читать свет с любой стороны. Что замедлит систему ещё вдвое.

Автор: Xatmo

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js