誰會拒絕一款開源的 3D 部落格呢?

語言: CN / TW / HK

說到部落格大家一定都不陌生,不管你是深耕職場多年的老鳥,還是在學校努力學習的小鳥,應該都有過一段“裝扮”部落格的經歷,比如:放上喜歡的圖片、新增炫酷的互動、換上 DIY 的部落格主題等等。但不管再怎麼“打扮”,也跳脫不出平面的“凡胎”。

今天 HelloGitHub 給大家帶來的是一款開源的 3D 部落格專案,實話說我第一次訪問這個部落格的時候都驚呆了,顛覆了我對部落格的認知。進去後需要通過操控一輛 3D 的小汽車,自己“找到”文章才可以閱讀,特別有意思!

線上:http://bruno-simon.com/

專案:http://github.com/brunosimon/folio-2019

接下來,本文會先介紹如何在本地把專案執行起來,然後講解其原始碼和相關技術,最後教你如何修改程式碼把自己的博文放進去,升級到 3D 部落格!下面讓就讓我們一起走進這個開源的 3D 部落格,將驚喜轉化為興趣。

一、介紹

1.1 安裝步驟

話不多說先跑起來再說,只需 4 步。命令如下:

```

1.下載專案

git clone [email protected]:brunosimon/folio-2019.git

2.安裝了 Parcel(Web 應用打包工具)

npm install -g parcel-bundler

3.安裝依賴(僅第一次需要)

npm i

4.執行

npm run dev ```

提示:這裡就不過多介紹 如何安裝 Node.js 啦!

1.2 執行效果

首先,我們來看看這個部落格長什麼樣子:

一輛紅色、可愛的小吉普車,在漫無邊際的地圖上自由地馳騁,還可以按按喇叭助助興,是的沒錯!這個部落格還自帶音效。

1.3 小遊戲

正當你“開”著小車唱著歌,突然就被作者創造的這一個 3D 世界給“綁架”了!因為他不止有簡單的模型,還附帶了一些非常有意思的小遊戲。

比如你可以猛地加速撞擊這三面“泡沫牆體”:

又或者是開著小吉普車“打保齡球”:

又或者是做一些“刺激”的越野訓練:

這些小遊戲是不是非常有意思呢?

1.4 關於作者

能寫出這麼有意思的 3D 部落格的作者,也是一定是位非常有意思的人,這不他把自己的生活也“刻畫”到了這個專案裡。

我們大致可以猜到這位大佬的日常生活大致是“一人一狗”,而且他非常愛自己的狗狗。除了打遊戲,健身、出門還有其他一些活動都是和它在一起。

啊!多麼愜意的生活呀~這哪裡是程式設計師的生活呢?這明明就是財富自由的生活啊!你以為這個部落格就只是記錄生活點滴那你就錯了,當我們的小吉普駕駛到 “project” 的時候,我們會看到很多作者寫的技術博文。

聊完家常,下面進入程式碼的世界!

二、專案概覽

2.1 專案結構

我們先看一下專案結構:

簡單地對這些目錄做一個概述:

是不是有很多檔案看不懂?沒關係,我們先從最主要的檔案開始,也就大家最熟悉的 src 資料夾,這裡放的都是核心程式碼。而且其中最主要的程式碼檔案,相信你一眼就能看出來,沒錯!就是 index.js

```javascript import './style/main.css' import Application from './javascript/Application.js'

window.application = new Application({ $canvas: document.querySelector('.js-canvas'), useComposer: true }) ```

這個 index.js 是不是非常簡單,它就是告訴我們其實剛剛看到的所有效果,都是一個 canvas!而處理邏輯的程式碼都來自於 Application.js,我們這裡摘出一些比較重要的技術棧、專案用到的核心庫,做一個簡單的介紹。

相信很多小夥伴早就猜到了,對!就是 Three.js

2.2 Three.js

Three.js 是一個 3D 的 JavaScript 庫,可以讓開發者在 Web 上創造 3D 體驗,它和 WebGL 是最佳拍檔。

地址:http://github.com/mrdoob/three.js

那 WebGL 又是個什麼東西呢?WebGL 是一個 JavaScript API,它與大多數現代瀏覽器相容,且直接使用圖形處理單元 (GPU),這可以實現出色的優化和更多的控制,而且速度很快。

不過原生的 WebGL 難且複雜,使用原生的 WebGL 在畫布上繪製一個三角形至少需要 100 行程式碼,在這種情況下再去新增透視、燈光、模型和動畫等等一切東西,可想而知有多令人頭禿了。為了保住程式設計師為數不多的頭髮,就誕生了 Three.js 庫。它的價值是簡化處理上述所有內容的過程,只需幾行程式碼即可獲得動畫 3D 場景

2.3 dat.gui

這個 3D 部落格專案用到的另外一庫是:dat.gui.js,它是一個用於在 JavaScript 中更改變數的輕量級圖形使用者介面,使用這個庫可以很容易地創建出能夠改變程式碼變數的介面元件。實現的介面大致樣子如下:

三、講解原始碼

介紹完重要的技術點,下面繼續回到邏輯處理的核心檔案 Application.js 開始講解原始碼。

3.1 構建世界

從程式碼結構上看,可以看到入口就在 this.setWorld 裡,讓我們來簡單看看這裡的程式碼:

```javascript constructor(_options) { // Options this.config = _options.config this.debug = _options.debug ... // Set up this.container = new THREE.Object3D() this.container.matrixAutoUpdate = false

this.setSounds() // 聲音 this.setControls() // 一些按鍵控制等 this.setFloor() // 地板設定 this.setAreas() // 區域設定 this.setStartingScreen() // 首屏動畫(loading > start) } ```

相信上面的一些設定你一眼就看懂了,這裡挑幾個可能產生疑惑的方法名再簡單說明一下。this.setControls 主要包含了兩個方法:this.setActionsthis.setKeyboard,這裡就是小吉普車行駛和按喇叭等的按鍵控制。

this.setFloor 主要是針對當前場景的地板樣式的配置檔案,如果你不喜歡原始的配色方案,你大可以自定義一個“五彩斑斕”的顏色,但前提是要具備一定的審美不然可能就翻車如下圖了。

這裡只是針對場景四個角(左上角、右上角、左下角、右下角)的顏色做了自定義,程式碼片段如下:

```javascript // Colors 修改前 this.colors = {} this.colors.topLeft = '#f5883c' this.colors.topRight = '#ff9043' this.colors.bottomRight = '#fccf92' this.colors.bottomLeft = '#f5aa58'

// Colors 修改後 this.colors = {} this.colors.topLeft = 'red' this.colors.topRight = 'yello' this.colors.bottomRight = 'blue' this.colors.bottomLeft = 'black' ```

剩下的 this.setAreas 主要是針對一些滑鼠事件做了處理,便於我們用滑鼠拖動和探索整個“世界”,包含了 mousemovemousedowntouchstart 等,相關程式碼就不貼出來了,感興趣的可以自行探索~

3.2 開始之後

以上這些構建都是在開始頁面出現的時候執行的程式碼,相當於提前做了一部分資源載入,那剩下的一些載入就在我們點選「start」之後開始。

World.js 檔案裡,我們也可以找到相應的程式碼 this.start

```javascript // On interact, reveal 點選 start 的互動 this.startingScreen.area.on('interact', () => { TweenLite.to(this.startingScreen.startLabel.material, 0.3, { opacity: 0, delay: 0.4 }) // 剩下的資源載入 this.start()

window.setTimeout(() => { this.reveal.go() }, 600) }) ```

start 這個方法裡面我們會看到更多內容的載入,比如小吉普車的載入,還有我們最想知道的文章列表的載入。

