原文:
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]
之间。这里有两个函数staturate
和clamp
都可以实现范围限定的功能,但是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)); 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); 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
#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给两小钱。总之,各位大爷,走过路过不要错过,有钱的捧个钱场,没钱的捧个人场:-)!!!