【Unity Shader】 Blinn-phong光照模型

Unity Shader Blinn-phong光照模型

一、镜面反射简介

对于许多物体,漫反射不够真实,比如擦亮的金属、光滑的塑料。要模拟光滑表面,还需要添加镜面高光反射的颜色,。在金属这样的光滑表现上,能模拟出光泽

1. 理想镜面反射

一束平行光射到平面镜上,入射光严格遵循光的反射定律平行的反射出去,这种反射叫做镜面反射。
这里写图片描述

2. 非理想镜面反射

现实中的物体表面都不是完全光滑的。在微观上物体的表面面元是由许多朝向的微小平面组成,其镜面反射光分布于物体镜面反射方向的周围。
这里写图片描述

3. 镜面反射光特点

  • 跟入射方向有关
  • 跟观察的角度有关

黑暗的房间中,手电筒的光射向一面镜子,只有眼睛与光线平行的时候才能看到光,改变观察的角度看到不到光。

二、光照模型

1. Phong光照模型

Phong光照模型属于经验模型,它在Lambert模型(纯漫反射模型)的基础上考虑了镜面反射的效果,该模型只考虑物体对方向光的镜面反射作用,不考虑环境光的镜面反射(认为环境光只发生漫反射),主要用来模拟光滑物体表面的光照现象。

此模型假设物体表面为非理想镜面反射体(既会发生漫反射也会发生镜面发射,且镜面反射为非理想的镜面反射),场景中存在两种光,一种为环境光,一种为方向光,然后我们分别计算这两种光照射到物体表面所产生的光照现象(注意:phong不考虑环境光的镜面反射,也就是说我们要考虑到环境光的漫反射和方向光的漫反射和镜面反射),最后再将两个结果相加,得出反射后的光强值。

首先是计算环境光的公式:

I_inDirectionDiffuse = K_d * I_a;

其中,K_d为粗糙物体表面材质对光的反射系数,这个系数由程序编写者在宿主程序中给出,I_a为环境光的光强,也就是环境光的颜色数值,一般是一个float3型的变量

然后是计算方向光的公式:

I_direction = I_directionDiffuse + I_directionSpecular;
I_directionDiffuse = K_d * I_l * max(0dot(N, L)); //方向光漫反射强度
I_directionSpecular = K_d * I_l * pow( max(0, dot(R,V)), n); //方向光镜面射强度

其中I_l为方向光的光强,也就是其颜色值;N为顶点的单位法向量;L为入射光的单位法向量(注意,光照向量是从顶点指向光源的向量;也就是,它与线的传播方向正好相反)。R表示反射光的单位向量,V为顶点到视点的向量。

我们知道,在理想状况下,镜面反射后的光之集中在一条线上,因此我们的视线离这条线的距离越近,射入我们眼中的光线就越多,我们看到的光强也就越强。同时,镜面反射也与物体表面的高光指数(物体表面光泽程度)有关,其数值越大,反射后的光线越集中,反之则越分散。方向光的镜面反射光照强度遵循Phong余弦定理。

综上,得出漫反射和镜面反射叠加后的光强为:

I_diff = K_d * I_a + K_d * I_l * max(0dot(N, L)) + K_d * I_l * pow( max(0, dot(R,V)), n);

反射方向的计算

这里写图片描述
L为入射光(顶点到光源)的单位法向量,N为顶点的单位法向量,R为反射光的单位法向量,V是观察方向。

推导过程:
L在N方向上的投影是|Lcosθ|=cosθ,那么投影矢量N’=Ncosθ
L+S = N’ = Ncosθ
R = L+2S = L+2(Ncosθ-L) = 2Ncosθ-L

结果:

R=2(N•L)N-L

Phong余弦定理

反射光的明亮度直接取决于反射光向量(R)和视角向量(V)两个向量将夹角的余弦值。
这里写图片描述

f(θ) = max(cosθ,0) = max(RV,0)

对材质表面粗糙程度对反射光强的影响

非理想镜面的表面并非完全光滑,也就是说会发生散射,其镜面反射光分布于物体镜面反射方向的周围,物体表面粗糙程度越大,散射越大。

理想镜面的反射光照强度为:R dot V

非理想镜面的反射光照强度为:(R dot V)^n
n为镜面放射指数,也称高光指数,反映物体表面的粗糙程度。n越大高光亮度随α的增大衰减的越快。

n的取值于表面粗糙程度有关,
- n越大,表面越光滑(散射现象小,稍一偏离,明暗亮度急剧下降)
- n越小,表面越毛躁(散射现象严重)
这里写图片描述

2. Blinn-phong光照模型

相比较Phong模型,Blinn-phong模型只适用N•H替换了V•R,但却获得了明显的提高,它能提供比Phong更柔和、更平滑的高光,而且由于Blinn-phong的光照模型省去了计算反射光线方向向量的两个乘法运算速度上也更快,因此成为很多CG软件中默认的光照渲染方法,同时也被集成到大多数的图形芯片中,在OpenGL和Direct3D渲染管线中,Blinn-Phong就是默认的渲染模型。

Blinn-phong光照模型中,用N•H的值取代了V•R。Blinn-phong光照模型的光强因子为:
(N•H)n
即H 越靠近N,光照越强。
由于这两个光照模型公式基本相同,所以只解释一下N•H:
N与前面相同,是顶点的单位法向量,而H则是入射光L和顶点到视点的单位向量的角平分线单位向量,通常也成为半角向量。其计算方法为:
H = (L + V) / (|L + V|)

Blinn-phong余弦定理

反射光的明亮度直接取决于入射光和视角向量的半角(H)和法线向量(N)两个向量将夹角的余弦值。
这里写图片描述

f(θ) = max(cosθ,0) = max(HN,0)

三、自定义UnityShader实现Blinn-phong光照模型

写法

Shader "Hidden/NewImageEffectShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DiffusePower("Diffuse Power", Float) = 1.0
        _SpecularTex("Specular Tex", 2D) = "white"{}
        _Gloss("Specular Gloss", Float) = 0.5
        _SpecularColor("Specular Color", Color) = (1,1,1,1)

    }
    SubShader
    {

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            #include "AutoLight.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 normalDir : TEXCOORD1;
                float4 posWorld : TEXCOORD2;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;

                // 将物体法线从物体坐标系转换到世界坐标系
                o.normalDir = UnityObjectToWorldNormal(v.normal);

                o.posWorld = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            sampler2D _MainTex;
            float _DiffusePower;
            sampler2D _SpecularTex;
            float _Gloss;
            float4 _SpecularColor;

            fixed4 frag (v2f i) : SV_Target
            {
                // 法线方向
                float3 normalDirection = normalize(i.normalDir);
                // 灯光方向
                float lightDirection = normalize(_WorldSpaceLightPos0.xyz);
                // 灯光颜色
                float3 lightColor = _LightColor0.rgb;
                // 视线方向
                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
                // 视线方先与法线方向的中间向量
                float3 halfDirection = normalize(viewDirection+lightDirection);

                // 计算灯光衰减
                float attenuation = LIGHT_ATTENUATION(i);
                float3 attenColor = attenuation * _LightColor0.xyz;

                // 基于兰伯特模型计算漫反射灯光
                float NdotL = max(0,dot(normalDirection,lightDirection));
                // 方向光
                float3 directionDiffuse = pow(NdotL, _DiffusePower) * attenColor;
                // 环境光  
                float3 inDirectionDiffuse = float3(0,0,0)+UNITY_LIGHTMODEL_AMBIENT.rgb;


                // 基于Blinn Phong计算镜面反射灯光
                float specPow = exp2( _Gloss * 10.0 + 1.0 );
                float3 directionSpecular = attenColor*  _SpecularColor*(pow(max(0,dot(halfDirection,normalDirection)),specPow));

                // 灯光与材质球表面颜色进行作用
                float3 texColor = tex2D(_MainTex, i.uv).rgb;
                float3 diffuseColor = texColor *(directionDiffuse+inDirectionDiffuse);
                float3 specularColor = directionSpecular;
                float4 finalColor = float4(diffuseColor+specularColor,1);

                return finalColor;
            }
            ENDCG
        }
    }
}

效果展示

这里写图片描述

参考

Phong光照模型、Blinn-phong光照模型归纳
光照模型PPT
光照模型
PBR Step by Step( 五)Phong反射模型

猜你喜欢

转载自blog.csdn.net/v_xchen_v/article/details/79433399