背景
近期学习了 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/o…
- 200 多页收费 3d 模型 https://www.turbosquid.com
- 收费 3D 雕像: https://threedscans.com
- 收费 3D 模型:https://free3d.com
- 艺术字体在线生成:https://cooltext.com
- 什么是酸性设计:https://www.shejipi.com/36125…
作者:dragonir 本文地址:https://segmentfault.com/a/11…