Будем разбираться в шейдерах

Всем привет! Благодарен всем за замечания и комментарии к предыдущим статьям. Благодаря всем нам мы наполняем интернет доступными знаниями и это действительно круто.

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

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

Теория

Давайте разберёмся с тем, как работает освещение по Фонгу. В первую очередь необходимо сказать, что в Unity модель освещения по Фонгу достигается комбинацией трёх компонентов - фонового освещения (ambient), диффузного освещения (diffuse) и глянцевого освещения (specular). Как правило, такие шейдера называются ADS - Ambient Diffuse Specular.

Итак, основное отличие от диффузного шейдера - наличие глянцевого освещения или же глянцевого отблеска. Глянцевые блики необходимы для создания реалистичного отображения некоторых объектов. Если же для диффузного шейдера нам было важно направления источника света, то для спекуляр шейдера нам важно будет направление камеры. Комбинируя два этих вектора мы сможем достичь красивой реалистичной картинки.

Комбинация компонентов освещения по Фонгу
Комбинация компонентов освещения по Фонгу

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

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

Типы отраженного света
Типы отраженного света

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

Для расчёта модели освещения Фонга необходимо произвести расчёты интенсивности трёх составляющих компонент освещения - фонового освещения (ambient), диффузного освещения (diffuse) и глянцевого освещения (specular).

Вычисления производятся по следующей формуле:

PhongLight = AmbientLight + DiffuseLight + SpecularLight;

Из прошлых частей мы знаем, что:

  • AmbientLight = rc * ia;

    • rc(reflection coefficient) - коэффициент отражения материала;

    • ia(intensity ambient) - интенсивность окружающего света.

  • DiffuseLight = rc * id * max(0,dot(N, L));

    • rc(reflection coefficient) - коэффициент отражения материала;

    • id(intensity direct) - интенсивность направленного света;

    • N - единичный вектор нормали к вершине;

    • L - единичный вектор нормали падающего света;

    • dot - скалярное произведение векторов.

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

SpecularLight = RdotV^SpecularIntensity * LightColor * NdotL * SpecularColor;

  • RdotV - скалярное произведение вектора отражения и направления камеры.

  • NdotL - скалярное произведение вектора нормали и направления освещения.

  • SpecularIntensity - интенсивность блика.

  • LightColor - цвет освещения.

  • SpecularColor - цвет блика.

Практика

Что ж, разобравшись с бликами, приступим к изменению шейдера.

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

Полный код шейдера
Shader "Chernov/Phong"
    {
    Properties
    {
        _MainTex ("Main Texture", 2D) = "white" {}
 
        [Header(Ambient)]
        _Ambient ("Intensity", Range(0., 1.)) = 0.1
        _AmbColor ("Color", color) = (1., 1., 1., 1.)
 
        [Header(Diffuse)]
        _Diffuse ("Val", Range(0., 1.)) = 1.
        _DifColor ("Color", color) = (1., 1., 1., 1.)

        [Header(Specular)]
        _SpecIntensity ("Shininess", Range(0.1, 10)) = 1.
        _SpecColor ("Specular color", color) = (1., 1., 1., 1.)
     }
 
    SubShader
    {
        Pass
        {
            Tags { "RenderType"="Transparent" "Queue"="Geometry" "LightMode"="ForwardBase" }
 
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
  
            #include "UnityCG.cginc"
 
            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                fixed4 light : COLOR0;
            };
 
            fixed4 _LightColor0;
            fixed _Diffuse;
            fixed4 _DifColor;
            fixed _Ambient;
            fixed4 _AmbColor;
            fixed _SpecIntensity;
            fixed4 _SpecColor;
 
            v2f vert(appdata_base v)
            {
                v2f o;
 
                // Clip position
                o.pos = UnityObjectToClipPos(v.vertex);
 
                // Light direction
                float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
 
                // Normal in WorldSpace
                float3 worldNormal = UnityObjectToWorldNormal(v.normal.xyz);
 
                 // World position
                float4 worldPos = mul(unity_ObjectToWorld, v.vertex);

                // Camera direction
                float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos.xyz));
 
                // Compute ambient lighting
                fixed4 amb = _Ambient * _AmbColor;
 
                // Compute the diffuse lighting
                fixed4 lightTemp = max(0., dot(worldNormal, lightDir) * _LightColor0);
                fixed4 diffuse = lightTemp * _Diffuse * _LightColor0 * _DifColor;
 
                 // Compute the specular lighting
                float3 refl = reflect(-lightDir, worldNormal);
                float RdotV = max(0., dot(refl, viewDir));
                fixed4 spec = pow(RdotV, _SpecIntensity) * _LightColor0 * ceil(lightTemp) * _SpecColor;
 
                o.light = diffuse + amb + spec;
               
                o.uv = v.texcoord;
                return o;
            }
 
            sampler2D _MainTex;

            fixed4 frag(v2f i) : SV_Target
            {
                fixed4 c = tex2D(_MainTex, i.uv);
                c.rgb *= i.light;
                return c;
            }
 
            ENDCG
        }
    }
}

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

Для добавления бликов добавим две новые переменные. Первая - будет отвечать за интенсивность блика, вторая - за цвет.

  • _SpecIntensity ("Shininess", Range(0.1, 10)) = 1. - интенсивность блика (в диапазоне 0.1 - 10);

  • _SpecColor ("Specular color", color) = (1., 1., 1., 1.) - цвет блика (по умолчанию - белый);

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

fixed _SpecIntensity;
fixed4 _SpecColor;

Теперь добавим вычисление бликов в вершинный шейдер. Изменим функцию следующим образом:

// Compute the specular lighting
float3 refl = reflect(-lightDir, worldNormal);
float RdotV = max(0., dot(refl, viewDir));
fixed4 spec = pow(RdotV, _SpecIntensity) * _LightColor0 * ceil(lightTemp) * _SpecColor;
 
o.light = diffuse + amb + spec;

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

Разберём детально что мы добавили: float3 refl = reflect(-lightDir, worldNormal); - вычисляем вектор отражённого света. reflect() - встроенная HLSL функция, возвращает вектор отражения.

float RdotV = max(0., dot(refl, viewDir)); - скалярное произведение векторов отражения и направления камеры. Функция max() возвращает большее из значений.

fixed4 spec = pow(RdotV, _SpecIntensity) * _LightColor0 * ceil(lightTemp) * _SpecColor; - вычисление блика по формуле. ceil(lightTemp) - необходимо для округлениф скалярного произведение вектора нормали и направления освещения.

o.light = diffuse + amb + spec; - сложение трёх компонент освещения.

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

Два шара: на одном диффузный шейдер из предыдущей главы, на втором обновлённый с бликами.
Два шара: на одном диффузный шейдер из предыдущей главы, на втором обновлённый с бликами.
Редактирование свойст материала - интенсивности и цвета блика
Редактирование свойст материала - интенсивности и цвета блика

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

Алексей Чернов

Team Lead at Program-Ace

Комментарии (0)