引言

个人网站是程序员的第二张简历。如果你有酷炫的个人网页,面试官对你的好感度会蹭蹭蹭往上涨。

在疫情隔离期间,我用Three.jsAmmo.js制作了一个可交互的3D个人网页。

在线预览地址: www.ryan-floyd.com/

Three.js的3D世界

当我在Google Experiments闲逛时,我发现非常多的作品都是用three.js写的。

three.js是一个让3D网页应用开发变得简单的库。它诞生于2010年,作者是Ricardo Cabello (Mr.doob),,在github上有超过1300多的贡献者,在所有仓库中star数排行第38。

当看到Google Experiments上那些酷炫的3D效果后,我决定开始学习three.js

Three.js的工作机制

(3D应用的组件结构,图片来自discoverthreejs.com)

Three.js使得在浏览器展示3D图像变得容易,它的底层是基于WebGL,它使浏览器能借助系统显卡在canvas中绘制3D画面。

WebGL自身只能绘制点(points)、线(lines)和三角形(triangles),而Three.jsWebGL进行了封装,使我们能够非常方便地创建 物体(objects), 纹理(textures), 进行 3D 计算等操作。

使用Three.js,我们将所有物体(objects)添加到场景(scene)中,然后将需要渲染的数据传递给渲染器(renderer),渲染器负责将场景在 <canvas> 画布上绘制出来。

(Three.js 应用架构,图片来自threejsfundamentals.org)

对于一个 Three.js 应用,最核心的就是场景(scene object),上面是一张场景图(scene graph)。

在一个3D引擎中,场景图是一个层级结构的树状图,树中的每一个节点代表空间中的一部分。这种结构有点像DOM树,但Three.js的场景(scene)更像虚拟DOM,它只更新和渲染场景中有变化的部分。而这一切的基础,是 Three.js 的 WebGLRenderer 类,它把我们的代码转换成 GPU 中的数据,浏览器再将这些数据渲染出来。

场景中的物体,也叫Mesh。在 Three.js 的世界中,Mesh 是由 几何体Geometry(决定物体形状) + 材质Material(决定物体外观)构成。

场景中的另一个重要元素,就是相机camera,它决定了场景中 哪些部分以怎样的视觉效果 被绘制在canvas画布上。

然后是动画,为了实现动画,渲染器(renderer)通常使用requestAnimationFrame()方法,以每秒60次的频率将场景更新绘制在canvas上。requestAnimationFrame()方法的原理和使用可以参考MDN。

下面这个例子来自Three.js官方文档,创建了一个旋转的 3D 立方体。

<html><head><title>My first three.js app</title><style>body {margin: 0;}canvas {display: block;}</style></head><body><script src="https://unpkg.com/three@0.119.0/build/three.js"></script><script>//创建场景和相机var scene = new THREE.Scene();var camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000);//创建渲染器,设置尺寸为窗口尺寸,并将渲染后的元素添加到bodyvar renderer = new THREE.WebGLRenderer();renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);//创建一个Mesh(绿色的3D立方体),并添加到场景中var geometry = new THREE.BoxGeometry();var material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });var cube = new THREE.Mesh(geometry, material);scene.add(cube);//设置照相机的位置camera.position.z = 5;//浏览器每次渲染的时候更新立方体的旋转角度var animate = function () {requestAnimationFrame(animate);cube.rotation.x += 0.01;cube.rotation.y += 0.01;renderer.render(scene, camera);};animate();</script></body>
</html>
复制代码

效果如下:

转存失败重新上传取消

Ammo.js物理引擎

Ammo.js 是将 Bullet物理引擎 直接移植到JavaScript的产物(Bullet Physics是一个开源的物理模拟引擎)。我对物理引擎底层的工作原理理解得不太深入,简而言之,物理引擎根据你传入的参数(比如重力),创建循环,在每次循环中更新状态,从而模拟出自然的物理运动和碰撞等效果。

