Основы поверхностных шейдеров.

Введение.

В дополнение к написанию шейдеров почти с нуля, Unity также позволяет нам определить некоторые параметры и дать ей генерировать код, который выполняет сложные вычисления света. Эти шейдеры называются «поверхностными шейдерами».
Чтобы понять поверхностные шейдеры, хорошо сначала познакомиться с основными unlit шейдерами, у меня есть урок по ним здесь.

Преобразование в простой поверхностный шейдер

При использовании поверхностных шейдеров нам не нужно делать несколько вещей, которые мы должны были бы сделать сами, потому что Unity создаст их для нас. Для преобразования в поверхностный шейдер мы можем полностью удалить наш вершинный шейдер. Мы можем удалить определения прагмы функции вершин и фрагментов. Мы можем удалить ввод, а также вершину для фрагмента структуры. Мы можем удалить переменную MainTex_ST для масштабирования текстур, и мы можем удалить включение файла включения UnityCG. И мы удаляем начало и конец Pass, Unity будет генерировать проходы для нас. После всего этого наш опустошенный шейдер должен выглядеть так:

Shader "Tutorial/005_surface" {
 Properties {
  _Color ("Tint", Color) = (0, 0, 0, 1)
  _MainTex ("Texture", 2D) = "white" {}
 }
 SubShader {
  Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

  CGPROGRAM

  sampler2D _MainTex;
  fixed4 _Color;

  fixed4 frag (v2f) : SV_TARGET {
   fixed4 col = tex2D(_MainTex, i.uv);
   col *= _Color;
   return col;
  }
  ENDCG
 }
 FallBack "Standard"
}
Теперь, когда мы сломали наш шейдер, мы можем добавить несколько вещей, чтобы заставить его снова работать уже как поверхностный шейдер.
Сначала мы добавляем новую структуру и называем ее Input, она будет содержать всю информацию, необходимую для установки цвета нашей поверхности. Для этого простого шейдера это просто UV-координаты. Тип данных для наших координат будет двумерным float, как в предыдущем шейдере. Здесь наименование важно, поэтому мы назовем его uv_MainTex. Тогда у него уже будет tiling и offset текстуры MainTex. Если текстура имела бы другое имя, нам нужно было бы использовать uvИмяТекстуры, чтобы получить координаты, соответствующие этой текстуре.

struct Input {
 float2 uv_MainTex;
};
Затем мы заменим нашу функцию фрагмента на функцию поверхности. Чтобы сделать это изменение очевидным, мы переименуем ее в surf. Затем мы заменяем возвращаемый тип (тип данных перед именем функции) на void, потому что эта функция ничего не возвращает
Затем мы расширим его, чтобы принять 2 аргумента. Во-первых, экземпляр входной структуры, которую мы только что определили, так чтобы у нас был доступ к информации, которая определена на основе каждой вершины. Во-вторых, структура называемая SurfaceOutputStandard. Как видно из названия, мы будем использовать его для возврата информации в сгенерированную часть шейдера. Чтобы этот «возврат» работал нам нужно написать ключевое слово inout перед ним. Эта вторая структура - это все данные, которые Unity будет использовать для расчетов освещения. Расчёты освещения основаны на физике (я объясню параметры позже в этом уроке).
Затем мы удалим атрибут sv_target из метода, потому что, как и все остальные, это делается где-то еще самим Unity.
Последнее изменение, которое мы должны сделать, чтобы заставить метод surf работать, - удалить оператор return (поэтому что мы изменили тип возврата на void). Вместо этого мы устанавливаем часть альбедо выходной структуры в значение цвета.

void surf (Input i, inout SurfaceOutputStandard o) {
 fixed4 col = tex2D(_MainTex, i.uv_MainTex);
 col *= _Color;
 o.Albedo = col.rgb;
}
Последний шаг, чтобы заставить шейдер работать снова и правильно его обработать, заключается в добавлении инструкции прагмы, объявляющей тип шейдера и используемые методы. (аналогично тому, как мы объявили методы вершин и фрагментов в базовом шейдере).

Добавление начинается с #pragma, за которым следует тип шейдера, который мы объявляем (surface), затем имя метода (surf) и последнее - модель освещения, которую мы хотим использовать Standard (стандарт).
Со всем этим наш шейдер должен снова работать и показывать правильное освещение.

Shader "Tutorial/005_surface" {
 Properties {
  _Color ("Tint", Color) = (0, 0, 0, 1)
  _MainTex ("Texture", 2D) = "white" {}
 }
 SubShader {
  Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

  CGPROGRAM

  #pragma surface surf Standard fullforwardshadows

  sampler2D _MainTex;
  fixed4 _Color;

  struct Input {
   float2 uv_MainTex;
  };

  void surf (Input i, inout SurfaceOutputStandard o) {
   fixed4 col = tex2D(_MainTex, i.uv_MainTex);
   col *= _Color;
   o.Albedo = col.rgb;
  }
  ENDCG
 }
}

Стандартные свойства освещения

Чтобы расширить шейдер, мы теперь можем использовать свойства материала, которые нам предоставляет Unity. В выходной структуре есть:
  • Albedo. Альбедо - основной цвет материала. Он будет тонироваться цветом светильников, которые его освещают, и темнеть в тени, что и должно быть. Цвет альбедо не повлияет на зеркальное освещение, поэтому вы можете сделать черный материал, который будет блестеть. Он хранится как трехмерный вектор цвета.
  • Normal. Это нормали поверхности. Нормали находятся в «касательном пространстве» (tangent space), а это означает, что после в Unityони будут пересчитаны на нормали в мировой системе координат. Нормали в tangent space удобны для кодировки их в карты нормалей,- мы можем копировать информацию напрямую из них в эту переменную. Нормали сохраняются как трехмерный вектор направления.
  • Emission. С этим вы можете сделать ваш материал светящимся. Если вы просто запишете значение в этот параметр, то шейдер будет выглядеть, как unlit шейдер, который мы делали ранее, но круче. На цвет излучения не влияют светильники, и поэтому вы можете делать пятна, которые всегда будут яркие. Вы можете записывать значения со значением выше 1 в канал излучения, если вы делаете визуализируете игру в HDR (вы можете установить это в настройках камеры), что позволяет сделать вещи действительно яркими и заставлять вещи расцветать больше, когда вы используете эффект постобработки цветка , цвет излучения также сохраняется как вектор 3D-цвета.
  • Metallic. Материалы выглядят по-разному, когда они металлы, и когда нет. Чтобы материалы выглядели металлическими, вы можете увеличить это значение. Это заставляет объект отражать по-другому, а значение альбедо будет оттенять отражения вместо диффузного освещения, которое вы получаете с неметаллами. Металлическое значение сохраняется как скалярное (одномерное) значение, где 0 представляет собой неметаллический материал и 1 полностью металлический.
  • Smoothness. С помощью этого значения мы можем указать, насколько гладким является материал. Материал с гладкостью 0 выглядит грубым, свет будет отражен во всех направлениях, и мы не можем видеть зеркальную подсветку или отражения окружающей среды. Материал с 1 гладкостью выглядит превосходно полированным. Если вы правильно настроили окружение, вы можете увидеть его отражение на вашем материале. Он также настолько отполирован, что вы не можете видеть зеркальные блики, потому что зеркальные блики становятся маленькими. Когда вы устанавливаете гладкость в значение чуть ниже 1, вы начинаете видеть блики от окружающих светильников. Они растут и становятся менее яркими, по мере уменьшения гладкости. Гладкость также сохраняется как скалярное значение.
  • Occlusion. Окклюзия удалит свет из вашего материала. С его помощью вы можете подделать свет, кторый не попадает в трещины модели, но вы, возможно, будете редко использовать Occlusion, за исключением случаев, когда вы идете на гиперреалистичный стиль. Окклюзия также сохраняется как скалярное значение, но контринтуитивно 1 означает, что пиксель имеет полную яркость, а 0 означает, что он находится в темноте.
  • Alpha. Альфа - это прозрачность материала. Наш текущий материал «непрозрачный», это означает, что не может быть никаких прозрачных пикселей, а альфа-значение не делает ничего. При создании прозрачного шейдера альфа будет определять, насколько мы можем видеть материал в этом пикселе, 1 полностью виден, а 0 полностью прозрачен. Альфа также сохраняется как скалярное значение.

Реализация нескольких свойств освещения

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

Сначала добавим 2 скалярных значения, гладкость и металличность. Начнем с добавления значений в виде half (тип данных, используемых в структуре вывода на выходе), в нашу глобальную область (вне функций или структур).

