Интерполяция цвета.

Введение.

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

Интерполяция цвета.

Первая версия этого шейдера, который мы сегодня сделаем, будет просто интерполировать между двумя равными цветами на основе значения. Из-за этого нам не нужны переменные, связанные с координатами uv или текстурами, вместо этого мы добавляем вторую цветовую переменную и простое значение, которое будет определять, показывает ли материал первый из второго цвета. Мы определяем свойство смешивания как «Range (диапазон)», поэтому мы получаем красивый ползунок в инспекторе.

//...

//показываем значения для редактирования в инспекторе
 Properties{
  _Color ("Color", Color) = (0, 0, 0, 1) //основной цвет
                // второй цвет для смешивания
                _SecondaryColor ("Secondary Color", Color) = (1,1,1,1)
                //0 - первый цвет, 1 - второй цвет
                _Blend ("Blend Value", Range(0,1)) = 0

//...

//значение, которое используется для смешивания цветов
float _Blend;

//цвета для смешивания
fixed4 _Color;
fixed4 _SecondaryColor;
 
Кроме удаления строк, связанных с UV координатами, мы можем сохранить вершинный шейдер как есть. Вместо этого мы редактируем шейдер фрагмента. В качестве первой версии мы можем просто добавить второй цвет в первый, основываясь на значении смешивания.

//фрагментный шейдер
fixed4 frag(v2f i) : SV_TARGET{
    fixed4 col = _Color + _SecondaryColor * _Blend;
    return col;
}
Мы уже видим, что цвет меняется, но он не меняется на вторичный цвет. Это связано с тем, что в то время как вторичный цвет получает значение, основной цвет все еще существует (похоже на существование двух огней разных цветов в одном месте).
Чтобы исправить это, мы можем уменьшить эффект основного цвета при увеличении значения смешивания. При значении смешивания 0 мы не видим вторичного цвета - только основной, а при значении смешивания 1 мы хотим видеть только вторичный цвет и никакого основного цвета. Чтобы сделать это, мы умножаем основной цвет на один минус значение смешивания.

//фрагментный шейдер
fixed4 frag(v2f i) : SV_TARGET{
    fixed4 col = _Color * (1 - _Blend) + _SecondaryColor * _Blend;
    return col;
}

Этот процесс также называется линейной интерполяцией, а функция встроена в hlsl, которая делает это для нас под названием lerp. Он принимает начальное значение для интерполяции, конечное значение интерполяции и коэффициент интерполяции.

//фрагментный шейдер
fixed4 frag(v2f i) : SV_TARGET{
    fixed4 col = lerp(_Color, _SecondaryColor, _Blend);
    return col;
}
Полный шейдер для интерполяции между двумя цветами выглядит следующим образом:

Shader "Tutorial/009_Color_Blending/Plain"{
        //показываем значения для редактирования в инспекторе
 Properties{
  _Color ("Color", Color) = (0, 0, 0, 1) //основной цвет
                // второй цвет для смешивания
                _SecondaryColor ("Secondary Color", Color) = (1,1,1,1)
                //0 - первый цвет, 1 - второй цвет
                _Blend ("Blend Value", Range(0,1)) = 0
 }

 SubShader{
  //материал полностью непрозрачный и визуализируется одновременно с непрозрачной геометрией
  Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

  Pass{
   CGPROGRAM

   //включаем полезные функции
   #include "UnityCG.cginc"

   //определяем вершинный и фрагментный шейдеры
   #pragma vertex vert
   #pragma fragment frag

   //значение, которое используется для смешивания цветов
   float _Blend;

   //the colors to blend between
   fixed4 _Color;
   fixed4 _SecondaryColor;

   //данные, которые передаются из Unity в вершинный шейдер
   struct appdata{
    float4 vertex : POSITION;
   };

   //данные, которые передаются из вершинного во фрагментный шейдер
   struct v2f{
    float4 position : SV_POSITION;
   };

   //вершинный шейдер
   v2f vert(appdata v){
    v2f o;
    //Конвертируем позицию вершины из пространства объекта в пространство экрана
    o.position = UnityObjectToClipPos(v.vertex);
    return o;
   }

   //фрагментный шейдер
   fixed4 frag(v2f i) : SV_TARGET{
    fixed4 col = lerp(_Color, _SecondaryColor, _Blend);
    return col;
   }

   ENDCG
  }
 }
}

Интерполяция текстур.

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

//...

//показываем значения для редактирования в инспекторе
Properties{
    _MainTex ("Texture", 2D) = "white" {} //основная текстура
    _SecondaryTex ("Secondary Texture", 2D) = "black" {} //вторая текстура для смешивания
    _Blend ("Blend Value", Range(0,1)) = 0 //0 цвет первой текстуры, 1 второй
}

//...

//данные, которые передаются из Unity в вершинный шейдер
struct appdata{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
};

//данные, которые передаются из вершинного во фрагментный шейдер
struct v2f{
    float4 position : SV_POSITION;
    float2 uv : TEXCOORD0;
};

//вершинный шейдер
v2f vert(appdata v){
    v2f o;
    //Конвертируем позицию вершины из пространства объекта в пространство экрана
    o.position = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    return o;
}

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

//фрагментный шейдер
fixed4 frag(v2f i) : SV_TARGET{
    //рассчитываем UV координаты, включая тайлинг и смещение
    float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
    float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);

    //читаем цвета из текстур
    fixed4 main_color = tex2D(_MainTex, main_uv);
    fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);

    //интерполируем между цветами
    fixed4 col = lerp(main_color, secondary_color, _Blend);
    return col;
}
Полный шейдер для интерполяции между двумя текстурами выглядит так:

