年前就一直研究了下WebGL相關的東西,看了很多資料和文檔,這裏做了一些小實踐,記錄分享一下。
demo:
前置知識
WebGL和Threejs的關係:
WebGL是一種 3D 繪圖協議,這種繪圖技術標準結合了JavaScript和OpenGL ES 2.0,在HTML5的Canvas元素中使用,從而可以在 Web 瀏覽器中呈現 3D 場景,
而Threejs是對WebGL的封裝,可以讓之前很少接觸OpenGL的研發人員直接上手3D開發。掌握WebGL有利於理解Threejs的各種api,理解threejs開發的理念。
上手Threejs之前,最好多看看理解理解WebGL,GLSL,線性代數,一些幾何算法。
具體相關,可以到網上搜索。
着手開發
創建三要素
threejs三要素:場景,相機,渲染器,這三個對象是threejs一個3d場景必須創建的三要素:
let scene = new THREE.Scene(); //創建場景
let camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
cameraFar
); //創建透視相機 (參數分別是 FOV:可視角度, aspect ratio:寬高比, near:近剪切面, far:遠剪切面)
// 渲染器
let renderer = new THREE.WebGLRenderer({
canvas
});
renderer.render(scene, camera);
創建天體物體
3d天體主要以太陽系爲模型,這裏需要創建中間的太陽和八大行星。物體的創建在three裏面有很全的幾何類,球體,圓環,正方體等等,這些類主要以Mesh爲基類,採用三角形網格。這裏我們把行星的初始封裝成一個方法:
function initStar(name, speed, angle, color, distance, volume, ringInfo) {
let mesh = new THREE.Mesh(
new THREE.SphereGeometry(volume, 16, 16),
new THREE.MeshLambertMaterial({
color
})
);
mesh.position.x = distance; // 右手座標系,x即爲在同一個平面上行星距離太陽的距離
// 其他自定義屬性
mesh.receiveShadow = true;
mesh.castShadow = true;
mesh.name = name;
// !行星軌道
let track = new THREE.Mesh(
new THREE.RingGeometry(distance - 0.2, distance + 0.2, 64, 1),
new THREE.MeshBasicMaterial({
color: 0x888888,
side: THREE.DoubleSide
})
);
track.rotation.x = -Math.PI / 2;
scene.add(track);
let star = {
name,
speed,
angle,
distance,
volume,
Mesh: mesh
};
// 有行星環的情況
if (ringInfo) {
// console.log("進入了ring,Info爲", ringInfo);
let ring = new THREE.Mesh(
new THREE.RingGeometry(ringInfo.innerRedius, ringInfo.outerRadius, 32, 6),
new THREE.MeshBasicMaterial({
color: ringInfo.color,
side: THREE.DoubleSide,
opacity: 0.7,
transparent: true
})
);
ring.name = `Ring of ${name}`;
ring.rotation.x = -Math.PI / 3;
ring.rotation.y = -Math.PI / 4;
scene.add(ring);
star.ring = ring;
}
scene.add(mesh);
return star;
}
name, speed, angle, color, distance, volume, ringInfo的參數意義分別是,行星名字,初始角度,距離太陽的直線距離,行星顏色,行星x軸座標(離恆星太陽的距離),半徑,行星環信息。
注意three中採用的是右手座標系
行星和恆星處於同一平面,所以y軸座標爲0,差別是x軸,以太陽爲中心當做原點的話,初始化行星的distance參數就是離原點恆星的距離。通過計算三角函數,可以算出座標系中的xy軸值。
運動和動畫
運動主要是動態計算設置每個行星的x,y軸。
這裏的y軸實際對應是three座標系中的z軸。天體都在一個平面,天體在three座標系中的y軸都爲0。
// 行星公轉
function revolution(star) {
star.angle += star.speed;
star.angle > Math.PI * star.distance &&
(star.angle -= Math.PI * star.distance);
star.Mesh.position.set(
star.distance * Math.sin(star.angle),
0,
star.distance * Math.cos(star.angle)
);
}
function move() {
//太陽自轉
Sun.rotation.y += 0.008; // 旋轉網格的x軸
// 行星公轉
stars.map((star) => revolution(star));
control.update(clock.getDelta()); //此處傳入的delta是兩次animationFrame的間隔時間,用於計算速度
renderer.render(scene, camera);
requestAnimationFrame(move);
}
注意threejs裏面幾乎所有的動畫都是用rFA做的,rFA做動畫的好處就是能保證整體動畫速度不會被“拖慢”,相對的保證動畫流暢。這一點其實網上很多博客資料都講了,但是都沒有說清楚是怎麼保證動畫流暢的,而且這裏的流暢是有歧義的,rFA會採用跳過某些幀的方式表現動畫,有時候動畫表現上會出現“卡頓”,所以這裏的流暢是相對結果而言。
什麼意思呢?打個比喻:
比如說你的遊戲邏輯
你有一個人物在移動,移動速度是每秒60px,也就是每幀1px
如果你的遊戲邏輯執行時間超過了 1/60 秒
那結果就是,一秒鐘過後,人物沒有正確的移動 60px
但如果你用 rAF 保證上一幀邏輯不阻塞下一幀邏輯
你的運算就不會堵住
但人物的位置是對的
再舉個例子 手機屏幕 你做一個方塊 手指拖動到哪他就移動到哪
如果運算卡住的話 他會不跟手 你手拖很遠了他還在慢慢移動
但是如果運算不阻塞 即便可能會有點瞬移 但方塊一直在你手指下。
所以rFA保證動畫流暢就是這麼個意思。
光源
做到這,跑來的話你發現是黑乎乎的一片,因爲場景裏還缺少光源。
定義光源和環境光。
光源就是真實的一個光源點,以中間的太陽恆星爲光源點,公轉的行星背部也有陰影的真實效果,光源點的參數可以定義光顏色,光照強度,以及光照到0強度的距離:
PointLight( color : Integer, intensity : Float, distance : Number,
decay : Float ) color - (可選參數)) 十六進制光照顏色。 缺省值 0xffffff (白色)。 intensity
- (可選參數) 光照強度。 缺省值 1。
distance - 這個距離表示從光源到光照強度爲0的位置。 當設置爲0時,光永遠不會消失(距離無窮大)。缺省值 0. decay -
沿着光照距離的衰退量。缺省值 1。 在 physically correct 模式中,decay = 2。
環境光主要是模擬整體環境的光,這種光每個狹隙都能照射到,理想中的均勻光。配合宇宙背景小點點行星亮光會更真實。
//環境光
let ambient = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambient);
/*太陽光*/
let sunLight = new THREE.PointLight(0xddddaa, 1.5, 500);
scene.add(sunLight);
行星運動的軌跡
爲了更好區別每個行星的運動,需要給每個行星公轉的軌跡顯示出來。
其實就是在初始化行星的時候,在行星的distance基礎上初始化一個圓環物體,設置內環外環半徑。
// !行星軌道
let track = new THREE.Mesh(
new THREE.RingGeometry(distance - 0.2, distance + 0.2, 64, 1),
new THREE.MeshBasicMaterial({
color: 0x888888,
side: THREE.DoubleSide
})
);
track.rotation.x = -Math.PI / 2;
scene.add(track);
注意需要旋轉默認圓環體是豎着的,需要旋轉一下。
視角控制
引入第一人稱視角控制,視角跟着鼠標和鍵盤的方向鍵控制視角和距離。
/*鏡頭控制*/
control = new THREE.FirstPersonControls(camera, canvas);
control.movementSpeed = 100; //鏡頭移速
control.lookSpeed = 0.125; //視角改變速度
control.lookVertical = true; //是否允許視角上下改變
camera.lookAt(new THREE.Vector3(0, 0, 0));
FirstPersonControls庫需要作爲文件單獨引入,three官方還有其他控制相關的庫。
其他一些細節
還有很多其他一些細節,太陽的外燃燒蒙層,限定視角範圍,行星環,鼠標移動到行星顯示文字,星星背景等,都可以在源碼裏看到或者待完善。
tip:在vscode裏沒有好用的three的Snippets,可以npm i three,利用npm three包的ts智能提示。three中的loader加載物體的紋理皮膚或者字體,3d模型等在本地會被cors block,需要本地工程化,起個node服務或者webpack server支持。