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 相关文章推荐
一个用javascript写的select支持上下键、首字母筛选以及回车取值的功能
Sep 09 Javascript
JQuery 绑定select标签的onchange事件,弹出选择的值,并实现跳转、传参
Jan 06 Javascript
jquery中的mouseleave和mouseout的区别 模仿下拉框效果
Feb 07 Javascript
jQuery.event兼容各浏览器的event详细解析
Dec 18 Javascript
ExtJS4 动态生成的grid导出为excel示例
May 02 Javascript
JQuery手速测试小游戏实现思路详解
Sep 20 Javascript
详解JavaScript对象的深浅复制
Mar 30 Javascript
ionic 3.0+ 项目搭建运行环境的教程
Aug 09 Javascript
[js高手之路]HTML标签解释成DOM节点的实现方法
Aug 31 Javascript
JS实现的哈夫曼编码示例【原始版与修改版】
Apr 22 Javascript
JavaScript判断对象和数组的两种方法
May 31 Javascript
JavaScript oncopy事件用法实例解析
May 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
PHP,ASP.JAVA,JAVA代码格式化工具整理
2010/06/15 PHP
PHP GD库生成图像的几个函数总结
2014/11/19 PHP
jQuery EasyUI API 中文文档 搜索框
2011/09/29 Javascript
js弹窗返回值详解(window.open方式)
2014/01/11 Javascript
JS面向对象基础讲解(工厂模式、构造函数模式、原型模式、混合模式、动态原型模式)
2014/08/16 Javascript
jquery实现无限分级横向导航菜单的方法
2015/03/12 Javascript
JS中sort函数排序用法实例分析
2016/06/16 Javascript
基于AngularJS实现iOS8自带的计算器
2016/09/12 Javascript
深入理解js中的加载事件
2017/02/08 Javascript
vue获取DOM元素并设置属性的两种实现方法
2017/09/30 Javascript
使用vue打包时vendor文件过大或者是app.js文件很大的问题
2018/06/29 Javascript
Echarts之悬浮框中的数据排序问题
2018/11/08 Javascript
小程序云开发实现数据库异步操作同步化
2019/05/18 Javascript
DWR内存兼容及无法调用问题解决方案
2020/10/16 Javascript
微信小程序基于ColorUI构建皮皮虾短视频去水印组件
2020/11/04 Javascript
基于vue的video播放器的实现示例
2021/02/19 Vue.js
[02:05]2014DOTA2国际邀请赛 BBC外卡赛赛后总结
2014/07/09 DOTA
从零学python系列之教你如何根据图片生成字符画
2014/05/23 Python
Python 使用多属性来进行排序
2019/09/01 Python
Python 统计位数为偶数的数字代码详解
2020/03/15 Python
利用python对excel中一列的时间数据更改格式操作
2020/07/14 Python
英国领先的酒杯和水晶玻璃器皿制造商:Dartington Crystal
2019/06/23 全球购物
AutoShack.com加拿大:北美主要的汽车零部件零售商
2019/07/24 全球购物
英国家居用品和家居装饰品购物网站:Cox & Cox
2019/08/25 全球购物
Bed Bath & Beyond加拿大官网:购买床上用品、浴巾、厨房电器等
2019/10/04 全球购物
周年庆典邀请函范文
2014/01/24 职场文书
《圆明园的毁灭》教学反思
2014/02/28 职场文书
投标诚信承诺书
2014/05/26 职场文书
服务行业口号
2014/06/11 职场文书
2014年酒店年度工作总结
2014/12/10 职场文书
2015年秋季小学开学标语
2015/07/16 职场文书
vue+echarts实现多条折线图
2022/03/21 Vue.js
世界无敌的ICOM IC-R9500宽频接收机
2022/03/25 无线电
Python OpenCV之常用滤波器使用详解
2022/04/07 Python
MySQL创建管理LIST分区
2022/04/13 MySQL
Apache Hudi 加速传统的批处理模式
2022/04/24 Servers