Shader "Tutorial/009_Color_Blending/Texture"{
 //показываем значения для редактирования в инспекторе
 Properties{
  _MainTex ("Texture", 2D) = "white" {} //основная текстура
  _SecondaryTex ("Secondary Texture", 2D) = "black" {} //вторая текстура для смешивания
  _Blend ("Blend Value", Range(0,1)) = 0 //0 цвет первой текстуры, 1 второй
 }

 SubShader{
  //материал полностью непрозрачный и визуализируется одновременно с непрозрачной геометрией
  Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

  Pass{
   CGPROGRAM

   //включаем полезные функции
   #include "UnityCG.cginc"

   //определяем вершинный и фрагментный шейдеры
   #pragma vertex vert
   #pragma fragment frag

   //значение, которое используется для смешивания цветов
   float _Blend;

   //текстуры для смешивания
   sampler2D _MainTex;
   float4 _MainTex_ST;

   sampler2D _SecondaryTex;
   float4 _SecondaryTex_ST;

   //данные, которые передаются из Unity в вершинный шейдер
   struct appdata{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
   };

   //данные, которые передаются из вершинного во фрагментный шейдер
   struct v2f{
    float4 position : SV_POSITION;
    float2 uv : TEXCOORD0;
   };

   //вершинный шейдер
   v2f vert(appdata v){
    v2f o;
    //Конвертируем позицию вершины из пространства объекта в пространство экрана
    o.position = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    return o;
   }

   //фрагментный шейдер
   fixed4 frag(v2f i) : SV_TARGET{
    //рассчитываем UV координаты, включая тайлинг и смещение
    float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
    float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);

    //читаем цвета из текстур
    fixed4 main_color = tex2D(_MainTex, main_uv);
    fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);

    //интерполируем между цветами
    fixed4 col = lerp(main_color, secondary_color, _Blend);
    return col;
   }

   ENDCG
  }
 }
}

Интерполяция, основанная на текстуре.

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

//...

//показываем значения для редактирования в инспекторе
Properties{
    _MainTex ("Texture", 2D) = "white" {} //основная текстура
    _SecondaryTex ("Secondary Texture", 2D) = "black" {} //вторая текстура для смешивания
    _BlendTex ("Blend Texture", 2D) = "grey" //черный цвет - первая текстура, белый - вторая
}

//...

//текстура, которая используется для смешивания как параметр
sampler2D _BlendTex;
float4 _BlendTex_ST;

//текстуры для смешивания
sampler2D _MainTex;
float4 _MainTex_ST;

sampler2D _SecondaryTex;
float4 _SecondaryTex_ST;

