边缘光
本来打算把边缘光和透视效果一起折腾的,忽然发现边缘光可以写的东西也不少,单独开个坑吧,凑凑字数。
菲涅尔
效果图

最常见的边缘光实现方式之一,通过获取法线和视线的点乘的结果来判断,越靠近模型的边缘,那么法线和视线的夹角就会越接近90度。

核心代码如下:
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
| v2f vert (appdata_base v) { ... //求法线坐标 o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject); //获得世界坐标系下顶点坐标 float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; //获得世界坐标系下视线向量 o.worldViewDir = _WorldSpaceCameraPos.xyz - worldPos; ... }
fixed4 frag (v2f i) : SV_Target { ... //标准化视线向量 float3 worldViewDir = normalize(i.worldViewDir); //标准化法线向量 fixed3 worldNormal = normalize(i.worldNormal); //获取 Rim float rim = 1 - saturate(dot(worldViewDir,worldNormal)); //获取 RimColor float4 rimColor = _RimColor * pow(rim, 1 / _RimPower) * _RimIntensity; ... }
|
- 注意求坐标的时候叉乘顺序不要错了,不然结果是不对的
- 法线坐标和视线向量我的参考文章说在 frag 中标准化会影响结果,但是我自己没看出来,可能我的效果比较单一,不过如果你在 vertex 中写下来没影响的话还是在 vertex 里面写,可以节省一些性能。
- 在 frag 中求 Rim 的时候,用 1 减去的原因是两个向量越接近 90 效果应该越大,但是点乘的结果却越接近 0.
- pow 的作用是用来平滑的,因为从视觉效果来看边缘光也应该是越接近边缘的地方效果越好,在下图里面,x 轴就是直接得到的 rim 结果。

深度信息法
深度信息有点类似于之前做过的描边,逻辑就是先将坐标偏移一个距离,然后用原来的坐标和新的坐标相减(或者相加都行,看你想要外描边还是内描边),再用一个阈值来判断原来的坐标是否处于你想要的描边边缘上,最后赋予一个颜色加起来就行。
这里最麻烦的就是如何获取深度值,步骤如下:
- 先找到屏幕点
- 向某个方向偏移,并且手动进行齐次除法
- 获取两个点的深度值
- 进行线性转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| //先计算屏幕点,并且去除屏幕分辨率的影响 float2 screenParams01 = float2(i.vertex.x/_ScreenParams.x,i.vertex.y/_ScreenParams.y);
//向左边偏移 float2 offectSamplePos = screenParams01-float2(_RimOffect/i.clipW,0);
//获取偏移点深度值 float offcetDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, offectSamplePos); //获取当前点深度值 float trueDepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenParams01);
//进行线性转换 float linear01EyeOffectDepth = Linear01Depth(offcetDepth); float linear01EyeTrueDepth = Linear01Depth(trueDepth);
|
硬边缘效果图用了黑色当原色,比较容易看出效果。


参考链接