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