最近在做一些UI使用的shader,大部分是对UV进行一些操作,今天看需求文档时发现美术同学的要求里有一项是类似磨皮的效果,本来我也比较好奇这些美颜效果都是怎么做的,所以就趁此机会实验一下。查了一大堆页面后发现可以实现磨皮效果的算法有很多,常用到的有 双边滤波器、表面模糊方法、选择性模糊方法等。勉强看明白了双边滤波和表面模糊方法的公式,所以以下就使用这两种方法在Unity中实现下磨皮效果。

双边滤波方式

参考 双边滤波器 中的解释,首先映入眼帘的就是一大堆公式,比如这个:


嗯还有积分,有点吓人了,再往下看终于找到了需要用的公式:

大概意思就是考虑了空间距离和颜色相近程度两个方面以后的计算,所以名字里有"双边"两个字,这篇文章 解释的挺清晰。

这个公式会看起来更适合转换为代码实现:

大概解释下:
g(i,j) 可以理解为纹理坐标为(i,j)的点的颜色
σ d 和 σ r 是空间距离维度和颜色差异维度上的两个平滑参数
f(i,j) 可以理解为对(i,j)点的颜色, 即 tex2D(_MainTex, float2(i,j))
exp是自然底数e的幂次方计算,即 exp(x) = e x
||f(i,j) - f(k,l)|| 意思是 f(i,j) - f(k,l) 向量的模, 模的符号也可以用单个 | 来表示即 |f(i,j) - f(k,l)|, 我猜可能有时为了和绝对值计算区分开而专门写成双竖线形式吧

好,接下来就可以在shader里实现上面的公式了,直接上代码吧。

float Luminance(float3 color)
{return dot(color, float3(0.2125, 0.7154, 0.0721));
}   float4 BilateralFilter(float2 uv)
{float i = uv.x;float j = uv.y;float sigmaSSquareMult2 = (2*_SigmaS*_SigmaS);float sigmaRSquareMult2 = (2*_SigmaR*_SigmaR);float3 centerCol = tex2D(_MainTex, uv).rgb;                 // 中心点像素的颜色 //float centerLum = Luminance(centerCol);                      // 中心点像素的亮度 //float3 sum_up;                                                // 分子 //float3 sum_down;                                                // 分母 //for(int k=-_Radius; k<=_Radius; k++){for(int l=-_Radius; l<=_Radius; l++){float2 uv_new = uv+_MainTex_TexelSize.xy*float2(k,l);float3 curCol = tex2D(_MainTex, uv_new).rgb;        // 当前像素的颜色 //float curLum = Luminance(curCol);                     // 当前像素的亮度 //float3 deltaColor = curCol-centerCol;float len = dot(deltaColor, deltaColor);// float exponent = -((i-k)*(i-k)+(j-l)*(j-l))/sigmaSSquareMult2 - (curLum-centerLum)*(curLum-centerLum)/sigmaRSquareMult2;float exponent = -((i-k)*(i-k)+(j-l)*(j-l))/sigmaSSquareMult2 - len/sigmaRSquareMult2;float weight = exp(exponent);sum_up += curCol*weight;sum_down += weight;}}float3 rgb = sum_up/sum_down;return float4(rgb*_Brightness, 1);
}v2f vert (appdata v)
{v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;
}float4 frag (v2f i) : SV_Target
{return BilateralFilter(i.uv);
}

核心方法就是 BilateralFilter,此方法根据给定的采样半径_Radius来对周围(2*_Radius+1)(2_Radius+1)个像素进行采样,并分别计算出这些采样点的权重,然后把颜色和权重相乘后累加起来作为分子,同时权重也累加起来作为分母,这些点采样结束后把分子累加值除以分母累加值,就是当前像素点的颜色。

表面模糊方法

主要参考 这篇文章,公式如下:

参数图里解释的很清晰,那就直接去shader中实现就好了,直接上代码。

float3 CalculateWeight(float3 xi, float3 x1)
{return 1-abs(xi-x1)/(2.5*_Threshold);
}float4 SurfaceFilter(float2 uv)
{float3 x1 = tex2D(_MainTex, uv).rgb;float3 sum_up;                // 分子 //float3 sum_down;            // 分母 //// 对 (2*_Radius+1)*(2*_Radius+1) 大小的矩形区域内所有像素采样 //for(int i=-_Radius; i<=_Radius; i++){for(int j=-_Radius; j<=_Radius; j++){float2 uv_new = uv + float2(j,i) * _MainTex_TexelSize.xy;float3 xi = tex2D(_MainTex, uv_new).rgb;sum_up += CalculateWeight(xi, x1)*xi;sum_down += CalculateWeight(xi, x1);}}float3 rgb = sum_up/sum_down;return float4(rgb,1);
}v2f vert (appdata v)
{v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.uv, _MainTex);return o;
}float4 frag (v2f i) : SV_Target
{float4 col = SurfaceFilter(i.uv)*_Brightness;return col;
}

核心方法是 SurfaceFilter, 表面模糊方法也需要指定一个采样半径,这样好确定对多大范围内的像素进行采样,整个过程和双边滤波器基本一致,只是计算权重的方法不一样,按照公式忠实的还原即可。执行效果如图:


看起来比较模糊,效果不太理想,如果想要清晰一些的话就会有非皮肤区域过亮的问题,像这样:

难怪看有的文章里说还要检测下皮肤区域,看样是要把这个算法只应用到皮肤上,而避开嘴和眼睛之类的地方,自己还是太naive,还得再继续研究,那可能要另起一篇文章了。

