背景

近期學習了 WebGLThree.js 的一些基礎知識,於是想結合最近流行的酸性設計風格,裝飾一下個人主頁,同時總結一些學到的知識。本文內容主要介紹,通過使用 React + three.js 技術棧,載入 3D模型、新增 3D文字、增加動畫、點選互動等,配合樣式設計,實現充滿設計感的 酸性風格頁面。

基礎知識

Three.js

Three.js 是一款基於原生 WebGL封裝執行在瀏覽器中的 3D引擎,可以用它建立各種三維場景,包括了攝影機、光影、材質等各種物件。是一款使用非常廣泛的三維引擎。可以在 three.js官方中文文件 進一步深入學習。

酸性設計

酸性設計 一詞翻譯自 Acid Graphics,起源於 上世紀90年代 的酸浩室音樂、電子舞曲以及嬉皮士文化。在設計領域,這種酸性美學承載一種 自由的主張,怪誕的圖形,大膽鮮明的配色,特殊的材料質感,搭配多種字型,組成了獨特的酸性設計風格。

總之,鮮豔高飽和度 的色彩組合;黑灰色打底高飽和 熒光色 點綴畫面的 五彩斑斕的黑;充滿未來感、炫酷、充滿科技感的液態金屬玻璃鋁箔塑料等材質;隨機 的元素、圖形的佈局;不斷 重複、裁切、組合 幾何圖形 等都是酸性設計風格。酸性風格在音樂專輯封面、視覺海報、書籍電影封面、網頁設計中也逐漸開始流行。

實現效果

線上預覽:https://tricell.fun

實現

3D模型

場景初始化

建立場景
  1. scene = new THREE.Scene();
初始化相機

透視相機 PerspectiveCamera4個 引數分別是指:視場、長寬比、近面、遠面。

  1. camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
  2. // 設定相機位置
  3. camera.position.set(600, 20, -200);
  4. // 相機聚焦到螢幕中央
  5. camera.lookAt(new THREE.Vector3(0, 0, 0));
初始化光源

新增 半球光源 HemisphereLight:建立室外效果更加自然的光源

  1. light = new THREE.HemisphereLight(0xffffff, 0x444444);
  2. light.position.set(0, 20, 0);
  3. scene.add(light);
  4. light = new THREE.DirectionalLight(0xffffff);
  5. light.position.set(0, 20, 10);
  6. light.castShadow = true;
  7. scene.add(light);

新增 環境光 AmbientLight

  1. var ambiColor = '#0C0C0C';
  2. var ambientLight = new THREE.AmbientLight(ambiColor);
  3. scene.add(ambientLight);

新增輔助工具(可選)

新增輔助網格

GridHelper 可用於新增網格輔助線,也可用於裝飾,通過 GridHelper(size, divisions, colorCenterLine, colorGrid) 實現。

  • size:網格寬度,預設值為 10
  • divisions:等分數,預設值為 10
  • colorCenterLine:中心線顏色,預設值為 0x444444
  • colorGrid: 網格線顏色,預設值為 0x888888
  1. var grid = new THREE.GridHelper(1000, 100, 0x000000, 0x000000);
  2. grid.material.opacity = 0.1;
  3. grid.material.transparent = true;
  4. grid.position.set(0, -240, 0);
  5. scene.add(grid);
新增相機控制元件

通過相機控制元件 OrbitControls 可以對三維場景進行縮放、平移、旋轉操作,本質上改變的並不是場景,而是相機的引數。開發時 OrbitControls.js 需要單獨引入。

  1. controls = new THREE.OrbitControls(camera, renderer.domElement);
  2. controls.target.set(0, 0, 0);
  3. controls.update();
新增效能檢視外掛

stats 是一個 Three.js 開發的輔助庫,主要用於檢測動畫執行時的幀數。stats.js 也需要單獨引入。

  1. stats = new Stats();
  2. container.appendChild(stats.dom);

載入模型

本文示例用到的 扔鐵餅的人 雕像 3D 模型來源於 threedscans.com,可 免費 下載使用,本文末尾提供了多個免費模型下載網站,有 200多頁 免費模型,大家感興趣的話可以挑選自己夏歡的自己下載使用。當然,有建模能力的同學,也可以使用 blender3dmax 等專業建模軟體生成自己喜歡的模型。

載入 objfbx 模型

需要單獨引入 FBXLoader.jsOBJLoader.js.fbx.obj 格式的模型載入方法是一樣的。

  1. // var loader = new THREE.FBXLoader();
  2. var loader = new THREE.OBJLoader();
  3. loader.load(model, function (object) {
  4. object.traverse(function (child) {
  5. if (child.isMesh) {
  6. child.castShadow = true;
  7. child.receiveShadow = true;
  8. }
  9. });
  10. object.rotation.y = Math.PI / 2;
  11. object.position.set(0, -200, 0);
  12. object.scale.set(0.32, 0.32, 0.32);
  13. model = object;
  14. scene.add(object);
  15. });
載入 gltf 模型

