Из этой статьи вы узнаете о картах высот (height maps), также называемых картами параллакса (parallax maps).

Что такое карта высот?


Как обычно, когда я говорю «карта», то имеют в виду текстуру, содержащую информацию о внешнем виде 3D-объекта. Карта высот/параллакса — это карта, которую можно использовать для того, чтобы создать иллюзию того, что одни части объекта выступают сильнее, чем другие, то есть имеют бОльшую высоту.


Без карты высот.


С картой высот.

По описанию это может показаться очень похожим на карту нормалей (normal map), благодаря которой 3D-объект кажется более рельефным, но действует она немного иначе. Карта нормалей использует освещение, чтобы объект казался более рельефным, чем на самом деле. Карта высот использует параллакс, чтобы сделать объект выше, чем на самом деле.

Параллакс


Замечали ли вы в детстве, что когда идёшь вечером, луна идёт за тобой? Объекты, мимо которых мы проходим — или движемся параллельно им, приближаются и удаляются, но луна всегда занимает в небе одно и то же место. Если вы, как и я, выросли рядом с горами, то могли также замечать, что они тоже не очень быстро двигаются, однако если уехать достаточно далеко, они всё-таки останутся позади. Чем дальше от нас предметы, тем медленнее они «движутся» при изменении угла взгляда на них (например, когда проезжаешь их на машине) и чем ближе объекты, тем быстрее они смещаются при смене вашей позиции и угла взгляда.

В таких двухмерных играх-сайдскроллерах, как Mario или Rayman, создатели использовали этот эффект. Они создали несколько слоёв фона, прокручивающихся с разной скоростью в зависимости от того, насколько близкими они должны казаться. Это просто иллюзия — разумеется, все спрайты плоские, но она придаёт сцене ощущение глубины!

Однако в 3D-графике мы тоже можем использовать это явление. Но вместо прокрутки здесь можно комкать или растягивать координаты текстур в зависимости от направления обзора, чтобы обмануть глаз и увидеть глубину! Именно этим данный эффект отличается от карты нормалей. Карта нормалей оставляет неизменными координаты текстур, но изменяет сторону, в которую по нашим ощущениям направлен объект. Карта высот сохраняет направление, но меняет координаты текстур, которые мы используем для всех остальных карт.

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

Если вы хотите создать собственный проект, то перейдите по адресу www.textures.com/download/pbr0419/137925 и скачайте файлы в папку своего проекта, например, в Assets/Textures/Acoustic Foam. (Не волнуйтесь, текстуры бесплатны!) Затем создайте материал и установите этот шахматный паттерн в слот основной текстуры, а потом задайте материал плоскости. У материала оставьте шейдер Standard shader.


Материал плоскости имеет только этот шахматный паттерн в качестве основной текстуры albedo и больше никаких других карт.


Здесь использован шахматный паттерн как основная текстура albedo и карта нормалей в виде акустического поролона (acoustic foam). Заметьте, что шахматный паттерн по-прежнему идеально ровный.


Здесь шахматный паттерн используется как основная текстура albedo, а acoustic foam — как карта высот.


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

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


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


А вот плоскость с шахматным паттерном в качестве текстуры albedo, картой нормалей acoustic foam и картой высот acoustic foam. Они отлично сочетаются! Ну ладно, хватит болтовни, давайте уже напишем шейдер, выполняющий эту задачу!

Подготовка


Откройте сцену в Unity. Добавьте плоскость, нажав правой клавишей мыши внутри иерархии и выбрав 3D Object > Plane.


Установите transform плоскости в 0, 0, 0 и оставьте на ней материал по умолчанию, она нужна будет, чтобы дать нам представление о масштабе. Добавьте таким же образом ещё одну или две фигуры. Я буду использовать сферу и куб. Расположите их, где угодно, на плоскости или над ней. Скачайте эти бесплатные текстуры в свой проект: https://www.textures.com/download/pbr0579/138828 (или используйте любой другой набор текстур с картами высот!), например в Assets/Textures/CrystalOre. В той же папке или в подпапке папки Materials создайте новый материал под названием Crystal Ore. Примените новый материал к двум фигурам. Нажмите правой клавишей мыши в Assets/Shaders/Standard и выберите Create > Shaders > Standard Surface Shader


Назовите новый файл шейдера BasicParallax, затем дважды щёлкните по нему, чтобы открыть его в Visual Studio. Измените первую строку на Shader «Xibanya/Standard/BasicParallax», затем сохраните его и вернитесь в Unity.


Назначьте шейдер BasicParallax материалу Crystal Ore. Теперь можно перетащить в слот текстуры карту albedo.


Создаём шейдер


Мы уже говорили о том, как использовать карту нормалей, metallic map и так далее, поэтому я буду предполагать, что вы уже знаете, как это делается. (Если нет, то прочитайте информацию по ссылкам и возвращайтесь!).

