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


Posted in Javascript onMarch 01, 2021

前言

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

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

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

撒,哈吉马路由!

准备工作

笔者的 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
 });
 }
}

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

创建材质

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

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.);
}

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

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;
}

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

光线步进

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

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

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

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

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

上图中,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;
 }
 ...
}

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

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

居中材质

目前的方块有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));
 ...
}

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

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

计算表面法线

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

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;
 }
 ...
}

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

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

动起来

让方块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;
}

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

融合效果

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

如何让球和方块贴在一起呢,你需要 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,她们成功地贴在了一起

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

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;
}

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

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);
 }
 ...
}

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

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

项目地址

Ray Marching Gooey Effect

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

Javascript 相关文章推荐
利用json获取字符出现次数的代码
Mar 22 Javascript
JS画5角星方法介绍
Sep 17 Javascript
判断在css加载完毕后执行后续代码示例
Sep 03 Javascript
Javascript模拟加速运动与减速运动代码分享
Dec 11 Javascript
javascript获取当前鼠标坐标的方法
Jan 10 Javascript
JavaScript学习小结(一)——JavaScript入门基础
Sep 02 Javascript
JQuery实现级联下拉框效果实例讲解
Sep 17 Javascript
JS实现线性表的顺序表示方法示例【经典数据结构】
Apr 11 Javascript
JS实现禁止高频率连续点击的方法【基于ES6语法】
Apr 25 Javascript
Vue中img的src属性绑定与static文件夹实例
May 18 Javascript
js通过Date对象实现倒计时动画效果
Oct 27 Javascript
微信小程序封装分享与分销功能过程解析
Aug 13 Javascript
详解js创建对象的几种方式和对象方法
Mar 01 #Javascript
vue 使用饿了么UI仿写teambition的筛选功能
Mar 01 #Vue.js
vue实现拖拽进度条
Mar 01 #Vue.js
vue 使用 v-model 双向绑定父子组件的值遇见的问题及解决方案
Mar 01 #Vue.js
js闭包和垃圾回收机制示例详解
Mar 01 #Javascript
vue前端和Django后端如何查询一定时间段内的数据
Feb 28 #Vue.js
vue-router路由懒加载及实现的3种方式
Feb 28 #Vue.js
You might like
留言板翻页的实现详解
2006/10/09 PHP
php 无限级缓存的类的扩展
2009/03/16 PHP
PHP使用PDO创建MySQL数据库、表及插入多条数据操作示例
2019/05/30 PHP
PHP rmdir()函数的用法总结
2019/07/02 PHP
如何判断图片地址是否失效
2007/02/02 Javascript
第一次接触神奇的Bootstrap表单
2016/07/27 Javascript
整理关于Bootstrap模态弹出框的慕课笔记
2017/03/29 Javascript
js案例之鼠标跟随jquery版(实例讲解)
2017/07/21 jQuery
Vue学习笔记进阶篇之函数化组件解析
2017/07/21 Javascript
浅谈函数调用的不同方式,以及this的指向
2017/09/17 Javascript
vuejs使用$emit和$on进行组件之间的传值的示例
2017/10/04 Javascript
javascript计算对象长度的方法
2017/10/25 Javascript
深入浅析Vue中的slots/scoped slots
2018/04/03 Javascript
mpvue将vue项目转换为小程序
2018/09/30 Javascript
详解vue中localStorage的使用方法
2018/11/22 Javascript
vue element-ui el-date-picker限制选择时间为当天之前的代码
2019/11/07 Javascript
Python第三方库xlrd/xlwt的安装与读写Excel表格
2017/01/21 Python
Python实现的快速排序算法详解
2017/08/01 Python
python处理Excel xlrd的简单使用
2017/09/12 Python
Python 使用with上下文实现计时功能
2018/03/09 Python
python 识别图片中的文字信息方法
2018/05/10 Python
Python unittest 简单实现参数化的方法
2018/11/30 Python
20行python代码的入门级小游戏的详解
2019/05/05 Python
pycharm新建一个python工程步骤
2019/07/16 Python
pytorch制作自己的LMDB数据操作示例
2019/12/18 Python
基于django和dropzone.js实现上传文件
2020/11/24 Python
使用CSS3的appearance属性改变任何元素的浏览器默认风格
2012/12/24 HTML / CSS
iKRIX意大利网上商店:男女豪华服装和配件
2019/10/09 全球购物
师德学习感言
2014/01/31 职场文书
父母寄语大全
2014/04/12 职场文书
个人自我剖析材料
2014/09/30 职场文书
事业单位聘任报告
2015/03/02 职场文书
2016年9月份红领巾广播稿
2015/12/21 职场文书
pytorch 中nn.Dropout的使用说明
2021/05/20 Python
Vue中插槽slot的使用方法与应用场景详析
2021/06/08 Vue.js
python数字转对应中文的方法总结
2021/08/02 Python