WebGL之3D地球
看了餓了麼小小倩老師的canvas作品,心血來潮,學著做了個3D地球,也算是入坑WebGL了吧。之前有用過原生的canvas畫2D的圖形,這次則是用了Three.js和stats.js的3D框架,邊學邊練手,效果還算比較滿意...畢竟第一次接觸WebGL
Talk is cheap show the code!
github專案原始碼地址: ofollow,noindex">github.com/FightingHao…
專案演示地址: fightinghao.github.io/codingDream…
程式碼還有很多不足,求大神review..
什麼是Three.js
隨著近幾年前端的飛速發展,網頁的表現能力越來越強大,瀏覽器提供了WebGL(Web圖形處理庫)介面,可以通過呼叫對應API進行3D圖形的繪製, Three.js
則是在此基礎介面之上又做了一層封裝。 Three.js
是當下最流行的網頁3D渲染JS引擎。
Three.js使用方法
準備階段
- 頁面新增canvas元素
<!-- 作為Three.js渲染器輸出3D圖形 --> <canvas id="webglcanvas"></canvas> 複製程式碼
- 引入Three.js庫檔案
- 本地引入
<!-- Three.js庫 --> <script src="./libs/three.min.js"></script> 複製程式碼
- CDN遠端引入
<script src='http://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js'></script> 複製程式碼
- 專案中用到的變數
let canvas,//畫布標籤 stats,//效能檢測器 camera,//相機 scene,//場景 renderer,//渲染器 group,//物體組 mouseX = 0,//滑鼠橫向位置 mouseY = 0,//滑鼠縱向位置 windowHalfX = window.innerWidth / 2,//視口大小的一般 windowHalfY = window.innerHeight / 2; //視口大小的一半 複製程式碼
建立場景素材
為了讓three.js顯示,需要三件事情:場景、相機和渲染器
-
場景,可以理解為舞臺。因為要繪製3D效果,必須要有一個舞臺來演示效果
建立場景 API:
THREE.Scene()
scene = new THREE.Scene() //建立場景 複製程式碼
-
有了場景,現在需要相機來拍攝出素材出來,所以第二步則是需要建立一個"相機"
建立相機 API:THREE.PerspectiveCamera(fov, aspect, near, far)
- fov 可視角度,可理解為視野,是在顯示器上看到的場景的範圍,以度為單位。
- aspect 為width/height,通常設定為canvas元素的高寬比。
- near近端距離
- far遠端距離
- 只有離相機的距離大於near值,小於far值,且在相機的可視角度之內,才能被相機投影到。
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000) camera.position.z = 500//相機的遠近 複製程式碼
-
場景和相機都有了,也就是能夠拍攝出素材了,但素材需要經過一些PS、美顏等才能變得好看。這時候就需要渲染器。
建立渲染器 API:
THREE.WebGLRenderer({})
renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, // true/false表示是否開啟反鋸齒, /* alpha: false,// true/false 表示是否可以設定背景色透明, precision: 'highp',// highp/mediump/lowp 表示著色精度選擇, premultipliedAlpha: false, // true/false 表示是否可以設定畫素深度(用來度量影象的解析度), maxLights: 3,// 最大燈光數, stencil: false// false/true 表示是否使用模板字型或圖案 */ }) 複製程式碼
指定渲染器寬高 API: renderer.setSize(window.innerWidth, window.innerHeight)
引數分別為寬和高
建立3D圖形
我們已經用相機在場景中拍攝出了素材,但這些素在畢竟還只是2D,現在我們要將該素材由2D變為3D,這時這就需要用一個3D圖形作為2D素材的載體。用個吃貨的簡單理解,就是雞肉卷外面的卷,開始卷是2D的平面的,把雞肉到卷面上,用捲包裹起雞肉,則就由2D的卷變成了3D的雞肉卷對吧。emmm...解釋的好尬。
API:
- 圖形形狀
THREE.SphereGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength)
- radius:球體半徑
- widthSegments, heightSegments:水平方向和垂直方向上分段數。widthSegments最小值為3,預設值為8。heightSegments最小值為2,預設值為6。
- phiStart:水平方向上的起始角,預設值0
- phiLenght:水平方向上球體曲面覆蓋的弧度,預設Math.PI * 2
- thetaStart : 垂直方向上的起始角, 預設0
- thetaLength: 垂直方向是球體曲面覆蓋的弧度,預設值為Math.PI
- 圖形材質
THREE.MeshBasicMaterial({})
圖形材質有很多種,網上查了查資料,覺得這個總結的挺好,大家可以看一看 blog.csdn.net/qq_30100043… - 圖形構成
THREE.Mesh(geometry, material)
- geometry 物體形狀
- material 物體材質
let geometry = new THREE.SphereGeometry(200, 20, 20)//形狀 let material = new THREE.MeshBasicMaterial({ map: texture })//材質 let mesh = new THREE.Mesh(geometry, material)//物體 複製程式碼
- 載入圖形
new THREE.TextureLoader().load(img, callback)
將上面的圖形加載出來
loader.load('./img/land_ocean_ice_cloud_2048.jpg', function (texture) { let geometry = new THREE.SphereGeometry(200, 20, 20)//形狀 let material = new THREE.MeshBasicMaterial({ map: texture })//材質 let mesh = new THREE.Mesh(geometry, material)//物體 }) 複製程式碼
建立組合
我們有了3D圖形,接下來就是將這些圖形組合在一起,變成多樣的3D介面
建立組合 API: THREE.Group()
//建立一個組合 group = new THREE.Group() scene.add(group)//將組合新增進場景中渲染 複製程式碼
將建立好的3D圖形新增進組合中
API: group.add(mesh)
- mesh 3D物體
運動起來
3D圖形已經在場景中渲染出來了,現在,則需要讓它們動起來!
function animate() { // 請求運動幀 requestAnimationFrame(animate) render() } // 地球旋轉邏輯函式 function render() { // 更新效能監視器 stats.update(); camera.position.x += (mouseX - camera.position.x) * 0.05 camera.position.y += (mouseX - camera.position.y) * 0.05 // 拍攝角度, 可改變地球視角 camera.lookAt(scene.position) // 地球自轉速度 group.rotation.y -= 0.005 // 運動核心 遞迴呼叫 renderer.render(scene, camera) } 複製程式碼
現在已經基本完成了3D地球的自轉
什麼是stats.js
說了Three.js,現在聊聊什麼是stats.js吧。stats.js 是一個 Three.js 開發的輔助庫,通過檢測動畫執行時的幀數,來測試WebGL程式碼的執行效能
stats.js使用
準備階段
類似於Three.js,stats.js也需要引入框架庫,並由div來渲染顯示效能測試介面
<!-- 用於顯示和統計圖形的效能 --> <div id="stats-output"></div> <!-- 引入stats.js庫 --> <script src="./libs/stats.min.js"></script> 複製程式碼
開始使用
首先需要初始化stats
// stats效能檢測器初始化 stats = initStats(); function initStats() { stats = new Stats(); //設定統計模式 stats.setMode(0); // 0: fps, 1: ms //統計資訊顯示在左上角 stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '10px'; stats.domElement.style.top = '10px'; //將統計物件新增到對應的<div>元素中 document.getElementById("stats-output").appendChild(stats.domElement); return stats; } 複製程式碼
當場景變換時,也就是3D運動時,需要實時更新stats檢測器 API: stats.update();
用於地球旋轉時,動態更新檢測情況
// 地球旋轉邏輯函式 function render() { // 更新效能監視器 stats.update(); camera.position.x += (mouseX - camera.position.x) * 0.05 camera.position.y += (mouseX - camera.position.y) * 0.05 // 拍攝角度, 可改變地球視角 camera.lookAt(scene.position) // 地球自轉速度 group.rotation.y -= 0.005 // 核心 遞迴呼叫 renderer.render(scene, camera) } 複製程式碼
可以看到兩種檢測情況 stats.setMode(0); // 0: fps, 1: ms
引數0 顯示FPS

stats.setMode(1); // 0: fps, 1: ms
引數1 顯示MS

最後優化
1.通過滑鼠可控制地球角度
// 繫結滑鼠移動事件 document.addEventListener('mousemove', onDocumentMouseMove, false) // 監聽滑鼠移動方向, 從而確定地球南北半球 function onDocumentMouseMove(ev) { ev = ev || event mouseX = ev.clientX - windowHalfX mouseY = ev.clientY - windowHalfY } 複製程式碼
2.響應式
可根據視窗大小自動改變渲染圖形大小
// 視窗大小改變監聽 window.addEventListener('resize', onWindowResize, false) // 監聽視窗大小, 從而根據視窗大小改變地球大小, 類似響應式 function onWindowResize() { windowHalfX = window.innerWidth / 2 windowHalfY = window.innerHeight / 2 camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) } 複製程式碼
好了,現在3D地球就基本上完成了~
完整程式碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>WebGL之3D地球</title> <style> /* 禁止系統預設滾動條 */ html, body { height: 100%; overflow: hidden; } </style> </head> <body> <!-- 用於顯示和統計圖形的效能 --> <div id="stats-output"></div> <!-- 作為Three.js渲染器輸出3D圖形 --> <canvas id="webglcanvas"></canvas> <!-- webgl庫 --> <script src="./libs/three.min.js"></script> <script src="./libs/stats.min.js"></script> <script> let canvas,//畫布標籤繪圖API stats,//效能檢測器 camera,//相機 scene,//場景 renderer,//渲染器 group,//物體組 mouseX = 0,//滑鼠橫向位置 mouseY = 0,//滑鼠縱向位置 windowHalfX = window.innerWidth / 2,//視口大小的一般 windowHalfY = window.innerHeight / 2; //視口大小的一半 init()//構建地球 animate()//使地球旋轉起來 function init() { // 獲取canvas畫布 canvas = document.getElementById('webglcanvas') // stats效能檢測器初始化 stats = initStats(); // 3D繪製 // 相機 camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000) camera.position.z = 500//相機的遠近 // 場景 scene = new THREE.Scene() // 建立一個組合 group = new THREE.Group() scene.add(group)//將組合新增進場景中渲染 // 地球 數學形狀 貼圖 let loader = new THREE.TextureLoader() loader.load('./img/land_ocean_ice_cloud_2048.jpg', function (texture) { // console.log(texture) let geometry = new THREE.SphereGeometry(200, 20, 20)//形狀 let material = new THREE.MeshBasicMaterial({ map: texture })//材質 let mesh = new THREE.Mesh(geometry, material)//物體 group.add(mesh) }) // 渲染器 renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, // true/false表示是否開啟反鋸齒, /* alpha: false,// true/false 表示是否可以設定背景色透明, precision: 'highp',// highp/mediump/lowp 表示著色精度選擇, premultipliedAlpha: false, // true/false 表示是否可以設定畫素深度(用來度量影象的解析度), maxLights: 3,// 最大燈光數, stencil: false// false/true 表示是否使用模板字型或圖案 */ }) // 指定渲染器寬高 renderer.setSize(window.innerWidth, window.innerHeight) // 繫結滑鼠移動事件 document.addEventListener('mousemove', onDocumentMouseMove, false) // 視窗大小改變監聽 window.addEventListener('resize', onWindowResize, false) } // 監聽滑鼠移動方向, 從而確定地球南北半球 function onDocumentMouseMove(ev) { ev = ev || event mouseX = ev.clientX - windowHalfX mouseY = ev.clientY - windowHalfY } // 監聽視窗大小, 從而根據視窗大小改變地球大小, 類似響應式 function onWindowResize() { windowHalfX = window.innerWidth / 2 windowHalfY = window.innerHeight / 2 camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) } function animate() { // 請求運動幀 requestAnimationFrame(animate) render() } // 地球旋轉邏輯函式 function render() { // 更新效能監視器 stats.update(); camera.position.x += (mouseX - camera.position.x) * 0.05 camera.position.y += (mouseX - camera.position.y) * 0.05 // 拍攝角度, 可改變地球視角 camera.lookAt(scene.position) // 地球自轉速度 group.rotation.y -= 0.005 // 核心 遞迴呼叫 renderer.render(scene, camera) } function initStats() { stats = new Stats(); //設定統計模式 stats.setMode(0); // 0: fps, 1: ms //統計資訊顯示在左上角 stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '10px'; stats.domElement.style.top = '10px'; //將統計物件新增到對應的<div>元素中 document.getElementById("stats-output").appendChild(stats.domElement); return stats; } // ecchat 資料視覺化 // 平面的世界是錯誤的, cssperspective:1000pxtransform-style:perserve-3d // Camera Scene render(渲染容器) Light->canvas </script> </body> </html> 複製程式碼
圖片素材和js庫可以到我的github上下載:
github.com/FightingHao…第一次在掘金上發文章,希望大家可以點點贊哈哈~