Еще один шейдер травы. Часть 1

<?the_title()?>

Введение

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

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

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

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

Обзор шейдера

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

  • Шейдер генерирует на каждый треугольник переменное количество травинок в случайных положениях, высотах и ориентациях.
  • Максимальное количество установленных травинок на данный момент составляет 15.
  • Шейдер также вычисляет новые UV-координаты для сгенерированных травинок.
  • Для небольшой оптимизации шейдер будет генерировать меньше травинок в зависимости от расстояния до камеры.
  • Травинки будут колебаться по текстуре смещения, напоминающей движение на ветру.
  • Травинки будут градиентно окрашены, с использованием собственных цветов вершин. Чтобы подчеркнуть движение на ветру, при изгибе они будут получать дополнительный серебристый цвет.

Алгоритм генерации травинки следующий:

  • Мы получаем вектор нормали текущего треугольника и количество травинок, которые породим (в зависимости от расстояния до камеры).
  • Для каждой травинки:
  • Мы получаем случайные числа, основанные на мировом положении вершин треугольника
  • Рассчитываем случайную точку в треугольнике, используя эту технику. Назовем ее «серединой».
  • Рассчитываем две другие точки, по одной на каждой стороне от средней точки. В качестве ориентира для направления смещения этих точек мы используем одну из вершин треугольника.
  • Рассчитываем вклад ветра путем выборки текстуры смещения.
  • Рассчитываем смещение высоты путем выборки текстуры шума.
  • Верхняя точка травинки будет в том же положении, что и средняя точка, но она будет смещена вдоль вектора нормали треугольника. Точка также будет смещена по осям X и Z на основе случайной величины, умноженной на внешнее свойство, и также смещена по величине ветра.
  • В цветах вершин для верхней точки мы сохраняем вклад ветра в зеленом канале и устанавливаем для других каналов значение 1.
  • Для остальных двух точек, которые мы рассчитали, цвет вершин будет установлен на черный.
  • Три точки добавляются к потоку треугольника
    Мы добавляем исходные вершины треугольника к потоку треугольника.
  • ???
  • PROFIT!!!

Более визуализированное описание алгоритма генерации травинки будет таким:

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

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

Код

Давайте теперь посмотрим на код:

И еще не паникуйте заранее. Этот шейдер следует иному формату, который будет полезен во второй части. Вы можете заметить, что проход отделен от большей части логики и вместо «CGPROGRAM» в строке 29 «CGINCLUDE». Этот формат означает, что мы можем заранее объявить функциональность фрагмента / вершинного / геометрического шейдера, а затем просто включить его в проход. Если бы нам, например, потребовалось сделать второй проход с тем же геометрическим шейдером, но с другим фрагментным, этот формат очень полезен, потому что не надо снова писать тело шейдера.

Перейдем теперь к свойствам:

Свойства

_Color Цвет будет умножен на результат карты градиента
_GradientMap Текстура карты градиента, которая будет использоваться для закрашивания
_NoiseTexture Текстура шума, которая определяет случайную высоту травинок
_WindTexture Текстура смещения для эффекта ветра
_WindStrength Сила эффекта ветра
_WindSpeed Скорость, с которой применяется текстура смещения ветром
_WindColor Это странное название говорит о том, что данный цвет будет добавлен к конечному цвету для имитации блеска травы, гнущейся под порывами ветра
_GrassHeight Максимальная высота травинок
_GrassWidth Ширина травинок
_PositionRandomness Величина случайного смещения, которое кончик травинки будет иметь по осям X и Z. При 0 травинки будут вертикально прямыми.
_GrassBlades Максимальное количество травинок на треугольник.
_MinimunGrassBlades Минимальное количество травинок на треугольник, когда камера находится далеко
_MaxCameraDistance Расстояние от камеры, при котором травинок становится меньше

Промежуточные рассуждения

Сначала в строках 33-48 я объявляю структуры, которые буду использовать для передачи данных из вершинного шейдера в геометрический шейдер, а затем во фрагментный шейдер. На данный момент мне просто нужно положение вершин и ничего больше. Поскольку я не добавляю текстуру в исходную сетку, мне даже не нужны UV-координаты объекта, я просто генерирую UV-координаты для травинок.

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

