Как сделать луч в юнити

Добавил пользователь Евгений Кузнецов
Обновлено: 05.10.2024

Как и всегда, есть несколько путей решения проблемы. Какой выбрать, решать вам. Например, Вы хотите что-бы пуля была большой и летела медленно, тогда ваш вариант это физическая пуля. Нагрузка больше из-за расчетов физики, но эффект броска снежка или камня есть. А вот если вы делаете автомат и пуль много, их все равно не видно, можно сделать через рейкаст, что собственно все и делают, а физику полета пули рассчитывают заранее, видим мы только эффект от прилетевшей пули, иногда с эффектом трастера.

Для начала рассмотрим все в теории, а потом уже возьмемся за реализацию и тестирование.

Теория

Физическое тело в виде снаряда

Итак, у нас есть задача бросить камень, надеюсь не в меня) Для этого делается следующее:

  • В момент нажатия на ЛКМ создается объект
  • Мы получает этот объект и толкаем его в нужном направлении
  • Далее движок считает все за нас, мы только смотрим
  • Возможно создаем эффекты приземления

Все просто. Часто именно такой подход и необходим. Так можно симулировать, достаточно медленные снаряды, которые не часто вылетают из оружия. К примеру, снежки, камни, ядра пушек, картофель из пататогана). Проблемы начинаются когда необходимо создать что-то дальнобойные или скорострельное.

При быстром движении такие снаряды могут пролетать сквозь объекты. Способ обхода этого есть, но нагрузка на процессор чуть выше обычного физического тела, а на дальнем расстоянии снаряд все равно особо не видно, так зачем грузить процессор?

Если же нам хочется создать много объектов, проблемы в принципе такие же. Они могут пролетать сквозь объекты, грузить процессор так же скорострельность оружия ограничена размером объекта, так как он будет задевать предыдущий снаряд и они полетят ни так как хотелось бы.

Как же быть? Для таких целей обычно используют рейкаст

Рейкаст для реализации выстрела

В простейшем случае работает это таким образом:

  • Мы выпускаем луч в нужном направлении
  • Определяем куда он попадет
  • рисуем эффект попадания или делаем что то еще

Для расчета падения пули можем провести расчет по скорости и дальности, затем опустив попадание ниже. У такого подхода есть свой минус. Пуля может пролететь сквозь стену если мы ее не просчитаем сами.

Пример как пуля пролетает препятствие

Пример как пуля пролетает препятствие

Как решить данную проблему?

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

Можно запустить симуляцию всего полета "на паузе". Расчет требует времени и соответственно ресурсов, однако точно просчитает полет пули. Это почти так же как и в варианте с физическим объектом, но расчет ведется отдельно от текущего времени и выдается только результат.

Так же выполняется создание обьекта и его пинок в нужном направлении но с игнорированием реального времени.

Либо по школьной физике просчитываем полет тела сами, но зачем? Все равно каждую точку проверять нужно на столкновения.

Практика

Физический снаряд

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

Тестовый стенд

Тестовый стенд

Стрелять будем в точку, без поворота камеры.

Тут все просто, как и описывалось выше, создается объект и мы его пинаем вперед, все.

Первый выстрел Последствия

Путь луча

Принцип описан выше, сцена та же самая, код чуть ниже. Для Визуализации я буду создавать "пулю" в точке соприкосновения луча и объекта. В реальном проекте, там нужно оставлять следы от пуль, искры, осколки, все что захотите.

При клике мгновенно появляется наш снаряд в месте куда он должен был попасть. Если у нас не большая локация, пуля летит быстро, то можно пренебречь скоростью пули и стрелять прямо так.

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

Итоги

В данной статье я привел лишь примеры того как можно сделать выстрелы в unity. Надеюсь она вам поможет определиться как действовать дальше и у вас получиться хорошая стрелялка =)

Будем делать вроде как лазерный луч, на примере, для игры с видом сверху или от третьего лица. Нижеприведенный скрипт можно сразу использовать в своем проекте, если нужно что-то подобное. Разумеется, при желании можно переделать под игру от первого лица, главное понять как всё это работает, что мы разберем тоже, конечно же. Итак, в результате мы получим луч, который как бы вылетает из пушки и тянется до точки клика, при этом он будет сталкиваться с другими объектами, то бишь, определение препятствий. А сам луч, это обычный меш, который можно будет разукрасить как угодно и прицепить какие-нибудь плюшки для красоты, если есть такая необходимость.

Создадим сам лазер.
С начало добавляем пустой объект, назовем его Laser. А в нем создаем дочерний Cylinder из стандартных наборов.

Вариант реализации лазерного луча в игре

Настраиваем цилиндр так, чтобы он был похож на луч и разворачиваем его по вектору Z родительского объекта.


Внимание! У цилиндра, оставляем длину равной единице. Например;


Это важно, иначе будет некорректный расчет длинны луча.

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


Объект Gun Point (откуда вылетает луч) должен иметь масштаб в единицу по всем осям, тоже самое относится и к объекту Laser, кроме того, все они должны быть ориентированны по си Z.

Обложка: Оптимизируем работу с физикой в Unity

В данном материале будет рассмотрено несколько полезных приёмов использования физики в играх и примеры их практического применения. Предполагается, что у читателя уже есть опыт разработки на Unity.

Слои и Матрица Коллизий

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

Для целей данной статьи была создана простая демо-сцена с 2 000 объектов (1 000 красных и 1 000 зелёных) внутри контейнера. Зелёные объекты должны взаимодействовать только сами с собой и со стенами (слой Wall ). В одном из тестов все объекты принадлежат слою Default и столкновения производятся путём строкового сравнения тега игровых объектов на слушателе коллизий. В другом тесте объекты разделены на два слоя. Взаимодействие этих слоёв было настроено в Матрице Коллизий.


Рис. 1: Настройка Матрицы Коллизий

Изображение ниже берётся из самой демонстрации. В ней есть простой менеджер, который считает количество столкновений и автоматически останавливается после 5 секунд. Количество ненужных столкновений в варианте с общим слоем впечатляет.


Рис. 2: Количество столкновений в течение 5 секунд

Вот результат профилировки для более конкретных данных о физическом движке


Рис. 3: Общие слои в сравнении с отдельными в данных профилировки

Как мы видим, существуют значительные различия во времени, которое процессор потратил на физику. На общем слое это ~27,7 мс, а на отдельных — ~17,6 мс.

Рейкастинг

Рейкастинг — довольно полезный и мощный инструмент в физическом движке. Он позволяет направлять луч определённой длины в определённом направлении и получить информацию о произошедших столкновениях. Однако это дорогостоящая операция. Производительность рейкастинга сильно зависит от длины луча и типов коллайдеров на сцене.

