本文内容主要介绍,通过使用React+three.js技术栈,加载3D模型、添加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页面内容请搜索51zixue.net以前的文章或继续浏览下面的相关文章希望大家以后多多支持51zixue.net! 下载地址: vue实现页面div盒子拖拽排序功能 Vue实现大屏页面的屏幕自适应 |