学了一年多的Shader,给我的感觉,入门挺容易的,在完整的看完冯大佬的Shader入门书就基本能达到入门了,也就是停留在能够看懂基础的顶点片元Shader的水平,但如果想要继续前进可太困难了,网络上关于这一块的学习资料的难度基本上分为新手和地狱难度两类,中间隔着一道坎,需要大量的经验积累和在实践中汲取细碎的知识点一步步跨过去。开启这个系列为了把学过的重新捋捋,方便今后查阅。
应用例子
边缘光、闪白等效果,全是基于菲涅尔(Fresnel)
闪白:这游戏行业内的术语就很专业,起初第一次听导师随口说出时,一脸懵逼啥玩意??? 所以在这儿有必要解释一下,闪白就是人物受击时,身上会闪一下的白色边缘光,也可能是其他颜色,多用来表示受击。
水面效果:近处折射效果强,远处反射效果强
菲涅尔是个啥?
在真实世界中,除了金属之外,其它物质均有不同程度的“菲涅尔效应”。视线垂直于表面时,反射较弱,而当视线非垂直表面时,夹角越小,反射越明显。如果你看向一个圆球,那圆球中心的反射较弱,靠近边缘较强。不过这种过度关系被折射率影响。——引用自百度百科
菲涅尔公式
要用计算机模拟真实世界中的现象,那必定存在数学公式,既是菲涅尔公式。
然而真正的菲涅尔公式太过复杂,计算量较多,所以在游戏中往往会使用简化版的菲涅尔公式,来达到近似的效果。
公式如下:
$$
Fresnel = 1 - dot(N, V)
$$
N:Normal 法线
V:ViewDir 视线方向
两者需要确保在同一坐标空间系中计算,一般均在世界空间坐标系中进行计算。
如何理解这条简单公式?
想象从正面看向一个球,球上的法线N,在球边缘的时候,刚好和视线方向V垂直,点乘后等于0,也即是黑色的,中间部分越接近平行,越接近1,既是白色的,如图:

这个时候再来个反相,1-dot(N,V),即可得到菲涅尔的值,在球上的表现如下,也即是边缘发亮的效果:

为了能够控制菲涅尔的强度,宽度等效果,通常还会对该简易公式进行处理,比如加上某个值,乘与某个值,或与某个值进行幂次运算,常用的最终公式如下:
$$
Fresnel = A + B * pow(1 - dot(N, V), C)
$$
(A,B,C均为常数)



Shader代码
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
| Shader "Draco/Fresnel" { Properties { _A("A", Range(-1, 1)) = 0 _B("B", Range(0, 10)) = 1 _C("C", Range(0, 5)) = 1 _fresnelColor("_fresnelColor", Color) = (1,1,1,1) }
SubShader { Tags { "RenderType"="Opaque" } LOD 100
Pass { tags{"lightmode="="forward"}
CGPROGRAM #pragma vertex vert #pragma fragment frag
#include "UnityCG.cginc"
struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; };
struct v2f { float4 vertex : SV_POSITION; float3 normalWorld : TEXCOORD2; float3 viewDir : TEXCOORD3; };
float _A; float _B; float _C;
float4 _fresnelColor;
v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); //将法线转到世界坐标 o.normalWorld = mul(v.normal, (float3x3)unity_WorldToObject); //获取世界坐标的视角向量 o.viewDir = WorldSpaceViewDir(v.vertex); return o; }
fixed4 frag (v2f i) : SV_Target { float3 normal = normalize(i.normalWorld); float3 viewDir = normalize(i.viewDir);
//菲涅尔公式 float fresnel = _A + _B * pow(1 - dot(normal, viewDir), _C); fixed3 col = _fresnelColor * fresnel;
return fixed4(col, 1); }
ENDCG } } }
|