开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,[点击查看活动详情]
前言
不知小伙伴们是否有过这样的场景?站在湖边向脚下看时,湖面的水是清澈的,甚至可以看清湖底的沙石,而如果向远方望去,却只能看到远处的倒影。此种光学现象称之为菲涅尔反射。本篇不会详细阐述菲涅尔方程的推导过程,而是注重其在实际开发中的应用。
菲涅尔反射
前言中的例子放在专业术语里可以这样来解释:当光由一种介质进入另一种不同的介质时,在物体表面会发生反射,另一部分进入物体内部发生折射或散射,反射的光和入射光之间存在着一种比率,而这种比率可以用菲涅尔方程来进行描述。不过由于原始的菲涅尔方程十分复杂,所以一般在实时渲染中,会使用Schlick菲涅尔近似等式来替代:
F = max(0,min(1,bias + scale X (1-v·n)^power))
其中的bias、scale、power都是控制项,可以简单理解为一个自己设置的常数项。由此等式可以知道对反射光与入射光的比率的求解,进而演化成了对视线方向v与物体表面的法向量之间夹角的余弦值的求值,即点乘结果。视线方向和物体表面的法向量在之前的文章中多次用到,所以复杂的问题变简单了。
菲涅尔单一效果
在将菲涅尔反射的效果与漫反射之类的其他颜色混合之前,先来看一下单纯使用公式来得到的效果是怎么样的。
- 1、在Project面板右键创建一个Unlit Shader,将其命名为Frenel。
- 2、选中刚刚创建的Frenel,右键创建一个Material。
- 3、在Hierarchy面板创建一个Sphere,并将第2步创建的Material拖拽赋值到Sphere上。
- 4、使用脚本IDE打开shader文件,添加如下代码。
Shader "Unlit/Fresnel"
{
Properties
{
_Power("Power",Range(0,10)) = 0.5
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Power;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 posWS : TEXCOORD0;
float3 nDirWS : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.posWS = mul(unity_ObjectToWorld, v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 nDirWS = i.nDirWS;
float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz);
//菲涅尔公式
float fresnel = pow((1.0 - dot(nDirWS,vDirWS)),_Power);
return fixed4(fresnel,fresnel,fresnel,1.0);
}
ENDCG
}
}
}
- 5、在Material的Inspector面板调整Power的值,查看效果。
与漫反射、环境反射结合
根据视线方向与法向量之间的点乘结果与其他效果混合,可以根据自己的实际需求灵活应用。这里以一种效果作为展示,修改上面的shader脚本:
Shader "Unlit/Fresnel"
{
Properties
{
_Power("Power",Range(0,10)) = 0.5
_Cubemap("CubeMap",Cube) = "_Skybox"{}
_EvnSpeculatInt("环境反射强度",Range(0,5)) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float _Power;
float3 _Color;
samplerCUBE _Cubemap;
float _EvnSpeculatInt;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 posWS : TEXCOORD0;
float3 nDirWS : TEXCOORD1;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.posWS = mul(unity_ObjectToWorld, v.vertex);
o.nDirWS = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 nDirWS = i.nDirWS;
float3 vDirWS = normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz);
float3 vrDirWS = reflect(-vDirWS,nDirWS);
float3 cubeMapColor = texCUBE(_Cubemap,vrDirWS).rgb;
//菲涅尔公式
float fresnel = pow((1.0 - dot(nDirWS,vDirWS)),_Power);
float3 envSpecLighting = cubeMapColor * fresnel * _EvnSpeculatInt;
return fixed4(envSpecLighting,1.0);
}
ENDCG
}
}
}
最终效果: