Three.js實(shí)現(xiàn)臉書元宇宙3D動(dòng)態(tài)Logo效果
本文主要講述通過 Three.js + Blender 技術(shù)棧,實(shí)現(xiàn) Meta 公司炫酷的 3D 動(dòng)態(tài) Logo,內(nèi)容包括基礎(chǔ)模型圓環(huán)、環(huán)面扭結(jié)、管道及模型生成、模型加載、添加動(dòng)畫、添加點(diǎn)擊事件、更換材質(zhì)等。
背景
Facebook
近期將其母公司改名為 Meta
,宣布正式開始進(jìn)軍 元宇宙 🪐
領(lǐng)域。本文主要講述通過 Three.js
+ Blender
技術(shù)棧,實(shí)現(xiàn) Meta
公司炫酷的 3D
動(dòng)態(tài) Logo
,內(nèi)容包括基礎(chǔ)模型圓環(huán)、環(huán)面扭結(jié)、管道及模型生成、模型加載、添加動(dòng)畫、添加點(diǎn)擊事件、更換材質(zhì)等。
什么是元宇宙
元宇宙 Metaverse 一詞源于 1992
年尼爾·斯蒂芬森的 《雪崩》
,該書描述了一個(gè)平行于現(xiàn)實(shí)世界的虛擬世界 Metaverse
,所有現(xiàn)實(shí)生活中的人都有一個(gè)網(wǎng)絡(luò)分身 Avatar
。維基百科
對(duì)元宇宙的描述是:通過虛擬增強(qiáng)的物理現(xiàn)實(shí),呈現(xiàn)收斂性和物理持久性特征的,基于未來互聯(lián)網(wǎng),具有鏈接感知和共享特征的 3D
虛擬空間。
元宇宙的內(nèi)涵是吸納了信息革命 5G/6G
、互聯(lián)網(wǎng)革命 web3.0
、人工智能革命,以及 VR
、AR
、MR
,特別是游戲引擎在內(nèi)的虛擬現(xiàn)實(shí)技術(shù)革命的成果,向人類展現(xiàn)出構(gòu)建與傳統(tǒng)物理世界平行的全息數(shù)字世界的可能性;引發(fā)了信息科學(xué)、量子科學(xué),數(shù)學(xué)和生命科學(xué)的互動(dòng),改變科學(xué)范式;推動(dòng)了傳統(tǒng)的哲學(xué)、社會(huì)學(xué)甚至人文科學(xué)體系的突破;囊括了所有的數(shù)字技術(shù)。正如電影 《頭號(hào)玩家》
的場(chǎng)景,在未來某一天,人們可以隨時(shí)隨地切換身份,自由穿梭于物理世界和數(shù)字世界,在虛擬空間和時(shí)間節(jié)點(diǎn)所構(gòu)成的元宇宙中生活學(xué)習(xí)。
實(shí)現(xiàn)效果
進(jìn)入正題,先來看看本文示例的實(shí)現(xiàn)效果。
🔗
在線預(yù)覽:https://dragonir.github.io/3d-meta-logo (由于模型較大,加載進(jìn)度可能比較緩慢,需要耐心等待)
開發(fā)實(shí)現(xiàn)
📌
注意:上述示例動(dòng)圖展示的是試煉四,不想看試錯(cuò)過程(試煉一、試煉二、試煉三)的,可直接跳轉(zhuǎn)到試煉四段落查看詳細(xì)實(shí)現(xiàn)流程。失敗流程中都列出了難點(diǎn),知道解決方案的大佬請(qǐng)?jiān)谠u(píng)論區(qū)不吝賜教。
開發(fā)之前我們先觀察一下 Meta Logo
,可以發(fā)現(xiàn)它是一個(gè)圓環(huán)經(jīng)過對(duì)折扭曲形成的,因此實(shí)現(xiàn)它的時(shí)候可以從實(shí)現(xiàn)圓環(huán)開始。
試煉一:THREE.TorusGeometry
Three.js
提供的基礎(chǔ)幾何體 THREE.TorusGeometry
(圓環(huán)),它是一種看起來像甜甜圈 🍩
的簡(jiǎn)單圖形。主要參數(shù):
radius
:可選。定義圓環(huán)的半徑尺寸。默認(rèn)值是1
。tube
:可選。定義圓環(huán)的管子半徑。默認(rèn)值是0.4
。radialSegments
:可選。定義圓環(huán)長(zhǎng)度方向上的分段數(shù)。默認(rèn)值是8
。tubularSegments
:可選。定義圓環(huán)寬度方向上的分段數(shù)。默認(rèn)值是6
。arc
:可選。定義圓環(huán)繪制的長(zhǎng)度。取值范圍是0
到2 * π
。默認(rèn)值是2 * π
(一個(gè)完整的圓)。
語法示例:
THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments, arc);
😭 失敗
:沒有找到扭曲圓環(huán)的方法。
試煉二:THREE.TorusKnotGeometry
THREE.TorusKnotGeometry
可以用來創(chuàng)建三維環(huán)面扭結(jié),環(huán)面扭結(jié)是一種比較特別的結(jié),看上去像一根管子繞著它自己旋轉(zhuǎn)了幾圈。主要參數(shù):
radius
:可選。設(shè)置完整圓環(huán)的半徑,默認(rèn)值是1
。tube
:可選。設(shè)置管道的半徑,默認(rèn)值是0.4
。radialSegments
:可選。指定管道截面的分段數(shù),段數(shù)越多,管道截面圓越光滑,默認(rèn)值是8
。tubularSegments
:可選。指定管道的分段數(shù),段數(shù)越多,管道越光滑,默認(rèn)值是64
。p
:可選。決定幾何體將繞著其旋轉(zhuǎn)對(duì)稱軸旋轉(zhuǎn)多少次,默認(rèn)值是2
。q
:可選。決定幾何體將繞著其內(nèi)部圓環(huán)旋轉(zhuǎn)多少次,默認(rèn)值是3
。
語法示例:
THREE.TorusKnotGeometry(radius, tube, radialSegments, tubularSegments , p, q);
😭 失敗
:沒找到能夠控制手動(dòng)扭曲程度的方法。
試煉三:THREE.TubeGeometry
THREE.TubeGeometry
沿著一條三維的樣條曲線拉伸出一根管。你可以指定一些定點(diǎn)來定義路徑,然后使用 THREE.TubeGeometry
創(chuàng)建這根管。主要參數(shù):
path
:該屬性用一個(gè)THREE.SplineCurve3
對(duì)象來指定管道應(yīng)當(dāng)遵循的路徑。segments
:該屬性指定構(gòu)建這個(gè)管所用的分段數(shù)。默認(rèn)值為64
.路徑越長(zhǎng),指定的分段數(shù)應(yīng)該越多。radius
:該屬性指定管的半徑。默認(rèn)值為1
.radiusSegments
:該屬性指定管道圓周的分段數(shù)。默認(rèn)值為8
,分段數(shù)越多,管道看上去越圓。closed
:如果該屬性設(shè)置為true
,管道的頭和尾會(huì)連起來,默認(rèn)值為false
。
代碼示例
// ... var controls = new function () { // 點(diǎn)的位置坐標(biāo) this.deafultpoints = [ [0, 0.4, -0.4], [0.4, 0, 0], [0.4, 0.8, 0.4], [0, 0.4, 0.4], [-0.4, 0, 0], [-0.4, 0.8, -0.4], [0, 0.4, -0.4] ] this.segments = 64; this.radius = 1; this.radiusSegments = 8; this.closed = true; this.points = []; this.newPoints = function () { var points = []; for (var i = 0; i < controls.deafultpoints.length; i++) { var _x = controls.deafultpoints[i][0] * 22; var _y = controls.deafultpoints[i][1] * 22; var _z = controls.deafultpoints[i][2] * 22; points.push(new THREE.Vector3(_x, _y, _z)); } controls.points = points; controls.redraw(); }; this.redraw = function () { redrawGeometryAndUpdateUI(gui, scene, controls, function() { return generatePoints(controls.points, controls.segments, controls.radius, controls.radiusSegments, controls.closed); }); }; }; controls.newPoints(); function generatePoints(points, segments, radius, radiusSegments, closed) { if (spGroup) scene.remove(spGroup); spGroup = new THREE.Object3D(); var material = new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: false }); points.forEach(function (point) { var spGeom = new THREE.SphereGeometry(0.1); var spMesh = new THREE.Mesh(spGeom, material); spMesh.position.copy(point); spGroup.add(spMesh); }); scene.add(spGroup); return new THREE.TubeGeometry(new THREE.CatmullRomCurve3(points), segments, radius, radiusSegments, closed); } // ...
😊 勉強(qiáng)成功
:但是管道連成的圓環(huán)不夠圓,實(shí)現(xiàn)完美的圓弧需要精確的坐標(biāo),暫時(shí)沒找到坐標(biāo)計(jì)算方法。
試煉四:Blender + Three.js
雖然使用 THREE.TubeGeometry
可以勉強(qiáng)實(shí)現(xiàn),但是效果并不好,要實(shí)現(xiàn)圓滑的環(huán),需要為管道添加精確的扭曲圓環(huán)曲線路徑函數(shù)。由于數(shù)學(xué)能力有限 🤕️
,暫時(shí)沒找到扭曲圓弧路徑計(jì)算的方法。因此決定從建模層面解決。
成功 😄
:但是手殘的我使用 Blender
建?;ㄙM(fèi)了大量的時(shí)間 💔
。
建模教程
逛 B站
的時(shí)候發(fā)現(xiàn)了這位大佬發(fā)的寶藏視頻,剛好解決了自己的難題。
🎦
傳送門:【動(dòng)態(tài)設(shè)計(jì)教程】AE+blender能怎么玩?臉書元宇宙Meta動(dòng)態(tài)logo已完全解析,100%學(xué)會(huì)
用Blender建模
使用 Blender
進(jìn)行建模,并導(dǎo)出可攜帶動(dòng)畫的 fbx
格式,導(dǎo)出的時(shí)候不要忘記勾選 烘焙動(dòng)畫
選項(xiàng)。
加載依賴
<script src="./assets/libs/three.js"></script> <script src="./assets/libs/loaders/FBXLoader.js"></script> <script src="./assets/libs/inflate.min.js"></script> <script src="./assets/libs/OrbitControls.js"></script> <script src="./assets/libs/stats.js"></script>
場(chǎng)景初始化
var container, stats, controls, compose, camera, scene, renderer, light, clickableObjects = [], mixer, mixerArr = [], manMixer; var clock = new THREE.Clock(); init(); animate(); function init() { container = document.createElement('div'); document.body.appendChild(container); // 場(chǎng)景 scene = new THREE.Scene(); scene.transparent = true; scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000); // 透視相機(jī):視場(chǎng)、長(zhǎng)寬比、近面、遠(yuǎn)面 camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 4, 16); camera.lookAt(new THREE.Vector3(0, 0, 0)); // 半球光源:創(chuàng)建室外效果更加自然的光源 light = new THREE.HemisphereLight(0xefefef); light.position.set(0, 20, 0); scene.add(light); // 平行光 light = new THREE.DirectionalLight(0x2d2d2d); light.position.set(0, 20, 10); light.castShadow = true; scene.add(light); // 環(huán)境光 var ambientLight = new THREE.AmbientLight(0xffffff, .5); scene.add(ambientLight); // 網(wǎng)格 var grid = new THREE.GridHelper(100, 100, 0xffffff, 0xffffff); grid.position.set(0, -10, 0); grid.material.opacity = 0.3; grid.material.transparent = true; scene.add(grid); renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.outputEncoding = THREE.sRGBEncoding; renderer.setSize(window.innerWidth, window.innerHeight); // 背景色設(shè)置為透明 renderer.setClearAlpha(0); // 開啟陰影 renderer.shadowMap.enabled = true; container.appendChild(renderer.domElement); // 添加鏡頭控制器 controls = new THREE.OrbitControls(camera, renderer.domElement); controls.target.set(0, 0, 0); controls.update(); window.addEventListener('resize', onWindowResize, false); // 初始化性能插件 stats = new Stats(); container.appendChild(stats.dom); } // 屏幕縮放 function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }
📌
想了解場(chǎng)景初始化的詳細(xì)流程,可閱讀我的另一篇文章《使用three.js實(shí)現(xiàn)炫酷的酸性風(fēng)格3D頁面》。
加載Logo模型
使用 FBXLoader
加載模型,并設(shè)置模型的位置和大小。
var loader = new THREE.FBXLoader(); loader.load('assets/models/meta.fbx', function (mesh) { mesh.traverse(function (child) { if (child.isMesh) { child.castShadow = true; child.receiveShadow = true; } }); mesh.rotation.y = Math.PI / 2; mesh.position.set(0, 1, 0); mesh.scale.set(0.05, 0.05, 0.05); scene.add(mesh); });
添加材質(zhì)
本文 Logo
使用的是 MeshPhysicalMaterial
材質(zhì),它是一種 PBR
物理材質(zhì),可以更好的模擬光照計(jì)算,相比較高光網(wǎng)格材質(zhì) MeshPhongMaterial
渲染效果更逼真。使用 THREE.TextureLoader
為材質(zhì)添加 map
屬性來加載模型貼圖。下圖是金屬質(zhì)感的紋理貼圖。
var texLoader = new THREE.TextureLoader(); loader.load('assets/models/meta.fbx', function (mesh) { mesh.traverse(function (child) { if (child.isMesh) { if (child.name === '貝塞爾圓') { child.material = new THREE.MeshPhysicalMaterial({ map: texLoader.load("./assets/images/metal.png"), metalness: .2, roughness: 0.1, exposure: 0.4 }); } } }); })
添加動(dòng)畫
AnimationMixer
對(duì)象是場(chǎng)景中特定對(duì)象的動(dòng)畫播放器。當(dāng)場(chǎng)景中的多個(gè)對(duì)象獨(dú)立動(dòng)畫時(shí),可以為每個(gè)對(duì)象使用一個(gè)AnimationMixer
。AnimationMixer
對(duì)象的clipAction
方法生成可以控制執(zhí)行動(dòng)畫的實(shí)例。
loader.load('assets/models/meta.fbx', function (mesh) { mesh.animations.map(item => { mesh.traverse(child => { // 因?yàn)槟P椭杏卸鄠€(gè)物體,并且各自有不同動(dòng)畫,示例中只為貝塞爾圓這個(gè)網(wǎng)格添加動(dòng)畫 if (child.name === '貝塞爾圓') { let mixer = new THREE.AnimationMixer(child); mixerArr.push(mixer); let animationClip = item; animationClip.duration = 8; let clipAction = mixer.clipAction(animationClip).play(); animationClip = clipAction.getClip(); } }) }) });
添加動(dòng)畫之后,不要忘了要在 requestAnimationFrame
中更新動(dòng)畫。
function animate() { renderer.render(scene, camera); // 獲得前后兩次執(zhí)行該方法的時(shí)間間隔 let time = clock.getDelta(); // 更新logo動(dòng)畫 mixerArr.map(mixer => { mixer && mixer.update(time); }); // 更新人物動(dòng)畫 manMixer && manMixer.update(time); stats.update(); requestAnimationFrame(animate); }
展示加載進(jìn)度
FBXLoader
同時(shí)返回兩個(gè)回調(diào)函數(shù),可以像下面這樣使用,用來展示模型加載進(jìn)程展示以及加載失敗的邏輯實(shí)現(xiàn)。
<div class="loading" id="loading"> <p class="text">加載進(jìn)度<span id="progress">0%</span></p> <div> var loader = new THREE.FBXLoader(); loader.load('assets/models/meta.fbx', mesh => { }, res => { // 加載進(jìn)程 let progress = (res.loaded / res.total * 100).toFixed(0); document.getElementById('progress').innerText = progress; if (progress === 100) { document.getElementById('loading').style.display = 'none'; } }, err => { // 加載失敗 console.log(err) });
實(shí)現(xiàn)效果
點(diǎn)擊更換材質(zhì)
監(jiān)聽頁面的點(diǎn)擊事件,通過 HREE.Raycaster
拿到當(dāng)前點(diǎn)擊對(duì)象,為了展示例子,我為點(diǎn)擊對(duì)象更換了一種材質(zhì) THREE.MeshStandardMaterial
,并賦予它隨機(jī)的 color
顏色、metalness
金屬質(zhì)感以及 roughness
粗糙程度。
//聲明raycaster和mouse變量 var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2(); function onMouseClick(event) { // 通過鼠標(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; // 通過鼠標(biāo)點(diǎn)的位置和當(dāng)前相機(jī)的矩陣計(jì)算出raycaster raycaster.setFromCamera(mouse, camera); // 獲取raycaster直線和所有模型相交的數(shù)組集合 let intersects = raycaster.intersectObjects(clickableObjects); if (intersects.length > 0) { console.log(intersects[0].object) let selectedObj = intersects[0].object; selectedObj.material = new THREE.MeshStandardMaterial({ color: `#${Math.random().toString(16).slice(-6)}`, metalness: Math.random(), roughness: Math.random() }) } } window.addEventListener('click', onMouseClick, false);
📌
更多關(guān)于網(wǎng)格材質(zhì)的知識(shí),可參考文章末尾的鏈接。
加載人物模型
人物模型的加載流程和 Logo
模型加載流程是一樣的。我添加了一個(gè)正在施展龜派氣功的人物,沒想到與 Logo
模型的旋轉(zhuǎn)動(dòng)畫非常契合 😂
。
loader.load('assets/models/man.fbx', function (mesh) { mesh.traverse(function (child) { if (child.isMesh) { child.castShadow = true; child.receiveShadow = true; } }); mesh.rotation.y = Math.PI / 2; mesh.position.set(-14, -8.4, -3); mesh.scale.set(0.085, 0.085, 0.085); scene.add(mesh); manMixer = new THREE.AnimationMixer(mesh); let animationClip = mesh.animations[0]; let clipAction = manMixer.clipAction(animationClip).play(); animationClip = clipAction.getClip(); }, res => { let progress = (res.loaded / res.total * 100).toFixed(0); document.getElementById('progress').innerText = progress + '%'; if (Number(progress) === 100) { document.getElementById('loading').style.display = 'none'; } }, err => { console.log(err) });
本文示例人物模型來源于mixamo.com,該網(wǎng)站有有上百種人物和上千種動(dòng)作可自由組合,免費(fèi)
下載。大家可以挑選自己喜歡的人物和動(dòng)畫動(dòng)作來練習(xí) Three.js
。
總結(jié)
本文中涉及到的主要知識(shí)點(diǎn)包括:
THREE.TorusGeometry
:圓環(huán)。THREE.TorusKnotGeometry
:環(huán)面扭結(jié)。THREE.TubeGeometry
:管道。Blender
: 建模。FBXLoader
: 加載模型,顯示加載進(jìn)度- 。
TextureLoader
:加載材質(zhì)。 THREE.AnimationMixer
:加載動(dòng)畫。THREE.Raycaster
:捕獲點(diǎn)擊模型。
🔗
完整代碼:https://github.com/dragonir/3d-meta-logo
參考資料
[1]. 使用three.js實(shí)現(xiàn)炫酷的酸性風(fēng)格3D頁面
[2]. ThreeJs認(rèn)識(shí)材質(zhì)
[3]. Three之Animation初印象
[4]. 什么是元宇宙?
作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/15574412.html
到此這篇關(guān)于Three.js實(shí)現(xiàn)臉書元宇宙3D動(dòng)態(tài)Logo的文章就介紹到這了,更多相關(guān)Three.js3D動(dòng)態(tài)Logo 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript 異步調(diào)用框架 (Part 4 - 鏈?zhǔn)秸{(diào)用)
我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的異步調(diào)用框架,然而還有一些美中不足,那就是順序執(zhí)行的異步函數(shù)需要用嵌套的方式來聲明。2009-08-08JS實(shí)現(xiàn)長(zhǎng)圖上下滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)長(zhǎng)圖上下滾動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-03-03JS中驗(yàn)證整數(shù)和小數(shù)的正則表達(dá)式
網(wǎng)上很多關(guān)于驗(yàn)證小數(shù)的正則表達(dá)式,但是很多都不是百分百正確,所以我結(jié)合一些前輩的經(jīng)驗(yàn),寫了一個(gè),分享到腳本之家平臺(tái),對(duì)小數(shù)或整數(shù)正則表達(dá)式的相關(guān)知識(shí)感興趣的朋友一起看看吧2018-10-10Lerna入門之管理TypeScript monorepo教程
這篇文章主要為大家介紹了Lerna入門之管理TypeScript monorepo教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11原生JS實(shí)現(xiàn)的簡(jiǎn)單輪播圖功能【適合新手】
這篇文章主要介紹了原生JS實(shí)現(xiàn)的簡(jiǎn)單輪播圖功能,結(jié)合實(shí)例形式分析了基于javascript定時(shí)器控制頁面元素動(dòng)態(tài)變換實(shí)現(xiàn)輪播圖的相關(guān)操作技巧,需要的朋友可以參考下2018-08-08Bootstrap模態(tài)框插入視頻的實(shí)現(xiàn)代碼
這篇文章主要介紹了Bootstrap模態(tài)框插入視頻的實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-06-06詳解JavaScript發(fā)送埋點(diǎn)請(qǐng)求的兩種方式
對(duì)于發(fā)送埋點(diǎn)請(qǐng)求這種應(yīng)用場(chǎng)景,我們有兩種簡(jiǎn)單的處理方式:動(dòng)態(tài)創(chuàng)建<script>和<img>兩種方式。本文就詳細(xì)講講二種方式的實(shí)現(xiàn),需要的可以參考一下2022-06-06導(dǎo)航跟隨滾動(dòng)條置頂移動(dòng)示例代碼
滾動(dòng)條滾動(dòng)時(shí)如何讓導(dǎo)航置頂移動(dòng),這種效果已經(jīng)在很多網(wǎng)看到了,所以本文也來實(shí)現(xiàn)一個(gè),感興趣的朋友可以學(xué)習(xí)下2013-09-09