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

总结

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

相关推荐
Jiaberrr3 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy3 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白3 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、3 小时前
Web Worker 简单使用
前端
web_learning_3213 小时前
信息收集常用指令
前端·搜索引擎
tabzzz4 小时前
Webpack 概念速通:从入门到掌握构建工具的精髓
前端·webpack
200不是二百4 小时前
Vuex详解
前端·javascript·vue.js
滔滔不绝tao4 小时前
自动化测试常用函数
前端·css·html5
码爸4 小时前
flink doris批量sink
java·前端·flink
深情废杨杨4 小时前
前端vue-父传子
前端·javascript·vue.js