找回密码
 立即注册
查看: 319|回复: 0

Unity Postprocessing :Outlines

[复制链接]
发表于 2022-8-2 07:59 | 显示全部楼层 |阅读模式
写在前面:
本文章为个人学习笔记,方便以后自己复习,也希望能帮助到他人。
由于本人水平有限难免出现错误,还请评论区指出,多多指教。
部分图元和素材来源于网络,如有侵权请联系本人删除。
参考资料与链接会在文章末尾贴出。
<hr/>本文简单介绍一下如何在unity中通过后处理实现描边效果。
轮廓描边是常见的后处理效果之一,相较于Hull outlines,它更擅长检测边缘,而且不必费心在所有物体的材质中添加描边功能的代码。
1 Depth Outlines
先在C#脚本中渲染深度法线纹理




要做描边效果首先我们要知道轮廓在哪,哪些像素属于是轮廓。
我们这里计算轮廓的方法是,我们将从我们正在渲染的像素周围的几个像素(上下左右)中读取数据,并计算中心像素与周围像素的深度和法线的差异。它们差异越大,轮廓理应越明显。

要计算相邻像素的位置,我们需要知道一个像素有多大。Unity提供了方法给我们,我们可以简单地添加一个具有特定名称的变量,然后Unity告诉我们大小。因为我们正在使用纹理像素,所以它被称为 texelsize。

我们可以为任何纹理创建一个名为 texturename_TexelSize 的变量并获取它的大小。
     Vector4(1/tex.width,1/tex.height,tex.width,tex.height)


接下来我们需要访问某一像素周围的像素(上下左右)
                float2 offset = float2(1,0);
                float4 neighborDepthNormal = tex2D(_CameraDepthNormalsTexture,i.uv + _CameraDepthNormalTexture_TexelSize.xy * offset);
                float3 neighborNormal;
                float neighborDepth;
                DecodeDepthNormal(neighborDepthNormal,neighborDepth,neighborNormal);
                neighborDepth *=_ProjectionParams.z;由于我们要访问四次,最好直接封装成方法:
            float ComparePixelAround(float baseDepth,float2 uv,float2 offset)
            {
                // read neighbor pixel
                float4 neighborDepthNormal = tex2D(_CameraDepthNormalsTexture, uv + _CameraDepthNormalsTexture_TexelSize.xy * offset);

                float3 neighborNormal;
                float neighborDepth;
                DecodeDepthNormal(neighborDepthNormal,neighborDepth,neighborNormal);

                neighborDepth *= _ProjectionParams.z;

                return baseDepth - neighborDepth;
            }接下来所我们在fragment shader中往四个方向采样像素,并将所有的结果加在一起。






2 Normal Outlines
使用深度纹理已经为我们提供了不错的效果,但我们还可以通过使用法线纹理做得更好。
因此我们还将在ComparePixelAround函数中对法线进行采样,不过函数在 hlsl 中只能返回一个值,因此我们不能在此处使用返回值。我们可以使用 inout 关键字添加新参数,而不是使用返回值。从法线生成轮廓的另一件事是我们需要中心像素的轮廓,因此我们也将其添加到参数列表中。
接下来考虑如何改变ComparePixelAround的方法。
假如两根法线处于边界的两边,显然他门指向的是不同的方向。比较两个归一化向量的一种简单快捷的方法是采用点积。点积的计算是,当两个向量指向相同的方向时,点积为 1,当两个向量指向完全不同的方向,值为-1,这与我们想要的相反。解决的方法是从1中减去点积结果。然后,当点积的结果为1时,整体结果为0,当点积的结果变小时,最终结果变大。
            //depth and normal outline version
            void ComparePixelAround(inout float depthOutline, inout float normalOutline,
                                    float baseDepth ,float3 baseNormal,float2 uv,float2 offset)
            {
                // read neighbor pixel
                float4 neighborDepthNormal = tex2D(_CameraDepthNormalsTexture, uv + _CameraDepthNormalsTexture_TexelSize.xy * offset);

                float3 neighborNormal;
                float neighborDepth;
                DecodeDepthNormal(neighborDepthNormal,neighborDepth,neighborNormal);

                neighborDepth *= _ProjectionParams.z;

                float depthDifference = baseDepth - neighborDepth;;
                depthOutline +=depthDifference;

                float3 normalDifference = baseNormal - neighborNormal;
                normalDifference = normalDifference.r + normalDifference.g + normalDifference.b;
                normalOutline += normalDifference;
            }然后在fragment shader里面调用
                float depthDifference = 0;
                float normalDifference = 0;

                ComparePixelAround(depthDifference, normalDifference, depth, normal, i.uv, float2(1, 0));
                ComparePixelAround(depthDifference, normalDifference, depth, normal, i.uv, float2(-1, 0));
                ComparePixelAround(depthDifference, normalDifference, depth, normal, i.uv, float2(0, 1));
                ComparePixelAround(depthDifference, normalDifference, depth, normal, i.uv, float2(0, -1));

                float ouline = depthDifference + normalDifference;

3 Customizable Outlines
我们注意到,现在的描边粗细是固定的,此外轮廓线靠近的地方有一些灰色的部分,想要消除其实就是把值变小或为0。
因此我们来使轮廓更加可定制。我们为每个深度和法线轮廓添加了两个变量。使轮廓看起来更强或更弱的Mult和可以使轮廓的灰色部分(看上图)消失的Bias。


                float depthDifference = 0;
                float normalDifference = 0;

                ComparePixelAround(depthDifference, normalDifference, depth, normal, i.uv, float2(1, 0));
                ComparePixelAround(depthDifference, normalDifference, depth, normal, i.uv, float2(-1, 0));
                ComparePixelAround(depthDifference, normalDifference, depth, normal, i.uv, float2(0, 1));
                ComparePixelAround(depthDifference, normalDifference, depth, normal, i.uv, float2(0, -1));

                depthDifference *= _DepthMult;
                depthDifference = saturate(depthDifference);
                depthDifference = pow(depthDifference,_DepthBias);

                normalDifference *= _NormalMult;
                normalDifference = saturate(normalDifference);
                normalDifference = pow(normalDifference,_NormalBias);

                float outline = depthDifference + normalDifference;

                float4 sourceColor = tex2D(_MainTex,i.uv);
                float4 finalColor = lerp(sourceColor,_OutlineColor,outline);

                return finalColor;HLSL 的saturate函数,用于将变量限制在 0 和 1 之间,防止接下来的power运算出现奇怪的效果。
最后,我们从源图像中都读取颜色并应用描边效果:



现在我们来谈一谈后处理描边的主要缺点:
1.你会将描边应用于场景中的所有对象。
2.代码决定什么是轮廓,什么不是轮廓的方式和结果可能与你的预期不符合。
3.可以看到明显的锯齿问题。
4.对比图中cube和猴头你会法线,如果模型几何细节太多,描边效果反而会奇怪了起来;当你的几何细节足够丰富的时候,黑色描边会把你的模型一大半都涂成了黑色。


参考资料:
1.https://www.ronja-tutorials.com/post/019-postprocessing-outlines/

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-15 12:45 , Processed in 0.093620 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表