記一次three.js專案總結
專案簡介:
完善3D模型,並在移動端展示,可利用陀螺儀環視,同時手機開啟攝像頭,且3D模型要覆蓋在攝像頭影片上。3D模型動畫結束展示直播間二維碼,出現兩個按鈕,一個可以儲存二維碼,一個可以重新觀看3D模型動畫。
3D模型的原始樣子
3D模型的最終樣式(要求渲染出的樣子)
選用框架:
three.js
,
webpack
。
開發過程:
配置webpack
使用webpack搭建專案環境,直接用自己寫的webpack-preset,配置檔案目錄結構如下
三維專案因為用不到太多CSS和圖片(暫時是這樣的),所以webpack的很多外掛和loader其實用不到。
配置目錄結構
一開始初始化的時候覺得可以將scene,camera,renderer,lights子類的分到各個不同的資料夾裡,這樣目錄結構更清晰,程式碼也不會堆在一個檔案裡。
配好的目錄結構如下
結構還算清晰,loaders和controls都只是為了方便獲取對應的類,其他都是例項後的物件
比如loaders/index。js
只是為了方便獲取不同的Loader
而其他檔案都是輸出一些例項後的物件,比如lights/index。js
### 新增一個video標籤
template.html
裡建立
video
標籤,用來顯示相機影片。
為了讓
three.js
生成的
canvas
能覆蓋在影片流上,我們要設定CSS
因為
canvas
標籤是最後放入
body
標籤內的,所以不用設定
z-index
。
這裡設定了
video
標籤的
object-fit
屬性為
cover
,目的是為了讓影片能填滿整個手機螢幕,因為預設的影片顯示效果為佔滿整個螢幕寬度,但是高度卻遠遠達不到螢幕的高度,這樣會讓最終效果顯得很差,會有大量留白。(不知道是不是瀏覽器只支援橫屏展示相機影片,目前這個解決方案個人認為還不夠優雅,但確實還沒找到更好的辦法)。
開啟攝像頭
注意: Web呼叫攝像頭只支援在本地執行(http://localhost)或使用https!
呼叫攝像頭程式碼如下
export
function
initVideo
()
{
//medaDevices介面是新的規範,因此如果存在則優先呼叫
//navigator。getUserMedia方法已被廢棄,此處作為向後相容
//如果兩種方法都不存在,提示使用者升級瀏覽器版本
if
(
navigator
。
mediaDevices
)
{
navigator
。
mediaDevices
。
getUserMedia
({
video
:
{
width
:
window
。
innerWidth
,
height
:
window
。
innerHeight
,
//優先開啟後置攝像頭
facingMode
:
‘environment’
,
}
})
。
then
(
function
(
stream
)
{
let
video
=
document
。
querySelector
(
‘video’
)
//注意 這裡使用srcObject而不是src
video
。
srcObject
=
stream
video
。
onloadedmetadata
=
function
(
e
)
{
video
。
play
()
}
})
。
catch
(
function
(
err
)
{
alert
(
‘攝像頭開啟失敗’
)
})
}
else
{
navigator
。
getUserMedia
=
navigator
。
getUserMedia
||
navigator
。
webkitGetUserMedia
||
navigator
。
mozGetUserMedia
||
navigator
。
msGetUserMedia
if
(
navigator
。
getUserMedia
)
{
navigator
。
getUserMedia
({
video
:
{
width
:
window
。
innerWidth
,
height
:
window
。
innerHeight
,
facingMode
:
‘environment’
,
}
},
function
(
stream
)
{
let
video
=
document
。
querySelector
(
‘video’
)
video
。
srcObject
=
stream
video
。
onloadedmetadata
=
function
(
e
)
{
video
。
play
()
}
},
function
(
err
)
{
alert
(
‘攝像頭開啟失敗’
)
}
)
}
else
{
alert
(
‘您的瀏覽器暫不支援開啟攝像頭,您可嘗試升級您的瀏覽器版本以獲取最佳體驗’
)
}
}
}
建立three。js相關物件
scene,camera等相關物件的建立過程這裡就不細講了,比較基礎,大家可以去看three。js的官網教程學習。
要注意的地方有
renderer.alpha
屬性要設定為
true
,這是為了讓背景透明,從而能夠顯示攝像頭畫面。
夢(keng)開始的地方
一開始模型給的是FBX,二話不說直接使用FBXLoader,結果出來的模型只剩下非常少的一部分,如下圖,
第一眼根本看不出這是啥。
一開始以為是FBX檔案的問題,但是反覆確認檔案沒有問題,我不得不檢查自己的配置。先從渲染出來的物件資料入手,看了一會果然發現有貓膩,有相當一部分的Mesh,quaternion和rotation屬性居然存在
NaN
我斷定這就是模型渲染不出來的原因,因此我手動修正這些資料,仍然不行,設定完馬上又變NaN,我就開始懷疑是動畫軌道的問題,所以我去看了看
animationcCip.tracks
,,果然很多track的values資料都是NaN。
我嘗試把存在NaN的軌道刪除,果然模型能顯示了
但是顯示還是存在一定的問題,並且動畫效果不見了,現在留給我兩條路
1 手寫動畫,手動調整模型
2 換一個模型格式
我選擇後者,前者出來的模型應該不會太穩定。
先做一個小結:
FBXLoader
對FBX檔案格式要求較為嚴格,Loader外掛的相容性較差,謹慎使用。
模型格式更換
後來我得到了GTLF的檔案格式,使用
GLTFLoader
,正確配置好動畫後,這次基本所有的模型動畫都能顯示了。
對比一下開頭的模型,唯獨文字描邊位置非常不對勁。
中間的文字描邊都不見了,大家可以找一找,它跑去哪了呢?
揭曉答案
……
第一時間去查文字描邊的物件資料,
scale屬性不正常,有了之前的經驗,第一時間懷疑動畫軌道有問題,查了查
最後的正確資料應該為1而不是0。1,果然有問題,所以我不得不手動修改這些動畫資料。
包括quaternion屬性也存在相關問題,最終寫完了動畫修復程式碼
//修復描邊BUG
gltf
。
animations
[
0
]。
tracks
。
forEach
(
v
=>
{
let
len
=
v
。
values
。
length
if
(
v
。
values
[
len
-
1
]
<
0。2
&&
/(材質)\w*\。scale$/
。
test
(
v
。
name
))
{
for
(
let
i
=
0
;
i
<
6
;
i
++
)
{
v
。
values
[
len
-
1
-
i
]
=
1
}
}
else
if
(
+
v
。
values
[
len
-
1
]
!==
0。5
&&
/(材質)\w*\。quaternion$/
。
test
(
v
。
name
))
{
for
(
let
i
=
0
;
i
<
8
;
i
++
)
{
if
(
i
%
4
==
0
)
{
v
。
values
[
len
-
1
-
i
]
=
-
0。5
}
else
{
v
。
values
[
len
-
1
-
i
]
=
0。5
}
}
}
})
動畫顯示正常
調整模型的樣式
為了達到要求的樣式,我選擇先手動給材質打上外發光顏色(
emissive
),部分程式碼如下圖
效果
接下來我希望底部有一種光照的效果,因此我使用three。js原始碼內的lensflare圖片作為顏色貼圖,再調整了一下顏色,程式碼如下
效果(這裡打開了攝像頭)
對比一下
發現缺少一點明亮的感覺
使用bloom泛光效果
使用泛光效果可以將明亮部分更加逼真的渲染。
加入以下程式碼
//泛光特效
import
{
EffectComposer
}
from
‘three/examples/jsm/postprocessing/EffectComposer。js’
import
{
RenderPass
}
from
‘three/examples/jsm/postprocessing/RenderPass。js’
import
{
UnrealBloomPass
}
from
‘three/examples/jsm/postprocessing/UnrealBloomPass。js’
const
params
=
{
bloomStrength
:
0。8
,
bloomThreshold
:
0
,
bloomRadius
:
0
,
}
let
composer
let
renderScene
=
new
RenderPass
(
scene
,
camera
)
let
bloomPass
=
new
UnrealBloomPass
(
new
THREE
。
Vector2
(
window
。
innerWidth
,
window
。
innerHeight
),
1。5
,
0。4
,
0。85
)
bloomPass
。
threshold
=
params
。
bloomThreshold
bloomPass
。
strength
=
params
。
bloomStrength
bloomPass
。
radius
=
params
。
bloomRadius
composer
=
new
EffectComposer
(
renderer
)
composer
。
addPass
(
renderScene
)
composer
。
addPass
(
bloomPass
)
。。。
function
render
(
isMobile
=
false
)
{
//請求再次執行渲染函式render,渲染下一幀
requestAnimationFrame
(
render
)
。。。
//後期渲染
composer
。
render
()
。。。
}
有點晃眼,是不是
環繞的文字和中間的文字過亮,但其他部分效果正好,所以我希望文字不被後期渲染。
研究了一下官網的例子,官網不讓某些物體被渲染的方法如下
這種解決方案有缺點,因為這個方法會在render函數里不斷呼叫,十分影響效能。並且官網的例子是靜態的,如果將此解決辦法引入我們的專案,效果不一定會好。
因此我研究了
layer
,發現合理利用layer即可分層渲染。
首先設定文字物件的layers,將他們設定為1,與預設的0區分
接著設定相機分層渲染,改寫render函式
效果如下
對比
講道理還是差點特效,但是總比裸模型接近不少了。
二維碼、按鈕等部分的處理這裡就不講了,只是一些簡單的DOM操作。
總結
three。js相關的基礎概念很多,想要更高效的學習three。js最好先學習圖形學相關基礎。
不要害怕讀原始碼,原始碼可以教會你很多東西。
多看官方文件。
更新一些坑
陀螺儀控制可以使用three。js官方的DeviceOrientationControls,但是注意ios上safari需要使用者的主動操作才可以使用陀螺儀控制的核心API DeviceOrientationEvent,如果是頁面載入時指令碼自動使用這些API,陀螺儀是無法使用的。
解決方法
:一般會在頁面上新增一個按鈕,提醒使用者授權,在點選事件裡開啟這些功能即可。
使用了bloom泛光後期處理會導致三維場景背景不透明。
解決方法
:這個issue裡有具體的解決辦法。
WKWebkit核心(比如ios微信內嵌的瀏覽器)不支援WebRTC(也就沒法使用我的程式碼開啟攝像頭),這個問題的討論在這裡,大家有興趣可以看一看。
上一篇:你對電視攝影的理解是什麼?