开源 2D 实时水面反射效果,源码详解!

引言:插件 Easy NavMeshBenchMark 性能检测的作者孙二喵,从开发者王师傅的论坛分享中获得启发,实现了 2D 实时水面反射效果,Demo 免费开源。

2D 实时水面反射

3db8f6c2ce8f3485f0cdef1b07f48a99.gif

Demo 效果

资源地址:

https://store.cocos.com/app/detail/3900

功能特点

整个方案使用了3个摄像机:

  • RT 摄像机负责所有游戏物体,它会获取 Rendertexture 渲染到地面和水面的2个精灵上。

2f8292c960a2bff3baf5920bdaa64f2f.jpeg

  • FixCamera 是固定摄像机,只会拍摄地面和水面这2个精灵。

8c4e3f7421e092b8fbabc17abb0505e0.jpeg

  • UI 相机负责 UI 层级。

最终的 DrawCall 为8个,包括1个基础 DC +1个背景+1个角色+2个技能特效+1个 UI+ 1个水面渲染+1个地面渲染。

63f55a265dfce997090cce90e20575cc.jpeg

即使增加1个新的精灵类,我们整个 DrawCall 也只会增加1个。

c7f4e591bba08254231e688afb998200.jpeg

针对反射方案,第一个 RT 摄像机负责地面部分的截取,这里把 rect 设置成了 -0.37 , 只截取63%的上半部分屏幕。

246e83fd35424e7e719eb21af3e3bb6c.png

针对地面部分,我们使用了自定义 Shader,移植了默认的 Sample from RT,使用了和 Canvas 一样的尺寸,针对屏幕适配,Canvas 上的 rtAdapter 里有解决方案。

6a8c7263c07f93b13708e129adcbad87.png

水面部分只使用了一半的屏幕高度,这里加了 Tiling 的 Macro,设置了 UV。

db96e93a286adfbccb279e18d819f4d1.png

水浪效果,通过 update wavetexture 的 uv 得到一个偏移量 offset,并与原始图片的 uv 相加。

5be798a02a8699cd6c04bbee690c8d09.png

波浪效果有了,但是看起来比较单一,这里希望水面的颜色更有多彩,同时有一点邪恶的小紫色。

2bb291e1f4858897acbe821bb59d1e24.jpeg

加了 casutic 效果后,水面有了焦散的流动。

c60e3d5bf6ea7b974d62d2e1acbd6cd4.png

90196b7abe3861d1f3a907175dc68149.jpeg

为了让水面底部看起来颜色更深,这里使用了 smoothstep 根据 uv.y 做变化。

e0583d1eb87188be69afaf02b8bdd3ba.png

337906f6f0d501b7f7e1591bddb37def.jpeg

为了让水面上部看起来更通透,仿佛有水波打到岸,对透明度也使用了 smoothstep。

f48eb6bc0a9fa9226b3acedeb2550d42.png

322af8144a8d4f31b7bca19db21147ca.jpeg

7811380fcc69e85573254d6be1796e09.png

最后给小姐姐加上一个 FSM 状态机和一个 spriteatlas 的 animator controller,就可以拖到摇杆跑起来啦!

6a9d2283deca47f6122a216695536903.jpeg

这里要注意的是,我只移动了 RT 相机,没有移动水面的 renderer,所以水面会看起来比较奇怪,可以在人物移动的时候,给水面的水波和焦散一个反方向(下次一定,下次一定)。

资源链接

  • 点击文末【阅读原文】下载 Demo:

https://store.cocos.com/app/detail/3900

  • 论坛专贴:

https://forum.cocos.org/t/topic/137681

实现过程

以下为社区开发者「王师傅」发布在论坛的技术分享。作者花式使用噪声图实现了 2D 水面波浪效果,这也是上文孙二喵 Demo 的方案参考,感兴趣的小伙伴可以阅读一下。帖子地址:

https://forum.cocos.org/t/topic/121407

08f858fb355ea71a6417123fb4859c04.gif

最终效果

场景准备

首先弄个干净的 Shader,然后布个游戏场景。

ce5602411ce43673c7bc71a01efd301a.jpeg

主场景图

354bcade5fb4a80d44ff38e5015ca658.png

反转y轴,放在下方做水面用的

cb5610f1b721f16b3a83fafcedffe4c0.png

摆一下场景

NewSprite 缩放是 -0.55 ,负数是为了反转 y 轴做镜像效果。

NewSprite 用上我们新建的干净的 Shader 和材质,Shader 删掉片元着色器我们不准备用的代码,老规矩,从一张黑图开始。

precision highp float;
  
  #include <alpha-test>
  #include <texture>
  #include <cc-global>

  in vec4 v_color;

  #if USE_TEXTURE
  in vec2 v_uv0;
  uniform sampler2D texture;
  #endif

  void main () {
    vec3 color = vec3(0.);
    // 弄个t接收cc_time.x, *= 0.6是因为正常速太快,变慢点 备用
    float t = cc_time.x * 0.6;
    
    gl_FragColor = vec4(color,1.);
  }

466f1165d8eb884130e1143c29c356c4.png

噪声图

准备一张噪声图:

d3ab1a8503b697e09f30b37ef336fe3c.png

// 文件开头修改加入texture2的声明

      properties:
        texture: { value: white }
  // 添加下面的行 本行注释删掉,写在这里只是为了标识这是添加的
        texture2: {  value: white }
        
  ....
  ....
  
  
  // 片元着色器修改成下面这样:
  .....
  
  
  uniform sampler2D texture;
  // 添加下面的行
  uniform sampler2D texture2;

  void main () {
    vec3 color = vec3(0.);
    // 弄个t接收cc_time.x, *= 0.6是因为正常速太快,变慢点
    float t = cc_time.x * 0.6;

    // 对噪声图取样x通道,显示在屏幕上
    color += texture2D(texture2,v_uv0).x;
    
    gl_FragColor = vec4(color,1.);
  }

然后打开点击材质,把噪声图拖拽到材质面板中的 texture2 中。看下现在的样子:

926c05df4ca54ee02a13bfe089d7cd68.png

水波效果

接着我们用这个噪声图实现水波荡漾的效果。取噪声图的 xy 两个通道,作为对场景图取样所用 uv 的偏移值,会出来什么效果呢?

片元着色器 main 函数改成下面这样:

void main () {
    vec3 color = vec3(0.);
    // 弄个t接收cc_time.x, *= 0.6是因为正常速太快,变慢点
    float t = cc_time.x * 0.6;
// + t * vec2(.5,.1)
    
    vec2 off1 = texture2D(texture2,v_uv0).xy;
    // 偏移值缩放0.1倍,不然波纹太过分了
    off1 *= .1;

    color += texture2D(texture,off1 + v_uv0).xyz;
    
    gl_FragColor = vec4(color,1.);
  }

ad0693a7688a837f53a7f61143bb2fe2.png

可以看到,画面明显扭曲了,但是现在还是静态的,需要加入时间参数让这个扭曲动起来。

偏移值的0.1的缩放值还是太大,下面用0.01的试试:

void main () {
    vec3 color = vec3(0.);
    // 弄个t接收cc_time.x, *= 0.6是因为正常速太快,变慢点
    float t = cc_time.x * 0.6;

    // v_uv0 + t * vec2(.5,.1) 这里的t是上面的时间,会持续增大的一个数值
    // vec2(.5,.1) 是我随便写的一个方向向量,方向*时间 作为uv的偏移
    // 噪声图设置 WrapMode = Repeat 否则偏移值超过vec2(1.,1.)之后就取不到值了,要改成repeat才可以取值
    vec2 off1 = texture2D(texture2,v_uv0 + t * vec2(.5,.1)).xy;
    // 偏移值缩放0.1倍,不然波纹太过分了
    off1 *= .01;

    color += texture2D(texture,off1 + v_uv0).xyz;
    
    gl_FragColor = vec4(color,1.);
  }

72672e2751b14cecde67e7a0792d5b70.gif

现在,水面波动已经明显了。

水面明暗

接着实现一下水面的明暗变化,镜头越近颜色越暗(其实就是屏幕越往下变颜色越黑)。

color += texture2D(texture,off1 + v_uv0).xyz;

    // 参数0-1是正好 从黑到白的,用-.5->1.3这个范围,色值就是不太黑到不太白,免得颜色太极端
    color *= smoothstep(-.5,1.3,v_uv0.y) - .3;

6023bea2c8c2084d0fd8884665dec8f2.png

水面浮沫

添加一些水面的浮沫,丰富细节。

这里又要用噪声图了。我们对噪声图再来一次取值:

// baseuv做变换对噪声图取值
    vec2 baseuv = v_uv0;
    // 让噪声图x轴重复四次,y轴重复三次
    vec2 scale = vec2(4.,3.);
    baseuv = baseuv * scale;
    
    float c1 = texture2D(texture2,baseuv).x;
    color = vec3(c1);
    
    gl_FragColor = vec4(color,1.);

9060a2d79cb712a6dffd7336f1b18430.png

现在看着像海浪,并不是我们想要的浮沫效果。怎么从这个海浪变换一个浮沫的效果出来呢?

float c1 = texture2D(texture2,baseuv).x;
    // 加这一行
    c1 = smoothstep(0.23,0.,c1);
    color = vec3(c1);

42dfc79a211a0fee708bb34be8d12903.png

啥原理呢,上面那个大浪啊,值0-1的,用 smoothstep 对大浪做个变换,只留下 0-0.23 的色值,大于 0.23 的都变成了纯黑色。

大概画一个函数曲线:

63187062943e141c00a9917fbaa3174c.png

c1 的值输入函数曲线后,0-0.23范围输出了 0->1 的值,越黑的地方越亮,亮的就变黑色了,也就得到了上面图的效果。

接着让浮沫漂流起来。思考一下,如果让浮沫整体往一个方向漂是不是有点太单调太假了,不行,要做分行速度差。分20行吧,每行做移动速度不同的漂流:

float random (vec2 st) {
    return fract(sin(dot(st.xy,
                         vec2(12.9898,78.233)))*
        43758.5453123);
  }
  
  ......
  上面的函数加在main函数外面
  下面的东西是main里面的
  ......
  
  
    // 白沫的uv 所以取名mouv!
    vec2 mouv = v_uv0;
    // 做y轴分行,原理见彩虹那篇帖子
    mouv.y *= 20.;
    // n3就是行id 取名无力
    float n3 = floor(mouv.y);
    // n4就是用行id随机一个对应的随机值出来,每行一个随机值作为行的运动速度
    // 所以对于y轴在同一行(注意上面*=20,所以共20行),我们这里计算出一个改行的运动速度,放在这备用
    float n4 = random(vec2(n3,n3)) + .3;

    // baseuv做变换对噪声图取值
    vec2 baseuv = v_uv0;
    // 让噪声图x轴重复四次,y轴重复三次
    vec2 scale = vec2(4.,3.);
    baseuv = baseuv * scale;

    // 取值用的uv加上向左的移动 t是上面用过的那个cc_time.x哈,*0.1不然太快了
    // n4是上面计算好的速度哈
    baseuv.x += t * .1 * n4;
    
    float c1 = texture2D(texture2,baseuv).x;
    c1 = smoothstep(0.23,0.,c1);
    color = vec3(c1);

85afbfb7a3c80a42f4b8cc3a257ad88e.gif

最终效果

综合上述几个实现后,我们得到了最终的呈现效果,完整代码:

// Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.  

CCEffect %{
  techniques:
  - passes:
    - vert: vs
      frag: fs
      blendState:
        targets:
        - blend: true
      rasterizerState:
        cullMode: none
      properties:
        texture: { value: white }
        texture2: {  value: white }
        alphaThreshold: { value: 0.5 }
}%


CCProgram vs %{
  precision highp float;

  #include <cc-global>
  #include <cc-local>

  in vec3 a_position;
  in vec4 a_color;
  out vec4 v_color;

  #if USE_TEXTURE
  in vec2 a_uv0;
  out vec2 v_uv0;
  #endif

  void main () {
    vec4 pos = vec4(a_position, 1);

    #if CC_USE_MODEL
    pos = cc_matViewProj * cc_matWorld * pos;
    #else
    pos = cc_matViewProj * pos;
    #endif

    #if USE_TEXTURE
    v_uv0 = a_uv0;
    #endif

    v_color = a_color;

    gl_Position = pos;
  }
}%


CCProgram fs %{
  precision highp float;
  
  #include <alpha-test>
  #include <texture>
  #include <cc-global>

  in vec4 v_color;

  in vec2 v_uv0;
  uniform sampler2D texture;
  uniform sampler2D texture2;

  float random (vec2 st) {
    return fract(sin(dot(st.xy,
                         vec2(12.9898,78.233)))*
        43758.5453123);
  }

  void main () {
    vec3 color = vec3(0.);
    // 弄个t接收cc_time.x, *= 0.6是因为正常速太快,变慢点
    float t = cc_time.x * 0.6;

    // v_uv0 + t * vec2(.5,.1) 这里的t是上面的时间,会持续增大的一个数值
    // vec2(.5,.1) 是我随便写的一个方向向量,方向*时间 作为uv的偏移
    // 噪声图设置 WrapMode = Repeat 否则偏移值超过vec2(1.,1.)之后就取不到值了,要改成repeat才可以取值
    vec2 off1 = texture2D(texture2,v_uv0 + t * vec2(.5,.1)).xy;
    // 偏移值缩放0.1倍,不然波纹太过分了
    off1 *= .01;

    color += texture2D(texture,off1 + v_uv0).xyz;

    // 参数0-1是正好 从黑到白的,用-.5->1.3这个范围,色值就是不太黑到不太白,免得颜色太极端
    color *= smoothstep(-.5,1.3,v_uv0.y) - .3;

    // 白沫的uv 所以取名mouv!
    vec2 mouv = v_uv0;
    // 做y轴分行,原理见彩虹那篇帖子
    mouv.y *= 20.;
    // n3就是行id 取名无力
    float n3 = floor(mouv.y);
    // n4就是用行id随机一个对应的随机值出来,每行一个随机值作为行的运动速度
    // 所以对于y轴在同一行(注意上面*=20,所以共20行),我们这里计算出一个改行的运动速度,放在这备用
    float n4 = random(vec2(n3,n3)) + .3;

    // baseuv做变换对噪声图取值
    vec2 baseuv = v_uv0;
    // 让噪声图x轴重复四次,y轴重复三次
    vec2 scale = vec2(4.,3.);
    baseuv = baseuv * scale;

    // 取值用的uv加上向左的移动 t是上面用过的那个cc_time.x哈,*0.1不然太快了
    // n4是上面计算好的速度哈
    baseuv.x += t * .1 * n4;
    
    float c1 = texture2D(texture2,baseuv).x;
    c1 = smoothstep(0.23,0.,c1);
    color += vec3(c1);
    
    gl_FragColor = vec4(color,1.);
  }
}%

往期精彩

9074715b297a9f5a14ae4cd4e7e2aab9.png8dd71948b13633aa808256376dbe8535.png0e7ca44a3afdb1d0a0516934a43e0d3f.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值