javascript start() { this.setCar() // 汽車載入 this.areas.car = this.car this.setSections() // 文章載入 }

沒錯就是它,接下來我們要找文章入口的方向標。即 x 和 y 軸表示這個物件在畫布上的座標位置,我們也可以挪到自己喜歡的位置上去。

javascript // Projects this.sections.projects = new ProjectsSection({ ...options, x: 30, y: - 30 })

這裡需要稍微介紹下 Three.js 的座標軸系,它採用右手座標系, 如下圖所示,與此對應的還有左手座標系:

所以,當我們加大 x 的值後,我們會發現整個文章距離變得遠了,同理修改 y 值也是同樣的效果。

3.3. 文章列表

檢視 ProjectsSection.js 檔案,我們發現裡面有很多的圖片,先別管往下看就會看到一個 setList,改動如下:

javascript setList() { this.list = [ { name: 'HelloGitHub', imageSources: [ projectsThreejsJourneySlideESources ], floorTexture: this.resources.items.projectsThreejsJourneyFloorTexture, link: { href: 'http://hellogithub.com/', // 控制按鈕的位置 x: - 5.8, y: - 4.5, // 控制open按鈕大小(0,0)的時候無邊框 halfExtents: { x: 2, y: 1 } }, distinctions: [ // 一個3d模型的圖示,xy是座標軸位置 { type: 'fwa', x: 4.15, y: 5.15 } ] } ] }

如此一來,你就擁有了“自己”的 3D 部落格,只不過謙虛一點來說,3D 是別人的,部落格是你自己的。來張特寫看看:

結合圖片我們對上面的程式碼做一些解釋:

1. imageSources:就是“廣告牌”上面的圖片,我這裡只弄了一張 HelloGitHub 社群首頁的圖片,你也可以按照原部落格那樣放上好幾張圖。

圖片存放的地址和引入規則: javascript import projectsThreejsJourneySlideESources from '../../../models/projects/threejsJourney/slideE.jpg'

2. floorTexture 即“地板貼圖”,也就是我們看到“躺”在地上的那個圖,或許你會問:為什麼不能直接把圖放上去還需要變成一個 texture 貼圖呢?

因為,這是一個 3D 世界,隨著滑鼠上下拖動,我們對於這個圖的視覺會發生變化,所以需要把圖也“放”到一個 3D 容器中去,這樣我們所構建的 3D 世界才真實可信。所以這裡我們跟著程式碼去倒推這個圖是怎麼生成。

javascript floorTexture: this.resources.items.projectsThreejsJourneyFloorTexture,

我們得知它是“掛”在 resources 物件下面的。一層一層查下去,我們發現他是在 Application.js 的時候載入進來的。進到 Resources.js 檔案,我們可以發現有非常多的資原始檔例如 .png.glb 之類的。

什麼是 glb 檔案?

上文提到的 .glb 字尾的檔案,它包含以 GL 傳輸格式(glTF)儲存的三維模型。它以二進位制格式儲存有關三維模型的資訊,例如節點層次結構、攝影機和材質、動畫和網格。.glb 檔案是 .glTF 檔案的二進位制版本。

3.4 再深入一點

接著上面探究:貼圖是怎麼生成的,順著 Resources.js 檔案接著看。

如果,直接在 Resources.js 裡面直接搜上面的 projectsThreejsJourneyFloorTexture 關鍵詞是搜不到的,那就刪掉後面的一些英文再試試,我們會發現這樣的一行程式碼在 this.loader.load 這個方法裡:

javascript { name: 'projectsThreejsJourneyFloor', source: projectsThreejsJourneyFloorSource, type: 'texture' },

跟著它的 source 欄位再搜,會發現它關聯的圖片檔案:

javascript import projectsThreejsJourneyFloorSource from '../models/projects/threejsJourney/floorTexture2.png'

你肯定好奇,為什麼直接搜搜不到呢?讓我們摺疊長程式碼再來看一下,你就會發現有一個名字的處理函式:

```javascript this.loader.on('fileEnd', (_resource, _data) => { this.items[_resource.name] = _data

  // Texture
  if(_resource.type === 'texture')
  {
      const texture = new THREE.Texture(_data)
      texture.needsUpdate = true

      this.items[`${_resource.name}Texture`] = texture
  }

  // Trigger progress
  this.trigger('progress', [this.loader.loaded / this.loader.toLoad])

}) ```

沒錯,處理函式會自動加上 Texture 作為物件名的字尾,可以說是非常嚴謹了,因為他從一個普通的圖片檔案變成了一個“貼圖”檔案,要貼合在 3D 容器裡面。

現在,我們再回到上面的 setList 方法。其它的檔案連結、模型的座標軸位置,相信你已經對它們一目瞭然!

五、結尾

閱讀原始碼一定不是一個容易的過程,尤其是從初學者開始。雖然難熬但也經常有一些小驚喜激勵著我,比如:一遍又一遍地閱讀原始碼的過程中,突然發現一個小小的知識點,還是挺有意思的。

舉個例子,這個 3D 部落格專案中很多這樣的圖片:

點開就是一張黑不溜秋的圖片,起初我根本就不知道這是什麼東西,但隨著我對程式碼的理解加深。突然恍然大悟,原來這些是模型的位置和陰影效果渲染。

最後,這一趟短暫的 3D 部落格入門之旅到這裡,就要跟各位乘客說再見了。其實整個原檔案的內容非常豐富,包括很多 3D 模型的檔案、打包的配置優化等等,值得更加深入和系統地學習,但是由於篇幅和能力有限,今天就先寫到這裡。如果你有興趣翻閱原始碼的話,你會有更多意想不到的發現。

以上就是本文的所有內容,如果您覺得這篇文章寫得還不錯,就請給我點一個,您的支援就是我更新的動力。

本文正在參加「金石計劃」