three.js 实现露珠滴落动画效果的示例代码

 更新时间:2021年3月1日 15:00  点击:2513

前言

大家好,这里是 CSS 魔法使——alphardex。

本文我们将用three.js来实现一种很酷的光学效果——露珠滴落。我们知道,在露珠从一个物体表面滴落的时候,会产生一种粘着的效果。2D平面中,这种粘着效果其实用css滤镜就可以轻松实现。但是到了3D世界,就没那么简单了,这时我们就得依靠光照来实现,其中涉及到了一个关键算法——光线步进(Ray Marching)。以下是最终实现的效果图

撒,哈吉马路由!

准备工作

笔者的 three.js模板 :点击右下角的fork即可复制一份

正片

全屏相机

首先将相机换成正交相机,再将平面的长度调整为2,使其填满屏幕

class RayMarching extends Base {
 constructor(sel: string, debug: boolean) {
 super(sel, debug);
 this.clock = new THREE.Clock();
 this.cameraPosition = new THREE.Vector3(0, 0, 0);
 this.orthographicCameraParams = {
  left: -1,
  right: 1,
  top: 1,
  bottom: -1,
  near: 0,
  far: 1,
  zoom: 1
 };
 }
 // 初始化
 init() {
 this.createScene();
 this.createOrthographicCamera();
 this.createRenderer();
 this.createRayMarchingMaterial();
 this.createPlane();
 this.createLight();
 this.trackMousePos();
 this.addListeners();
 this.setLoop();
 }
 // 创建平面
 createPlane() {
 const geometry = new THREE.PlaneBufferGeometry(2, 2, 100, 100);
 const material = this.rayMarchingMaterial;
 this.createMesh({
  geometry,
  material
 });
 }
}

创建材质

创建好着色器材质,里面定义好所有要传递给着色器的参数

const matcapTextureUrl = "https://i.loli.net/2021/02/27/7zhBySIYxEqUFW3.png";

class RayMarching extends Base {
 // 创建光线追踪材质
 createRayMarchingMaterial() {
 const loader = new THREE.TextureLoader();
 const texture = loader.load(matcapTextureUrl);
 const rayMarchingMaterial = new THREE.ShaderMaterial({
  vertexShader: rayMarchingVertexShader,
  fragmentShader: rayMarchingFragmentShader,
  side: THREE.DoubleSide,
  uniforms: {
  uTime: {
   value: 0
  },
  uMouse: {
   value: new THREE.Vector2(0, 0)
  },
  uResolution: {
   value: new THREE.Vector2(window.innerWidth, window.innerHeight)
  },
  uTexture: {
   value: texture
  },
  uProgress: {
   value: 1
  },
  uVelocityBox: {
   value: 0.25
  },
  uVelocitySphere: {
   value: 0.5
  },
  uAngle: {
   value: 1.5
  },
  uDistance: {
   value: 1.2
  }
  }
 });
 this.rayMarchingMaterial = rayMarchingMaterial;
 }
}

顶点着色器 rayMarchingVertexShader ,这个只要用模板现成的就可以了

重点是片元着色器 rayMarchingFragmentShader

片元着色器

背景

作为热身运动,先创建一个辐射状的背景吧

varying vec2 vUv;

vec3 background(vec2 uv){
 float dist=length(uv-vec2(.5));
 vec3 bg=mix(vec3(.3),vec3(.0),dist);
 return bg;
}

void main(){
 vec3 bg=background(vUv);
 vec3 color=bg;
 gl_FragColor=vec4(color,1.);
}

sdf

如何在光照模型中创建物体呢?我们需要sdf。

sdf的意思是符号距离函数:若传递给函数空间中的某个坐标,则返回那个点与某些平面之间的最短距离,返回值的符号表示点在平面的内部还是外部,故称符号距离函数。

如果我们要创建一个球,就得用球的sdf来创建。球体方程可以用如下的glsl代码来表示

float sdSphere(vec3 p,float r)
{
 return length(p)-r;
}

方块的代码如下

float sdBox(vec3 p,vec3 b)
{
 vec3 q=abs(p)-b;
 return length(max(q,0.))+min(max(q.x,max(q.y,q.z)),0.);
}

