前言💬

在计算机图形学中,光照模型是非常重要的一部分。

为了模拟现实中的光照效果,我们需要理解一些基本的光学原理。

本文将介绍反射向量的概念以及如何在Unity引擎中使用Cg reflect函数实现反射效果

_specular_highlight

一、什么是反射向量

在图形学中,反射向量是一个描述光线如何反射的重要概念。当光线照射到一个表面上,它会被反射到不同的方向。反射向量的计算是基于光线的入射角和法线向量的。通过计算反射向量,我们可以模拟出光线在表面上的反射效果,从而制作出逼真的光照效果。

SpecularLighting

二、Cg reflect函数

Cg中的reflect函数可以帮助我们快速计算反射向量

在Unity中改函数被定义在

函数原型如下:

float3 reflect(float3 incidentVector, float3 normalVector);

其中,incidentVector 是入射光线的方向,normalVector 是表面的法线向量。

函数返回一个反射光线的方向向量。

原理

CG当中reflect函数的实现

// i为入射光线,n为法线  
float3 reflect( float3 i, float3 n )  
{  
  return i - 2.0 \* n \* dot(n,i);  
}

推导过程

反射模型

求反射向量OB

$$ \vec{OB}=\vec{AB}-\vec{AO} $$

$$ \vec{OB}=2\vec{AP}-\vec{AO}=2(\vec{AO}+\vec{OP})-\vec{AO}=\vec{AO}+2\vec{OP} $$

所以此时重点在于求向量OP

那么如何求向量OP

我们据下图举例模型进行推导

举例模型

根据点积的性质可知:

$$ \vec{OA}\cdot\cosθ=\vec{OA^{’}}
$$

$$ \vec{OA}\cdot\vec{n}=\vec{OA}\cdot\vec{n}\cdot\cosθ
$$

所以就有

$$ \vec{OA^{’}}=\vec{OA}\cdot\cosθ\cdot\vec{n}
$$

$$ \vec{OA^{’}}=\vec{OA}\cdot\frac{\vec{OA}\cdot\vec{n}}{\vec{OA}*\vec{n}}\cdot\vec{n} $$

$$ 设\vec{n}=1,就有\vec{OA^{’}}=(\vec{OA}\cdot\vec{n})\cdot\vec{n} $$

所以根据此推导的原理可知

$$ \vec{OP}=-(\vec{AO}\cdot\vec{n})\cdot\vec{n} $$

即:

$$ \vec{OB}=\vec{AO}-2\vec{n}\cdot(\vec{AO}\cdot\vec{n})
$$

所以Cg当中的reflect函数中的实现

i - 2.0 * n * dot(n,i)

就是这样推导来的

更详细的推导过程可以看下图

reflect推导过程

三、实现Specular高光

首先我们需要计算出反射光的向量,利用reflect函数

float3 N=normalize(mul(unity_WorldToObject,v.normal));
float3 L=normalize(_WorldSpaceLightPos0);
// L也可以替换为WorldSpaceLightDir(v.vertex)
float3 R= reflect(-L,N);//由于L是朝向光源方向的向量取反-L就示入射光的向量了

计算出反射光向量后,用反射光向量和朝向摄像机视角向量进行点积,并且将值映射在0~1之间

float3 V= normalize(WorldSpaceViewDir(v.vertex));
float specularL= saturate(dot(V,R));

为了实现指数级的衰减效果,可以使用pow函数,根据_Shininess调整衰减效果,_Shininess可以在Properties中定义暴露的参数

specularL=pow(specularL,_Shininess);

最后将灯光颜色乘上Specular的亮度,附加到color上,即可

o.color+=_LightColor0*specularL;

四、完整代码


Shader "ShaderLearning/Diffuse/SpecularDiffuse"  
{  
    Properties  
    {  
        \_Shininess("Shininess",Range(1,64))=1  
    }  
    SubShader  
    {  
        Tags { "LightMode"="ForwardBase" }  
        Pass  
        {  
            CGPROGRAM  
            #pragma vertex vert  
            #pragma fragment frag  
 
            #include "UnityCG.cginc"  
            #include "Lighting.cginc"  

            struct v2f  
            {  
               float4 vertex: POSITION;  
               fixed4 color: COLOR;  
            };  

            float \_Shininess;  

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

                float3 N=normalize(mul(unity\_WorldToObject,v.normal));  
                float3 L=normalize(\_WorldSpaceLightPos0);  
                float dotL=saturate(dot(N,L));  
                o.color=\_LightColor0\*dotL;  

                // L也可以替换为WorldSpaceLightDir(v.vertex)  
                float3 R= reflect(-L,N);  

                float3 V= normalize(WorldSpaceViewDir(v.vertex));  

                float specularL= saturate(dot(V,R));  

                specularL=pow(specularL,\_Shininess);  

                o.color+=\_LightColor0\*specularL;  

                return o;  
            }  

            fixed4 frag (v2f i) : COLOR  
            {  

                return i.color+UNITY\_LIGHTMODEL\_AMBIENT;  
            }  
            ENDCG  
        }  
    }  
}  

效果如下图所示

实现了一个简单的Specular的效果

要注意的是,在使用reflect的函数的要注意,入射光和法线要在同一个坐标系空间

最终效果