需要單獨引入 GLTFLoader.js,載入 .gltf 格式模型方法稍有不同,需要注意的是模型的遍歷物件和最終新增到場景中的是 object.scene 而不是 object

  1. var loader = new THREE.GLTFLoader();
  2. loader.load(model, function (object) {
  3. object.scene.traverse(function (child) {
  4. if (child.isMesh) {
  5. child.castShadow = true;
  6. child.receiveShadow = true;
  7. }
  8. });
  9. object.scene.rotation.y = Math.PI / 2;
  10. object.scene.position.set(0, -240, 0);
  11. object.scene.scale.set(0.33, 0.33, 0.33);
  12. model = object.scene;
  13. scene.add(object.scene);
  14. });

新增網格、載入完成模型之後的效果如下圖所示。

新增轉盤動畫

通過 requestAnimationFrame 重新整理頁面的方法新增轉盤動畫效果。window.requestAnimationFrame() 告訴瀏覽器希望執行一個動畫,並且要求瀏覽器在下次重繪之前呼叫指定的回撥函式更新動畫。該方法需要傳入一個回撥函式作為引數,該回調函式會在瀏覽器下一次重繪之前執行。

  1. function animate () {
  2. requestAnimationFrame(animate);
  3. // 隨著頁面重回不斷改變場景的rotation.y來實現旋轉
  4. scene.rotation.y -= 0.015;
  5. renderer.render(scene, camera);
  6. }

新增點選互動

Three.js 場景中我們要點選某個模型獲取它的資訊、或者做一些其他操作,要用到 Raycaster(光線投射),原理就是在你滑鼠點選的位置發射一束射線,被射線中的物體都被記錄下來。基本語法是 Raycaster(origin, direction, near, far),其中:

  • origin:射線的起點向量。
  • direction:射線的方向向量。
  • near:所有返回的結果應該比 near 遠。值不能為負,預設值為 0
  • far:所有返回的結果應該比 far 近。不能小於 near,預設值為 無窮大

程式碼實現的基本步驟是:獲取滑鼠在螢幕的座標 螢幕座標轉標準裝置座標 標準裝置座標轉世界座標 拿到滑鼠在場景的世界座標 根據世界座標和相機生成射線投射方向單位向量 根據射線投射方向單位向量建立射線投射器物件。

  1. //宣告raycaster和mouse變數
  2. var raycaster = new THREE.Raycaster();
  3. var mouse = new THREE.Vector2();
  4. onMouseClick = event => {
  5. // 將滑鼠點選位置的螢幕座標轉成threejs中的標準座標,以螢幕中心為原點,值的範圍為-1到1.
  6. mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  7. mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  8. // 通過滑鼠點的位置和當前相機的矩陣計算出raycaster
  9. raycaster.setFromCamera(mouse, camera);
  10. // 獲取raycaster直線和所有模型相交的陣列集合
  11. let intersects = raycaster.intersectObjects(scene.children);
  12. if (intersects.length > 0) {
  13. alert('HELLO WORLD')
  14. // 可以通過遍歷實現點選不同mesh觸發不同互動,如:
  15. let selectedObj = intersects[0].object;
  16. if (selectedObj.name === 'car') {
  17. alert('汽車')
  18. }
  19. }
  20. }
  21. window.addEventListener('click', onMouseClick, false);

新增3D文字

使用 TextGeometry(text : String, parameters : Object) 新增 3D文字,以下是可設定屬性的說明:

  • size:字號大小,一般為大寫字母的高度。
  • height:文字的厚度。
  • weight:值為 normalbold,表示是否加粗。
  • font:字型,預設是 helvetiker,需對應引用的字型檔案。
  • style:值為 normalitalics,表示是否斜體
  • bevelThickness:倒角厚度。
  • bevelSize:倒角寬度。
  • curveSegments:弧線分段數,使得文字的曲線更加光滑。
  • bevelEnabled:布林值,是否使用倒角,意為在邊緣處斜切。
  1. var loader = new THREE.FontLoader();
  2. loader.load('gentilis_regular.typeface.json', function (font) {
  3. var textGeo = new THREE.TextGeometry('HELLO WORLD', {
  4. font: font,
  5. size: .8,
  6. height: .8,
  7. curveSegments: .05,
  8. bevelThickness: .05,
  9. bevelSize: .05,
  10. bevelEnabled: true
  11. });
  12. var textMaterial = new THREE.MeshPhongMaterial({ color: 0x03c03c });
  13. var mesh = new THREE.Mesh(textGeo, textMaterial);
  14. mesh.position.set(0, 3.8, 0);
  15. scene.add(mesh);
  16. });

優化

現在模型載入已經基本完成了,但是 3D 模型的體積一般比較大,部署之後我發現網頁載入非常慢,影響使用者體驗,減小模型體積是十分必要的,在網上找了很久壓縮工具,發現在不需要安裝大型 3D建模軟體 的情況下,使用 obj2gltf 可以將體積較大的 OBJ 格式模型轉化為 gltf 模型,有效優化模型體積,提升網頁載入速度。

安裝
  1. npm install obj2gltf --save
將obj模型複製到以下目錄中
  1. node_modules\obj2gltf\bin
執行轉碼指令
  1. node obj2gltf.js -i demo.obj -o demo.gltf

如圖出現類似上述內容,轉碼完成,對比轉化前後的檔案體積,本例中 kas.obj 初始檔案大小為 9.7M 轉化後的檔案 kas.gltf 只有 4.6M,體積縮小一半,此時將轉化後的模型載入到頁面上,肉眼幾乎看不出模型效果的變化,同時頁面載入速度得到明顯提升。

obj2gltf 也可以作為庫使用,通過 node服務 實時轉化模型,感興趣的同學可以通過文章末尾連結深入學習。

也可以是使用 3D 建模軟體如 blender 等手動通過減少模型 面數縮小體積 等途徑對模型壓縮優化,這種優化效果更明顯。

完整程式碼

  1. var model = require('@/assets/models/kas.gltf');
  2. var container, stats, controls;
  3. var camera, scene, renderer, light, model;
  4. class Kas extends React.Component {
  5. render () {
  6. return (
  7. <div id="kas"></div>
  8. )
  9. }
  10. componentDidMount () {
  11. this.initThree();
  12. }
  13. initThree () {
  14. init();
  15. animate();
  16. function init () {
  17. container = document.getElementById('kas');
  18. scene = new THREE.Scene();
  19. scene.fog = new THREE.Fog(0xa0a0a0, 200, 1000);
  20. // 透視相機:視場、長寬比、近面、遠面
  21. camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
  22. camera.position.set(600, 20, -200);
  23. camera.lookAt(new THREE.Vector3(0, 0, 0));
  24. // 半球光源:建立室外效果更加自然的光源
  25. light = new THREE.HemisphereLight(0xffffff, 0x444444);
  26. light.position.set(0, 20, 0);
  27. scene.add(light);
  28. light = new THREE.DirectionalLight(0xffffff);
  29. light.position.set(0, 20, 10);
  30. light.castShadow = true;
  31. scene.add(light);
  32. // 環境光
  33. var ambiColor = '#0C0C0C';
  34. var ambientLight = new THREE.AmbientLight(ambiColor);
  35. scene.add(ambientLight);
  36. // 網格
  37. var grid = new THREE.GridHelper(1000, 100, 0x000000, 0x000000);
  38. grid.material.opacity = 0.1;
  39. grid.material.transparent = true;
  40. grid.position.set(0, -240, 0);
  41. scene.add(grid);
  42. // 載入gltf模型
  43. var loader = new THREE.GLTFLoader();
  44. loader.load(model, function (object) {
  45. object.scene.traverse(function (child) {
  46. if (child.isMesh) {
  47. child.castShadow = true;
  48. child.receiveShadow = true;
  49. }
  50. });
  51. object.scene.rotation.y = Math.PI / 2;
  52. object.scene.position.set(0, -240, 0);
  53. object.scene.scale.set(0.33, 0.33, 0.33);
  54. model = object.scene;
  55. scene.add(object.scene);
  56. });
  57. renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  58. renderer.setPixelRatio(window.devicePixelRatio);
  59. renderer.setSize(window.innerWidth, window.innerHeight);
  60. renderer.setClearAlpha(0);
  61. renderer.shadowMap.enabled = true;
  62. container.appendChild(renderer.domElement);
  63. window.addEventListener('resize', () => {
  64. camera.aspect = window.innerWidth / window.innerHeight;
  65. camera.updateProjectionMatrix();
  66. renderer.setSize(window.innerWidth, window.innerHeight);
  67. }, false);
  68. stats = new Stats();
  69. container.appendChild(stats.dom);
  70. }
  71. function animate () {
  72. var clock = new THREE.Clock()
  73. requestAnimationFrame(animate);
  74. var delta = clock.getDelta();
  75. scene.rotation.y -= 0.015;
  76. renderer.render(scene, camera);
  77. stats.update();
  78. }
  79. // 增加點選事件
  80. //宣告raycaster和mouse變數
  81. var raycaster = new THREE.Raycaster();
  82. var mouse = new THREE.Vector2();
  83. function onMouseClick(event) {
  84. // 通過滑鼠點選位置計算出raycaster所需要點的位置,以螢幕中心為原點,值的範圍為-1到1.
  85. mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  86. mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  87. // 通過滑鼠點的位置和當前相機的矩陣計算出raycaster
  88. raycaster.setFromCamera(mouse, camera);
  89. // 獲取raycaster直線和所有模型相交的陣列集合
  90. var intersects = raycaster.intersectObjects(scene.children);
  91. if (intersects.length > 0) {
  92. alert('HELLO WORLD')
  93. }
  94. }
  95. window.addEventListener('click', onMouseClick, false);
  96. }
  97. }

其他設計元素

本文主要介紹 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 專案來一起體驗和學習,無論是頁面互動、視覺設計還是效能優化都做到了極致,可以從中學到很多。

參考資料

作者:dragonir 本文地址:https://www.cnblogs.com/dragonir/p/15350537.html