入門 Three.js 之后,我真的把一輛車“塞進了”瀏覽器里面……
最近有很多同學特別關注 WebGL 和 ThreeJS 的知識,通過 ThreeJS
我們可以完成很多酷炫的 3D 效果。
所以,咱們今天就花上 一個多小時 的時間,借助 ThreeJS的一個官方示例,來看下,對于我們前端開發者而言,如何快速的入門,并完成一個酷炫的 ThreeJS 項目。
該項目可以直接通過 https://webgl.web.lgdsunday.club/
進行訪問:
圖片
那么廢話不多說,讓我們開始吧~
圖片
1.canvas、OpenGL、WebGL 到底都是干嘛的
我們知道 canvas
本質上是一個 html 標簽
,它額外提供了一些 API
用來進行繪制。
canvas
的 API
主要用來繪制 2D 圖形
,但是在日常的開發中,除了 2D
圖形之外,還存在一些 3D
的圖形,那么想要繪制 3D
圖形就需要使用到 WebGL。
WebGL
是一個 JavaScript API
,可在任何兼容的 Web
瀏覽器中渲染高性能的交互式 3D
和 2D
圖形,而無需使用插件。
注意: 有些瀏覽器是可能不支持
WebGL
的。我們可以通過這個 網站 來檢測你的瀏覽器是否支持WebGL
。如果你使用的是
Chrome
,但是依然提示不支持,那么可以在Chrome
中開啟 硬件加速 以獲得支持。
WebGL
中提供了很多的 API
方法,這些方法和 OpenGL 提供的方法是非常相似的。
OpenGL (Open Graphics Library)
是一套用來渲染2D
和3D
矢量圖形的跨語言的、跨平臺的應用程序接口 (API) . 這種接口通常用來與圖形處理單元 (GPU) 交互,來達到 硬件加速渲染 的目的。
WebGL
正是利用了 瀏覽器中的圖形加速硬件(如GPU)來進行圖形渲染(這也是為什么我們必須要開啟 瀏覽器硬件加速 的原因)。它通過JavaScript
與 HTML5的Canvas元素
進行交互,并提供了在Web
上展示復雜的3D
圖形和交互式體驗的能力。
本章的內容,我們總結來說:
Canvas
提供了一個基本的2D渲染環境,OpenGL
是一個跨平臺的圖形編程接口規范,而 WebGL
是基于OpenGL ES
的JavaScript API
,用于在Web
瀏覽器中進行高性能的3D
圖形渲染。WebGL
可以看作是在Canvas
上使用OpenGL ES
進行3D
渲染的一種實現方式。
2.WebGL 框架 three.js 初體驗-理論篇
在上一小節中,我們初步了解了 canvas
、OpenGL
和 WebGL
之間的關系,我們知道 WebGL
本質上是基于 OpenGL API
的一個 3D
圖形渲染的實現方式。
但是,如果我們直接基于 WebGL 原生 API
進行項目開發的話,那么使用起來會比較復雜。所以在日常的企業開發中,我們都會 WebGL
框架來進行開發。
基于 WebGL
的框架其實非常多,這些框架在視頻中,我們不一個一個介紹,大家感興趣的話,可以看一下課程講義:
- Three.js: JavaScript 3D 渲染庫,它封裝了底層的 WebGL API,提供了簡化的接口和功能,可在 Web 上創建和展示 3D 場景
- Babylon.js : 一個功能強大的 WebGL 渲染引擎,它專注于游戲開發和實時圖形應用
- A-Frame: 一個基于 Three.js 的 WebVR 框架,它能創建虛擬現實(VR)和增強現實(AR)的交互體驗
- PlayCanvas: 一個面向游戲開發的 WebGL 引擎,它提供了可視化的場景編輯器和強大的腳本編輯器,使開發者能夠創建高性能的 HTML5 游戲。
而我們接下來,主要要說的其實就是 Three.js。
threejs
是目前國內使用率最多的 JavaScript 3D
渲染庫,它提供了 非常完善的文檔 來幫助我們完成 3D
渲染的開發。
那么接下來,咱們就創建一個項目,然后在這個項目中為大家演示 threejs
的導入和最基礎的用法。
- 利用
vue-cli
創建項目:
vue create threejs-car
- 配置項如下:
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, CSS Pre-processors
? Choose a version of Vue.js that you want to start the project with 3.x
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass)
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
- 安裝
threejs
到項目:
cnpm install --save three@0.152.2
那么接下來,咱們就在 App.vue
中利用 threejs
繪制出一個基本的正方體。
圖片
整個正方體的繪制,咱們將分成兩個小節來說明:理論篇 和 實戰篇。
這一小節,咱們先說理論。讓大家對 threejs
中的一些基礎概念有一個大概的認知。
首先,先導入 threejs
:
// 導入整個 three.js核心庫
import * as THREE from 'three'
接下來,我們先了解一些 threejs
中的基本概念:
- 場景
Scene
:它表示要繪制的內容,是整個threejs
繪制的基礎 - 相機
camera
:相機分為很多種,咱們這里主要使用PerspectiveCamera
透視相機。它是一個最常用的相機模式,主要可以模擬人眼看到的效果 - 渲染器
renderer
:它的作用比較簡單,主要用來渲染場景 - 幾何體
Geometry
:要渲染的物體形狀。物體形狀有非常多,咱們這里主要使用立方體BoxGeometry
- 材質
material
:為物體賦予一些材質,比如是光滑的鏡面,還是粗糙的表面。咱們這里主要使用的是基礎材質MeshBasicMaterial
- 網格基類
mesh
:將幾何體與材質合并成基類。最后可以加入到場景中 - 渲染函數
renderer.render
:利用渲染器的渲染函數,可以根據場景和攝像頭進行渲染 - 重繪函數
requestAnimationFrame
:它與渲染函數配合,可以重復進行渲染,即:生成動畫
最后匯總成一句話:
threejs
本質上是:利用 renderer.render 渲染函數,渲染指定的 scene
和 camera
。scene
中可以放置任何的幾何體與材質。當渲染函數與 requestAnimationFrame
配合時,可以產生動畫。
3.WebGL 框架 three.js 初體驗-實戰篇
在上一小節中,我們明確了 threejs
中的一些基本概念,那么接下來咱們就根據所學的概念,來完成一個基礎的正方體渲染。
整個正方體渲染分為三大步進行:
- 創建場景、相機、渲染器,并把生成的
canvas
添加到body
中 - 創建立方體,并把它加入到場景中
- 渲染場景
以下是具體代碼:
<template>
<div></div>
</template>
<script setup>
// 導入整個 three.js核心庫
import * as THREE from 'three'
// 第一大步:創建場景、相機、渲染器,并把生成的 canvas 添加到 body 中
// 1. 場景:渲染 threejs 內容的地方
const scene = new THREE.Scene()
// 2. 相機:用來模擬人眼所看到的的場景
/**
* 參數:
* 1. 視野垂直角度 fov — 能夠看到的場景范圍
* 2. 長寬比 aspect ratio - 當前攝像機的長寬比
* 3. 近截面 near - 比近截面近的,用戶看不到
* 4. 遠截面 far - 比遠截面遠的,用戶看不到
*/
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
// 為 camera 設置攝像縱深
camera.position.z = 2
// 3. 渲染器:用來渲染 3D 場景
const renderer = new THREE.WebGLRenderer()
// 4. webgl 的渲染會輸出的 canvas 中,這里用來設置 canvas 的大小
renderer.setSize(window.innerWidth, window.innerHeight)
// 5. 將生成的 canvas 標簽插入到 body 中
document.body.appendChild(renderer.domElement)
// 第二大步:創建立方體,并把它加入到場景中
// 1. BoxGeometry是四邊形的原始幾何類,三個參數分別為:
/**
* width — X軸上面的寬度,默認值為1。
* height — Y軸上面的高度,默認值為1。
* depth — Z軸上面的深度,默認值為1。
*/
const geometry = new THREE.BoxGeometry(1, 1, 1)
// 2. material 一個具備簡單著色的基礎材質。表示為立方體的材質
const material = new THREE.MeshBasicMaterial({ color: 0x3f7b9d })
// 3. Mesh 一個網格類,將材質與四邊形合并
const cube = new THREE.Mesh(geometry, material)
// 4. 將當前生成的立方體加入到場景中
scene.add(cube)
// 第三大步:渲染場景
// 1. 創建一個方法,通過 requestAnimationFrame 重復回調這個方法
function animate() {
// 2. requestAnimationFrame:是 JS 原生 API,它會根據屏幕刷新率來回調指定方法
requestAnimationFrame(animate)
// 3. 不斷修改 cube 的角度,產生旋轉的效果
cube.rotation.x += 0.01
cube.rotation.y += 0.01
// 4. 利用渲染器的 render 方法重復渲染場景
renderer.render(scene, camera)
}
animate()
</script>
4.threejs 配置解碼器載入 glb 格式 3D 模型
那么這一小節,咱們就導入一個 3D
模型,利用 threejs
進行渲染。同時構建出最終的 html
結構。
首先,咱們需要先導入一些資料(可以從源碼中獲取):
然后 復制 上一小節的 App.vue
,重命名為 01:基礎幾何體渲染.vue
。然后在原有的 App.vue
中對代碼進行重構:
首先是構建出對應的 html
結構:
<template>
<div>
<div id="info">
<h2>
請確保你的瀏覽器支持 WebGL,可以點擊
<a target="_blank">這里</a>
進行檢測。如果不支持,可百度處理方案。
</h2>
<br /><br />
<span class="colorPicker">
<input id="body-color" type="color" value="#ff0000" />
<br />
車身
</span>
<span class="colorPicker">
<input id="details-color" type="color" value="#ffffff" />
<br />
輪轂
</span>
<span class="colorPicker">
<input id="glass-color" type="color" value="#ffffff" />
<br />
玻璃
</span>
</div>
<div id="container"></div>
</div>
</template>
<style lang="scss">
body {
color: #bbbbbb;
background: #333333;
text-align: center;
overflow: hidden;
}
a {
color: #08f;
}
.colorPicker {
display: inline-block;
margin: 0 10px;
}
</style>
如果想要導入 glb 格式
的 3D
模型,那么我們需要借助兩個東西:
- 新的
webpack-loader
:因為webpack
默認只能處理js
文件,所以想要處理glb
文件的話,那么需要安裝file-loader
,并進行配置:
cnpm i --save-dev file-loader@6.2.0
在 vue.config.js
中配置對應的 loader
:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
chainWebpack: (config) => {
config.module
.rule('glb')
.test(/\.(glb|gltf|hdr)$/)
.use('file-loader')
.loader('file-loader')
.options({
name: 'assets/[name].[hash:8].[ext]'
})
.end()
}
})
- 使用新的
threejs API
,包括DRACOLoader
和GLTFLoader
。
DRACOLoader
:它是一個使用 Draco
庫壓縮的幾何加載器 ,咱們需要借助它的 setDecoderPath
方法來指定 DRACO 解碼器的路徑
GLTFLoader
:該 loader
是加載 gltf
文件的 loader
。glTF(gl傳輸格式)是一種開放格式的規范 ,可以高效的加載 3D
內容。咱們資源中的 .glb
文件就屬于 3D 內容
的一部分。
那么明確好了這兩塊內容之后,接下來咱們完成對應得代碼:
<script setup>
// 導入Vue的onMounted鉤子函數
import { onMounted } from 'vue'
// 導入Three.js庫
import * as THREE from 'three'
// 導入GLTF模型加載器
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
// 導入DRACO解碼器加載器
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
// 定義相機、場景、渲染器對象
let camera, scene, renderer
function init() {
// 獲取容器元素
const container = document.getElementById('container')
// 創建場景對象
scene = new THREE.Scene()
// 設置場景的背景顏色為黑色
scene.background = new THREE.Color(0x333333)
// 創建透視相機對象
camera = new THREE.PerspectiveCamera(
40,
window.innerWidth / window.innerHeight,
0.1,
100
)
// 為 camera 設置攝像縱深
camera.position.set(1, 1, 10)
// 創建WebGL渲染器對象,啟用抗鋸齒(更平滑)
renderer = new THREE.WebGLRenderer({ antialias: true })
// 設置渲染器的大小
renderer.setSize(window.innerWidth, window.innerHeight)
// 設置渲染循環函數(代替 requestAnimationFrame 的替代函數)
renderer.setAnimationLoop(render)
// 將渲染器的畫布元素添加到容器中
container.appendChild(renderer.domElement)
// 創建DRACO解碼器加載器對象
const dracoLoader = new DRACOLoader()
// 設置DRACO解碼器的路徑
dracoLoader.setDecoderPath('decoder/')
// 創建GLTF模型加載器對象
const loader = new GLTFLoader()
// 設置模型加載器的DRACO解碼器
loader.setDRACOLoader(dracoLoader)
// 加載 3D 文件
loader.load(require('@/assets/ferrari.glb').default, function (gltf) {
// 獲取加載的模型對象
const carModel = gltf.scene.children[0]
// 將車輛模型添加到場景中
scene.add(carModel)
})
}
/**
* 渲染
*/
function render() {
// 更新控制器
controls.update()
// 渲染場景
renderer.render(scene, camera)
}
// 在掛載時調用初始化函數
onMounted(() => {
init()
})
</script>
此時運行項目,我們可以看到一個黑色的車子輪廓。
圖片
5.添加控制器,讓模型支持 360°
我們這一小節,希望可以調整車輛的角度,讓模型支持 360°
旋轉。
那么想要支持這樣的功能,咱們需要借助另外一個 threejs API
叫做 OrbitControls 軌道控制器
具體代碼如下:
// 導入軌道控制器OrbitControls
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
// 定義控制器對象
let controls
function init () {
.....
// 創建軌道控制器對象
controls = new OrbitControls(camera, container)
// 啟用阻尼效果
controls.enableDamping = true
// 設置控制器能夠縮放的最大距離
controls.maxDistance = 9
// 設置控制器的焦點位置
controls.target.set(0, 0.5, 0)
// 更新控制器
controls.update()
.....
}
6.添加材質,并基于 RGBELoader 為 3D 模型增加環境貼圖
現在咱們的模型已經可以進行 360°
旋轉了,但是它現在還比較難看,所以接下來,咱們就需要給它添加一些 “顏色”,讓他變得更加漂亮。
那么想要達到這個目的,需要分成兩步去做:
- 通過
RGBELoader
添加環境貼圖 - 通過
Material
渲染物理材質
那么首先,咱們先做第一步: 通過 RGBELoader
添加環境貼圖
RGBELoader
允許你從文件中加載HDR圖像,并將其用作Three.js場景中的環境貼圖。這對于創建逼真的照明效果非常有用,因為HDR圖像能夠捕捉到真實世界中的光照細節和環境反射。
// 導入HDR貼圖加載器
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'
// 加載HDR貼圖作為場景的環境貼圖
scene.environment = new RGBELoader().load(
require('@/assets/venice_sunset_1k.hdr').default
)
// 設置環境貼圖的映射方式為球形映射
scene.environment.mapping = THREE.EquirectangularReflectionMapping
現在車身應該具備一個基礎顏色。
接下來我們來處理第二步,通過 Material
渲染物理材質, 讓他具備反光效果:
- 創建對應材質:
// 材料設置
// 創建車身材質對象,設置顏色和物理屬性
const bodyMaterial = new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 1.0,
roughness: 0.5,
clearcoat: 1.0,
clearcoatRoughness: 0.03,
sheen: 0.5
})
// 創建細節材質對象,設置顏色和物理屬性
const detailsMaterial = new THREE.MeshStandardMaterial({
color: 0xffffff,
metalness: 1.0,
roughness: 0.5
})
// 創建玻璃材質對象,設置顏色和物理屬性
const glassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
metalness: 0.25,
roughness: 0,
transmission: 1.0
})
- 在
loader.load
中,設置對應材質:
// 加載 3D 文件
loader.load(require('@/assets/ferrari.glb').default, function (gltf) {
// 獲取加載的模型對象
const carModel = gltf.scene.children[0]
// 設置車身部分的材質為車身材質
carModel.getObjectByName('body').material = bodyMaterial
// rim_xx 參考 glb 文件配置
// 設置前左輪輞的材質為細節材質
carModel.getObjectByName('rim_fl').material = detailsMaterial
// 設置前右輪輞的材質為細節材質
carModel.getObjectByName('rim_fr').material = detailsMaterial
// 設置后右輪輞的材質為細節材質
carModel.getObjectByName('rim_rr').material = detailsMaterial
// 設置后左輪輞的材質為細節材質
carModel.getObjectByName('rim_rl').material = detailsMaterial
// 設置車輛細節部分的材質為細節材質
carModel.getObjectByName('trim').material = detailsMaterial
// 設置玻璃部分的材質為玻璃材質
carModel.getObjectByName('glass').material = glassMaterial
// 將車輛模型添加到場景中
scene.add(carModel)
})
這樣,咱們的車輛模型就具備了一個基本的反光效果,會非常漂亮。
7.根據 GridHelper 設置網格動態效果
現在咱們的車輛本身已經非常好看的,那么這一小節,咱們就構建一個地面網格效果,并且讓網格具備一個動態后移的動效。
想要構建網格效果,那么我們需要通過 THREE.GridHelper
類構建:
// 定義網格幫助器對象
let grid
function init () {
// 創建網格幫助器對象,設置網格的大小和顏色
grid = new THREE.GridHelper(20, 40, 0xffffff, 0xffffff)
// 設置網格材質的透明度
grid.material.opacity = 0.2
// 禁用網格材質的深度寫入
grid.material.depthWrite = false
// 設置網格材質為透明材質
grid.material.transparent = true
// 將網格幫助器添加到場景中
scene.add(grid)
}
那么此時,咱們將具備一個地面網格。
我們可以調整 camera
的角度,讓車輛位置更好看一些:
// 設置相機位置
camera.position.set(4.25, 1.4, -4.5)
最后,我們可以在被 持續回調 的 render
函數中,動態修改 z軸
的值:
/**
* 渲染
*/
function render() {
// 獲取當前時間:https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now
const time = -performance.now() / 1000
// 設置網格的位置
grid.position.z = -time % 1
// 渲染場景
......
}
這樣,咱們可以得到一個持續后撤的地面效果。
8.構建車輪模型組,讓車輪轉起來
現在地面可以持續后撤了,但是車輪還是固定死的,所以這一小節,咱們就要讓車輪可以轉動起來。
在項目中,每一個車輪都是一個單獨的模型,我們可以通過:
carModel.getObjectByName('wheel_fl')
獲取 前左 車輪。
然后可以在 render
函數中 通過 車輪.rotation.x = time * Math.PI * 2
讓它轉動。
所以,我們如果想要讓四個車輪全部轉動,那么就需要構建一個數組,然后循環數組達到轉動的效果:
// 定義一個數組用于存儲車輪模型對象
const wheels = []
在 load
中:
// 將車輛的四個車輪模型對象添加到數組中
wheels.push(
carModel.getObjectByName('wheel_fl'),
carModel.getObjectByName('wheel_fr'),
carModel.getObjectByName('wheel_rl'),
carModel.getObjectByName('wheel_rr')
)
在 render
函數中:
// 設置車輪的旋轉角度
for (let i = 0; i < wheels.length; i++) {
wheels[i].rotation.x = time * Math.PI * 2
}
那么此時,就模擬出了一個車輛移動的效果。
9.監聽選擇器的變化,修改車身配置
這一小節,咱們來完成 修改車身顏色 的功能。
所謂的修改車身顏色,其實本質上就是利用 Material.color.set(色值)
方法重新設置色值。
明確了這一點之后,下面的實現就會非常簡單了:
// 獲取車身顏色輸入框元素
const bodyColorInput = document.getElementById('body-color')
// 監聽車身顏色輸入框的變化,設置車身材質的顏色
bodyColorInput.addEventListener('input', function () {
bodyMaterial.color.set(this.value)
})
// 獲取細節顏色輸入框元素
const detailsColorInput = document.getElementById('details-color')
// 監聽細節顏色輸入框的變化,設置細節材質的顏色
detailsColorInput.addEventListener('input', function () {
detailsMaterial.color.set(this.value)
})
// 獲取玻璃顏色輸入框元素
const glassColorInput = document.getElementById('glass-color')
// 監聽玻璃顏色輸入框的變化,設置玻璃材質的顏色
glassColorInput.addEventListener('input', function () {
glassMaterial.color.set(this.value)
})
那么此時,咱們就可以直接修改車身配置了。
10.創建陰影貼圖,讓場景更加真實
現在咱們的功能其實已經大致完成了。
最后還剩下一個小功能,就是車身下的陰影效果:
圖片
有了這個效果之后,會讓整體變得更加立體。
而想要構建出這個效果,那么需要使用 THREE.TextureLoader
,加載陰影圖:
// 車輛設置
// 加載車輛陰影貼圖
const shadow = new THREE.TextureLoader().load(
require('@/assets/ferrari_ao.png')
)
然后利用 mesh
構建出 **平面幾何 PlaneGeometry
**,并把它添加到 carModel
中:
// 陰影配置
// 創建陰影平面模型
const mesh = new THREE.Mesh(
new THREE.PlaneGeometry(0.655 * 4, 1.3 * 4),
new THREE.MeshBasicMaterial({
map: shadow,
// 混合
blending: THREE.MultiplyBlending,
// 映射
toneMapped: false,
// 透明
transparent: true
})
)
// 設置陰影平面模型的旋轉角度
mesh.rotation.x = -Math.PI / 2
// 設置陰影平面模型的渲染順序
mesh.renderOrder = 2
// 將陰影平面模型添加到車輛模型中
carModel.add(mesh)
那么此時,我們就具備了陰影效果。