综上,通过在shader中按照公式实现两种算法来达到简单的磨皮效果,这里只是验证两种方法的效果,所以没有进行优化,采样数也过大,实际运行在移动设备上应该耗电和发热都不小,还有很多经过优化的算法实现,可以多google了解下。

非常感谢参考文章里的各位大神。

package下载地址
提取码:qcgs

参考链接:
https://blog.csdn.net/trent1985/article/details/49864397
https://blog.csdn.net/piaoxuezhong/article/details/78302920
https://blog.csdn.net/mumusan2016/article/details/54578038
https://zh.wikipedia.org/zh-s
https://zh.wikipedia.org/zh/雙邊濾波器

Unity Shader 实现磨皮效果相关推荐

  1. Unity Shader 窗前雨滴效果衍生(表面水滴附着)

    Unity Shader 窗前雨滴效果衍生(表面水滴附着) 霓虹中国视频截图 现实中的水珠附着效果 实现思路 1.首先创建一个Cube来作为实现效果的物体 2.创建一个Shader开始着色器的编写 实 ...

  2. Unity Shader·屏幕破碎效果

    Unity Shader·屏幕破碎效果 前言 最近在做一个新的MMD(用Unity来实现),其中用到了一些好看的渲染技术在这里分享一下. 视频链接 https://www.bilibili.com/v ...

  3. Unity Shader 之 透明效果

    本文引用 Unity Shader入门精要 开启透明混合后,一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值外,还有--透明度.透明度为1,则完全不透明,透明度为0,则完全不会显示. 在Unity ...

  4. Unity Shader - 翻书效果

    今天实现一个简单的翻书的效果,话不多说,先上一张效果图: 这里就随便用的一张纹理了,我们还是称为"翻木板"吧,哈哈. 实现过程: 其实这个效果实现起来还是挺简单的,大概思路其实就是 ...

  5. unity shader 抖音效果

    最近开始学习了unity shader,所以想要做一些简单的效果,来巩固一下知识.我第一个想做的就是做一些类似于抖音的效果.(PS:最近学习了markdown,所以就用markdown开始写博客了 ) ...

  6. Unity Shader 实现鬼魂效果

    Shader 实现鬼魂效果 前言 前言 我们在游戏中经常会角色碰到角色的情况,大多数游戏中角色和角色重叠的时候会显示一个虚幻的鬼影而不是完全遮挡,那么这个鬼影效果怎么实现呢?接下来我们就实现这样的一个 ...

  7. Unity Shader 屏幕后效果——高斯模糊

    高斯模糊是图像模糊处理中非常经典和常见的一种算法,也是Bloom屏幕效果的基础. 实现高斯模糊同样用到了卷积的概念,关于卷积的概念和原理详见我的另一篇博客: https://www.cnblogs.c ...

  8. Unity Shader 屏幕后效果——Bloom外发光

    Bloom的原理很简单,主要是提取渲染图像中的亮部区域,并对亮部区域进行模糊处理,再与原始图像混合而成. 一般对亮部进行模糊处理的部分采用高斯模糊,关于高斯模糊,详见之前的另一篇博客: https:/ ...

  9. Unity Shader 2D水流效果

    水流的模拟主要运用了顶点变换和纹理动画的结合: 顶点变换中,利用正弦函数模拟河流的大致形态,例如波长,振幅等. 纹理动画中,将纹理坐标朝某一方向持续滚动以形成流动的效果. 脚本如下: 1 Shader ...

最新文章

  1. eclipse字体大小设置_Java 设置Excel单元格格式—基于Spire.Cloud.SDK for Java
  2. 二十八、事务的提交与回滚演示
  3. 【DIY】最简单粗暴便宜的DIY定时器方法,没有之一
  4. python相关 MOOC第一周
  5. leetcode_longest substring without repeating characters
  6. python 连接数据库-设置oracle ,mysql 中文字符问题
  7. Linux图片马PHP,php 根据请求生成缩略图片保存到Linux图片服务器的代码
  8. 【项目相关】MVC中将WebUploader进行封装
  9. jsonobject修改key的值_JSONObject(org.json)的一点修改
  10. Xamarin Android 应用程序内图标上数字提示
  11. 百度地图Map属性和方法
  12. java的HashCode方法(转载)
  13. Linux运维第二课----Linux发展史、环境准备
  14. 尼康数码相机照片数据恢复怎么办
  15. python画围棋棋盘_python3 turtle 画围棋棋盘
  16. GANs学习系列(2):GANs最新进展二
  17. NRF24L01使用
  18. 微信小程序实现简单的点击切换功能(微信开发者工具)
  19. 最全总结 | 聊聊 Python 办公自动化之 Word(中)
  20. html 图片左上角圆角,圆角

热门文章

  1. C语言——倒置的字母三角形
  2. zabbix 自定义监控文本内容
  3. 如何在iPhone,iPad和Mac上禁用Safari经常访问的起始页
  4. 识花草小程序全新改版上线,智能识花新玩法
  5. 基于C语言的新冠疫情通报系统设计与实现 报告+项目源码
  6. 魅族mx四核即将使用android,魅族mx四核怎么样ne?魅族mx四核怎么样ne? 爱问知识人...
  7. C#关键字之override详解
  8. java override报错_java @override 报错处理
  9. Mac M1 Java 开发环境配置
  10. 微信公众平台对接C#-服务号开发配置