使用three.js實(shí)現(xiàn)炫酷的酸性風(fēng)格3D頁(yè)面效果
本文內(nèi)容主要介紹,通過(guò)使用React+three.js技術(shù)棧,加載3D模型、添加3D文字、增加動(dòng)畫(huà)、點(diǎn)擊交互等,配合樣式設(shè)計(jì),實(shí)現(xiàn)充滿(mǎn)設(shè)計(jì)感的 🤢`酸性風(fēng)格頁(yè)面。
背景
近期學(xué)習(xí)了 WebGL
和 Three.js
的一些基礎(chǔ)知識(shí),于是想結(jié)合最近流行的酸性設(shè)計(jì)風(fēng)格,裝飾一下個(gè)人主頁(yè),同時(shí)總結(jié)一些學(xué)到的知識(shí)。本文內(nèi)容主要介紹,通過(guò)使用 React + three.js
技術(shù)棧,加載 3D模型
、添加 3D文字
、增加動(dòng)畫(huà)、點(diǎn)擊交互等,配合樣式設(shè)計(jì),實(shí)現(xiàn)充滿(mǎn)設(shè)計(jì)感的 🤢
酸性風(fēng)格頁(yè)面。
基礎(chǔ)知識(shí)
Three.js
Three.js
是一款基于原生 WebGL
封裝運(yùn)行在瀏覽器中的 3D引擎
,可以用它創(chuàng)建各種三維場(chǎng)景,包括了攝影機(jī)、光影、材質(zhì)等各種對(duì)象。是一款使用非常廣泛的三維引擎??梢栽?three.js官方中文文檔 進(jìn)一步深入學(xué)習(xí)。
酸性設(shè)計(jì)
酸性設(shè)計(jì)
一詞翻譯自 Acid Graphics
,起源于 上世紀(jì)90年代
的酸浩室音樂(lè)、電子舞曲以及嬉皮士文化。在設(shè)計(jì)領(lǐng)域,這種酸性美學(xué)承載一種 自由的主張
,怪誕的圖形,大膽鮮明的配色,特殊的材料質(zhì)感,搭配多種字體,組成了獨(dú)特的酸性設(shè)計(jì)風(fēng)格。
總之,鮮艷高飽和度
的色彩組合;黑灰色打底高飽和 熒光色
點(diǎn)綴畫(huà)面的 五彩斑斕的黑
;充滿(mǎn)未來(lái)感、炫酷、充滿(mǎn)科技感的液態(tài)金屬
、玻璃
、鋁箔塑料
等材質(zhì);隨機(jī)
的元素、圖形的布局;不斷 重復(fù)
、裁切、組合 幾何圖形
等都是酸性設(shè)計(jì)風(fēng)格。酸性風(fēng)格在音樂(lè)專(zhuān)輯封面、視覺(jué)海報(bào)、書(shū)籍電影封面、網(wǎng)頁(yè)設(shè)計(jì)中也逐漸開(kāi)始流行。
實(shí)現(xiàn)效果
在線(xiàn)預(yù)覽:https://tricell.fun
實(shí)現(xiàn)
3D模型
場(chǎng)景初始化
🌏
創(chuàng)建場(chǎng)景
scene = new THREE.Scene();
📷
初始化相機(jī)
透視相機(jī) PerspectiveCamera
的 4個(gè)
參數(shù)分別是指:視場(chǎng)、長(zhǎng)寬比、近面、遠(yuǎn)面。
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000); // 設(shè)置相機(jī)位置 camera.position.set(600, 20, -200); // 相機(jī)聚焦到屏幕中央 camera.lookAt(new THREE.Vector3(0, 0, 0));
💡
初始化光源
添加 半球光源 HemisphereLight
:創(chuàng)建室外效果更加自然的光源
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);
添加 環(huán)境光 AmbientLight
:
var ambiColor = '#0C0C0C'; var ambientLight = new THREE.AmbientLight(ambiColor); scene.add(ambientLight);
添加輔助工具(可選)
📦
添加輔助網(wǎng)格
GridHelper
可用于添加網(wǎng)格輔助線(xiàn),也可用于裝飾,通過(guò) GridHelper(size, divisions, colorCenterLine, colorGrid)
實(shí)現(xiàn)。
size
:網(wǎng)格寬度,默認(rèn)值為10
。divisions
:等分?jǐn)?shù),默認(rèn)值為10
。colorCenterLine
:中心線(xiàn)顏色,默認(rèn)值為0x444444
。colorGrid
: 網(wǎng)格線(xiàn)顏色,默認(rèn)值為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);
📦
添加相機(jī)控件
通過(guò)相機(jī)控件 OrbitControls
可以對(duì)三維場(chǎng)景進(jìn)行縮放、平移、旋轉(zhuǎn)操作,本質(zhì)上改變的并不是場(chǎng)景,而是相機(jī)的參數(shù)。開(kāi)發(fā)時(shí) OrbitControls.js
需要單獨(dú)引入。
controls = new THREE.OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.update();
📦
添加性能查看插件
stats
是一個(gè) Three.js
開(kāi)發(fā)的輔助庫(kù),主要用于檢測(cè)動(dòng)畫(huà)運(yùn)行時(shí)的幀數(shù)。stats.js
也需要單獨(dú)引入。
stats = new Stats(); container.appendChild(stats.dom);
加載模型
本文示例用到的 扔鐵餅的人
雕像 3D
模型來(lái)源于 threedscans.com
,可 免費(fèi)😄
下載使用,本文末尾提供了多個(gè)免費(fèi)模型下載網(wǎng)站,有 200多頁(yè)
免費(fèi)模型,大家感興趣的話(huà)可以挑選自己喜歡的模型下載使用。當(dāng)然,有建模能力的同學(xué),也可以使用 blender
、3dmax
等專(zhuān)業(yè)建模軟件生成自己喜歡的模型。
加載 obj
或 fbx
模型
需要單獨(dú)引入 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 模型
需要單獨(dú)引入 GLTFLoader.js
,加載 .gltf
格式模型方法稍有不同,需要注意的是模型的遍歷對(duì)象和最終添加到場(chǎng)景中的是 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); });
添加網(wǎng)格、加載完成模型之后的效果如下圖所示。
添加轉(zhuǎn)盤(pán)動(dòng)畫(huà)
通過(guò) requestAnimationFrame
刷新頁(yè)面的方法添加轉(zhuǎn)盤(pán)動(dòng)畫(huà)效果。window.requestAnimationFrame()
告訴瀏覽器希望執(zhí)行一個(gè)動(dòng)畫(huà),并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動(dòng)畫(huà)。該方法需要傳入一個(gè)回調(diào)函數(shù)作為參數(shù),該回調(diào)函數(shù)會(huì)在瀏覽器下一次重繪之前執(zhí)行。
function animate () { requestAnimationFrame(animate); // 隨著頁(yè)面重繪不斷改變場(chǎng)景的rotation.y來(lái)實(shí)現(xiàn)旋轉(zhuǎn) scene.rotation.y -= 0.015; renderer.render(scene, camera); }
添加點(diǎn)擊交互
在 Three.js
場(chǎng)景中我們要點(diǎn)擊某個(gè)模型獲取它的信息、或者做一些其他操作,要用到 Raycaster(光線(xiàn)投射)
,原理就是在你鼠標(biāo)點(diǎn)擊的位置發(fā)射一束射線(xiàn),被射線(xiàn)中的物體都被記錄下來(lái)?;菊Z(yǔ)法是 Raycaster(origin, direction, near, far)
,其中:
origin
:射線(xiàn)的起點(diǎn)向量。direction
:射線(xiàn)的方向向量。near
:所有返回的結(jié)果應(yīng)該比near
遠(yuǎn)。值不能為負(fù),默認(rèn)值為0
。far
:所有返回的結(jié)果應(yīng)該比far
近。不能小于near
,默認(rèn)值為無(wú)窮大
。
代碼實(shí)現(xiàn)的基本步驟是:獲取鼠標(biāo)在屏幕的坐標(biāo) →
屏幕坐標(biāo)轉(zhuǎn)標(biāo)準(zhǔn)設(shè)備坐標(biāo) →
標(biāo)準(zhǔn)設(shè)備坐標(biāo)轉(zhuǎn)世界坐標(biāo) →
拿到鼠標(biāo)在場(chǎng)景的世界坐標(biāo) →
根據(jù)世界坐標(biāo)和相機(jī)生成射線(xiàn)投射方向單位向量 →
根據(jù)射線(xiàn)投射方向單位向量創(chuàng)建射線(xiàn)投射器對(duì)象。
//聲明raycaster和mouse變量 var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2(); onMouseClick = event => { // 將鼠標(biāo)點(diǎn)擊位置的屏幕坐標(biāo)轉(zhuǎn)成threejs中的標(biāo)準(zhǔn)坐標(biāo),以屏幕中心為原點(diǎn),值的范圍為-1到1. mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = - (event.clientY / window.innerHeight) * 2 + 1; // 通過(guò)鼠標(biāo)點(diǎn)的位置和當(dāng)前相機(jī)的矩陣計(jì)算出raycaster raycaster.setFromCamera(mouse, camera); // 獲取raycaster直線(xiàn)和所有模型相交的數(shù)組集合 let intersects = raycaster.intersectObjects(scene.children); if (intersects.length > 0) { alert('HELLO WORLD') // 可以通過(guò)遍歷實(shí)現(xiàn)點(diǎn)擊不同mesh觸發(fā)不同交互,如: let selectedObj = intersects[0].object; if (selectedObj.name === 'car') { alert('汽車(chē)🚗') } } } window.addEventListener('click', onMouseClick, false);
添加3D文字
使用 TextGeometry(text : String, parameters : Object)
添加 3D文字
,以下是可設(shè)置屬性的說(shuō)明:
size
:字號(hào)大小,一般為大寫(xiě)字母的高度。height
:文字的厚度。weight
:值為normal
或bold
,表示是否加粗。font
:字體,默認(rèn)是helvetiker
,需對(duì)應(yīng)引用的字體文件。style
:值為normal
或italics
,表示是否斜體bevelThickness
:倒角厚度。bevelSize
:倒角寬度。curveSegments
:弧線(xiàn)分段數(shù),使得文字的曲線(xiàn)更加光滑。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); });
優(yōu)化
現(xiàn)在模型加載已經(jīng)基本完成了,但是 3D
模型的體積一般比較大,部署之后我發(fā)現(xiàn)網(wǎng)頁(yè)加載非常慢,影響用戶(hù)體驗(yàn),減小模型體積是十分必要的,在網(wǎng)上找了很久壓縮工具,發(fā)現(xiàn)在不需要安裝大型 3D建模軟件
的情況下,使用 obj2gltf
可以將體積較大的 OBJ
格式模型轉(zhuǎn)化為 gltf
模型,有效優(yōu)化模型體積,提升網(wǎng)頁(yè)加載速度。
安裝
npm install obj2gltf --save
將obj模型復(fù)制到以下目錄中
node_modules\obj2gltf\bin
執(zhí)行轉(zhuǎn)碼指令
node obj2gltf.js -i demo.obj -o demo.gltf
如圖出現(xiàn)類(lèi)似上述內(nèi)容,轉(zhuǎn)碼完成,對(duì)比轉(zhuǎn)化前后的文件體積,本例中 kas.obj
初始文件大小為 9.7M
轉(zhuǎn)化后的文件 kas.gltf
只有 4.6M
,體積縮小一半,此時(shí)將轉(zhuǎn)化后的模型加載到頁(yè)面上,肉眼幾乎看不出模型效果的變化,同時(shí)頁(yè)面加載速度得到明顯提升。
obj2gltf
也可以作為庫(kù)使用,通過(guò) node服務(wù)
實(shí)時(shí)轉(zhuǎn)化模型,感興趣的同學(xué)可以通過(guò)文章末尾鏈接深入學(xué)習(xí)。
也可以是使用 3D
建模軟件如 blender
等手動(dòng)通過(guò)減少模型 面數(shù)
和 縮小體積
等途徑對(duì)模型壓縮優(yōu)化,這種優(yōu)化效果更明顯。
完整代碼
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); // 透視相機(jī):視場(chǎng)、長(zhǎng)寬比、近面、遠(yuǎn)面 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)); // 半球光源:創(chuàng)建室外效果更加自然的光源 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); // 環(huán)境光 var ambiColor = '#0C0C0C'; var ambientLight = new THREE.AmbientLight(ambiColor); scene.add(ambientLight); // 網(wǎng)格 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(); } // 增加點(diǎn)擊事件 //聲明raycaster和mouse變量 var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2(); function onMouseClick(event) { // 通過(guò)鼠標(biāo)點(diǎn)擊位置計(jì)算出raycaster所需要點(diǎn)的位置,以屏幕中心為原點(diǎn),值的范圍為-1到1. mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = - (event.clientY / window.innerHeight) * 2 + 1; // 通過(guò)鼠標(biāo)點(diǎn)的位置和當(dāng)前相機(jī)的矩陣計(jì)算出raycaster raycaster.setFromCamera(mouse, camera); // 獲取raycaster直線(xiàn)和所有模型相交的數(shù)組集合 var intersects = raycaster.intersectObjects(scene.children); if (intersects.length > 0) { alert('HELLO WORLD') } } window.addEventListener('click', onMouseClick, false); } }
其他設(shè)計(jì)元素
本文主要介紹 3D元素
的加載,由于文章篇幅以及時(shí)間有限(博主太懶😂
)其他元素的實(shí)現(xiàn)不做詳細(xì)講解(可能后續(xù)有時(shí)間會(huì)總結(jié)整理 maybe
)感興趣的同學(xué)可以擴(kuò)展閱讀以下其他大神優(yōu)秀的文章。
流體背景
靜態(tài)
液態(tài)背景圖可以通過(guò) SVG filter
實(shí)現(xiàn),可以閱讀《Creating Patterns With SVG Filters》,實(shí)現(xiàn) 動(dòng)態(tài)
流體背景,可以使用Three.js 結(jié)合原生GLSL實(shí)現(xiàn),可參考《CodePen Shader Template》示例來(lái)實(shí)現(xiàn)。
金屬、霓虹、故障效果等酸性效果字體可以閱讀我的另一篇文章《僅用CSS幾步實(shí)現(xiàn)賽博朋克2077風(fēng)格視覺(jué)效果》,也可以使用設(shè)計(jì)生成,由于時(shí)間關(guān)系,本文項(xiàng)目中的金屬效果文字以及本文banner頭圖中的文字都是使用在線(xiàn)藝術(shù)字體生成網(wǎng)站生成的,感興趣的同學(xué)可以自行嘗試設(shè)計(jì)。
未來(lái)進(jìn)一步優(yōu)化
#todo
酸性風(fēng)格液態(tài)背景實(shí)現(xiàn)。#todo
3D模型
液態(tài)金屬效果。
three.js
優(yōu)秀案例推薦
最后給大家推薦幾個(gè)非常驚艷的 three.js
項(xiàng)目來(lái)一起體驗(yàn)和學(xué)習(xí),無(wú)論是頁(yè)面交互、視覺(jué)設(shè)計(jì)還是性能優(yōu)化都做到了極致,可以從中學(xué)到很多。
github首頁(yè):3D地球
實(shí)時(shí)顯示全球熱門(mén)倉(cāng)庫(kù)。
kodeclubs:低面數(shù) 3D城市
第三人稱(chēng)小游戲。
球鞋展示:720度
球鞋動(dòng)態(tài)展示。
沙雕dance:沙雕動(dòng)物舞者。
Zenly軟件:Zenly App
中文主頁(yè)。
參考資料
three.js: https://threejs.org
obj2gltf: https://github.com/CesiumGS/obj2gltf
200多頁(yè)免費(fèi)3d模型 https://www.turbosquid.com
免費(fèi)3D雕像: https://threedscans.com
免費(fèi)3D模型:https://free3d.com
藝術(shù)字體在線(xiàn)生成:https://cooltext.com
什么是酸性設(shè)計(jì):https://www.shejipi.com/361258.html
作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/15350537.html
到此這篇關(guān)于使用three.js實(shí)現(xiàn)炫酷的酸性風(fēng)格3D頁(yè)面的文章就介紹到這了,更多相關(guān)three.js酸性風(fēng)格3D頁(yè)面內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS實(shí)現(xiàn)table表格內(nèi)針對(duì)某列內(nèi)容進(jìn)行即時(shí)搜索篩選功能
這篇文章主要介紹了JS實(shí)現(xiàn)table表格內(nèi)針對(duì)某列內(nèi)容進(jìn)行即時(shí)搜索篩選功能,涉及javascript針對(duì)HTML元素的遍歷、屬性動(dòng)態(tài)修改相關(guān)操作技巧,需要的朋友可以參考下2018-05-05前端開(kāi)發(fā)基礎(chǔ)javaScript的六大作用
這篇文章主要介紹了前端開(kāi)發(fā)基礎(chǔ)javaScript的六大作用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08JS如何遍歷帶有子集的數(shù)組集合(嵌套數(shù)組)
這篇文章主要介紹了JS如何遍歷帶有子集的數(shù)組集合(嵌套數(shù)組)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06javascript 常見(jiàn)的閉包問(wèn)題的解決辦法
javascript 常見(jiàn)的閉包問(wèn)題的解決辦法,需要的朋友可以參考下。2009-11-11jquery實(shí)現(xiàn)下拉菜單的二級(jí)聯(lián)動(dòng)利用json對(duì)象從DB取值顯示聯(lián)動(dòng)
這篇文章主要介紹了jquery實(shí)現(xiàn)下拉菜單的二級(jí)聯(lián)動(dòng)利用json對(duì)象從DB取值顯示聯(lián)動(dòng),需要的朋友可以參考下2014-03-03五步輕松實(shí)現(xiàn)JavaScript HTML時(shí)鐘效果
這篇文章主要為大家詳細(xì)介紹了五步輕松實(shí)現(xiàn)JavaScript HTML時(shí)鐘效果的代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11