本文参考文章:【UE4】皮肤下雨效果复现
大体的思路就是使用UV坐标生成水滴遮罩以及法线。

1.原理简单阐述

首先建一个简单的Shader来输出UV坐标:

fixed4 frag (v2f i) : SV_Target
{fixed4 col = tex2D(_MainTex, i.uv);col.rg=i.uv;return col;
}


为了方便计算,这里将UV的原点移到中心。接下来第一步我们先取UV坐标的长度(Length);第二步,取负并加0.3(截取其 [0,0.3] 部分);第三步,对其进行 Smoothstep 便可获取到一个 [0,1] 的平滑遮罩:

                float2 uv=i.uv-0.5;float len=-length(uv)+0.3;float mask=smoothstep(0.15,0.2,len);col.rgb=len;


然后我们就可以根据,文章中的方法使用UV坐标和Mask来计算切线空间的法线

             float3 TUV(float2 uv){float3 normal; normal.y=abs(uv.x)*abs(uv.x)+uv.y;normal.x=abs(uv.y)*abs(uv.y)+uv.x;normal.z=0.2;return normalize(normal);}...float3 up=float3(0,0,1);float3 normal=TUV(uv);normal=lerp(up,normal,mask);...


然后给它算一下高光,这个Shader原理的最基本的一个流程就走完了,需要注意的是计算出来的法线是切线空间的,所以要进行转换:

             float3 TransfromTanToWorld(float3 normal,float3x3 tanToworld){float3 worldNormal;worldNormal.x = dot(tanToworld[0], normal);worldNormal.y = dot(tanToworld[1], normal);worldNormal.z = dot(tanToworld[2], normal);return normalize(worldNormal);}......float3x3 tanToWorld;tanToWorld[0]=i.TtoW0.xyz;tanToWorld[1]=i.TtoW1.xyz;tanToWorld[2]=i.TtoW2.xyz;float3 worldPos=float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);float3 lightDir=normalize(UnityWorldSpaceLightDir(worldPos));float3 viewDir=normalize(UnityWorldSpaceViewDir(worldPos));fixed4 col = tex2D(_MainTex, i.uv);float2 uv=i.uv-0.5;float len=-length(uv)+0.3;float mask=smoothstep(0.15,0.2,len);float3 up=float3(0,0,1);float3 normal=TUV(uv);normal=lerp(up,normal,mask);normal=TransfromTanToWorld(normal,tanToWorld);fixed spec=saturate(dot(viewDir,normal));spec=smoothstep(0.9,1,spec)*mask;col.rgb+=spec;return col;

2.静态水滴

1.UV分块及错位

第一步:是将UV分块,每一块UV计算一个水滴 (_PointAmount=15)

             float2 uv=i.uv*_PointAmount;uv=frac(uv);col.rg=uv;

但是这样的UV块过于整齐,所以在第二步我们需要引入随机数对UV进行偏移。这里我们对Y轴进行偏移,所以可以用X轴(floor取整是保证UV块偏移一致——伪随机的好处,同一个值得到的是同一个随机值)做随机数的输入:

             float2 uv=i.uv;//frac将偏移值控制在0~1float random=frac(sin(floor(_PointAmount*uv.x)*12345.580078)*7658.759766);uv.y+=random;uv=frac(uv*_PointAmount);col.rg=uv;

第三步, 我们可以将这一步骤封装成一个函数,这里引入了timescale是为了做移动和拉伸,Out.zw是为了下一步生成随机数:

         // XY:frac  ZW:floorfloat4 UVConfigure(float2 uv,float time,float2 scale,float amount){uv=uv*scale;uv.y+=time;float factor=frac(sin(floor(amount*uv.x)*12345.580078)*7658.759766);uv.y+=factor;uv*=amount;float4 Out=0;//floor:返回小于等于x的最大整数。Out.zw=floor(uv);//frac返回输入值的小数部分。//x[0,1]---->[-0.5,0.5]Out.xy=frac(uv)-float2(0.5,0);//Out.xy=frac(uv);return Out;}......float4 configure=UVConfigure(uv,0,1, _PointAmount);col.rg=configure.xy;

2.随机偏移和消失

还记得前一步我们在函数里输出了floor,这其实是给每一小块UV赋予了一个单独的id。为了让每个UV块都有一个偏移,这里再引入一个随机数N13,输入一个UV,输出一个 [0,1]float3

         float3 N13(float2 uv){float p=uv.x*35.2+uv.y*2376.1;float3 p3 = frac(float3(p,p,p) * float3(.1031,.11369,.13787));p3 += dot(p3, p3.yzx + 19.19);return frac(float3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x));}float2 uv=i.uv;float4 configure=UVConfigure(uv,0,1, _PointAmount);col.rgb=N13(configure.zw);

