...

суббота, 14 июля 2018 г.

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

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

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

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

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

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


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

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

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

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

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

Пример

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

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

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


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

Недостатки:


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

Способ 2: RenderTexture


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

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

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


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

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

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


Под конец настраиваем 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.
  • Эта система не учитывает некоторые редкие случаи. Например, на персонажа светят фонариком. Персонаж хорошо освещен, но свет падает на него и за ним, а не вниз, соответственно, наш детектор света показывает низкий уровень освещенности. Решить проблему можно, например, размещая детектор не у пола, а на уровне груди персонажа. Тогда нужно ставить две камеры с противоположных сторон, чтобы читать свет с любой стороны. Что замедлит систему ещё вдвое.

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий