Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

菲涅尔反射

一、菲涅尔反射是什么

一种光学效果 实现垂直于表面观察,反射较弱,而当实现非垂直于表面观察,夹角越小,反射越明显

二、菲涅尔反射效果在 UnityShader 中的作用

在 Unity 中,使用菲涅尔反射比直接使用反射能更加贴近真实世界的效果,在实现一些金属感、陶瓷感、玻璃感时效果更好

三、菲涅尔反射实现效果的原理

菲涅尔反射的原理还是利用立方体纹理进行环境映射 我们在计算反射时,还是利用之前的那一套反射计算规则 只是为我们在决定采样颜色时,需要使用的反射率变量添加新的计算规则

反射率使用菲涅尔等式来计算 但是在物理学中,菲涅尔等式非常复杂 所以在 UnityShader 中我们使用Schlick 菲尼尔近似等式来计算 R(θ) = R0 + (1 - R0 )(1 - cos(θ))⁵R(θ) = R0 + (1 - R0 )(1 - V·N)⁵

  • R(θ) 表示入射角为 θ 时的反射率
  • R0 表示垂直入射时某介质的反射率
  • V 表示视角方向的单位向量
  • N 表示顶点法向量

四、菲尼尔反射的基本实现

1.实现思路

  1. 在普通反射的基础上进行修改,将普通的反射率通过为菲涅尔近似等式计算菲涅尔反射率
  2. 属性声明 将反射率变量修改为 _Fresnel 菲涅尔反射中的介质反射率
  3. 结构体 添加
    1. 视角方向
    2. 法线方向
  4. 顶点着色器 结构体中变量的赋值
  5. 片元着色器 通过 Schlick 菲涅尔近似公式(R(θ) = R0 + (1 - R0 )(1 - V·N)⁵) 计算菲尼尔反射率

2.实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
Shader "Unlit/FresnelBase"
{
Properties
{
//立方体纹理
_Cube("Cubemap",Cube) = ""{}
//菲涅尔反射中对应介质的反射率
_FresnelScale("FresnelBase",Range(0,1)) = 1
}
SubShader
{
Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}

Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"

samplerCUBE _Cube;
float _FresnelScale;

struct v2f
{
float4 pos:SV_POSITION;//裁剪坐标下的顶点坐标
//世界空间下的法线向量
float3 worldNormal:NORMAL;
float3 worldViewDir:TEXCOORD0;
//世界空间下的顶点坐标
//我们将把反射向量的的计算放在顶点坐标系下 节约性能 表现效果肉眼几乎和片元着色器一致
float3 worldRefl:TEXCOORD1;

};
v2f vert(appdata_base v)
{
v2f o;
//顶点坐标转裁剪坐标
o.pos = UnityObjectToClipPos(v.vertex);
//计算计算反射光向量
//1.计算世界空间下的法线向量
o.worldNormal = UnityObjectToWorldNormal(v.normal);
//2.计算世界空间下的顶点坐标
fixed3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
//3.计算视角方向 内部是用摄像机位置-世界坐标位置
o.worldViewDir = UnityWorldSpaceViewDir(worldPos);
//计算反射向量
o.worldRefl = reflect(-o.worldViewDir,o.worldNormal);
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
//对立方体纹理利用相应的反射函数进行采样
fixed4 cubemapColor = texCUBE(_Cube,i.worldRefl);
//通过菲尼尔定律计算反射率
fixed fresenl = _FresnelScale + (1 - _FresnelScale)*pow(1-dot(normalize(i.worldNormal),normalize(i.worldViewDir)),5);
//用采样颜色*反射率 得到最终效果
return cubemapColor * fresenl;
}
ENDCG
}
}
}

五、菲涅尔反射结合漫反射实现

1.基本思路

  1. 基于反射结合漫反射的思路
  2. 属性修改
    1. 去掉反射的颜色
    2. 修改反射率 为 菲涅尔反射相关的反射率 R0
  3. 修改 v2f 结构体
    1. 添加视角方向
  4. 顶点着色器
    1. 修改视角方向的临时变量
  5. 片元着色器
    1. 计算菲涅尔反射率
    2. 用菲涅尔反射率参与 lerp 计算

2.实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
Shader "Unlit/Fresnel"
{
Properties
{
//立方体纹理
_Cube("Cubemap",Cube) = ""{}
//反射率
_FresnelScale("FresnelScale",Range(0,1)) = 1

//漫反射颜色
_Color("Color",Color) = (1,1,1,1)

}
SubShader
{
Tags{"RenderType" = "Opaque" "Queue" = "Geometry"}

Pass
{
Tags{"LightMode" = "ForwardBase"}

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase

#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"

samplerCUBE _Cube;
float _FresnelScale;
fixed4 _Color;

struct v2f
{
float4 pos:SV_POSITION;//裁剪坐标下的顶点坐标
//世界空间下的法线
float3 worldNormal:NORMAL;
//世界坐标下的顶点
float3 worldPos:TEXCOORD0;
//世界空间下视角的方向
fixed3 worldViewDir:TEXCOORD1;
//世界空间下的顶点坐标
//我们将把反射向量的的计算放在顶点坐标系下 节约性能 表现效果肉眼几乎和片元着色器一致
float3 worldRefl:TEXCOORD2;

//阴影相关
SHADOW_COORDS(3)

};
v2f vert(appdata_base v)
{
v2f o;
//顶点坐标转裁剪坐标
o.pos = UnityObjectToClipPos(v.vertex);
//计算计算反射光向量
//1.计算世界空间下的法线向量
o.worldNormal = UnityObjectToWorldNormal(v.normal);
//2.计算世界空间下的顶点坐标
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
//3.计算视角方向 内部是用摄像机位置-世界坐标位置
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
//计算反射向量
o.worldRefl = reflect(-o.worldViewDir,o.worldNormal);
//阴影相关处理
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
//进行漫反射光照相关计算
//得到光的方向
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
//计算漫反射颜色
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0,dot(normalize(i.worldNormal),worldLightDir));

//对立方体纹理利用相应的反射函数进行采样
fixed3 cubemapColor = texCUBE(_Cube,i.worldRefl).rgb;
//得到光照衰减以及阴影相关的衰减值
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
//菲尼尔反射率计算
fixed fresnel = _FresnelScale + (1 - _FresnelScale) * pow(1-dot(normalize(i.worldNormal),normalize(i.worldViewDir)),5);
//我们在利用lerp 在漫反射和反射颜色之间 进行插值
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb + lerp(diffuse,cubemapColor,fresnel) * atten;
//用采样颜色*反射率 得到最终效果
return fixed4(color,1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}

评论