Вот несколько советов по использованию рейкастинга:

  • Хоть это и очевидно, но нужно стремиться к наименьшему количеству лучей.
  • Не делайте лучи длиннее, чем это необходимо. Чем длиннее луч, тем больше объектов нужно проверять на столкновение с ним.
  • Не используйте рейкасты внутри метода FixedUpdate() . А в некоторых случаях даже использование их внутри метода Update() может быть излишним.
  • Будьте осторожнее с типами коллайдеров, которые используете. Рейкастинг с меш-коллайдером обходится довольно дорого.
    • Неплохое решение — создать дочерний объект с примитивными коллайдерами и повторить с их помощью приблизительную форму меша. Все дочерние коллайдеры у родительского Rigidbody ведут себя как составные коллайдеры.
    • Если вам очень нужно использовать меш-коллайдеры, то как минимум сделайте их выпуклыми.


    Рис.9: Предупреждение перемещения статических коллайдеров

    Как вы можете видеть, около 2 000 предупреждений генерируется на каждом игровом объекте. Также средние затраты времени, потраченного ЦПУ на физику, возросли с ~17,6 мс до ~35,85 мс. Поэтому если мы двигаем игровой объект, важно добавить на него Rigidbody . Если вы хотите вручную контролировать его движение, то просто пометьте его как Kinematic в свойствах Rigidbody .

    Фиксированные временные участки (Timestep)

    Настройка значения фиксированных timestep в Time Manager непосредственно влияет на FixedUpdate() и частоту обновления физики. Изменяя это значение, вы можете достигнуть золотой середины между точностью и временем, затраченным ЦПУ на физику.

    Заключение

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

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

    В этом проекте вы узнаете следующие аспекты разработки в Unity:

    • создание 2D проекта в Unity
    • ознакомление с интерфейсом Unity
    • создание префаба
    • добавление скриптов к игровым объектам
    • обработка столкновений
    • использование таймеров
    • работа со звуком

    Создание проекта в Unity

    Загрузите Unity и выберите New Project в меню File меню, чтобы открыть новый диалог проекта. Выберите каталог для вашего проекта и установить настройки по умолчанию для 2D.Open

    Создание игры в Unity

    Настройки

    На следующем этапе вы познакомитесь с интерфейсом Unity. Создайте проект на мобильных разработок, щелкнув на Build Settings в меню File и выбрав нужную вам платформу для вашей будующей игры.

    Создание игры в Unity

    В Unity можно создавать игры под iOS, Android, BlackBerry и Windows Phone 8. Поскольку мы собираемся создать 2D игры, первое, что мы должны сделать после выбора платформы, - это выбрать размер картинок, которые мы будем использовать в игре:

    • iPad без Retina: 1024px x 768px
    • iPad с Retina: 2048px x 1536px
    • 3.5" iPhone/iPod Touch без Retina: 320px x 480px
    • 3.5" iPhone/iPod с Retina: 960px x 640px
    • 4" iPhone/iPod Touch: 1136px x 640px

    Поскольку Android является открытой платформой, существует множество устройств различными разрешениями экрана и плотности пикселей. Некоторые из наиболее распространенных из них перечислены ниже:

    • Asus Nexus 7 Tablet: 800px x 1280px, 216 ppi
    • Motorola Droid X: 854px x 480px, 228 ppi
    • Samsung Galaxy SIII: 720px x 1280px, 306 ppi

    Для Widows Phone и BlackBerry:

    • Blackberry Z10: 720px x 1280px, 355 ppi
    • Nokia Lumia 520: 400px x 800px, 233 ppi
    • Nokia Lumia 1520: 1080px x 1920px, 367 ppi

    Но, даже если мы будем концентрироваться на платформе iOS в этом уроке, игру можно будет воспроизвести на любой другой платформе.

    Экспорт изображений

    Экспорт изображений

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

    Интерфейс Unity

    Создание игры в Unity

    Не забудьте нажать кнопку 2D в панели Scene . Вы также можете изменить разрешение, которое используется для отображения сцены, на панели Game.

    Игровой интерфейс

    Создание игры в Unity

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

    Языки программирования в Unity

    Если вы хотите программировать на другом языке, то обратитесь к Unity's Script Reference для изучения.

    Прежде чем мы начнем кодирование, нам нужно добавить наши ассеты в проект Unity. Есть несколько способов сделать это:

    • выбрать Import New Asset из меню Assets
    • добавить детали в папку assets в проектеt
    • перетащить ассеты в окно проекта

    После завершения этого шага, вы должны увидеть ассеты вашего проекта в папке Assets в панели Project.

    Создание сцены

    Мы готовы создать сцену нашей игры путем перетаскивания объектов в панель Hierarchy (Иерархия) или Scene (Сцена).

    Создание игры в Unity. Hierarchy

    Перетащите фон (файл gameBg.jpg ) в панель Hierarchy панели. Затем он должен появиться в панели Scene .

    Поскольку вкладка Scene (Сцена) установлена в режим 2D, вы можете выбрать Основную камеру (Main Camera) в панели Иерархия, при этом вы увидете то, что камера будет отображать. Вы также можете увидеть это переключившись из вкладки Сцена во вкладку Game (Игра). Чтобы вся сцена видна, измените параметр Size (Размер) для Основной камеры на 1.6 в панели Inspector (Инспектор).

    Создание игры в Unity

    Летающая тарелка

    Корабль в нашей игре является статическим элементом и игрок с ним никак не взаимодействует. Расположим его в центре сцены, для этого просто ппертащите летательный аппарат из папки Assets, находящейся в панели Project , на вкладку Scene . Точно также как и с НЛО перенесем на сцену сарайчик для наших коровок и расположим его так, как показано на скриншоте:

    Создание игры в Unity

    Коллайдер сарая

    Чтобы убедиться, что сарай реагирует на столкновение с коровой когда она пытается войти внутрь – нужно добавить один компонент, а точнее Box Collider 2D.

    Выберите сарай в сцене и во в вкладке Inspector нажмите на кнопку Add Component (добавить компонент). Из списка компонентов выберите раздел Physics 2D , а в нем Box Collider 2D . Убедитесь в том, что напротив параметра Is Trigger стоит галочка. Если ее нет - поставьте.

    Создание игры в Unity

    Мы хотим, чтобы наш триггер срабатывал, корова попадала на дверь сарая, поэтому мы должны сделать коллайдер поменьше. Перейдите на вкладку Inspector и измените значения коллайдера напротив Size (высота и ширина коллайдера) и Center (координаты центра коллайдера) так, чтобы он располагался поближе к двери сарая. У меня это получилось примерно так:

    Создание игры в Unity. Добавление коллайдера.

    Скрипт для обработки столкновений

    Создание игры в Unity. Добавление скрипта

    Откройте вновь созданный файл и добавьте следующий фрагмент кода:

    Сниппет реагирует на столкновение между объектами, к которым привязан скрипт, сараем и объектом под именем cow(Clone) - один из экземпляров коровы, префаб которой мы создадим позже. Когда происходит столкновение, проигрывается звук и объект корова разрушается.

    Добавляем звук

    Для воспроизведения звука, когда корова попадает в сарай, нужно для начала прикрепить этот звук к сараю. Щелкните мышью на сарае во вкладке Hierarchy или на самой сцене (вкладка Scene ), нажмите кнопку Add Component во вкладке Inspector и выберите в разделе Audio пункт Audio Source. Мы только что добавили компонент Audio Source. Теперь нам нужно связать с ним наш звуковой файл. Щелкните на кружочке справа от пункта Audio Clip и выберите звук barn . Снимите галочку напротив Play on Awake

    Создание игры в Unity. Добавление звука.

    Вы можете увеличить размер значков в пользовательском интерфейсе Unity (Gizmos), нажав на Gizmos в вкладке Scene и отрегулировав положение ползунка.

    Создание игры в Unity. Gizmos.

    Космический луч

    Перетащите изображение с космическим лучем из вкладки Assets на сцену и добавьте коллайдер к нему (как это делать вы уже знаете). Это необходимо для обнаружения столкновения луча с нерасторопными коровамии. Убедитесь, что переключатель Is Trigger включен.

    Скрипт луча

    Создайте новый скрипт, повторяя шаги, проделанные ранее. Назовите скрипт Bullet и напишите в нем следующий код:

    Здесь много кода, но он вовсе не сложный. Давайте посмотрим, что происходит. Сначала мы создаем экземпляр AudioClip под названием cowSound , который мы будем использовать для хранения аудиофайла. Это еще один вариант проигрывания звука, если вы не хотите привязывать к объекту два аудиокомпонента. Мы объявили переменную как публичную, поэтому мы можем получить к ней доступ из вкладки Inspector. Нажмите на маленькую точку справа от cowSound и выберите аудиофайл cowSound .

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

    Мы обнаруживаем касания экрана, делаем луч видимым и воспроизводим звук луча (см. ниже). Когла наш луч становится видимым, он начинает двигаться вниз чтобы попасть в корову.

    Также мы предусмотрели проверку не оказался ли наш луч за границами сцены. Если это так, то мы возвращаем его на место и наше НЛО пожет стрелять снова (проверьте значения х и у луча в Инспекторе).

    Последняя часть проверяет коснулся ли луч коровы. Если это произойдет, то проигрывается звук и корова исчезает. Затем луч снова делается невидимыми, перемещается в исходное положение и НЛО готово к стрельбе снова.

    Добавляем звук для луча

    Создание игры в Unity. Добавление звука

    Чтобы добавить звуковое сопровождение к нашему космическому лучу необходимо выбрать во вкладке Hierarchy или Scene наш лучик и нажать на кнопку Add Component во вкладке Inspector. Выберите Audio Source из раздела Audio. Снимите галочку с Play on Awake и нажмите маленькую точку справа от Audio Clip, чтобы выбрать звуковой файл по имени bizzed .

    Добавляем корову

    Перетащите нашу буренку из панели Assets на вкладку Сцена так, как показано на рисунке ниже:

    Создание игры в Unity

    Rigid Body 2D

    Создание игры в Unity. Rigid Body 2D

    Коллайдер коровы

    Мы также должны привязать к корове коллайдер (как это делать вы уже знаете), чтобы определять столкновения с сараем и лучом. Не забудьте поставить галочку в чекбоксе Is Trigger в Inspector.

    Скрипт движения коровы

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

    Класс MoveCow анимирует корову во время ее движения на экране с помощью переменной moveSpeed . Метод InvokeRepeating изменяет скорость коровы, заставляя ее пускаться галопом начиная с того момента, когда она достигает центра сцены. Это делает игру более сложной.

    Создаем префаб для коровки

    Теперь, когда мы привязали к корове все необходимые компоненты, пора конвертировать ее в префаб.

    "Префаб – это GameObject для многократного использования, который хранится в Project View. Префаб может быть вставлен в любое количество сцен, несколько раз. Когда вы добавляете префаб в сцену, вы создаете его экземпляр. Все экземпляры префаба связаны с оригинальным префабом и, по сути, являются его клонами. Независимо от того, сколько копий вы создали для своего проекта, любые изменения, коснувшиеся оригинального префаба, будут применены ко всем его экземплярам".

    Для преобразования коровы в префаб, перетащите корову из вкладки Иерархия в панель Assets. В результате название в Hierarchy станет синим.

    Создание игры в Unity. Создание префаба

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

    Скрипт Spawner

    Мы используем метод InvokeRepeating для клонирования коров с применением значений, установленных для spawnTime и spawnDelay . GameObject cow установлен на значение public и создан с использованием Inspector. Нажмите на маленькую точку справа и выберите префаб коровы.

    Spawner Game Object

    Чтобы создать несколько экземпляров префаба коровы, используйте график коровы, который мы добавили к сцене несколько минут назад. Выделите ее и удалите ее компоненты. Затем добавьте скрипт Spawner.

    Spawner Game Object

    Тестирование

    Настало время протестировать нашу игру. Нажмите на Command + P для запуска игры в Unity. Если все работает как надо, вы готовы к заключительному шагу.

    Настройки плеера

    После успешного тестирования, можно сосредоточиться на настройках плеера. Выберите Build Settings из меню меню File и нажмите на кнопку Player Settings (настройки плеера). Теперь можно настроить параметры для вашего приложения, например Иконку приложения (Icon) или Картинку-заставку (Splash Image), которая показывается при запуске приложения. Используйте графические редакторы чтобы создать красивый значок для вашей игры. Unity покажет в разделе Иконки (Icon) вам необходимые размеры, которые зависят от вашего приложения.

    Создание игры в Unity

    Публикация

    Создание игры в Unity

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

    Xcode

    Создание игры в Unity

    Если вы делаете игры для iOS, вам понадобится Xcode, чтобы скомпилировать конечный двоичный код для приложения. Откройте проект Xcode и выберите Build из меню Product.

    Заключение

    В этом уроке мы узнали о новых 2D возможностях Unity, обнаружение столкновений и другие аспекты разработки игр под Unity.

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


    Сегодня мы совершим большой скачок. Мы отойдём от исключительно сферических конструкций и бесконечной плоскости, которые трассировали ранее, и добавим треугольники — всю суть современной компьютерной графики, элемент, из которого состоят все виртуальные миры. Если вы хотите продолжить с того, чем мы закончили в прошлый раз, то воспользуйтесь кодом из части 2. Готовый код того, что мы будем делать сегодня, выложен здесь. Давайте приступим!

    Треугольники

    Код из статьи легко можно портировать в код шейдера на HLSL:


    Чтобы воспользоваться этой функцией, нам нужен луч и три вершины треугольника. Возвращаемое значение сообщает нам, произошло ли пересечение треугольника. В случае пересечения вычисляются три дополнительных значения: t описывает расстояние вдоль луча до точки пересечения, а u / v — это две из трёх барицентрических координат, определяющих местоположение точки пересечения на треугольнике (последнюю координату можно вычислить как w = 1 - u - v ). Если вы пока не знакомы с барицентрическими координатами, то прочитайте их превосходное объяснение на Scratchapixel.

    Без лишних промедлений давайте оттрассируем один треугольник с указанными в коде вершинами! Найдите в шейдере функцию Trace и добавьте в неё следующий фрагмент кода:


    Как я и говорил, t хранит расстояние вдоль луча, и мы можем напрямую использовать это значение для вычисления точки пересечения. Нормаль, которая важна для вычисления правильного отражения, может быть вычислена с помощью векторного произведения любых двух рёбер треугольника. Запустите режим игры и полюбуйтесь своим первым оттрассированным треугольником:


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

    Меши из треугольников

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

    В компьютерной графике меш задаётся несколькими буферами, самыми важными из которых являются буферы вершин и индексов. Буфер вершин — это список 3D-векторов, описывающий позицию каждой вершины в пространстве объекта (это значит, что такие значения не нужно изменять при переносе, повороте или масштабировании объекта — они преобразуются из пространства объекта в мировое пространство на лету, с помощью матричного умножения). Буфер индексов — это список целочисленных значений, являющихся индексами, указывающими на буфер вершин. Каждые три индекса составляют треугольник. Например, если буфер индексов имеет вид [0, 1, 2, 0, 2, 3], то в нём есть два треугольника: первый треугольник состоит из первой, второй и третьей вершин в буфере вершин, а второй треугольник состоит из первой, третьей и четвёртой вершин. Следовательно, буфер индексов также определяет вышеупомянутый порядок обхода. Кроме буферов вершин и индексов могут существовать дополнительные буферы, добавляющие к каждой вершине другую информацию. Самые часто встречающиеся дополнительные буферы хранят нормали, координаты текстур (которые называются texcoords или просто UV), а также цвета вершин.

    Использование GameObjects

    В первую очередь нам нужно узнать, какие GameObjects должны стать частью процесса трассировки лучей. Наивным решением было бы просто воспользоваться FindObjectOfType () , но сделаем нечто более гибкое и быстрое. Давайте добавим новый компонент RayTracingObject :


    Этот компонент добавляется к каждому объекту, который мы хотим использовать для трассировки лучей и занимается их регистрацией с помощью RayTracingMaster . Добавим в мастер следующие функции:


    … а теперь давайте сделаем то же самое в шейдере. Вы ведь к этому уже привыкли?


    Структуры данных готовы, и мы можем заполнить их настоящими данными. Мы собираем все вершины всех мешей в один большой List , а все индексы в большой List . С вершинами никаких проблем не возникает, но индексы нужно изменить так, чтобы они продолжали указывать на верную вершину в нашем большом буфере. Представьте, что мы уже добавили объекты с 1000 вершин, а теперь добавляем простой меш-куб. Первый треугольник может состоять из индексов [0, 1, 2], но так как у нас в буфере уже было 1000 вершин, то перед добавлением вершин куба нужно сместить индексы. То есть они превратятся в [1000, 1001, 1002]. Вот как это выглядит в коде:


    Вызовем RebuildMeshObjectBuffers в функции OnRenderImage , и не забудем освободить новые буферы в OnDisable . Вот две вспомогательные функции, которые я использовал в показанном выше коде, чтобы немного упростить обработку буферов:


    Отлично, мы создали буферы и они заполнены нужными данными! Теперь нам просто нужно сообщить об этом шейдеру. Добавим в SetShaderParameters следующий код (и благодаря новым вспомогательным функциям мы можем сократить код буфера сфер):


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

    Трассировка мешей

    Давайте не будем заставлять его ждать. В шейдере у нас уже есть код трассировки отдельного треугольника, а меш — это, по сути, просто множество треугольников. Единственный новый аспект здесь заключается в том, что мы используем матрицу для преобразования вершин из пространства объекта в мировое пространство с помощью встроенной функции mul (сокращение от multiply). Матрица содержит перенос, поворот и масштаб объекта. Она имеет размер 4×4, поэтому для умножения нам понадобится 4d-вектор. Первые три компонента (x, y, z) берутся из буфера вершин. Четвёртому компоненту (w) мы задаём значение 1, потому что имеем дело с точкой. Если бы это было направление, то мы бы записали в него 0, чтобы игнорировать все переносы и масштаб в матрице. Вас это сбивает с толку? Тогда прочитайте не меньше восьми раз этот туториал. Вот код шейдера:


    Мы всего в одном шаге от того, чтобы увидеть всё это в действии. Давайте немного реструктурируем функцию Trace и добавим трассировку объектов-мешей:

    Результаты

    Вот и всё! Давайте добавим несколько простых мешей (вполне подойдут примитивы Unity), дадим им компонент RayTracingObject и понаблюдаем за магией. Не используйте пока детализированных мешей (больше нескольких сотен треугольников)! Нашему шейдеру не хватает оптимизации, и если перестараться, то для трассировки хотя бы одного сэмпла на пиксель могут уйти секунды или даже минуты. В результате система остановит драйвер GPU, движок Unity может вылететь, а компьютеру потребуется перезагрузка.


    Заметьте, что наши меши имеют не плавное, а плоское затенение. Так как мы пока не загрузили в буфер нормали вершин, для получения нормали вершины каждого треугольника нужно выполнять векторное произведение. Кроме того, мы не можем интерполировать по площади треугольника. Мы займёмся этой проблемой в следующей части туториала.

    Ради интереса я скачал стэнфордского кролика (Stanford Bunny) из архива Моргана Макгвайра и с помощью модификатора decimate пакета Blender снизил количество вершин до 431. Вы можете поэспериментировать с параметрами освещения и жёстко прописанным материалом в функции шейдера IntersectMeshObject . Вот диэлектрический кролик с красивыми мягкими тенями и небольшим рассеянным глобальным освещением в Grafitti Shelter:


    … а вот металлический кролик под сильным направленным освещением Cape Hill, отбрасывающий на плоскость пола дискотечные блики:


    … а вот два маленьких кролика, прячущихся под большой каменной Сюзанной под голубым небом Kiara 9 Dusk (я прописал альтернативный материал для второго объекта, проверяя, равно ли смещение индексов нулю):


    Что дальше?

    Очень здорово впервые видеть реальный меш в собственном трейсере, правда? Сегодня мы обработали кое-какие данные, узнали о пересечении по алгоритму Меллера-Трэмбора и собрали всё так, чтобы сразу же можно было использовать GameObjects движка Unity. Кроме того, мы увидели одно из преимуществ трассировки лучей: как только ты добавляешь в код новое пересечение, все красивые эффекты (мягкие тени, отражённое и рассеянное глобальное освещение, и так далее) сразу начинают работать.

    Рендеринг блестящего кролика занял кучу времени, и мне всё равно пришлось использовать немного фильтрации, чтобы избавиться от самого очевидного шума. Для решения этой проблемы сцена обычно записывается в пространственную структуру, например, в сетку, K-мерное дерево или иерархию ограничивающих объёмов, что значительно повышает скорость рендеринга крупных сцен.

    Но нужно двигаться по порядку: дальше мы устраним проблему с нормалями, чтобы наши меши (даже низкополигональные) выглядели более гладкими, чем сейчас. Также неплохо было бы автоматически обновлять матрицы при перемещении объектов и напрямую ссылаться на материалы Unity, а не просто прописывать их в коде. Этим мы и займёмся в следующей части серии туториалов. Спасибо за чтение, и до встречи в части 4!

    Читайте также: