使用three.js实现炫酷的酸性风格3D页面
背景
近期学习了 WebGL
和 Three.js
的一些基础知识,于是想结合最近流行的酸性设计风格,装饰一下个人主页,同时总结一些学到的知识。本文内容主要介绍,通过使用 React + three.js
技术栈,加载 3D模型
、添加 3D文字
、增加动画、点击交互等,配合样式设计,实现充满设计感的 酸性风格页面。
基础知识
Three.js
Three.js
是一款基于原生 WebGL
封装运行在浏览器中的 3D引擎
,可以用它创建各种三维场景,包括了摄影机、光影、材质等各种对象。是一款使用非常广泛的三维引擎。可以在 three.js官方中文文档 进一步深入学习。
酸性设计
酸性设计
一词翻译自 Acid Graphics
,起源于 上世纪90年代
的酸浩室音乐、电子舞曲以及嬉皮士文化。在设计领域,这种酸性美学承载一种 自由的主张
,怪诞的图形,大胆鲜明的配色,特殊的材料质感,搭配多种字体,组成了独特的酸性设计风格。
总之,鲜艳高饱和度
的色彩组合;黑灰色打底高饱和 荧光色
点缀画面的 五彩斑斓的黑
;充满未来感、炫酷、充满科技感的液态金属
、玻璃
、铝箔塑料
等材质;随机
的元素、图形的布局;不断 重复
、裁切、组合 几何图形
等都是酸性设计风格。酸性风格在音乐专辑封面、视觉海报、书籍电影封面、网页设计中也逐渐开始流行。
实现效果
在线预览:https://tricell.fun
实现
3D模型
场景初始化
创建场景
scene = new THREE.Scene();
初始化相机
透视相机 PerspectiveCamera
的 4个
参数分别是指:视场、长宽比、近面、远面。
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
// 设置相机位置
camera.position.set(600, 20, -200);
// 相机聚焦到屏幕中央
camera.lookAt(new THREE.Vector3(0, 0, 0));
初始化光源
添加 半球光源 HemisphereLight
:创建室外效果更加自然的光源
light = new THREE.HemisphereLight(0xffffff, 0x444444);
light.position.set(0, 20, 0);
scene.add(light);
light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 20, 10);
light.castShadow = true;
scene.add(light);
添加 环境光 AmbientLight
:
var ambiColor = '#0C0C0C';
var ambientLight = new THREE.AmbientLight(ambiColor);
scene.add(ambientLight);
添加辅助工具(可选)
添加辅助网格
GridHelper
可用于添加网格辅助线,也可用于装饰,通过 GridHelper(size, divisions, colorCenterLine, colorGrid)
实现。
size
:网格宽度,默认值为10
。divisions
:等分数,默认值为10
。colorCenterLine
:中心线颜色,默认值为0x444444
。colorGrid
: 网格线颜色,默认值为0x888888
。
var grid = new THREE.GridHelper(1000, 100, 0x000000, 0x000000);
grid.material.opacity = 0.1;
grid.material.transparent = true;
grid.position.set(0, -240, 0);
scene.add(grid);
添加相机控件
通过相机控件 OrbitControls
可以对三维场景进行缩放、平移、旋转操作,本质上改变的并不是场景,而是相机的参数。开发时 OrbitControls.js
需要单独引入。
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.update();
添加性能查看插件
stats
是一个 Three.js
开发的辅助库,主要用于检测动画运行时的帧数。stats.js
也需要单独引入。
stats = new Stats();
container.appendChild(stats.dom);
加载模型
本文示例用到的 扔铁饼的人
雕像 3D
模型来源于 threedscans.com
,可 免费
下载使用,本文末尾提供了多个免费模型下载网站,有 200多页
免费模型,大家感兴趣的话可以挑选自己夏欢的自己下载使用。当然,有建模能力的同学,也可以使用 blender
、3dmax
等专业建模软件生成自己喜欢的模型。
加载 obj
或 fbx
模型
需要单独引入 FBXLoader.js
或 OBJLoader.js
,.fbx
和 .obj
格式的模型加载方法是一样的。
// var loader = new THREE.FBXLoader();
var loader = new THREE.OBJLoader();
loader.load(model, function (object) {
object.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
object.rotation.y = Math.PI / 2;
object.position.set(0, -200, 0);
object.scale.set(0.32, 0.32, 0.32);
model = object;
scene.add(object);
});
加载 gltf
模型
需要单独引入 GLTFLoader.js
,加载 .gltf
格式模型方法稍有不同,需要注意的是模型的遍历对象和最终添加到场景中的是 object.scene
而不是 object
。
var loader = new THREE.GLTFLoader();
loader.load(model, function (object) {
object.scene.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
object.scene.rotation.y = Math.PI / 2;
object.scene.position.set(0, -240, 0);
object.scene.scale.set(0.33, 0.33, 0.33);
model = object.scene;
scene.add(object.scene);
});
添加网格、加载完成模型之后的效果如下图所示。
添加转盘动画
通过 requestAnimationFrame
刷新页面的方法添加转盘动画效果。window.requestAnimationFrame()
告诉浏览器希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
function animate () {
requestAnimationFrame(animate);
// 随着页面重回不断改变场景的rotation.y来实现旋转
scene.rotation.y -= 0.015;
renderer.render(scene, camera);
}
添加点击交互
在 Three.js
场景中我们要点击某个模型获取它的信息、或者做一些其他操作,要用到 Raycaster(光线投射)
,原理就是在你鼠标点击的位置发射一束射线,被射线中的物体都被记录下来。基本语法是 Raycaster(origin, direction, near, far)
,其中:
origin
:射线的起点向量。direction
:射线的方向向量。near
:所有返回的结果应该比near
远。值不能为负,默认值为0
。far
:所有返回的结果应该比far
近。不能小于near
,默认值为无穷大
。
代码实现的基本步骤是:获取鼠标在屏幕的坐标 →
屏幕坐标转标准设备坐标 →
标准设备坐标转世界坐标 →
拿到鼠标在场景的世界坐标 →
根据世界坐标和相机生成射线投射方向单位向量 →
根据射线投射方向单位向量创建射线投射器对象。
//声明raycaster和mouse变量
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
onMouseClick = event => {
// 将鼠标点击位置的屏幕坐标转成threejs中的标准坐标,以屏幕中心为原点,值的范围为-1到1.
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// 通过鼠标点的位置和当前相机的矩阵计算出raycaster
raycaster.setFromCamera(mouse, camera);
// 获取raycaster直线和所有模型相交的数组集合
let intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
alert('HELLO WORLD')
// 可以通过遍历实现点击不同mesh触发不同交互,如:
let selectedObj = intersects[0].object;
if (selectedObj.name === 'car') {
alert('汽车')
}
}
}
window.addEventListener('click', onMouseClick, false);
添加3D文字
使用 TextGeometry(text : String, parameters : Object)
添加 3D文字
,以下是可设置属性的说明:
size
:字号大小,一般为大写字母的高度。height
:文字的厚度。weight
:值为normal
或bold
,表示是否加粗。font
:字体,默认是helvetiker
,需对应引用的字体文件。style
:值为normal
或italics
,表示是否斜体bevelThickness
:倒角厚度。bevelSize
:倒角宽度。curveSegments
:弧线分段数,使得文字的曲线更加光滑。bevelEnabled
:布尔值,是否使用倒角,意为在边缘处斜切。
var loader = new THREE.FontLoader();
loader.load('gentilis_regular.typeface.json', function (font) {
var textGeo = new THREE.TextGeometry('HELLO WORLD', {
font: font,
size: .8,
height: .8,
curveSegments: .05,
bevelThickness: .05,
bevelSize: .05,
bevelEnabled: true
});
var textMaterial = new THREE.MeshPhongMaterial({ color: 0x03c03c });
var mesh = new THREE.Mesh(textGeo, textMaterial);
mesh.position.set(0, 3.8, 0);
scene.add(mesh);
});
优化
现在模型加载已经基本完成了,但是 3D
模型的体积一般比较大,部署之后我发现网页加载非常慢,影响用户体验,减小模型体积是十分必要的,在网上找了很久压缩工具,发现在不需要安装大型 3D建模软件
的情况下,使用 obj2gltf
可以将体积较大的 OBJ
格式模型转化为 gltf
模型,有效优化模型体积,提升网页加载速度。
安装
npm install obj2gltf --save
将obj模型复制到以下目录中
node_modules\obj2gltf\bin
执行转码指令
node obj2gltf.js -i demo.obj -o demo.gltf
如图出现类似上述内容,转码完成,对比转化前后的文件体积,本例中 kas.obj
初始文件大小为 9.7M
转化后的文件 kas.gltf
只有 4.6M
,体积缩小一半,此时将转化后的模型加载到页面上,肉眼几乎看不出模型效果的变化,同时页面加载速度得到明显提升。
obj2gltf
也可以作为库使用,通过node服务
实时转化模型,感兴趣的同学可以通过文章末尾链接深入学习。
也可以是使用3D
建模软件如blender
等手动通过减少模型面数
和缩小体积
等途径对模型压缩优化,这种优化效果更明显。
完整代码
var model = require('@/assets/models/kas.gltf');
var container, stats, controls;
var camera, scene, renderer, light, model;
class Kas extends React.Component {
render () {
return (
<div id="kas"></div>
)
}
componentDidMount () {
this.initThree();
}
initThree () {
init();
animate();
function init () {
container = document.getElementById('kas');
scene = new THREE.Scene();
scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000);
// 透视相机:视场、长宽比、近面、远面
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(600, 20, -200);
camera.lookAt(new THREE.Vector3(0, 0, 0));
// 半球光源:创建室外效果更加自然的光源
light = new THREE.HemisphereLight(0xffffff, 0x444444);
light.position.set(0, 20, 0);
scene.add(light);
light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 20, 10);
light.castShadow = true;
scene.add(light);
// 环境光
var ambiColor = '#0C0C0C';
var ambientLight = new THREE.AmbientLight(ambiColor);
scene.add(ambientLight);
// 网格
var grid = new THREE.GridHelper(1000, 100, 0x000000, 0x000000);
grid.material.opacity = 0.1;
grid.material.transparent = true;
grid.position.set(0, -240, 0);
scene.add(grid);
// 加载gltf模型
var loader = new THREE.GLTFLoader();
loader.load(model, function (object) {
object.scene.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
object.scene.rotation.y = Math.PI / 2;
object.scene.position.set(0, -240, 0);
object.scene.scale.set(0.33, 0.33, 0.33);
model = object.scene;
scene.add(object.scene);
});
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearAlpha(0);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}, false);
stats = new Stats();
container.appendChild(stats.dom);
}
function animate () {
var clock = new THREE.Clock()
requestAnimationFrame(animate);
var delta = clock.getDelta();
scene.rotation.y -= 0.015;
renderer.render(scene, camera);
stats.update();
}
// 增加点击事件
//声明raycaster和mouse变量
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick(event) {
// 通过鼠标点击位置计算出raycaster所需要点的位置,以屏幕中心为原点,值的范围为-1到1.
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// 通过鼠标点的位置和当前相机的矩阵计算出raycaster
raycaster.setFromCamera(mouse, camera);
// 获取raycaster直线和所有模型相交的数组集合
var intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
alert('HELLO WORLD')
}
}
window.addEventListener('click', onMouseClick, false);
}
}
其他设计元素
本文主要介绍 3D元素
的加载,由于文章篇幅以及时间有限(博主太懒
)其他元素的实现不做详细讲解(可能后续有时间会总结整理 maybe
)感兴趣的同学可以扩展阅读以下其他大神优秀的文章。
流体背景
静态
液态背景图可以通过 SVG filter
实现,实现 动态
流体背景,可以阅读《Creating Patterns With SVG Filters》,可以使用Three.js 结合原生GLSL实现,可参考《CodePen Shader Template》示例来实现。
金属、霓虹、故障效果等酸性效果字体可以阅读我的另一篇文章《仅用CSS几步实现赛博朋克2077风格视觉效果》,也可以使用设计生成,由于时间关系,本文项目中的金属效果文字以及本文banner头图中的文字都是使用在线艺术字体生成网站生成的,感兴趣的同学可以自行尝试设计。
未来进一步优化
#todo
酸性风格液态背景实现。#todo
3D模型液化金属化效果。
three.js
优秀案例推荐
最后给大家推荐几个非常惊艳的 three.js
项目来一起体验和学习,无论是页面交互、视觉设计还是性能优化都做到了极致,可以从中学到很多。
- github首页:
3D地球
实时显示全球热门仓库。 - kodeclubs:低面数
3D城市
第三人称小游戏。 - 球鞋展示:
720度
球鞋动态展示。 - 沙雕dance:沙雕动物舞者。
- Zenly软件:Zenly App中文主页。
参考资料
- three.js: https://threejs.org
- obj2gltf: https://github.com/CesiumGS/obj2gltf
- 200多页免费3d模型 https://www.turbosquid.com
- 免费3D雕像: https://threedscans.com
- 免费3D模型:https://free3d.com
- 艺术字体在线生成:https://cooltext.com
- 什么是酸性设计:https://www.shejipi.com/361258.html
作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/15350537.html
使用three.js实现炫酷的酸性风格3D页面的更多相关文章
- 使用Three.js实现炫酷的赛博朋克风格3D数字地球大屏 🌐
声明:本文涉及图文和模型素材仅用于个人学习.研究和欣赏,请勿二次修改.非法传播.转载.出版.商用.及进行其他获利行为. 背景 近期工作有涉及到数字大屏的需求,于是利用业余时间,结合 Three.js ...
- CSS3和js炫酷点击按钮3D翻转动画特效
简要教程 flipside是一款使用CSS3和js制作的炫酷点击按钮无缝过渡到确认面板的过渡动画特效.该点击按钮特效在按钮不同方向的边部点击时,产生的过渡动画特效是不一样的. 在线预览 源码下载 ...
- Droptiles - 炫酷的 Metro 风格的层叠式 Web 面板
介绍 Droptiles是一套Metro风格的类似Win8的Web2.0控制面板.它采用图块(tiles)建立用户体验.图块(tiles)是一些可以从外部资源中获取数据的迷你应用.点击图块(tile) ...
- 炫酷实用的CSS3代码垂直手风琴菜单
今天在微博上看到别人分享的代码,自己拿来自己保存着. 代码效果如下: 下面是源码: index.html <!DOCTYPE html> <html > <head> ...
- js和CSS3炫酷3D相册展示
<!doctype html> <html> <head> <meta charset="UTF"> <title>js ...
- 炫酷的jQuery对话框插gDialog
js有alert,prompt和confirm对话框,不过不是很美体验也不是很好,用jQuery也能实现, 体验效果:http://hovertree.com/texiao/jquery/34/ 代码 ...
- html5跟随鼠标炫酷网站引导页动画特效
html5跟随鼠标炫酷网站引导页动画特效一款非常不错的引导页,文字效果渐变,鼠标跟随出绚丽的条纹.html5炫酷网站引导页,鼠标跟随出特效. 体验效果:http://hovertree.com/tex ...
- 一款炫酷的幻灯片播放框架介绍(附demo及使用方法)
废话不多说,先上demo(建议在chrome下打开 F键全屏 esc退出全屏): 我的demo-博客园简介 官网demo 更多demo 今天为大家介绍一款基于css3和JavaScript的幻灯片播放 ...
- 炫酷的jquery瀑布流
最近做了一个瀑布流效果,思路很简单 首先计算屏幕一行可以放多少个图片,然后在第二行开始,计算每一列的高度并取出最小值,将新图片加载在最小列高度下,如此循环,并且设定一个条件,当滑动到一定距离后,开始重 ...
随机推荐
- 【mysql】关联查询_子查询_排序分组优化
1. 关联查询优化 1.1 left join 结论: ①在优化关联查询时,只有在被驱动表上建立索引才有效! ②left join 时,左侧的为驱动表,右侧为被驱动表! 1.2 inner join ...
- uwp 中的appservice
在上篇里,我使用的是寄宿在WPF上的WCF进行两个程序间的通信,在解决问题的同时,我的同事也在思考能否使用UWP来做这件事.于是,我们发现了App Service,两个UWP应用沟通的桥梁. App ...
- 使用filter过滤GZIP压缩(二)
在代码之前,讲一下用filter实现GZIP压缩的原理: 因为GZIP压缩之后,是从服务器端传输到浏览器端,从servlet到浏览器(从jsp到浏览器),其实是response带回内容,所以我们要在f ...
- ES6——简单的多态
简单的多态 多态: 1.同一个接口,在不同情况下做不一样的事情:相同的接口,不同的表现: 2.接口本身只是一组定义,实现都是子类里面:需要子类去实现的方法(子类只需重写与父类同名的方法,即可达到覆盖的 ...
- 在C++中使用openmp进行多线程编程
在C++中使用openmp进行多线程编程 一.前言 多线程在实际的编程中的重要性不言而喻.对于C++而言,当我们需要使用多线程时,可以使用boost::thread库或者自从C++ 11开始支持的st ...
- ❤️用武侠小说的形式来阅读LinkedList的源码,绝了!
一.LinkedList 的剖白 大家好,我是 LinkedList,和 ArrayList 是同门师兄弟,但我俩练的内功却完全不同.师兄练的是动态数组,我练的是链表. 问大家一个问题,知道我为什么要 ...
- Cookie及通过Cookie常见应用
会话的概念 会话可以简单理解为:用户打开一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话. 会话需要解决的问题 每个用户与服务器进行交互的过程中,各自会有一 ...
- 常用ADB命令汇总
网络连接 通过TCP/IP连接设备 adb connect <ip:port> 断开已有的TCP/IP连接 adb disconnect <ip:port> 监听设备上指定的端 ...
- Vs code自动生成Doxygen格式注释
前言 程序中注释的规范和统一性的重要性不言而喻,本文就推荐一种在用vscode编写代码时自动化生成标准化注释格式的方法,关于Doxygen规范及其使用可查看博文 代码注释规范之Doxygen. ...
- Appium问题解决方案(5)- selenium.common.exceptions.InvalidSelectorException: Message: Locator Strategy 'name' is not supported for this session
背景 使用Appium Server 1.15.1版本 执行了以下脚本 test = driver.find_element_by_name("自动化测试") print(test ...