循环中的物体(通常也是刚体),具有力、质量、惯性、摩擦力等物理属性。每次循环,通过不断检查所有物体的位置、状态和运动来检测碰撞和交互。如果发生交互,对象位置将根据经过的时间和对象的物理属性进行更新。下面是我代码中的一个片段,显示了如何创建物理引擎循环以及如何将它添加到Three.js的sphere球体中。

//引入库
import * as THREE from "three";
import * as Ammo from "./builds/ammo";
import {scene} from "./resources/world";//初始化 Ammo.js 物理引擎
Ammo().then((Ammo) => {// 创建物理世界function createPhysicsWorld() {//完全碰撞检测算法let collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();// 重叠对/碰撞的调度计算let dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);// 所有可能碰撞对的宽相位碰撞检测列表let overlappingPairCache = new Ammo.btDbvtBroadphase();// 使物体正确地交互,考虑重力、力、碰撞等let constraintSolver = new Ammo.btSequentialImpulseConstraintSolver();// 根据这些参数创建物理世界。 参考bullet physics文档let physicsWorld = new Ammo.btDiscreteDynamicsWorld(dispatcher,overlappingPairCache,constraintSolver,collisionConfiguration);// 添加重力physicsWorld.setGravity(new Ammo.btVector3(0, -9.8, 0));}//创建球体function createBall(){//球体参数let pos = {x: 0, y: 0, z: 0};let radius = 2;let quat = {x: 0, y: 0, z: 0, w: 1};let mass = 3;//three.js相关代码//创建球体并添加到场景中let ball = new THREE.Mesh(new THREE.SphereBufferGeometry(radius), new THREE.MeshStandardMaterial({color: 0xffffff}));ball.position.set(pos.x, pos.y, pos.z);scene.add(ball);//Ammo.js相关代码//设置位置和旋转let transform = new Ammo.btTransform();transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));//设置物体运动let motionState = new Ammo.btDefaultMotionState(transform);//设置碰撞边界框let collisionShape = new Ammo.btSphereShape(radius);collisionShape.setMargin(0.05);//设置惯性let localInertia = new Ammo.btVector3(0, 0, 0);collisionShape.calculateLocalInertia(mass, localInertia);//生成创建刚体(物体)的结构信息let rigidBodyStructure = new Ammo.btRigidBodyConstructionInfo(mass,motionState,collisionShape,localInertia);//基于上面的结构信息创建物体let body = new Ammo.btRigidBody(rigidBodyStructure);//当物体运动时,为其添加摩擦力body.setFriction(10);body.setRollingFriction(10);// 将物体添加到物理世界,这样Ammo.js引擎才能不断更新物体的状态physicsWorld.addRigidBody(body);}createPhysicsWorld();createBall()
}
复制代码

运动和交互

在Ammo.js模拟的物理世界中,交互是基于属性和力计算的。

每个对象有一个边界框(bounding box)属性,物理引擎会根据这个边界框来检测物体的位置。

在每个动画循环中检查所有对象的边界框后,如果任意两个对象的边界框位于同一位置,引擎将记录为“碰撞”,并相应地更新对象。 对于刚体来说,这意味着阻止两个物体处于同一位置。

下面是我的代码片段,显示了渲染循环和世界物理是如何更新的。

//渲染框架
function renderFrame() {//记录上一次渲染的时间let deltaTime = clock.getDelta();//基于用户输入,计算球会受到的力和产生的速度moveBall();//根据时间更新物理世界状态updatePhysics(deltaTime);//进行渲染renderer.render(scene, camera);// 循环requestAnimationFrame(renderFrame);
}//更新物理世界状态的方法定义
function updatePhysics(deltaTime) {physicsWorld.stepSimulation(deltaTime, 10);//遍历“刚体”列表,并更新物理世界中的所有刚体状态for (let i = 0; i < rigidBodies.length; i++) {//变量定义:three.js需要的meshObject,和ammo.js需要的ammoObjectlet meshObject = rigidBodies[i];let ammoObject = meshObject.userData.physicsBody;//获取物体当前运动状态let objectMotion = ammoObject.getMotionState();//如果物体正在移动,则获取物体的当前位置和旋转信息if (objectMotion) {objectMotion.getWorldTransform(transform);let mPosition = transform.getOrigin();let mQuaternion = transform.getRotation();// 更新物体的位置和旋转状态meshObject.position.set(mPosition.x(), mPosition.y(), mPosition.z());meshObject.quaternion.set(mQuaternion.x(), mQuaternion.y(), mQuaternion.z(), mQuaternion.w());}}
}
复制代码

用户输入

我们希望用户在桌面和触摸屏移动设备上都能够在应用中移动球体。

对于键盘事件,当按下箭头键时,通过监听“keydown”和“keyup”事件对球体添加相应方向的力。

对于触摸屏,在屏幕上创建了一个操纵杆控制器。然后,我们将“touchstart”、“touchmove”和“touchend”事件监听器添加到用于控制的div元素(控制器)中。

控制器会跟踪用户手指移动的起始、当前和结束坐标,然后在每次渲染时相应地更新球的受力。

下面只是控制器代码的一个片段,展示了一些大致的概念。有关完整代码,请从本文底部的源代码地址获取。

// 在坐标平面上保持对当前球体运动的跟踪
let moveDirection = { left: 0, right: 0, forward: 0, back: 0 };//控制器div在屏幕上的位置坐标
let coordinates = { x: 0, y: 0 };//保存触摸事件的起始坐标的变量
let dragStart = null;//创建控制器div元素
const stick = document.createElement("div");//监听用户触摸点的移动
function handleMove(event) {//没有移动,返回if (dragStart === null) return;//有移动,获取新的触摸点的x、y坐标if (event.changedTouches) {event.clientX = event.changedTouches[0].clientX;event.clientY = event.changedTouches[0].clientY;}//根据触摸点的移动,计算出控制器div的实时坐标const xDiff = event.clientX - dragStart.x;const yDiff = event.clientY - dragStart.y;const angle = Math.atan2(yDiff, xDiff);const distance = Math.min(maxDiff, Math.hypot(xDiff, yDiff));const xNew = distance * Math.cos(angle);const yNew = distance * Math.sin(angle);coordinates = { x: xNew, y: yNew };//根据实时坐标更新样式stick.style.transform = `translate3d(${xNew}px, ${yNew}px, 0px)`;//根据坐标计算出球的运动方向touchEvent(coordinates);
}//根据用户的触摸点移动坐标计算出球的运动方向
function touchEvent(coordinates) {// 向右运动if (coordinates.x > 30) {moveDirection.right = 1;moveDirection.left = 0;// 向左运动} else if (coordinates.x < -30) {moveDirection.left = 1;moveDirection.right = 0;} else {moveDirection.right = 0;moveDirection.left = 0;}//向前运动if (coordinates.y > 30) {moveDirection.back = 1;moveDirection.forward = 0;//向后运动} else if (coordinates.y < -30) {moveDirection.forward = 1;moveDirection.back = 0;} else {moveDirection.forward = 0;moveDirection.back = 0;}
}

用Three.js打造酷炫3D个人网站(含源码)相关推荐

  1. 【014】基于Vue.js的移动端购物商城网站(含源码、课设报告)

    文章目录 一.项目介绍 二.代码及报告获取 一.项目介绍 基于Vue.js的移动端购物商城网站(含源码.课设报告),代码获取放在文末了,码字不易,感谢点赞~ 一.系统概述 本部分主要是对项目进行简要描 ...

  2. Vue.js 打造酷炫的可视化数据大屏

    可视化技术与 Vue 介绍 实验介绍 在本节实验中,将对可视化技术的应用场景.发展历程进行介绍,让大家对可视化技术有一个基础的概念.随后将介绍如今流行的可视化框架与其之间的优缺点对比.最后介绍 Vue ...

  3. 直播系统源码App中Android酷炫礼物动画直播平台源码搭建教程(上篇)

    直播系统源码App中Android酷炫礼物动画直播平台源码搭建教程(上篇) 在当下移动直播火爆的年代,如果你曾经使用过移动端直播应用,相信会被里面那令人惊叹的礼物动画效果迷住,比如像下面这样的效果. ...

  4. 酷炫猜歌喝酒小程序源码_可开源带流量主

    好友分享的一套小程序,很有意思啊,UI也漂亮,白色应用类型,不违规, 可以自己试着做一个,朋友自己现在运营的,收入还不错. 开源的程序,可以任意二开,程序很小,也不挑服务器.东西如下图,感兴趣的自己拿 ...

  5. 推荐收藏|又带来了12个炫酷的数据可视化作品含源码

    之前分享了10个炫酷的数据可视化大屏源码,反响很大,好多人在后台回复获取源码,这次,我又带来了12个炫酷的数据可视化大屏源码,都是从网上收集的,都测试后可以使用,在我电脑(1920X1080分辨率下) ...

  6. android+酷炫动画效果,Android简单酷炫点击动画(附源码)

    在Android5.0之前, Android的点击效果一直很low, 即使5.0的波纹效果也不尽如人意. 而我之前写过一种比较酷炫的点击效果, 最近抽了点时间完善一下, 把阴影效果加入了进去, 大家先 ...

  7. 计算机毕业设计Node.js+Vue慧学IT精品课程网站(程序+源码+LW+部署)

    该项目含有源码.文档.程序.数据库.配套开发软件.软件安装教程.欢迎交流 项目运行 环境配置: Node.js+ Vscode + Mysql5.7 + HBuilderX+Navicat11+Vue ...

  8. HTML+CSS+JS实现 ❤️酷炫3D瀑布流动画特效❤️

  9. three.js打造酷炫下雪效果

    代码: <!doctype html> <html><head><meta charset="UTF-8"><meta nam ...

最新文章

  1. 巧用CSS的 Mask 滤镜
  2. 【机器学习PAI实践十】深度学习Caffe框架实现图像分类的模型训练
  3. hystrix thread pool Metrics
  4. CCF 201503-2 数字排序
  5. 华为开发的新型智能手机的正面和背面采用玻璃材料制成
  6. 二叉树的遍历实验报告C语言,数据结构-二叉树的遍历(类C语言描写叙述)
  7. PR预设 16个毛刺效果抖音故障特效视频转场PR预设V2版本
  8. WizTree——一个扫描快似Everything的硬盘空间分析工具
  9. mysql数据库文件查找网站后台密码_怎么查看数据库的密码?
  10. 高通apq8098平台sd卡总结
  11. css中表格内容从顶部开始,CSS粘性定位固定表格thead部分元素小方法
  12. CloneZilla再生龙 | 打造自己的深度学习镜像
  13. 【听课笔记】复旦大学遗传学_01孟德尔遗传
  14. “九型人格”-你的团队用了吗
  15. (宏) Word图片题注“图一-1”转化为“图1-1”
  16. ArcGIS Pro数据加载学习总结
  17. 第16周项目3--用函数指针调用函数(吃饭,睡觉,打豆豆)
  18. ST-GAN: Spatial Transformer Generative Adversarial Networks for Image Compositing 原文翻译
  19. POI使用word模板文件循环输出行并导出word
  20. 【数据结构】 栈的知识点

热门文章

  1. 图片翻译软件哪个好用?快把这些软件收好
  2. 软考备考--信息处理技术员
  3. 深度学习保姆级别之NLP入门文本分类之情感分析
  4. python数据分析及可视化(十六)金融量化(金融工具、金融分析、Tushare安装使用、双均线分析)
  5. 2020牛客国庆集训派对day2 H-STROOP EFFECT(英语题)
  6. 视频编辑软件‘编辑星V3’中英版本上线了
  7. 软件测试技术大全 第二版 目录
  8. C2039 “OpenNI2Grabber”: 不是“pcl”的成员
  9. R语言绘图——组间差异箱线图图,小提琴图、聚类热力图、相关系数热力图
  10. 小牛电动光环:生来野心