half _Smoothness;
half _Metallic;
Затем мы также добавляем свойства для этих данных, чтобы иметь возможность изменять их в инспекторе. Свойства не знают тип half, поэтому мы говорим им, что переменная имеет тип float. Этого достаточно, чтобы переменные отображались в инспекторе, но мы пока не используем их.

Properties {
 _Color ("Tint", Color) = (0, 0, 0, 1)
 _MainTex ("Texture", 2D) = "white" {}
 _Smoothness ("Smoothness", float) = 0
 _Metallic ("Metalness", float) = 0
}
Подобно тому, как мы назначили цветовую переменную альбедо материала, теперь мы можем назначить гладкость в Smoothness выходной структуры и металличности к Metallic выходной переменной.

void surf (Input i, inout SurfaceOutputStandard o) {
 fixed4 col = tex2D(_MainTex, i.uv_MainTex);
 col *= _Color;
 o.Albedo = col.rgb;
 o.Metallic = _Metallic;
 o.Smoothness = _Smoothness;
}
Это прекрасно работает, но легко присваивать значения, превышающие 1 или ниже 0, и получать очень неправильные результаты, и трудно понять, насколько высока величина. Чтобы исправить это, мы можем назначить значения как свойства диапазона, а не свойства float. Свойства диапазона позволяют нам определить минимум и максимум, и Unity покажет нам слайдер между ними.

Properties {
 _Color ("Tint", Color) = (0, 0, 0, 1)
 _MainTex ("Texture", 2D) = "white" {}
 _Smoothness ("Smoothness", Range(0, 1)) = 0
 _Metallic ("Metalness", Range(0, 1)) = 0
}

Затем добавим эмиссионный цвет. Сначала как переменную в коде hlsl, а затем как свойство. Мы используем тип свойства цвета, как и для оттенка. Мы сохраняем его как тип half3, потому что это цвет RGB без альфы, и он может иметь значения больше 1 (также выходная структура использует half3). Затем мы также присваиваем значение на выходе surf, как это было сделано с другими.

// ...

_Emission ("Emission", Color) = (0,0,0,1)

// ...

half3 _Emission;

// ...

o.Emission = _Emission;




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

[HDR] _Emission ("Emission", Color) = (0,0,0,1)

Незначительные улучшения

Наконец, я покажу вам две мелочи, которые сделают ваш шейдер немного лучше. Во-первых, вы можете добавить резервный (fallback) шейдер как подшейдер. Это позволяет Unity использовать функции этого другого шейдера, и нам не нужно их реализовывать самостоятельно. Для этого мы установим стандартный шейдер как fallback, и Unityвозьмет у него «теневой проход», заставив наш материал отбрасывать тени на другие объекты. Затем мы можем расширить наши прагма-директивы. Мы добавляем параметр fullforwardshadows в директиву поверхностного шейдера, тем самым получаем лучшие тени. Также мы добавляем директиву, устанавливающую цель сборки на 3.0, что означает, что Unityбудет использовать более высокие значения точности, которые должны привести к немного более красивому освещению.

Shader "Tutorial/005_surface" {
 Properties {
  _Color ("Tint", Color) = (0, 0, 0, 1)
  _MainTex ("Texture", 2D) = "white" {}
  _Smoothness ("Smoothness", Range(0, 1)) = 0
  _Metallic ("Metalness", Range(0, 1)) = 0
  [HDR] _Emission ("Emission", color) = (0,0,0)
 }
 SubShader {
  Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

  CGPROGRAM

  #pragma surface surf Standard fullforwardshadows
  #pragma target 3.0

  sampler2D _MainTex;
  fixed4 _Color;

  half _Smoothness;
  half _Metallic;
  half3 _Emission;

  struct Input {
   float2 uv_MainTex;
  };

  void surf (Input i, inout SurfaceOutputStandard o) {
   fixed4 col = tex2D(_MainTex, i.uv_MainTex);
   col *= _Color;
   o.Albedo = col.rgb;
   o.Metallic = _Metallic;
   o.Smoothness = _Smoothness;
   o.Emission = _Emission;
  }
  ENDCG
 }
 FallBack "Standard"
}
Надеюсь, я смог показать вам, как создавать шейдеры с красивым освещением с помощью простых инструментов.


Исходный код можно найти здесь: https://github.com/ronja-tutorials/ShaderTutorials/blob/master/Assets/005_Surface_Basics/simple_surface.shader
Перевод Беляев В.А ака seaman

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

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