涟漪这个效果我相信很多人都尝试实现过,也有各种实现方法。在这里,我实现的方法是使用Custom节点,用算法生成法线。接下来向大家分享一下思路,看一下最终效果图。文末提供了材质球百度云链接。

最终效果图

简单地说一下原理:先用UV做出伪随机的格子,每个格子就是一个单独的UV,不过具有不同的灰度值,然后在格子的中心生成多个不同大小的同心圆,再做缩放和边缘混合。

随机噪波生成

首先,我们先定义一个三维向量用来进行三层不同大小涟漪的计算,因为UV的取值范围是0-1,所以我们定义的float3的值必须在0-1之间。这个值是一个系数,并不是实际的大小:

float3 ripple_scale3=float3(0.1,0.2,0.3)

然后,我们需要生成带有不同的灰度的UV格子:

float3 p3 = frac(float3(p.xyx) * ripple_scale3);
p3 += dot(p3, p3.yzx +20);
return frac((p3.xy + p3.yz) * p3.zy);

p是一个二维的向量,为了能和ripple_scale相乘,所以我们可以随便取它的.XYX或者.XYY。p3则是一个累加值,最后返回的值则是一个float2,因为UV是float2,随便取两个轴进行上述的运算就可以。然后我们定义一个一维的向量再次进行如下运算:

float ripple_scale1 = 0.1;
float3 p3 = frac(float3(p.xyx) * ripple_scale1);
p3 += dot(p3, p3.yzx + 10);
return frac((p3.x + p3.y) * p3.z);

上述的两个运算主要是为了得出一个足够随机的值,也可以用其它算法替代。我们如果将UV tiling 10次,然后floor之后作为上面代码中p的值,先进行三维的运算然后和下面一维的相乘可以得到如下结果(只要得到类似如下结果的算法都可以):

这个算法我们需要将其作为一个function,因为需要循环计算,所以得用一个strcut结构体进行声明后调用。

float ripple_scale1 = 0.1;一维随机数种子
float3 ripple_scale3 = float3(0.1, 0.11, 0.09);//三维随机数种子
float max_radius = 1;
struct rain
{float ripple1(float2 p){float3 p3 = frac(float3(p.xyx) * ripple_scale1);p3 += dot(p3, p3.yzx + 10);return frac((p3.x + p3.y) * p3.z);}float2 ripple2(float2 p){float3 p3 = frac(float3(p.xyx) * ripple_scale3);p3 += dot(p3, p3.yzx +20);return frac((p3.xy + p3.yz) * p3.zy);}
};
rain ra;

涟漪形状生成

接下来就是通过随机值产生涟漪并且动起来,这步需要循环采样,先把需要用到的变量声明一下:

float tiling = 10;//UVtiling次数
float2 uv = (UV) * tiling;//UV
float2 p0 = floor(uv);//floor之后会产生tiling个数长宽的UV格子
float i = 0;//x轴循环次数
float j = 0;//y轴循环次数,因为UV是双轴的所以有两个方向
float2 pi = 0;记录每个UV格子的不同灰度
float2 circles = 0;//圆圈
float2 p = 0;//初始位置

再准备循环体,把pi放入循环体进行累加:

for (j = (- max_radius);j <= max_radius; j++)for (i = - max_radius; i<= max_radius; i++){pi = p0 +float2(i, j);}}

由于上述累加结果过大,我们将其进行除以tiling次数方便观察,很明显每个格子已经有了不一样的灰度值,因为i、j的值一直在累加。

pi循环后的值

然后我们在pi下面将pi的值代入三维的随机数function里进行运算:

for (j = (- max_radius);j <= max_radius; j++){for (i = - max_radius; i<= max_radius; i++){pi = p0 +float2(i, j);float2 hsh = ra.ripple2(pi);//第一次随机运算}}

hsh循环后的值(随机化)

有了第一次就有第二次,我们继续将hsh的值再代入三维的随机值进行二次随机化,然后和pi的值加起来就能得到带有pi(UV位置信息的随机值),也就是我们的p。

for (j = (- max_radius);j <= max_radius; j++){{pi = p0 +float2(i, j);float2 hsh = ra.ripple2(pi);//第一次随机运算p = pi + ra.ripple2(hsh);//得到带位置信息的随机值}}

p的值,同样便于观察除以了tiling值

然后,我们需要定义一个时间值t,同样需要随机化,但是是用一维的随机化函数(如果继续用三维会产生tiling,参考下图(下图中的tiling值为20)),然后frac做0-1循环。

for (j = (- max_radius);j <= max_radius; j++){for (i = - max_radius; i<= max_radius; i++){pi = p0 +float2(i, j);float2 hsh = ra.ripple2(pi);//第一次随机运算p = pi + ra.ripple2(hsh);//得到带位置信息的随机值float t = frac(0.3 * iTime + ra.ripple1(hsh));//随机时间产生,0.3为速度,可在外部调整}}

只采用一种随机化的t

采用两种随机化的t

然后我们需要得出实际的位置,我这边用v表示,只需要减去UV值就行了,因为需要和UV对应起来。如果不减,我们最后将得不到法线(平的):

for (j = (- max_radius);j <= max_radius; j++){for (i = - max_radius; i<= max_radius; i++){pi = p0 +float2(i, j);float2 hsh = ra.ripple2(pi);//第一次随机运算p = pi + ra.ripple2(hsh);//得到带位置信息的随机值float t = frac(0.3 * iTime + ra.ripple1(hsh));//随机时间产生,0.3为速度,可在外部调整float2 v = p - uv;//实际位置信息}}

接下来就是计算圆了。圆的计算公式是length(position)-R,其中position是圆心在UV中的位置,R是圆的半径。我们这边是max_radius+1,如果不加1,所有值都会比原来的小,这样会导致法线强度太弱,然后乘以我们得到的t就可以产生扩散的圆了:

for (j = (- max_radius);j <= max_radius; j++){for (i = - max_radius; i<= max_radius; i++){pi = p0 +float2(i, j);float2 hsh = ra.ripple2(pi);//第一次随机运算p = pi + ra.ripple2(hsh);//得到带位置信息的随机值float t = frac(0.3 * iTime + ra.ripple1(hsh));//随机时间产生,0.3为速度,可在外部调整float2 v = p - uv;//实际位置信息float d = length(v) - (max_radius + 1) * t;//计算圆}}

计算出来的扩散圆

但是因为这个只是其中一部分,圆形状的累加我们等会再做,先把涟漪的形状做出来。其实很简单,将得到的d进行sine函数运算一下就能得到涟漪,将d和一个值相乘能得到不同圈数的涟漪,然后用smoothstep控制涟漪的边缘虚实效果。我们这边要做两层,用两层的插值来模拟渐变,用h来控制涟漪的偏移值。

for (j = (- max_radius);j <= max_radius; j++){for (i = - max_radius; i<= max_radius; i++){pi = p0 +float2(i, j);float2 hsh = ra.ripple2(pi);//第一次随机运算p = pi + ra.ripple2(hsh);//得到带位置信息的随机值float t = frac(0.3 * iTime + ra.ripple1(hsh));//随机时间产生,0.3为速度,可在外部调整float2 v = p - uv;//实际位置信息float d = length(v) - (max_radius + 1) * t;//计算圆float h = 1e-3;//就是0.001float d1 = d - h;float d2 = d + h;float p1 = sin(31. * d1) * smoothstep(-0.6, -0.3, d1) * smoothstep(0., -0.3, d1);float p2 = sin(31. * d2) * smoothstep(-0.6, -0.3, d2) * smoothstep(0., -0.3, d2);}}

d*30

d*60

p1的效果

涟漪渐变效果

能动起来后,我们需要一个到达最大值后渐隐的效果,通过两者的差值乘以时间的反向,即1-0来模拟边缘的渐变效果,乘以两次时间是为了增强对比度。

for (j = (- max_radius);j <= max_radius; j++){for (i = - max_radius; i<= max_radius; i++){pi = p0 +float2(i, j);float2 hsh = ra.ripple2(pi);//第一次随机运算p = pi + ra.ripple2(hsh);//得到带位置信息的随机值float t = frac(0.3 * iTime + ra.ripple1(hsh));//随机时间产生,0.3为速度,可在外部调整float2 v = p - uv;//实际位置信息float d = length(v) - (max_radius + 1) * t;//计算圆float h = 1e-3;//就是0.001float d1 = d - h;float d2 = d + h;float p1 = sin(31. * d1) * smoothstep(-0.6, -0.3, d1) * smoothstep(0., -0.3, d1);float p2 = sin(31. * d2) * smoothstep(-0.6, -0.3, d2) * smoothstep(0., -0.3, d2);circles = (p2 - p1) / (2. * h) * (1. - t) * (1. - t);//渐隐效果}}

达到最大值后渐隐效果

涟漪形状累加

然后我们乘以它原来的normalize后的position(即v),即可得到现在的正确的法线效果,最后将每次循环的结果累加起来就可以得到我们想要的涟漪,再乘以数值可以控制法线强度:

for (j = (- max_radius);j <= max_radius; j++){for (i = - max_radius; i<= max_radius; i++){pi = p0 +float2(i, j);float2 hsh = ra.ripple2(pi);//第一次随机运算p = pi + ra.ripple2(hsh);//得到带位置信息的随机值float t = frac(0.3 * iTime + ra.ripple1(hsh));//随机时间产生,0.3为速度,可在外部调整float2 v = p - uv;//实际位置信息float d = length(v) - (max_radius + 1) * t;//计算圆float h = 1e-3;//就是0.001float d1 = d - h;float d2 = d + h;float p1 = sin(31. * d1) * smoothstep(-0.6, -0.3, d1) * smoothstep(0., -0.3, d1);float p2 = sin(31. * d2) * smoothstep(-0.6, -0.3, d2) * smoothstep(0., -0.3, d2);circles = (p2 - p1) / (2. * h) * (1. - t) * (1. - t);//渐隐效果circles = 0.5 * normalize(v) * ((p2 - p1) / (2. * h) * (1. - t) * (1. - t));//得到正确法线方向circles=circles+circles;//效果累加}}

circles法线累加效果

有了这个,我们法线的形状对了,但是效果不美观。因为是累加起来的,所以循环结束后除以循环总次数,即可得到正确的效果:

circles /= float(max_radius*2+1)*(max_radius*2+1);

颜色矫正后的效果

生成法线

最后用求法线B通道的方式(开平方)求出B通道输出即可,为什么用点积做平方,我想大家都懂:

float3 n = float3(circles, sqrt(1. - dot(circles, circles)));

法线效果

下面是完整代码:

float3 ripple_scale= float3(0.1,0.2,0.3);
float max_radius = 2;
struct rain
{float2 ripple(float2 p){float3 p3 = frac(float3(p.xyx) * ripple_scale);p3 +=p3;return frac((p3.xy + p3.yz) * p3.zy);}
};
rain ra;float tiling = 10;
float2 uv = (UV) * tiling;
float2 p0 = floor(uv);
float j = 0;
float i = 0;
float2 pi = 0;
float2 circles = 0;
float2 p = 0;
for (j = (- max_radius);j <= max_radius; j++){for (i = - max_radius; i<= max_radius; i++){pi = p0 +float2(j, i);p = pi+ra.ripple(pi);float t = frac(iTime + ra.ripple(pi));float2 v = p - uv;float d = length(v) - (max_radius + 1) * t;float h = 0.01;float d1 = d - h;float d2 = d + h;float p1 = sin(30 * d1) * smoothstep(-0.6, -0.3, d1) * smoothstep(0., -0.3, d1);float p2 = sin(30. * d2) * smoothstep(-0.6, -0.3, d2) * smoothstep(0., -0.3, d2);circles += 0.5 * normalize(v)* ((p2-p1 )/(2. * h) * (1. - t) * (1. - t)) ;}}
circles /= pow((max_radius*2+1),2);
float3 n = float3(circles, sqrt(1. - dot(circles, circles)));
return n;

水底石头生成

石头部分直接用Parallax就可以了,如果看过我前面文章的朋友可以用我前面改过的算法:
《如何在UE4中用raymarch实现面片水体(采样贴图)》

POM石头的高度图

反射

水面反射依旧使用Reflection Vector,我们最后的法线就是输入到这个normal接口,我这边用另一张法线和上面的ripple做了min让圆圈稍微产生了些变化:

输出到Reflection Vector的结果

折射

对于折射,我这边是将上面的法线混合结果直接加到石头颜色的UV上就可以模拟了,当然强度得小一些。

折射和反射

透明度

最后用菲涅尔做出深度和透明度的变化就可以了:

菲涅尔制作深度和透明度

完整节点

材质球的百度云链接:
链接:百度网盘 请输入提取码
提取码:fp37

这是侑虎科技第1021篇文章,感谢作者落月满江树供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者主页:落月满江树 - 知乎,再次感谢落月满江树的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

如何在UE4中做出涟漪的效果相关推荐

  1. 如何在UE4中制作赛博朋克LED效果

    我们日常生活中常见的LED灯,如何在UE4中实现呢?其实实现起来非常简单,今天就带大家制作一个赛博朋克LED效果. 一.基础LED形状制作 正常LED的形状是由一个个小圆形组成,然后将圆形排列成想要的 ...

  2. mfc三视图和斜等测图实现_如何在UE4中实现NPR(非真实感)渲染效果?

    如何在UE4中实现NPR(非真实感)渲染效果?本文作者尝试在UE4中制作了秋叶原南出口的画面,并分享了全部的制作过程,希望对大家有所帮助. 在虚幻引擎中制作秋叶原南出口 我经常在artstation上 ...

  3. 在UE4中实现锥体下雨效果

    在UE4中实现锥体下雨效果 终于不懒,打起精神更新一下前段时间做过的一些东西.. 本文主要讲述一个特别的下雨效果在UE4中的制作过程.这个效果是模仿<天涯明月刀>手游的下雨效果做的,一开始 ...

  4. 如何在 UE4 中设置光线追踪功能

    在计算机图形领域中,光线追踪被看作是下一代极具颠覆性的图像技术.打造同真实世界一样"逼真"的视觉效果,则是光线追踪技术不断吸引开发者的关键,特别是游戏中更加真实的光照.3D人物,能 ...

  5. 【UE4】:如何在ue4中实现类刺客信条的鹰眼视觉效果

    对于游戏<刺客信条>(Assassin's Creed),相信很多人并不陌生. <刺客信条>是由育碧蒙特利尔工作室研发的动作冒险类游戏系列,于2007年发行第一部,游戏平台为P ...

  6. ue4渲染速度太慢_技术汇丨如何在UE4中实现最佳性能和高质量视觉效果

    合并网格 如之前在线框可视化解释中提到的,减少三角形和顶点的数量永远都是提高性能的方法,但是很多时候,一个单独网格比多个网格刻画集合图形的性能要好得多(一个有1000个顶点的网格可能比10个有100个 ...

  7. 如何在UE4中创建线程

    FRunnable和FRunnableThread方法对于大多数问题来说无疑是一个可行的解决方案. 但是,在创建许多任务时,您可能会达到CPU可以处理的并发上限,此时并发线程实际上会在争用CPU时间时 ...

  8. 如何在Unity中实现震动反馈效果

    一.Unity中提供了震动的接口:Handheld.Vibrate(); Unity提供的这个接口的震动时长是0.5s.不能缩短震动时长 二.在Unity中要自定义震动时长的话,那就得在android ...

  9. 自学虚幻引擎图文笔记:如何在UE4中做积雪材质

    废话不多说,直接来干货! 首先要思考一下 积雪是什么样的,比如积雪有厚的 有薄的,但是整体都可以总结为A+B,A物体,B是雪. 那么问题来了,这个积雪的材质怎么做呢?很难吗? 不!很简单~ 往下看 捋 ...

最新文章

  1. 剑指offer:面试题28. 对称的二叉树
  2. 明明程序员很累,为什么还有这么多人想入行?
  3. 计算机科学导论链式存储,计算机科学导论3.pdf
  4. ABAP-DOI技术的优化
  5. 小米手机安装https证书报错:无法安装该证书 因为无法读取该证书文件
  6. MySQL连接查询—笛卡尔乘积
  7. 返回后的数据处理_【掘金使用技巧2】掘金返回数据中时间的处理方法
  8. LuckyDraw app被评为Microsoft365 App Award
  9. 论文浅尝 | emrKBQA: 一个面向临床医疗问答的KBQA数据集
  10. require()与 require_once()、 include与include_once()
  11. 剑指offer面试题31. 栈的压入、弹出序列(链表)
  12. html5与css3基础教程课件,揭秘HTML5和CSS3教学幻灯片.ppt
  13. 监控系统中的几种服务器,监控系统各种服务器
  14. 正确的座机号码格式_固定电话的格式
  15. [转帖]团队管理 - 盖洛普Q12测评法
  16. 《动手学深度学习》学习笔记(五)-几种常见的卷积神经网络整理。
  17. 微信推出史上最简单「拍一拍」新功能,仅需一行代码,好友们都玩疯了!
  18. Stapler#攻略
  19. setClickable,setEnabled,setFocusable 的区别
  20. Android 限制启动应用最大使用内存,可供极限测试时使用

热门文章

  1. 【Linux云计算架构:第四阶段-Linux虚拟化-私有云-docker】第12章—— 搭建 Kubernetes 的 web 管理界面和基于 k8s 搭建+redis 集群案例
  2. Android实现雪花特效自定义view
  3. Ocam录屏软件安装包以及使用说明
  4. 怎样修复grub开机引导以及在Ubuntu中添加win7开机启动项
  5. 区块链是如何实现民主的?
  6. python实现画图画猪
  7. Android开机画面的具体修改方法
  8. JavaSE_第14章 File类与IO流
  9. 小程序流量主能赚多少_微信小程序流量主广告收入分成比例上调:由30%上调至50%[多图]...
  10. creator小功能----关于帧动画Animation和骨骼动画Skeleton一些有趣的东西