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 相关文章推荐
取键盘键位ASCII码的网页
Jul 30 Javascript
JavaScript中继承的一些示例方法与属性参考
Aug 07 Javascript
jQuery判断checkbox是否选中的3种方法
Aug 12 Javascript
javascript中eval和with用法实例总结
Nov 30 Javascript
JS简单循环遍历json数组的方法
Apr 22 Javascript
手机Web APP如何实现分享多平台功能
Aug 19 Javascript
canvas实现手机端用来上传用户头像的代码
Oct 20 Javascript
详解小程序退出页面时清除定时器
Apr 28 Javascript
JavaScript如何处理移动端拍摄图片旋转问题
Nov 16 Javascript
Angular 多模块项目构建过程
Feb 13 Javascript
TypeScript的安装、使用、自动编译的实现
Apr 10 Javascript
原生JS实现相邻月份日历
Oct 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
把从SQL中取出的数据转化成XMl格式
2006/10/09 PHP
php数组函数序列之end() - 移动数组内部指针到最后一个元素,并返回该元素的值
2011/10/31 PHP
如何让thinkphp在模型中自动完成session赋值小教程
2014/09/05 PHP
PHP学习笔记(一):基本语法之标记、空白、和注释
2015/04/17 PHP
php使用正则表达式获取字符串中的URL
2016/12/29 PHP
PHP 范围解析操作符(::)用法分析【访问静态成员和类常量】
2020/04/14 PHP
JS getMonth()日期函数的值域是0-11
2010/02/15 Javascript
JS实现根据出生年月计算年龄
2014/01/10 Javascript
js中opener与parent的区别详细解析
2014/01/14 Javascript
JS实现CheckBox复选框全选、不选或全不选功能
2020/07/28 Javascript
js实现密码强度检测【附示例】
2016/03/30 Javascript
python爬取安居客二手房网站数据(实例讲解)
2017/10/19 Javascript
jQuery+ajax读取json数据并按照价格排序示例
2018/03/28 jQuery
vue-cli 组件的导入与使用教程详解
2018/04/11 Javascript
jQuery实现DIV响应鼠标滑过由下向上展开效果示例【测试可用】
2018/04/26 jQuery
基于layui实现高级搜索(筛选)功能
2019/07/26 Javascript
vue中如何实现后台管理系统的权限控制的方法步骤
2019/09/05 Javascript
浅谈vue websocket nodeJS 进行实时通信踩到的坑
2020/09/22 NodeJs
JS检测浏览器开发者工具是否打开的方法详解
2020/10/02 Javascript
Python网络爬虫之爬取微博热搜
2019/04/18 Python
Python迷宫生成和迷宫破解算法实例
2019/12/24 Python
探索欧洲最好的品牌:Bombinate
2019/06/14 全球购物
ManoMano英国:欧洲第一家专注于DIY和园艺市场的电商平台
2020/03/12 全球购物
荣耀商城:HIHONOR
2020/11/03 全球购物
上课迟到检讨书
2014/01/19 职场文书
放飞蜻蜓反思
2014/02/05 职场文书
工作分析计划书
2014/04/30 职场文书
防灾减灾活动总结
2014/08/30 职场文书
中学生爱国演讲稿
2014/09/05 职场文书
2015年社区宣传工作总结
2015/05/20 职场文书
感恩教师节主题班会
2015/08/12 职场文书
pyqt5打包成exe可执行文件的方法
2021/05/14 Python
教你使用pyinstaller打包Python教程
2021/05/27 Python
MySQL8.0的WITH查询详情
2021/08/30 MySQL
python使用matplotlib绘制图片时x轴的刻度处理
2021/08/30 Python
Zabbix6通过ODBC方式监控Oracle 19C的详细过程
2022/09/23 Servers