这里把前一步的UV做输入值,每一个分区都有一个单独的float3


这三个随机值其实怎么用都行,这里先用最简单的,首先用xy做偏移(偏移过大的问题将在动态水滴的部分给出解决办法),并输出长度小于0.1的部分为白色:

             float3 N=N13(configure.zw);half2 offsetUV=0;offsetUV.x=N.x-0.5;offsetUV.y=N.y;uv=configure.xy-offsetUV;col.rg=uv;if(length(uv)<0.1){return 1;}return col;


然后根据开头讲的原理做水滴遮罩,但是我们不需要这么圆的水滴,所以可以给它加上一个缩放值:

             float mask=-length(uv*float2(3.54,2.1))+0.5;mask=smoothstep(0,0.3,mask);col.rgb=mask;


接下来,可以用z给水滴遮罩做由大到小的消失效果了:

             float fade=smoothstep(0.2,1,1-frac(t*0.05+N.z));float mask=-length(uv*float2(3.54,2.1))+0.5*fade;mask=smoothstep(0,0.3,mask);col.rgb=mask;


接下来把所有函数都封装一下,原文为了有两个高光,计算了两层法线——一层是凸出来一层是凹进去的,所以Mask也要有两个,凹的小一点,凸的大一点。还添加了一个Cut值,该值范围为 [0,1] ,1时显示所有水滴,0时全不显示:

         float2 RainDropAndPointUV(float3 N,float2 fracUV,float Cut,out float cut){cut=Cut;cut=floor(cut+N.y);half2 offsetUV=0;offsetUV.x=N.x-0.5;offsetUV.y=N.y;float2 uv=(fracUV-offsetUV);return uv;}float2 Mask(float2 uv,float3 scaleAndDir,float4 smoothRange,float2 cutAndBase){//取负突出暗部,再和噪声相加获取渐变效果float maskBase=length(uv*scaleAndDir.xy)*scaleAndDir.z+cutAndBase.y;float2 mask=1;//内圈 凹mask.x=smoothstep(smoothRange.x,smoothRange.y,maskBase)*cutAndBase.x;//外圈 凸mask.y=smoothstep(smoothRange.z,smoothRange.w,maskBase)*cutAndBase.x;return mask;}float cut;uv=RainDropAndPointUV(N,configure.xy,_PointCut,cut);float fade=smoothstep(0.2,1,1-frac(t*0.05+N.z));float2 mask=Mask(uv,float3(3.54,2.1,-1),float4(0.3,0.44,0.1,0.45),float2(cut,fade));col.rgb=mask.r*cut;

调节 _PointCutMask的影响

3.法线和高光

由于需要计算两层法线,所以TUV函数也要修改,PosiNegaNormal用来决定法线的朝向,最后根据Mask对法线进行插值:

         void TUV(float2 uv,float2 PosiNegaNormal,out float3 normal,out float3 causticNormal){normal=0;causticNormal=0;normal.y=abs(uv.x)*abs(uv.x)+uv.y;normal.x=abs(uv.y)*abs(uv.y)+uv.x;causticNormal.xy=-normal.xy;normal.z=PosiNegaNormal.x;normal=normalize(normal);causticNormal.z=PosiNegaNormal.y;causticNormal=normalize(causticNormal);}.....float3 normal,causticNormal;//_PointPositiveNormal=0.235,_PointNegativeNormal=0.14float2 factor=float2(_PointPositiveNormal,_PointNegativeNormal); TUV(uv,factor,normal,causticNormal);float3 up=float3(0,0,1);normal=lerp(up,normal,mask.y);causticNormal=lerp(up,causticNormal,mask.x);col.rgb=causticNormal;


简单计算一下高光,已经稍微体现出体积感了,但需要调整的地方还有很多:

                 normal=TransfromTanToWorld(normal,tanToWorld);causticNormal=TransfromTanToWorld(causticNormal,tanToWorld);fixed spec1=smoothstep(0.9,1.2,dot(viewDir,normal))*mask.r*2;fixed spec2=smoothstep(0.5,1.5,dot(viewDir,causticNormal))*mask.g;col.rgb+=spec1+spec2;

3.动态水滴

1.让UV动起来

静态水滴的UVConfigure函数可以接着用,但参数需要改一下,这里讲V方向拉长了五倍,因为静态水滴需要有水痕,照例把两个结果输出一下:

             float2 uv=i.uv;float4 configureDrop=UVConfigure(uv,0.02*t,float2(5,1), _RainDropAmount);//col.rg=configureDrop.xy;col.rgb=N13(configureDrop.zw);if(length(configureDrop.xy)<0.1){return 1;}



然后把静态水滴剩余部分的代入到其中,就可得到和静态水滴一样的结果,不同的是这次水滴是运动的:

             float2 uv=i.uv;float cut;float4 configureDrop=UVConfigure(uv,0.02*t,float2(5,1), _RainDropAmount);float3 N=N13(configureDrop.zw);//因为之前对uv做了拉伸,所以这里要逆回来,不然水滴会拉长float2 rainDrop=RainDropAndPointUV(N,configureDrop.xy*float2(1,5),_RainDropAmount,cut);float2 rianDropMask=Mask(rainDrop,float3(3.54,2.1,-1),float4(0.3,0.44,0.1,0.45),float2(cut,1));float3 dropNormal,dropCausticNormal;float2 factor=float2(_RainDropPositiveNormal,_RainDropNegativeNormal);TUV(rainDrop,factor,dropNormal,dropCausticNormal);dropNormal*=rianDropMask.y;dropCausticNormal*=rianDropMask.x;dropNormal=TransfromTanToWorld(dropNormal,tanToWorld);dropCausticNormal=TransfromTanToWorld(dropCausticNormal,tanToWorld);fixed spec1=smoothstep(0.9,1.2,dot(viewDir,dropNormal))*rianDropMask.r*2;fixed spec2=smoothstep(0.5,1.5,dot(viewDir,dropCausticNormal))*rianDropMask.g;col.rgb+=spec1+spec2;


可以法线这里问题还是很多,接下来一个一个解决。

2.运动和UV限制

这里的水滴是匀速的,看起来比较怪,所以我们修改一下RainDropAndPointUV函数,这里同时对UV的偏移做了限制:

         float2 RainDropAndPointUV(float3 N,float2 fracUV,float time,float4 tillingOffset,float Cut,out float cut){cut=Cut;cut=floor(cut+N.y);half2 offsetUV=0;offsetUV.x=(N.x-0.5)*0.5;float v=frac(N.y+time);v=smoothstep(0,0.85,v)*smoothstep(1,0.85,v)-0.5;offsetUV.y=v*0.5+0.5;float2 uv=(fracUV-offsetUV)*tillingOffset.xy;//float2 uv=fracUV*2;return uv;}float4 configureDrop=UVConfigure(uv,0,float2(5,1), _RainDropAmount);float3 N=N13(configureDrop.zw);float2 rainDrop=RainDropAndPointUV(N,configureDrop.xy,t*0.5,float4(1,5,0,0),_RainDropAmount,cut);

两个smoothstep的函数效果如图所示:


这里把第一层的的运动值设为0,就可以很清楚的看到水滴显示往下移动到1,再由最大值变回0:


再加上第一层的运动,水滴的效果就看起来是时而快时而慢的了

             float4 configureDrop=UVConfigure(uv,0.02*t,float2(5,1), _RainDropAmount);float3 N=N13(configureDrop.zw);float2 rainDrop=RainDropAndPointUV(N,configureDrop.xy,t*0.05,float4(1,5,0,0),_RainDropAmount,cut);col.rg=rianDropMask.rg;

3.水滴拖尾

接下来可以制作水滴的拖尾效果了,为此我们需要两个变量,一个是未经偏移的UV坐标、另一个是偏移值,所以我们需要RainDropAndPointUV返回偏移UV值:

         float4 RainDropAndPointUV(float3 N,float2 fracUV,float time,float4 tillingOffset,float Cut,out float cut){cut=Cut;cut=floor(cut+N.y);half2 offsetUV=0;offsetUV.x=(N.x-0.5)*0.5;float v=frac(N.y+time);v=smoothstep(0,0.85,v)*smoothstep(1,0.85,v)-0.5;offsetUV.y=v*0.5+0.5;float2 uv=(fracUV-offsetUV)*tillingOffset.xy;return float4(offsetUV,uv);}float4 rainDrop=RainDropAndPointUV(N,configureDrop.xy,t*0.05,float4(1,5,0,0),_RainDropCut,cut);//rainDrop.xy:偏移值,configureDrop.xy:原始坐标float rainDropTrace=RainTrace(rainDrop.xy,configureDrop.xy)*cut;

首先需要确定拖尾的长度,其次是水滴的宽度

         float RainTrace(float2 uv,float2 fracUV){//横向X轴以水滴为中心向两边的渐变float lineWidth=abs(fracUV.x-uv.x);//纵向Y轴,以Y轴最大边缘到水滴位置的0~1的渐变float lineHeight=smoothstep(1,uv.y,fracUV.y);}


原文章使用了以下代码对拖尾宽度的进行限制:

             float lineWidth=abs(fracUV.x-uv.x);float lineHeight=smoothstep(1,uv.y,fracUV.y);float base=sqrt(lineHeight);float widthMax=base*0.23;float widthMin=base*0.15*base;lineWidth=smoothstep(widthMax,widthMin,lineWidth);

uv.y=0fracUV.yX轴,x越大,有一部分它们间距基本一样,而越后面间距越小:

如果我们这时直接输出lineWidth会得到以下效果:


这是因为lineWidth在fracUV.y小于uv.y后都等于1,所以我们需要进行限制,并使拖尾有一个渐变,最后的函数为:

         float RainTrace(float2 uv,float2 fracUV){float lineWidth=abs(fracUV.x-uv.x);float lineHeight=smoothstep(1,uv.y,fracUV.y);float base=sqrt(lineHeight);float widthMax=base*0.23;float widthMin=base*0.15*base;lineWidth=smoothstep(widthMax,widthMin,lineWidth);float trace=smoothstep(-0.02,0.02,fracUV.y-uv.y)*lineHeight;trace*=lineWidth;return max(0,trace);}

3.阴影和结束

最后一部分也没什么好写的,大都是水滴效果上的东西,值得说到的是他用两层Mask——一层大、一层小,然后用一个正上方的光照去照亮他,就得到了水滴下的一小圈阴影:

     float3 yDir=float3(0,1,0);fixed downMask=mask.x+dropMask.x;fixed riseMask=mask.y+dropMask.y;float RdotZ=smoothstep(-1.3,0.3,dot(yDir,riseNormal));fixed fullMask=saturate(lerp(1,RdotZ,riseMask)+downMask);


最后的效果:



完整代码在Github——Alutemurat,以后写的效果都会放那了。
感觉自己写得有点乱,不知道能不能讲明白。

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 实现磨皮效果

    最近在做一些UI使用的shader,大部分是对UV进行一些操作,今天看需求文档时发现美术同学的要求里有一项是类似磨皮的效果,本来我也比较好奇这些美颜效果都是怎么做的,所以就趁此机会实验一下.查了一大堆 ...

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

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

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

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

最新文章

  1. 【图论】用一道题从本质上讲清楚Floyd算法
  2. 1.2 初窥输入输出、控制语句
  3. .net core 生成html,ASP.NET Core 中如何将 .cshtml 视图文件生成为 .cs 文件
  4. 十一在家都逛哪些技术网站?(程序员必备58个网站汇总)
  5. js中的json ajax,js结合json实现ajax简单实例
  6. 【12期 3月期刊 自荐】
  7. apache camel_您的Apache Camel应用程序现在包括现成的文档
  8. 每天一个linux命令(lsof)
  9. koa mysql mongodb_koa 操作MongoDB数据库
  10. 使用Intent启动常用的应用与服务
  11. 一文看尽预训练语言模型
  12. Mybatis实现高级映射一对一、一对多查询
  13. android soundpool 播放音效,Android使用SoundPool播放短音效
  14. 电容器充放电的原理是什么?
  15. 离线安装selenium
  16. 小米电视es65、ea65、ex65和ec65区别
  17. Reog Ponorogo是爪哇族人在印尼的一个部落的传统舞蹈
  18. 趣谈网络协议-第二模块-底层网络知识详解:4陌生的数据中心2CDN和数据中心
  19. 当前NBIoT设备的省电技术之PSM/DRX/eDRX
  20. tomcat服务莫名其妙停止

热门文章

  1. 手把手教你用WOS数据库解决英文文献
  2. -source 1.6 中不支持 diamond 运算符
  3. html5项目心得体会,HTML5开发心得。
  4. 创客匠人用户分组:用户定向运营,不浪费一滴流量
  5. 面向中国企业关系抽取的双向门控递归单元神经网络
  6. gpu精粹2.高性能图形芯片和通用计算机编程技巧,哎哟喂啊
  7. java signal信号_Java Signal实例
  8. Linux signal捕获
  9. 雅虎免费邮箱开通POP3和自动转发的方法
  10. 计算机版音乐教学反思,音乐教学反思(通用3篇)