Приход в карту иммерсивных возможностей начал менять сложившееся положение вещей: моделей стало больше, они стали красивее и детальнее, появилось больше желания призумиться и рассматривать их. Но любоваться картой стали мешать POI — значки, которыми на карте отмечаем места, компании и другие полезные вещи. Решили с этим разобраться.
Если коротко, то проблема с POI и 3D-объектами выглядела так:
Наши POI по-прежнему ощущают себя на двухмерной карте и игнорируют трёхмерные геометрии. Это дезориентирует и создаёт визуальный мусор, ведь глядя на башни Москва-Сити меньше всего хочешь видеть иконки, которые находятся за 300−700 метров позади. Нужно было научить наши POI корректно рисоваться вместе с 3D-объектами, скрываясь за ними при необходимости.
И мы начали экспериментировать.
Решение c буфером глубины
WebGL, как и все другие трехмерные API, предоставляет стандартную возможность для скрытия одних 3D-объектов другими —
буфер глубины. Если просто взять POI и включить для них глубину, картинка будет такая:
Да, иконки начинают скрываться зданиями, но вовсе не таким способом, каким ожидаешь. Видно, что иногда здание отъедает кусок POI или часть надписи, делая её нечитабельной.
Поэтому мы стали продумывать альтернативные способы решения.
Аналитический подход
Такой подход предполагает расчёт скрытия на CPU при помощи алгоритмов
бросания луча.
Из камеры по направлению к каждой POI пробрасывается луч, для которого проверяется пересечение с каждым 3D-объектом.
У нас на карте запросто может быть несколько тысяч POI и несколько тысяч 3D-объектов на экране, что в итоге даёт миллионы итераций. А проверку пересечения нужно проводить на каждый кадр, то есть максимум за 10−15 миллисекунд.
Итого: этот способ не проходит по производительности.
Отдельная сцена скрытия POI
В этом подходе мы сгружаем вычисления на GPU (графический процессор). Для этого во вспомогательном
фреймбуфере
отрисовываем POI вместе с 3D-объектами с использованием буфера глубины. Данные из этого фреймбуфера идут в шейдеры при отрисовке основной сцены. Условно говоря, если под центром POI во фреймбуфере красный цвет, то считаем его видимым, если красного цвета нет, то скрытым.
В результате мы почти достигли нужного результата, но остался один момент:
Теперь POI начали скрываться целиком и не обрезаться прилегающими зданиями, но они стали неприятно мерцать.
Причина этого кроется в том, что при пересечении POI и 3D-объекта возникают пиксельные решётки, при проходе по которым у центра POI (по которому мы судим о видимости) слишком часто меняются состояния.
Преимущество этого подхода — скорость работы — обернулась для нас недостатком.
Борьба с пиксельной решёткой впоследствии оказалась достаточно нетривиальным делом, но в итоге мы остановились на этом способе, хоть и несколько модифицированном.
Текстура глубины и интерполяция прозрачности
После нескольких попыток борьбы с мерцанием решили подключить к проверке не только цвет, но и глубину, чтобы при отрисовке POI понимать, насколько её глубина больше или меньше глубины здания или модели.
Теперь при отрисовке POI в главной сцене у нас есть возможность сравнивать значения глубины зданий из текстуры и глубины POI: если глубина POI меньше глубины здания, то POI показывается, если наоборот — не показывается. Но проблема мерцания POI никуда не ушла. Для её решения мы ввели некоторое значение дельты глубины, в границах которого мы интерполируем прозрачность POI. Это позволило визуально сгладить процесс скрытия. Другими словами, у POI появилось переходное полупрозрачное состояние, если она расположена неглубоко за зданием.
Остается ещё одна нерешенная проблема: если POI расположена внутри здания, то она всегда будет скрываться.
Мы сделали предположение, что если POI находится внутри здания, то она ему принадлежит. Для этого в данные каждой POI добавили id здания, к которому она относится. И так как этот id совпадает с id объекта самого здания, то этому id мы присвоили уникальный цвет, и в буфере цвета отрисовали им и POI, и само здание. То есть теперь как бы мы ни повернули камеру, из-за совпадения цвета здание перестало влиять на скрытие POI.
В итоге весь процесс скрытия построен на двух проверках: цвет — на перекрытие/неперекрытие POI собственным или другим зданием, глубина — насколько глубоко находится POI за зданием.
Итог
Не хотелось бы использовать избитые формулировки, но к этой фиче как нельзя лучше подходит: «было трудно, но мы справились».
Фича получилась, и она на бою — уже в онлайн-версии (можно посмотреть, например, на тот же
комплекс Москва-Сити), в мобильных приложениях появится позже. Иногда можно заметить мерцание POI — победить его до конца не смогли, оно всё равно изредка происходит в особо сложных геометрических ситуациях. Будем надеяться, что в будущем мы найдём силы полностью искоренить эту проблему.
Зато теперь стало значительно проще понять, где именно находится POI. А наши красивые 3D-модели можно рассматривать без помех.