Перейдите к созданному мной базовому шейдеру, нажмите на кнопку Raw, скопируйте и вставьте шейдер в BasicParallax (но перед сохранением обязательно измените название сверху на Xibanya/Standard/BasicParallax!). Теперь можно перетащить карту нормалей и карту AO.


Я создала этот минималистичный шейдер для записи всех свойств, используемых стандартной моделью освещения (Standard lighting model), и больше в нём ничего нет, поэтому требуется дополнительная настройка, чтобы шейдер мог использовать roughness map. Добавьте эту строку в свойства (например, над gloss map)

[Toggle(ROUGHNESS)]_Roughness("Roughness?", float) = 0

Добавьте это под surface pragma

#pragma shader_feature_local ROUGHNESS


Затем измените часть, после которой распаковывается glossmap, на это:


(Если вы незнакомы с roughness map или ключевыми словами, то о них можно прочитать здесь!) Завершив с этим, сохранитесь и возвращайтесь в Unity. Теперь перетащите roughness map и переключите флаг Roughness на true.


Отлично! Наконец мы подготовили основу, настало время приступать к параллаксу!

Создание шейдера параллакса


Наверху, в разделе свойств добавьте следующие пять строк:

[Space]
[Header(Parallax)]
[Toggle(_PARALLAXMAP)]_Parallaxmap("Parallax?", float) = 0
_ParallaxMap("Parallax Map", 2D) = "white" {}
_Parallax("Parallax Strength", Range(0, 0.1)) = 0.005

Мы сделаем параллакс функцией, которая активируется/отключается ключевым словом _PARALLAXMAP. Для правильно применения карты параллакса нам нужно использовать касательное направление обзора. Если мы пишем поверхностный шейдер, то если ключевое слово задано (то есть мы его включили), это будет направление обзора, получаемое от структуры Input. Если ключевое слово _PARALLAXMAP не задано, мы всё равно будем получать касательное направление обзора, если выполняем запись в o.Normal. Однако возможно, что мы не всегда будем это делать, поэтому об этом легко забыть и запутаться, поэтому лучше привыкнуть использовать его каждый раз, когда нам нужно распаковать карту параллакса! В pragma ключевого слова Roughness добавим следующую строку:

#pragma shader_feature_local _PARALLAXMAP

и добавим в структуру Input float3 viewDir.


Наконец, объявим в подшейдере текстуру карты параллакса и свойства силы параллакса.

sampler2D _ParallaxMap;
half _Parallax;

Сохраним код и вернёмся в Unity, чтобы убедиться, что не сделали никаких опечаток или ошибок. Если всё правильно, то теперь мы можем перетащить текстуру CrystalOre_1k_height в слот карты параллакса материала. Не забудьте там же включить параллакс.


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


Мы распакуем текстуру и возьмём зелёный канал (большинство карт высот чёрно-белые, из-за чего все каналы одинаковы, поэтому нам достаточно использовать один), а затем передадим его, силу параллакса и касательное направление обзора (удобно вычисленное за нас магией поверхностного шейдера Unity) во встроенную функцию под названием ParallaxOffset. ParallaxOffset — это величина, определяющая сжатие или растягивание UV-координат в зависимости от направления обзора. Её можно найти в библиотеке UnityCG.cginc, включаемой в каждый поверхностный шейдер. ParallaxOffset возвращает значение float2, которое мы затем можем прибавить к координатам текстуры IN.uv_MainTex (которые также являются float2!), чтобы каждая распаковываемая после этого текстура распаковывалась со смещением. Если вам любопытно, то это выглядит так, но вам необязательно знать, как это работает, чтобы добавить параллакс к своим поверхностным шейдерам!


(не добавляйте это в свой шейдер, этот код уже встроен!) И наша работа здесь на этом закончена! Сохранитесь и вернитесь в Unity. Поиграйтесь с ползунком Parallax Strength, чтобы увидеть эффект в действии.


Сила равна 0


Сила равна 0.1

В свойствах я при помощи ползунка с интервалом ограничил силу параллакса в пределах от 0 до 0.1, потому что при значениях выше 0.1 всё начинает выглядеть довольно безумно даже при самых идеальных углах обзора, но вы можете изменить свойство с интервала на float, чтобы поэкспериментировать с различной силой параллакса.


Глючно!

Созданный в этом туториале шейдер приложен к этому посту как BasicParallax.shader. Если у вас есть вопросы или вы хотите поделиться тем, что у вас получилось, то напишите в комментариях к оригиналу статьи, в Twitter или в Discord.

Этот туториал лицензирован по условиям Creative Commons Attribution-NonCommercial-ShareAlike4.0 International License. Код, выложенный с этим туториалом, лицензирован по условиям CreativeCommonsAttribution 4.0 International License.

UVChecker.png, BasicParallax.shader