0%

Fresnel

原文:
Fresnel

Summary

菲涅尔效果是渲染中常用的效果。使用菲涅尔效果我们可以很方便的对模型的边缘进行增亮、加黑等边缘效果,加强场景的深度感。

本篇将采用表面着色器的实现方法,所以如果你对表面着色器还不了解的话,建议你先看看这篇文章。当然你也可以将菲涅尔效果应用到其他类型的着色器,来增强模型平滑度、或突出重点。

Highlighting one Side of the Model

我们通过修改表面着色器来实现菲涅尔效果。菲涅尔效果是根据法向来计算效果的密度、或厚度。因为要在片段着色器中使用到世界坐标系中的法向量,所以我们首先在顶点着色器中计算好世界坐标系中的顶点法向,然后通过中间插值结构传递给片段着色器。当然,我们的输入结构体和中间插值结构体都需要定义法向量。

三维平面映射我们介绍了如何计算顶点的世界法向量。

1
2
3
4
5
6
//输入的模型网格数据,其中采用宏定义来定义部分数据
struct Input {
float2 uv_MainTex;
float3 worldNormal;
INTERNAL_DATA
};

我们可以通过计算相邻向量之间的点乘,从而了解模型表明的平滑度、或者梯度。单位向量之间的点乘越大,说明它们方向越一致。

首先,我将所有法向量与一个固定向量做点乘,可以让我们更容易理解点乘的意义。然后将点乘的结果传递给emission变量,将点乘结果通过自发光的形式表现出来。

1
2
3
4
5
6
7
8
9
10
//表面着色器函数,主要用了计算光照模型的输入参数,然后由光照模型进行最中的颜色计算
void surf (Input i, inout SurfaceOutputStandard o) {

//...

//两个向量之间的点乘
float fresnel = dot(i.worldNormal, float3(0, 1, 0));
//应用菲涅尔效果
o.Emission = _Emission + fresnel;
}

上图我们可以看到,越朝上的地方越亮、越朝下越暗。为了避免自发光出现负数,这里将菲涅尔值限定在[0,1]之间。这里有两个函数staturateclamp都可以实现范围限定的功能,但是staturate在GPU上执行速度更快。下面是限定后的效果。

1
2
3
4
5
6
7
8
9
10
11
12
//表面着色器函数
void surf (Input i, inout SurfaceOutputStandard o) {

//...

//计算菲涅尔值
float fresnel = dot(i.worldNormal, float3(0, 1, 0));
//限定为 0 - 1 之间
fresnel = saturate(fresnel);
//应用菲尼尔效果
o.Emission = _Emission + fresnel;
}

Highlighting the outer Parts

接下来我们使用视角方向来计算菲涅尔值。视角放向可以直接定义输入结构体中,然后在表面着色器函数中就可以访问。

如果是在无光照着色器中实现菲涅尔效果,那么视角方向可以通过摄像机的位置和顶点世界坐标来计算。其中摄像机坐标存储在_WorldSpaceCameraPos这个内置变量中,由Unity来赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//表面着色器的输入结构体
struct Input {
float2 uv_MainTex;
float3 worldNormal;
float3 viewDir;
INTERNAL_DATA
};

//表面着色器函数
void surf (Input i, inout SurfaceOutputStandard o) {

//...

//金属度与光滑度
o.Metallic = _Metallic;
o.Smoothness = _Smoothness;

//菲尼尔值
float fresnel = dot(i.worldNormal, i.viewDir);
//范围限定在 0 - 1
fresnel = saturate(fresnel);
//应用菲涅尔值
o.Emission = _Emission + fresnel;
}

总体来说实现了菲涅尔效果,但是整个材质是靠近中心区域更亮,而不是边缘更亮。如果我们想让边缘更亮,我们可以将1减去菲涅尔值。

1
2
3
4
5
6
7
8
9
10
11
12
//表面着色器函数
void surf (Input i, inout SurfaceOutputStandard o) {

//...

//菲涅尔值
float fresnel = dot(i.worldNormal, i.viewDir);
//效果反转
fresnel = saturate(1 - fresnel);
//应用菲涅尔效果
o.Emission = _Emission + fresnel;
}

Add Fresnel Color and Intensity

最后,我想增加一些自定义属性,比如菲涅尔颜色。

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
//...

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

//...

float3 _FresnelColor;

//...

//表面着色器函数
void surf (Input i, inout SurfaceOutputStandard o) {

//...

//菲涅尔值
float fresnel = dot(i.worldNormal, i.viewDir);
//效果反转
fresnel = saturate(1 - fresnel);
//使用菲涅尔自定义颜色
float3 fresnelColor = fresnel * _FresnelColor;
//应用菲涅尔效果
o.Emission = _Emission + fresnelColor;
}

//...

然后再增加一个控制菲涅尔强度的属性。这里我们使用指数函数来调节菲涅尔强度。

因为指数函数计算消耗非常大,所以如果有其他方法能实现相同的效果,尽量不要使用指数函数。当然,指数函数用法简单、便捷,在设计效果的时候可以直接使用指数函数。

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
//...

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

//...

float _FresnelExponent;

//...

//表面着色器函数
void surf (Input i, inout SurfaceOutputStandard o) {

//...

//菲涅尔值
float fresnel = dot(i.worldNormal, i.viewDir);
//效果反转
fresnel = saturate(1 - fresnel);
//使用菲涅尔自定义颜色
float3 fresnelColor = fresnel * _FresnelColor;
//强度调节
fresnel = pow(fresnel, _FresnelExponent);
//应用菲涅尔效果
o.Emission = _Emission + fresnelColor;
}

菲涅尔效果除了用来突出轮廓外,还可以用来实现各种渐变效果,这里不展开,有兴趣可以自行尝试。

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
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 是被定义为表面着色器函数
//fullforwardshadows 告诉Unity将所有阴影Pass都复制过来
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0

sampler2D _MainTex;
fixed4 _Color;

half _Smoothness;
half _Metallic;
half3 _Emission;

float3 _FresnelColor;
float _FresnelExponent;

//表面着色器的输入结构
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;

//菲涅尔值
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

希望你能喜欢这个教程哦!如果你想支持我,可以关注我的推特,或者通过ko-fi、或patreon给两小钱。总之,各位大爷,走过路过不要错过,有钱的捧个钱场,没钱的捧个人场:-)!!!