Френель.

Введение.

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

Освещаем одну сторону модели.

Мы начнем с поверхностного шейдера, и изменим его, чтобы показать френель. Френель использует нормали объекта для определения интенсивности эффекта. Чтобы получить нормали в мировом пространстве в нашем шейдере, мы добавляем атрибут worldNormal к нашей входной структуре, а также к внутреннему макросу данных. Мы вообще не будем взаимодействовать с внутренними данными, но Unity нуждается в этом, чтобы генерировать нормали в мировом пространстве.
Вы можете генерировать нормали мира в не поверхностных шейдерах простым умножением матрицы, это объясняется в моем уроке по трипланарным картам.

//Входная структура, автоматически заполняемая Unity
struct Input {
    float2 uv_MainTex;
    float3 worldNormal;
    INTERNAL_DATA
};
Чтобы получить градиент от этого, возьмем dot продукт с другим нормализованным вектором. Когда вы берете dot продукт двух нормализованных векторов, вы получаете значение, представляющее, насколько они выровнены. Если они указывают в одном направлении, dot продукт возвращает 1, если они ортогональны, он возвращает 0, и если векторы противоположны, он возвращает -1.
Сначала мы просто получим dot продукт нормали и статического векторов, чтобы лучше рассмотреть, как это работает. Затем мы записываем результат в эмиссионный канал, поэтому мы можем видеть его хорошо.

//поверхностная функция шейдера, которая устанавливает параметры функции освещения
void surf (Input i, inout SurfaceOutputStandard o) {
    
    //...

    //берем dot продукт между нормалью и направлением
    float fresnel = dot(i.worldNormal, float3(0, 1, 0));
    //применяем значение френеля к каналу излучения
    o.Emission = _Emission + fresnel;
}
Теперь мы можем видеть, что поверхность становится ярче, где сверху, и темнее снизу. Чтобы предотвратить странные результаты с отрицательной эмиссией, мы можем ограничить значение френеля между 0 и 1, прежде чем использовать его. Для этого мы будем использовать метод насыщения (saturate). Он делает то же самое, что и ограничение (clamp) от 0 до 1, но быстрее на некоторых графических процессорах. С этим изменением мы увидим, что френель влияет только на верх наших объектов.

// поверхностная функция шейдера, которая устанавливает параметры функции освещения
void surf (Input i, inout SurfaceOutputStandard o) {
    
    //...

    // берем dot продукт между нормалью и направлением
    float fresnel = dot(i.worldNormal, float3(0, 1, 0));
    //ограничиваем значение меджу 0 и 1, чтобы не получать темные артефакты снизу
    fresnel = saturate(fresnel);
    // применяем значение френеля к каналу излучения
    o.Emission = _Emission + fresnel;
}

Подсветка внешних частей.

Следующий шаг - сделать эффект относительно нашего направления просмотра вместо фиксированного направления. В поверхностных шейдерах мы получаем направление просмотра, просто добавляя его к нашей структуре ввода.
Если вы делаете unlit френелевский шейдер, вы можете получить направление просмотра, вычитая положение камеры из положения мира вашей вершины (я объяснил, как получить позицию вершины в мироваом пространстве в моем уроке по планарному маппингу, и вы можете получить положение камеры из встроенной переменной _WorldSpaceCameraPos (вы можете просто использовать ее в своем коде без добавления чего-либо еще)).

// Входная структура, автоматически заполняемая Unity
struct Input {
    float2 uv_MainTex;
    float3 worldNormal;
    float3 viewDir;
    INTERNAL_DATA
};

// поверхностная функция шейдера, которая устанавливает параметры функции освещения 
void surf (Input i, inout SurfaceOutputStandard o) {
    
    //...

    // просто примените значения для металличности и гладкости
    o.Metallic = _Metallic;
    o.Smoothness = _Smoothness;

    // берем dot продукт между нормалью и направлением обзора
    float fresnel = dot(i.worldNormal, i.viewDir);
    // ограничиваем значение меджу 0 и 1, чтобы не получать темные артефакты снизу
    fresnel = saturate(fresnel);
    // применяем значение френеля к каналу излучения
    o.Emission = _Emission + fresnel;
}
Это уже работает очень хорошо, но вместо внешней части модели он освещает ее центр. Чтобы инвертировать это, мы просто вычитаем из 1, поэтому яркие области в этой версии больше не влияют на цвет, а незатронутые части подсвечиваются.

// поверхностная функция шейдера, которая устанавливает параметры функции освещения
void surf (Input i, inout SurfaceOutputStandard o) {
    
    //...

    // берем dot продукт между нормалью и направлением обзора
    float fresnel = dot(i.worldNormal, i.viewDir);
    //инвертируем френель, чтобы большие значения были на внешней части
    fresnel = saturate(1 - fresnel);
    // применяем значение френеля к каналу излучения
    o.Emission = _Emission + fresnel;
}

Добавляем цвет френеля и интенсивность.

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

//...

_FresnelColor ("Fresnel Color", Color) = (1,1,1,1)

//...

float3 _FresnelColor;

//...

// поверхностная функция шейдера, которая устанавливает параметры функции освещения
void surf (Input i, inout SurfaceOutputStandard o) {
    
    //...

    // берем dot продукт между нормалью и направлением обзора
    float fresnel = dot(i.worldNormal, i.viewDir);
    // инвертируем френель, чтобы большие значения были на внешней части
    fresnel = saturate(1 - fresnel);
    //Объединяем значение френеля и цвет
    float3 fresnelColor = fresnel * _FresnelColor;
    // применяем значение френеля к каналу излучения
    o.Emission = _Emission + fresnelColor;
}

//...
Затем мы добавим возможность сделать эффект френеля сильнее или слабее, добавив к нему экспоненту. Мы также добавляем атрибут powerslider к свойству экспоненты. Таким образом, значения, близкие к 0, занимают больше места на ползунке и могут быть скорректированы более точно (в этом примере часть слайдера от 0,25 до 1 почти такая же, как и часть от 1 до 4).
Экспоненты довольно дороги в расчетах, поэтому, если вы найдете способ настроить свой френель, который вам подходит точно так же, вероятно, лучше переключиться на это, но все же экспоненты легко и приятно использовать.

//...

[PowerSlider(4)] _FresnelExponent ("Fresnel Exponent", Range(0.25, 4)) = 1

//...

float _FresnelExponent;

//...

// поверхностная функция шейдера, которая устанавливает параметры функции освещения
void surf (Input i, inout SurfaceOutputStandard o) {
    
    //...

    // берем dot продукт между нормалью и направлением обзора
    float fresnel = dot(i.worldNormal, i.viewDir);
    // инвертируем френель, чтобы большие значения были на внешней части
    fresnel = saturate(1 - fresnel);
    // Объединяем значение френеля и цвет
    float3 fresnelColor = fresnel * _FresnelColor;
    //возводим значение френеля в экспоненту, чтобы можно было настроить интенсивность
    fresnel = pow(fresnel, _FresnelExponent);
    // применяем значение френеля к каналу излучения
    o.Emission = _Emission + fresnelColor;
}

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

Shader "Tutorial/012_Fresnel" {
    //показываем значения для редактирования в инспекторе
    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)

        _FresnelColor ("Fresnel Color", Color) = (1,1,1,1)
        [PowerSlider(4)] _FresnelExponent ("Fresnel Exponent", Range(0.25, 4)) = 1
    }
    SubShader {
        // материал полностью не прозрачный и визуализируется с остальными непрозрачными
        Tags{ "RenderType"="Opaque" "Queue"="Geometry"}

        CGPROGRAM

        //шейдер – поверхностный, что значит, что он будет в фоне расширен Unity,
        // чтобы получить хорошее освещение и другие фичи
        //наша поверхностная функция шейдера называется surf и мы используем стандартную
        // модель освещения, что означает PBR освещение
        //fullforwardshadows гарантирует, что Unity добавит тени, если нужно
        #pragma surface surf Standard fullforwardshadows
        #pragma target 3.0

        sampler2D _MainTex;
        fixed4 _Color;

        half _Smoothness;
        half _Metallic;
        half3 _Emission;

        float3 _FresnelColor;
        float _FresnelExponent;

        // Входная структура, автоматически заполняемая Unity
        struct Input {
            float2 uv_MainTex;
            float3 worldNormal;
            float3 viewDir;
            INTERNAL_DATA
        };

        // поверхностная функция шейдера, которая устанавливает параметры функции освещения
        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;

            // берем dot продукт между нормалью и направлением обзора
            float fresnel = dot(i.worldNormal, i.viewDir);
            // инвертируем френель, чтобы большие значения были на внешней части
            fresnel = saturate(1 - fresnel);
            // возводим фзначение френенля в экспоненту, чтобы можно было настроить интенсивность
            fresnel = pow(fresnel, _FresnelExponent);
            // Объединяем значение френеля и цвет
            float3 fresnelColor = fresnel * _FresnelColor;
            // применяем значение френеля к каналу излучения
            o.Emission = _Emission + fresnelColor;
        }
        ENDCG
    }
    FallBack "Standard"
}

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


Здесь вы также можете найти исходный код для этого шейдера: https://github.com/ronja-tutorials/ShaderTutorials/blob/master/Assets/012_Fresnel/Fresnel.shader

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

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

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