//...
Затем мы также генерируем преобразованные uv-координаты для этой текстуры. С ними мы читаем значение цвета из текстуры. Теперь у нас есть полный цвет с красными, зелеными, синими и альфа-компонентами, но нам нужно простое скалярное значение 0-1. Чтобы преобразовать цвет в простой float, мы предполагаем, что текстура имеет оттенки серого и просто берем красное значение. Затем мы используем это значение для интерполяции между двумя другими текстурами, как это было раньше.

//фрагментный шейдер
fixed4 frag(v2f i) : SV_TARGET{
    //рассчитываем UV координаты, включая тайлинг и смещение
    float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
    float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
    float2 blend_uv = TRANSFORM_TEX(i.uv, _BlendTex);

    //читаем цвета из текстур
    fixed4 main_color = tex2D(_MainTex, main_uv);
    fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
    fixed4 blend_color = tex2D(_BlendTex, blend_uv);

    //читаем параметр смешивания из текстуры, используя красный канал
    fixed blend_value = blend_color.r;

    //интерполируем между цветами
    fixed4 col = lerp(main_color, secondary_color, blend_value);
    return col;
}
Полный шейдер для интерполяции на основе текстуры выглядит следующим образом:

Shader "Tutorial/009_Color_Blending/TextureBasedBlending"{
 //показываем значения для редактирования в инспекторе
 Properties{
  _MainTex ("Texture", 2D) = "white" {} //основная текстура
  _SecondaryTex ("Secondary Texture", 2D) = "black" {} //вторая текстура для смешивания
  _BlendTex ("Blend Texture", 2D) = "grey" //черный цвет - первая текстура, белый - вторая
 }

 SubShader{
  //материал полностью непрозрачный и визуализируется одновременно с непрозрачной геометрией
  Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

  Pass{
   CGPROGRAM

   //включаем полезные функции
   #include "UnityCG.cginc"

   //определяем вершинный и фрагментный шейдеры
   #pragma vertex vert
   #pragma fragment frag

   //текстура, которая используется для смешивания как параметр
   sampler2D _BlendTex;
   float4 _BlendTex_ST;

   //текстуры для смешивания
   sampler2D _MainTex;
   float4 _MainTex_ST;

   sampler2D _SecondaryTex;
   float4 _SecondaryTex_ST;

   //данные, которые передаются из Unity в вершинный шейдер
   struct appdata{
    float4 vertex : POSITION;
    float2 uv : TEXCOORD0;
   };

   //данные, которые передаются из вершинного во фрагментный шейдер
   struct v2f{
    float4 position : SV_POSITION;
    float2 uv : TEXCOORD0;
   };

   //вершинный шейдер
   v2f vert(appdata v){
    v2f o;
    //Конвертируем позицию вершины из пространства объекта в пространство экрана
    o.position = UnityObjectToClipPos(v.vertex);
    o.uv = v.uv;
    return o;
   }

   //фрагментный шейдер
   fixed4 frag(v2f i) : SV_TARGET{
    //рассчитываем UV координаты, включая тайлинг и смещение
    float2 main_uv = TRANSFORM_TEX(i.uv, _MainTex);
    float2 secondary_uv = TRANSFORM_TEX(i.uv, _SecondaryTex);
    float2 blend_uv = TRANSFORM_TEX(i.uv, _BlendTex);

    //читаем цвета из текстур
    fixed4 main_color = tex2D(_MainTex, main_uv);
    fixed4 secondary_color = tex2D(_SecondaryTex, secondary_uv);
    fixed4 blend_color = tex2D(_BlendTex, blend_uv);

    //читаем параметр смешивания из текстуры, используя красный канал
    fixed blend_value = blend_color.r;

    //интерполируем между цветами
    fixed4 col = lerp(main_color, secondary_color, blend_value);
    return col;
   }

   ENDCG
  }
 }
}
Надеюсь, этот урок поможет вам понять, как работать с цветами в шейдерах и в частности с интерполяцией их.


Здесь вы можете найти исходный код шейдеров: https://github.com/ronja-tutorials/ShaderTutorials/blob/master/Assets/009_Color_Blending

Переводчик Беляев В.А. ака seaman

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

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