看不懂怎么办?没关系,国外已经有大牛把 常用的sdf公式 都整理出来了

在sdf里先创建一个方块

float sdf(vec3 p){
 float box=sdBox(p,vec3(.3));
 return box;
}

画面上仍旧一片空白,因为我们的嘉宾——光线还尚未入场。

光线步进

接下来就是本文的头号人物——光线步进了。在介绍她之前,我们先来看看她的好姬友光线追踪吧。

首先,我们需要知道光线追踪是如何进行的:给相机一个位置 eye ,在前面放一个网格,从相机的位置发射一束射线 ray ,穿过网格打在物体上,所成的像的每一个像素对应着网格上的每一个点。

而在光线步进中,整个场景会由一系列的sdf的角度定义。为了找到场景和视线之间的边界,我们会从相机的位置开始,沿着射线,一点一点地移动每个点,每一步都会判断这个点在不在场景的某个表面内部,如果在则完成,表示光线击中了某东西,如果不在则光线继续步进。

上图中,p0是相机位置,蓝色的线代表射线。可以看出光线的第一步p0p1就迈的非常大,它也恰好是此时光线到表面的最短距离。表面上的点尽管是最短距离,但并没有沿着视线的方向,因此要继续检测到p4这个点

shadertoy上有一个 可交互的例子

以下是光线步进的glsl代码实现

const float EPSILON=.0001;

float rayMarch(vec3 eye,vec3 ray,float end,int maxIter){
 float depth=0.;
 for(int i=0;i<maxIter;i++){
  vec3 pos=eye+depth*ray;
  float dist=sdf(pos);
  depth+=dist;
  if(dist<EPSILON||dist>=end){
   break;
  }
 }
 return depth;
}

在主函数中创建一条射线,将其投喂给光线步进算法,即可获得光线到表面的最短距离

void main(){
 ...
 vec3 eye=vec3(0.,0.,2.5);
 vec3 ray=normalize(vec3(vUv,-eye.z));
 float end=5.;
 int maxIter=256;
 float depth=rayMarch(eye,ray,end,maxIter);
 if(depth<end){
  vec3 pos=eye+depth*ray;
  color=pos;
 }
 ...
}

在光线步进的引诱下,野生的方块出现了!

居中材质

目前的方块有2个问题:1. 没有居中 2. x轴方向上被拉伸

居中+拉伸素质2连走起

vec2 centerUv(vec2 uv){
 uv=2.*uv-1.;
 float aspect=uResolution.x/uResolution.y;
 uv.x*=aspect;
 return uv;
}

void main(){
 ...
 vec2 cUv=centerUv(vUv);
 vec3 ray=normalize(vec3(cUv,-eye.z));
 ...
}

方块瞬间飘到了画面的正中央,但此时的她还没有颜色

计算表面法线

在光照模型中,我们需要 计算出表面法线 ,才能给材质赋予颜色

vec3 calcNormal(in vec3 p)
{
 const float eps=.0001;
 const vec2 h=vec2(eps,0);
 return normalize(vec3(sdf(p+h.xyy)-sdf(p-h.xyy),
 sdf(p+h.yxy)-sdf(p-h.yxy),
 sdf(p+h.yyx)-sdf(p-h.yyx)));
}

void main(){
 ...
 if(depth<end){
  vec3 pos=eye+depth*ray;
  vec3 normal=calcNormal(pos);
  color=normal;
 }
 ...
}

此时方块被赋予了蓝色,但我们还看不出她是个立体图形

动起来

让方块360°旋转起来吧,3D旋转函数直接在 gist 上搜一下就有了

uniform float uVelocityBox;

mat4 rotationMatrix(vec3 axis,float angle){
 axis=normalize(axis);
 float s=sin(angle);
 float c=cos(angle);
 float oc=1.-c;
 
 return mat4(oc*axis.x*axis.x+c,oc*axis.x*axis.y-axis.z*s,oc*axis.z*axis.x+axis.y*s,0.,
  oc*axis.x*axis.y+axis.z*s,oc*axis.y*axis.y+c,oc*axis.y*axis.z-axis.x*s,0.,
  oc*axis.z*axis.x-axis.y*s,oc*axis.y*axis.z+axis.x*s,oc*axis.z*axis.z+c,0.,
 0.,0.,0.,1.);
}