Вы могли также заметить, что структура appdata точно такая же, как структура v2g, и да, мы могли бы фактически использовать одну и ту же структуру в обоих случаях! Но чтобы было понятнее и чтобы сохранить весь конвейер вершинный > геометрический > фрагментный, я сделал их отдельными.

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

В строках 50-67 я заново объявляю свои свойства (не забывайте поля _ST!), а затем в строках 69-73 я добавляю случайную хэш-функцию, которую вы, возможно, уже видели в других уроках, и почти во всех шейдерах, которые используют случайные величины.

Наконец, чтобы избежать повторения кода, в строках 76-82 у меня есть метод для передачи необходимой мне информации в объект g2f и добавления ее в поток. Этот метод всего лишь конвертирует заданную позицию вершины в пространстве объектов в пространство отсечения и передает ее, UV и цвет во вновь созданный объект g2f. Достаточно просто.

Вершинный шейдер

Здесь шейдер вершин теряет значительную часть своего гламура и всего лишь передает положение из appdata в объект v2g. Даже немного грустно.

Геометрический шейдер

Вот где все сходится вместе.

Во-первых, в строке 92 максимальное число вершин у меня 48. Это потому, что, как я упоминал выше, у нас может быть максимум 15 травинок на треугольник. Следовательно, нам нужно 3 стандартных вершины граней плюс 3 вершины для каждой травинки, поэтому 3 * 15 + 3 = 48. Если мы хотим больше травинок на треугольник, это число должно увеличиться на 3 для каждой дополнительной травинки.

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

В строке 96 я определяю количество травинок, которые будут появляться в текущем треугольнике. Я делаю это, используя lerp для интерполяции между максимальным и минимальным количеством травинок, основываясь на расстоянии первой вершины от камеры. Более правильным подходом было бы взять барицентр треугольника, но так тоже работает. В большинстве случаев смещение не будет значительным. Суть данной строки кода в том, что чем меньше расстояние от камеры, тем больше будет сгенерированных травинок. Если расстояние равно или больше «_MaxCameraDistance», то количество сгенерированных травинок равно «_MinimumGrassBlades».

Двигаясь внутри цикла для каждой травинки, в строках 99 и 100 я получаю два случайных числа, основанные на положении в мире первой и второй вершин текущего треугольника, соответственно. Опять же, использование первых двух точек треугольников немного произвольно, но преобразование в мировое пространство помогает удерживать начало привязанным к мировому пространству и иметь множество участков травы без повторений, если мы захотим. Умножение на (i + 1) также помогает получить разные начала для каждой травинки. Опять же, не совсем правильно, но вы понимаете, в чем суть.

В строке 103 я вычисляю случайную среднюю точку травинки, используя этот метод, а затем в строках 105 и 106 я переназначаю случайные числа из промежутка [0,1] в [-1,1], который будет использоваться для рандомизации положения.

В строках 108-109 я вычисляю две точки основания вновь сгенерированного треугольника, смещая среднюю точку на значение «_GrassWidth» в направлении точки треугольника. Еще раз, «i% 3» является произвольным, и оно нужно, чтобы травинки не вращались одинаково. Сейчас это работает так, что основание первой травинки направлено вдоль линии к первой точке треугольника, и каждая травинка будет циклически проходить через точки.

В строке 111 я получаю положение в мировом пространстве для «средней точки», чтобы использовать в качестве координат для текстур ветра и шума. Сэмплирование текстуры ветра происходит в строках 113-114, и это все тот же самый метод, который мы видели множество раз, когда дело касается текстур смещения. Сэмплировать текстуру, сместить UV по времени (здесь это мировое положение компонентов x и z) и затем поместить все в диапазон от -1 до 1 и умножить на силу эффекта. Здесь я также умножаю мировую позицию на «_WindTexture_ST.xy», чтобы можно было регулировать масштаб ветра с помощью наложения текстуры в инспекторе материалов.

В строках 116 и 117 я вычисляю окончательную высоту травинки путем сэмплирования текстуры шума так же, как для текстуры ветра, и умножения результата на “_GrassHeight”.

Держите в голове, что оба сэмплирования текстур здесь происходят с “tex2Dlod” вместо“tex2D”, потому что мы находимся не во фрагментном шейдере.

Теперь, наконец-то пришло время делать травинки! Перед тем, как перейти к коду, я хочу объяснить что делать здесь с UV-шками травинок:

UV координаты травинки

Если мы хотим добавить к травинкам текстуру, вот хороший способ сделать это. Как вы знаете, UV координаты идут от (0,0) внизу слева к (1,1) вверху справа. Поэтому если нам нужно, чтобы травинки покрывали весь спектр равнобедренных треугольников, первая точка в основании должна иметь UV (0,0), вторая (1,0), а верхняя — (0.5,1).

Сказав это, в строке 119 я добавляю первую точку травинки с (0,0) в качестве UV координаты и черным в качестве цвета вершины. Затем в строке 121 я вычисляю положение верхней точки следующим образом:

Я получаю среднюю точку и сначала смещаю ее по вектору нормали с помощью “heightFactor”, затем я смещаю ее по осям x и z, сначала используя полученные раньше и умноженные на “_PositionRandomness” случайные числа “r1” и “r2”, затем используя ветер.

Затем я добавляю эту точку в поток, назначая (0.5,1) в качестве ее UV координат. В качестве цвета я использую 1 для красного, синего и альфа каналов, и длину красного и зеленого значений из текстуры ветра для зеленого канала. Таким способом в дальнейшем я смогу настроить соответствие цветов и интенсивности ветра.

После этого я добавляю третью точку с UV (1,0) и также назначаю ей черную вершину. Потом я еще перезагружаю стрип, поскольку когда цикл перезагружается, мы делаем новый треугольник.

Наконец, в строках 130-132 я добавляю реальные вершины поверхности треугольника к потоку и также использую (0,0) как UV и черный как цвет вершины.

Фрагментный шейдер

Фрагментный шейдер сейчас достаточно прост: я лишь сэмплирую карту градиента в красном канале цвета вершин, который, как вы помните, был 1 на кончике травинок и 0 у основания. Это дает нам красивый серый градиент, от черного основания до белого кончика. Вдобавок, левая сторона текстуры градиента должна быть темнее, а правая светлее. Я применил описанную текстуру для этого изображения:

карта градиента для шейдера

Окончательный цвет затем вычисляется прибавлением “_WindColor”, умноженного на зеленый канал цвета вершин , к цвету из карты градиента, и затем умножению суммы на “_Color”. Цвет получился, все отлично!

Реальный проход

Как я отметил вначале, здесь применен несколько отличающийся формат. Наши шейдеры находятся не в блоке «Pass», как мы привыкли, а в состоянии неопределенности внутри блока «CGINCLUDE». Круто, однако, что они все еще находятся в одном файле с проходом. Поэтому в блоке «Pass» в строках 148–158 нам просто нужно сопоставить соответствующие директивы «#pragma» с именами шейдеров, и мы в порядке! Они будут в просто включены в проход, как если бы мы написали их там сначала.

Поскольку наши шейдеры носят имена “vert”, “geom” и “frag” для вершинного, геометрического и фрагментного соответственно, тэги “#pragma” в строках 135-155 делают для нас всю работу. Также не забудьте добавить директиву “Cull off” в строке 151, иначе большая часть травинок станет невидима с определенной точки просмотра, относительно порядка из визуализации.

Заключение

Это окончание первой части шейдера для травы! В следующий раз мы увидим некоторые более увлекательные вещи вроде лайтинга, теней и т.п. Но даже с таким шейдером вы можете сделать множество различных штук! Например, вместо генерации отдельных травинок вы можете попробовать генерацию квадов в форме звезды (смотрите GPU Gems on grass) и добавлять к ним текстуру травы. Надеюсь, при помощи этого тьюториала вы сделаете потрясающие сцены с травой. Мне бы хотелось, чтобы вы поделились своими работами или хотя бы показали их мне. Так что любым способом дайте знать, если вы хоть как-то смогли использовать данный шейдер!

Увидимся в следующий раз.


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

Источник: https://halisavakis.com/my-take-on-shaders-grass-shader-part-i/

Понравилась статья? Поделиться с друзьями:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *