用three.js实现边缘浪花和退潮湿岸特效

在three.js的官网上有一个国外大佬写的Webgl网页,名叫coastalworld

可以看到在海平面和其他物体接触时,会有一条颜色带,这个就是简易版的边缘浪花效果,如何实现呢?

1.深度纹理

由于篇幅的原因,深度纹理的这种实现方式这里先不阐述了,感兴趣的话将来可能还会再出一片文章来解析一下这种实现方式(实际上是懒癌犯了)

2.修改被影响的物体的着色器

这种方式也是coastalworld 所采用的,即在h的这段高度(也可以称为浪花的厚度)内的顶点的颜色是白色, 需要用到onBeforeCompile这个函数,我们需要去修改他片元着色器中的代码,在此之前我们还需要做一些准备工作,获取海平面在世界空间坐标系下的position.y,可以通过getWorldPosition这个函数获取 并且作为uniform传入到着色器中

js 复制代码
const localToWorld = new THREE.Vector3()
mesh.getWorldPosition(localToWorld)

通过localToWorld.y即可取到世界空间坐标系下的y值了,到此位置我们的准备工作算是做完了,接下来开始修改片元着色器的代码

由于我们无法在外界访问到onBeforeCompile回调函数中的参数,所以我们可以利用js的特性去修改shader中的数据

js 复制代码
const params = {
uPosY:{value:0
}

同时也不要忘了在帧动画函数中更新数据

js 复制代码
params.uPosY.value = ocean.PosY                           

要想在规定的厚度(高度内)让顶点颜色变成白色我们可以使用glsl的内置函数step,同岁为了让边缘不是那么锐利,可以使用smoothstep来进行处理

step(edge,x):阶跃函数。当x < edge时返回0,否则返回1

smoothstemp(edge0, edge1, x):平滑阶跃函数。当x <= edge0,返回0,当x >= edge1时,返回1。否则在[0, 1]区间执行Hermite插值(edge0 < x < edge1)

js 复制代码
 m.onBeforeCompile = (shader) => {
                    shader.uniforms.uPosY = params.uPosY
                   shader.vertexShader = shader.vertexShader.replace(
                        /* glsl */ `#include <common>`,
                        /* glsl */ `#include <common>
                        varying vec4 world_pos;
                        `
                    ).replace(
                        /* glsl */ `#include <project_vertex>`,
                        /* glsl */ `#include <project_vertex>
                        //获取世界空间坐标系下的顶点位置
                        vec4 modelPosition = modelMatrix *  vec4( transformed, 1.0 );
                        world_pos = modelPosition;
                        `
                    );
                    shader.fragmentShader = shader.fragmentShader.replace(
                        /* glsl */ `#include <common>`,
                        /* glsl */ `#include <common>
                        uniform float uPosY 
                        varying vec4 world_pos;
                    ).replace(
                        /* glsl */ `#include <opaque_fragment>`,
                        /* glsl */ `#include <opaque_fragment>
                        /* 边缘浪花 */
                        float thicknesses = .05; //厚度
                        float edge = 0.01;//边缘
                        float c = step(uPosY,world_pos.y) - smoothstep(uPosY+thicknesses,uPosY+thicknesses+edge,world_pos.y);
                        gl_FragColor = vec4(vec3(c) ,gl_FragColor.a);
                       `
                    )

                }

注:在three.js的最近几个版本中片元着色器需要替换的位置为#include <opaque_fragment> 至此,我们的边缘浪花特效算是完成了

接下来就是边缘浪花特效的实现了,这里我只提供一种我自己的思路,如果有更好的思路还请大佬补充

由于该项目里海平面涨潮和退潮的高度都是固定的,所以我们实现退潮湿岸的效果就相对容易一些,我们需要做一些准备工作,获取涨潮时的最大高度退潮的总时间是否退潮

在帧动画函数中

js 复制代码
  lastPosY = PosY //需要世界空间坐标系下的position.y
  
  //海平面 退/涨潮的函数
  mesh.position.y = Power1.easeInOut(Math.abs((this._uniform.uTime.value % 1) - 0.5) * 2) * 0.5 + SEA_LEVEL;

  if (lastPosY > PosY) {
      maxPosY = Math.max(lastPosY, maxPosY)
      ebbTime += dt;
      // 退潮
      Ebb = true
    } else {
      Ebb = false
      ebbTime = 0
         }

我们需要让h这一段内的顶点颜色暗一点,使用一个系数乘以原本的颜色即可,但是这样子会让沙滩整体变暗,要怎么做呢,依然是利用step 函数,算出一个需要减去的rgb 值,让原本在这一段内的顶点的原本颜色减去我们算出来的这个值 就可以实现了,通过计算当前时间和总退潮时间的百分比,应用到需要减去的这个值上,就实现湿岸慢慢褪去的效果

代码如下:

glsl 复制代码
 float d = 0.;
    //是否进行退潮
  if(uEbb){
  //利用smoothstep函数让边缘不是那么锐利,利用clamp函数限制变暗的程度
    d=clamp(step(uPosY,world_pos.y) - smoothstep(uMaxPosY,uMaxPosY+.05,world_pos.y),0.0,0.2);
    }else{
    d=0.0;
     }
     
    //计算退潮的当前时间和总时间的比值,并取反
     float progress = 1.-smoothstep(0.,1.,uEbbTime / 5.);
    //计算出当前需要减去的颜色
     vec3 reduceColor = vec3(gl_FragColor.xyz)*d*progress;
    //最终的颜色
     vec3 finColor = gl_FragColor.xyz +vec3(c)-reduceColor;

 gl_FragColor = vec4(finColor ,gl_FragColor.a);

总结

到此为止,我们已经实现了一个简易版的边缘浪花和退潮的特效了,其实还有很多很多可以优化的地方,希望大家可以从中学到思路,并且优化自己的算法,当然如果你有更好的思路也可以和我分享,谢谢!

相关推荐
也无晴也无风雨27 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤5 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui