前言
个人博客是程序员的第二张简历。如果你有酷炫的个人网页,面试官对你的好感度会蹭蹭蹭往上涨。
说到博客大家一定都不陌生,不管你是深耕职场多年的老鸟,还是在学校努力学习的小鸟,应该都有过一段“装扮”博客的经历,比如:放上喜欢的图片、添加炫酷的交互、换上 DIY 的博客主题等等。还有的利用github或者coding来搭建免费的个人静态博客,更有甚者或者土豪,直接购买服务器搭建自己的个人博客……但不管再怎么“打扮”,也跳脱不出平面的“凡胎”。
今天宏哥给大家带来的是一款开源的 3D 博客项目,实话说我第一次访问这个博客的时候都惊呆了,颠覆了我对博客的认知。进去后需要通过操控一颗3D 的小地球,自己“找到”文章才可以阅读,特别有意思!这可能就是传说中的寓教于乐吧,这种存在于神话中的学习方式,宏哥今天带你来体验一把。说了这么多废话,3D博客先一睹为快,如下图所示:
项目:https://github.com/0xFloyd/Portfolio_2020
接下来,宏哥本来是打算本文会先介绍如何在本地把项目运行起来,然后讲解其源码和相关技术,最后教你如何修改代码把自己的博文放进去,升级到 3D 博客!但是由于项目需要搭建node.js的开发环境,需要宏哥去下载安装Node.js 而且项目需要对应的Node.js版本特别啰嗦麻烦,而且宏哥只是想体验一下3D博客,又不做深入的研究开发,在本地搭建这么一套环境有点不划算,刚好Cloud Studio推出正文活动:有奖征文丨【玩转Cloud Studio】第二季来啦!-腾讯云开发者社区-腾讯云 (tencent.com) 宏哥想到这不是一举两得的好事吗,想也不用想了直接拍脑袋那就在Cloud Studio上来实践一下吧。
【免费试用 CS 】省时又省力
Cloud Studio 是基于浏览器的集成式开发环境(IDE),为开发者提供稳定的云端工作站。在使用 Cloud Studio 时无需安装,打开浏览器即可快速启动项目。底层资源自动弹性扩缩,极大地节省成本,低代码开发省时又省力:
● 基于 Web 端的代码编辑器,包含代码高亮、自动补全、Git 集成、终端等 IDE 的基础功能,同时支持实时调试、插件扩展等,提升开发、编译与部署工作效率 ;
● 支持远程访问云服务器,为腾讯云 SCF 行业用户提供开发-测试-部署完整闭环的云原生开发体验 ;
● 自研多款插件以满足开发需求,例如协作插件、自定义模板插件、预览插件、部署插件等,助力施展编程潜能。
点击链接免费试用:Cloud Studio – 开启云端开发模式 WebIDE
每月赠送 3000 分钟免费额度。
查看你的免费额度或者余额操作步骤如下:
1.将鼠标放在头像处,如下图所示:
2.点击人民币符号“¥”,如下图所示:
3.点击人民币符号“¥”后,查看你的免费额度或者余额,如下图所示:
下面让就跟随宏哥的脚步一起走进这个开源的 3D 博客,将惊喜转化为兴趣。
一、介绍
1.1Cloud Studio账号
1.因为宏哥有社区的账号就直接点击“控制台”,如下图所示:
2.搜索Cloud Studio后,点击“Cloud Studio(云端IDE)”,如下图所示:
3.点击立即使用,就可以了。如下图所示:
其他方式也有很多,因为这是Cloud Studio的第二期活动了,一期以及官方文档已经对这方面进行了大量的介绍和讲解,宏哥这里就不做赘述了。
1.2创建工作空间
1.登录上Cloud Studio,点击左下角的“新建工作空间”按钮。如下图所示:
2.点击左下角的“新建工作空间”按钮后,进入工作空间新建页面。如下图所示:
3.填写“空间名称”、“空间描述”、“代码来源”、“开发环境”、“版本”和“规格配置”等信息。如下图所示:
(1)代码来源:宏哥选择仓库地址: https://github.com/0xFloyd/Portfolio_2020.git
a.点击SSH公钥的复制图标,如下图所示:
b.登录你自己注册的github账号,点击头像下拉菜单的“Settings”,如下图所示:
c.点击“SSH AND GPG keys”,如下图所示:
d.点击“New SSH Key”,将刚刚复制的SSH公钥添加到你的github中的个人私钥里,如下图所示:
(2)开发环境:Node.js 版本:14.现在最新版本到18多了。
(3)规格配置根据自己的需要选择就可以。
1.3安装步骤
话不多说先跑起来再说,只需 2 步。命令如下:
# 1.安装依赖(仅第一次需要)
npm i
# 2.运行
npm run dev
1.4 运行效果
首先,我们来看看这个博客长什么样子:
1.5替换工作空间的文件
1.首先我们需要在本地电脑运行ssh-keygen命令。否则上传文件就会报错Permission denied (publickey).
2.按照官方说明上传文件进行替换
3.获取工作空间的地址
4.复制即可使用
2.Three.js的3D世界
当我在Google Experiments闲逛时,我发现非常多的作品都是用three.js
写的。
three.js
是一个让3D网页应用开发变得简单的库。它诞生于2010年,作者是Ricardo Cabello (Mr.doob),,在github上有超过1300多的贡献者,在所有仓库中star数排行第38。
当看到Google Experiments
上那些酷炫的3D效果后,我决定开始学习three.js
。
3.Three.js的工作机制
Three.js
使得在浏览器展示3D图像变得容易,它的底层是基于WebGL
,它使浏览器能借助系统显卡在canvas中绘制3D画面。
WebGL
自身只能绘制点(points)、线(lines)和三角形(triangles),而Three.js
对WebGL
进行了封装,使我们能够非常方便地创建 物体(objects), 纹理(textures), 进行 3D 计算等操作。
使用Three.js
,我们将所有物体(objects)添加到场景(scene)中,然后将需要渲染的数据传递给渲染器(renderer),渲染器负责将场景在 画布上绘制出来。
对于一个 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 立方体。
xml复制代码
My first three.js app
效果如下:
4.Ammo.js物理引擎
Ammo.js 是将 Bullet物理引擎 直接移植到JavaScript的产物(Bullet Physics是一个开源的物理模拟引擎)。我对物理引擎底层的工作原理理解得不太深入,简而言之,物理引擎根据你传入的参数(比如重力),创建循环,在每次循环中更新状态,从而模拟出自然的物理运动和碰撞等效果。
循环中的物体(通常也是刚体),具有力、质量、惯性、摩擦力等物理属性。每次循环,通过不断检查所有物体的位置、状态和运动来检测碰撞和交互。如果发生交互,对象位置将根据经过的时间和对象的物理属性进行更新。下面是我代码中的一个片段,显示了如何创建物理引擎循环以及如何将它添加到Three.js的sphere球体中。
java复制代码//引入库
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()
}
5.运动和交互
在Ammo.js模拟的物理世界中,交互是基于属性和力计算的。
每个对象有一个边界框(bounding box)属性,物理引擎会根据这个边界框来检测物体的位置。
在每个动画循环中检查所有对象的边界框后,如果任意两个对象的边界框位于同一位置,引擎将记录为“碰撞”,并相应地更新对象。 对于刚体来说,这意味着阻止两个物体处于同一位置。
下面是我的代码片段,显示了渲染循环和世界物理是如何更新的。
scss复制代码//渲染框架
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需要的ammoObject
let 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());
}
}
}
6.用户输入
我们希望用户在桌面和触摸屏移动设备上都能够在应用中移动球体。
对于键盘事件,当按下箭头键时,通过监听“keydown”和“keyup”事件对球体添加相应方向的力。
对于触摸屏,在屏幕上创建了一个操纵杆控制器。然后,我们将“touchstart”、“touchmove”和“touchend”事件监听器添加到用于控制的div元素(控制器)中。
控制器会跟踪用户手指移动的起始、当前和结束坐标,然后在每次渲染时相应地更新球的受力。
下面只是控制器代码的一个片段,展示了一些大致的概念。有关完整代码,请从本文底部的源代码地址获取。
ini复制代码// 在坐标平面上保持对当前球体运动的跟踪
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;
}
}
下面是使用操纵杆的效果:
7.小结
7.1中文乱码
因为之前都是英文没有乱码,但是在创建博客的时候可能会出现中文乱码。开始宏哥以为是编码问题,查资料也加上了utf-8的编码格式,但是仍然出现中文乱码,后来查了半天才知道是之前的字体库不支持中文,因此换成支持中文的字体库就可以了。具体怎么换宏观就不在这里赘述了,可以查一下资料,如果实在不会的,可以给宏哥评论区留言,看到会及时回复你的。
现在,我们有了创建一个模拟真实物理世界的、可交互的3D应用
的所有工具。用你富有想象力的头脑,创造美的愿望来打造属于你的3D应用吧!在互联网时代,任何人都是终身学习者。
8.扩展
有兴趣的童鞋们可以看看这位大佬的3D应用,上边宏哥说的算是入门吧,下边这才算是深入研究。
在线:https://bruno-simon.com/
项目:https://github.com/brunosimon/folio-2019
同样可以在CLoud Studio中跑起来。
一辆红色、可爱的小吉普车,在漫无边际的地图上自由地驰骋,还可以按按喇叭助助兴,是的没错!这个博客还自带音效。
8.1小游戏
正当你“开”着小车唱着歌,突然就被作者创造的这一个 3D 世界给“绑架”了!因为他不止有简单的模型,还附带了一些非常有意思的小游戏。
比如你可以猛地加速撞击这三面“泡沫墙体”:
又或者是开着小吉普车“打保龄球”:
又或者是做一些“刺激”的越野训练:
这些小游戏是不是非常有意思呢?
8.2关于作者
能写出这么有意思的 3D 博客的作者,也是一定是位非常有意思的人,这不他把自己的生活也“刻画”到了这个项目里。
我们大致可以猜到这位大佬的日常生活大致是“一人一狗”,而且他非常爱自己的狗狗。除了打游戏,健身、出门还有其他一些活动都是和它在一起。
啊!多么惬意的生活呀~这哪里是程序员的生活呢?这明明就是财富自由的生活啊!你以为这个博客就只是记录生活点滴那你就错了,当我们的小吉普驾驶到 “project” 的时候,我们会看到很多作者写的技术博文。
以上就是本文的所有内容,如果您觉得这篇文章写得还不错,就请给我点一个赞,您的支持就是我更新的动力。
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=20062,转载请注明出处。
评论0