用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);

总结

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

相关推荐
Ten peaches6 分钟前
Selenium-Java版(环境安装)
java·前端·selenium·自动化
心.c18 分钟前
vue3大事件项目
前端·javascript·vue.js
姜 萌@cnblogs28 分钟前
【实战】深入浅出 Rust 并发:RwLock 与 Mutex 在 Tauri 项目中的实践
前端·ai·rust·tauri
蓝天白云下遛狗35 分钟前
google-Chrome常用插件
前端·chrome
多多*1 小时前
Spring之Bean的初始化 Bean的生命周期 全站式解析
java·开发语言·前端·数据库·后端·spring·servlet
linweidong1 小时前
在企业级应用中,你如何构建一个全面的前端测试策略,包括单元测试、集成测试、端到端测试
前端·selenium·单元测试·集成测试·前端面试·mocha·前端面经
满怀10152 小时前
【HTML 全栈进阶】从语义化到现代 Web 开发实战
前端·html
东锋1.32 小时前
前端动画库 Anime.js 的V4 版本,兼容 Vue、React
前端·javascript·vue.js
满怀10152 小时前
【Flask全栈开发指南】从零构建企业级Web应用
前端·python·flask·后端开发·全栈开发
小杨升级打怪中2 小时前
前端面经-webpack篇--定义、配置、构建流程、 Loader、Tree Shaking、懒加载与预加载、代码分割、 Plugin 机制
前端·webpack·node.js