vec3 rotate(vec3 v,vec3 axis,float angle){
 mat4 m=rotationMatrix(axis,angle);
 return(m*vec4(v,1.)).xyz;
}

float sdf(vec3 p){
 vec3 p1=rotate(p,vec3(1.),uTime*uVelocityBox);
 float box=sdBox(p1,vec3(.3));
 return box;
}

融合效果

单单一个方块太孤单了,创建一个球来陪陪她吧

如何让球和方块贴在一起呢,你需要 smin 这个函数

uniform float uProgress;

float smin(float a,float b,float k)
{
 float h=clamp(.5+.5*(b-a)/k,0.,1.);
 return mix(b,a,h)-k*h*(1.-h);
}

float sdf(vec3 p){
 vec3 p1=rotate(p,vec3(1.),uTime*uVelocityBox);
 float box=sdBox(p1,vec3(.3));
 float sphere=sdSphere(p,.3);
 float sBox=smin(box,sphere,.3);
 float mixedBox=mix(sBox,box,uProgress);
 return mixedBox;
}

uProgress 的值设为0,她们成功地贴在了一起

uProgress 的值调回1,她们又分开了

动态融合

接下来就是露珠滴落的动画实现了,其实就是对融合图形应用了一个位移变换

uniform float uAngle;
uniform float uDistance;
uniform float uVelocitySphere;

const float PI=3.14159265359;

float movingSphere(vec3 p,float shape){
 float rad=uAngle*PI;
 vec3 pos=vec3(cos(rad),sin(rad),0.)*uDistance;
 vec3 displacement=pos*fract(uTime*uVelocitySphere);
 float gotoCenter=sdSphere(p-displacement,.1);
 return smin(shape,gotoCenter,.3);
}

float sdf(vec3 p){
 vec3 p1=rotate(p,vec3(1.),uTime*uVelocityBox);
 float box=sdBox(p1,vec3(.3));
 float sphere=sdSphere(p,.3);
 float sBox=smin(box,sphere,.3);
 float mixedBox=mix(sBox,box,uProgress);
 mixedBox=movingSphere(p,mixedBox);
 return mixedBox;
}

matcap贴图

默认的材质太土了?我们有帅气的matcap贴图来助阵

uniform sampler2D uTexture;

vec2 matcap(vec3 eye,vec3 normal){
 vec3 reflected=reflect(eye,normal);
 float m=2.8284271247461903*sqrt(reflected.z+1.);
 return reflected.xy/m+.5;
}

float fresnel(float bias,float scale,float power,vec3 I,vec3 N)
{
 return bias+scale*pow(1.+dot(I,N),power);
}

void main(){
 ...
 if(depth<end){
  vec3 pos=eye+depth*ray;
  vec3 normal=calcNormal(pos);
  vec2 matcapUv=matcap(ray,normal);
  color=texture2D(uTexture,matcapUv).rgb;
  float F=fresnel(0.,.4,3.2,ray,normal);
  color=mix(color,bg,F);
 }
 ...
}

安排上了matcap和菲涅尔公式后,瞬间cool了有没有?!

项目地址

Ray Marching Gooey Effect

到此这篇关于three.js 实现露珠滴落动画效果的示例代码的文章就介绍到这了,更多相关three.js 实现露珠滴落动画内容请搜索猪先飞以前的文章或继续浏览下面的相关文章希望大家以后多多支持猪先飞!

[!--infotagslink--]

相关文章

  • ps动态环绕动画效果怎么制作

    ps动态环绕动画效果是现在很多人都非常喜欢的,大多数人还不知道ps动态环绕动画效果怎么制作下面文章就给大家介绍下ps怎么制作科技感十足的动态环绕动画效果,一起来看看...2017-07-06
  • 详解vue过度效果与动画transition使用示例

    Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果,Vue 提供了内置的过渡封装组件transition,该组件用于包裹要实现过渡效果的组件...2021-10-10
  • jQuery动画效果相关方法实例分析

    这篇文章主要介绍了jQuery动画效果相关方法,结合实例形式较为详细的分析了jQuery实现动画效果所用到的常见方法与相关注意事项,需要的朋友可以参考下...2016-01-05
  • 利用PS制作“签名”GIF小动画的例子

    本文章来为各位介绍一篇利用PS制作“签名”GIF小动画的例子,希望这个制作过程能够帮助到各位朋友,各位进入看看吧。 先给大家看下效果,有兴趣的童鞋&rarr;_&rarr;可以...2016-09-14
  • 微信小程序实现登录页云层漂浮的动画效果

    微信小程序目前的火热程度相信不用多言,最近利用空余时间用小程序实现了个动态的登录页效果,所以下面这篇文章主要给大家介绍了利用微信小程序实现登录页云层漂浮动画效果的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2017-05-09
  • 公众号SVG动画交互实战代码

    这篇文章主要介绍了公众号SVG动画交互实战代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-01
  • js实现缓动动画

    这篇文章主要为大家详细介绍了js实现缓动动画,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-11-25
  • jQuery实现的给图片点赞+1动画效果(附在线演示及demo源码下载)

    这篇文章主要介绍了jQuery实现的给图片点赞+1动画效果,并附带在线演示及demo源码下载,涉及jQuery鼠标事件响应及页面元素属性动态操作相关技巧,需要的朋友可以参考下...2016-01-05
  • jQuery实现的点赞随机数字显示动画效果(附在线演示与demo源码下载)

    这篇文章主要介绍了jQuery实现的点赞随机数字显示动画效果,并附带在线演示与demo源码供读者下载.涉及jQuery鼠标事件响应及基于animate动画效果实现技巧,需要的朋友可以参考下...2016-01-05
  • JS实现点击登录弹出窗口同时背景色渐变动画效果

    这篇文章主要介绍了JS实现点击登录弹出窗口同时背景色渐变动画效果,涉及JavaScript基于鼠标事件及时间函数定时触发形成渐变动画的相关技巧,需要的朋友可以参考下...2016-03-28
  • Photoshop利用智能对象将人物制作成花海里飘散消失的GIF动画教程

    今天小编在这里就来给Photoshop的这一款软件的使用者们来说一说利用智能对象将人物制作成花海里飘散消失的GIF动画的教程,各位想知道具体方法的使用者们,那么大家就快来...2016-09-14
  • three.js显示中文字体与tween应用详析

    这篇文章主要给大家介绍了关于three.js显示中文字体与tween应用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-04
  • three.js 实现露珠滴落动画效果的示例代码

    这篇文章主要介绍了three.js 实现露珠滴落动画效果的示例代码,非常不错,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-01
  • SwiftUI 中创建反弹动画的实现

    这篇文章主要介绍了SwiftUI 中创建反弹动画的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-30
  • AngularJS中实现显示或隐藏动画效果的方式总结

    AngularJS 是一组用于创建单页Web应用的丰富框架,给构建丰富交互地应用带来了所有功能,其中一项主要的特性是Angular对动画的支持。下面通过本文给大家介绍AngularJS中实现显示或隐藏动画效果的方式总结,对angularjs动画效果相关知识感兴趣的朋友一起学习...2016-01-05
  • Android Studio实现帧动画

    这篇文章主要为大家详细介绍了Android Studio实现帧动画,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-11-14
  • three.js 制作动态二维码的示例代码

    这篇文章主要介绍了three.js 制作动态二维码的示例代码,文中讲解非常细致,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-08-01
  • Android动画背景图自动播放的实现程序

    Android动画背景图自动播放的实现程序,我们在开发android应用的时候,经常会遇到类似从网络加载大图,在加载的过程中,在图片要显示的ImageView位置,先显示一个转圈的loadin...2016-09-20
  • Swift使用transform 实现重复平移动画效果

    这篇文章主要介绍了Swift使用transform 实现重复平移动画效果,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-07-21
  • 基于JavaScript的数据结构队列动画实现示例解析

    这篇文章主要介绍了基于JavaScript的数据结构队列动画实现示例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-08-06