這次要來玩的是環境,在3D中一個可以利用黑白圖快速生成地形的方式。白色式高點, 黑色是低點,灰階是介於兩者的高度。這種流行的方式提供了一種簡單且高效的方法來創建複雜的、有機的地形,例如山脈、山谷、丘陵或不平整的地面。不需要手動去調整每一個頂點,只需要編輯一張圖片即可。Unity, Unreal Engine, Blender都會使用這樣的概念,而Babylon.js也可以這樣做。
比方說今天我們有這樣的一張圖:
利用CreateGroundFromHeightMap
這個方法把圖丟進去,就能得到下方有高低起伏的地形:
const largeGround = BABYLON.MeshBuilder.CreateGroundFromHeightMap("largeGround", "https://assets.babylonjs.com/environments/villageheightmap.png" /* url to height map */,
{width:150, height:150, subdivisions: 20, minHeight:0, maxHeight: 10});
再加上草地的材質,就會得到一張有高低起伏的地形圖了
const largeGroundMat = new BABYLON.StandardMaterial("largeGroundMat");
largeGroundMat.diffuseTexture = new BABYLON.Texture("https://assets.babylonjs.com/environments/valleygrass.png");
largeGround.material = largeGroundMat;
playground 的樣子:https://playground.babylonjs.com/#KBS9I5#40
想要在3D環境加上天空稱之為天空盒(Skybox),Skybox 的概念很簡單,它是一個巨大的立方體或球體,包圍整個 3D 場景。這個立方體的內壁貼上了六張紋理圖片,每張圖片對應一個方向(上、下、前、後、左、右)。當你在場景中移動攝影機時,這個 Skybox 始終保持在最遠的位置,而且它的渲染方式經過特殊優化,即使攝影機旋轉,它看起來也像是無限遠處的環境但因為有了這個背景,看起來不會像是懸浮在一個空洞的空間裡。
本次範例中使用的六面立方體貼圖是最傳統的方法,需要準備六張分別代表天空盒不同方向的圖片。//
// 建立一個 Skybox 的網格(一個沒有光照、沒有深度測試的立方體)
const skybox = BABYLON.MeshBuilder.CreateBox("skyBox", {size:150}, scene);
// 建立一個立方體紋理(CubeTexture),並指定六張圖片的路徑
// 這些圖片需要遵循特定的命名格式:_px, _nx, _py, _ny, _pz, _nz
const skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene);
skyboxMaterial.backFaceCulling = false;
skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("textures/skybox", scene);
skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
skybox.material = skyboxMaterial; // 將材質應用到 Skybox 網格上
需要一張 HDR 格式的全景圖,用於需要基於物理的渲染(PBR)和真實光照的場景,更適合WebXR。
它的資料來源通常是一張球形投影的全景圖,但引擎會將這張全景圖的資料,高效地重新投影並渲染到一個立方體紋理(Cube Map)的六個面上。還會生成多層級的紋理提供準確的反射和環境光照。最後仍會以「立方體」的紋理形式被使用和渲染。
// 建立一個天空盒,並載入 HDR 全景圖
// 這裡使用了 "textures/environment.env" 這類檔案,它包含了天空盒與光照資訊
const hdrTexture = BABYLON.CubeTexture.CreateFromPrefilteredData("textures/environment.env", scene);
// 將 HDR 紋理設定為場景的反射紋理
// 這將同時作為天空盒背景,並提供環境光照(IBL)
scene.environmentTexture = hdrTexture;
// 額外設定,可選:調整背景亮度
scene.environmentTexture.gammaSpace = false;
這個單元最後是Sprite 的部分,有了天空跟地板的環境之後,想要加點花草樹木的話,如果放置一整片的模型會很耗效能對吧。因此,有個常用的手法是Sprite ,他是一個永遠面向攝影機的2D 平面圖片。很常應用在環境中的花草樹木、粒子效果(像是煙霧、下雨下雪)、血條或特效等。那種需要渲染大量,但不需要複雜 3D 幾何體的動態 2D 元素時,就很適合用他。
Babylon.js 的SpriteManager
優勢在於它能用single draw call來渲染所有Sprite,所以能有效的處理成千上百的Sprite。因為 GPU 不需要為每個單獨的精靈圖都執行一次繪製指令,所以極大地提高了效能。
Playground 範例中,是怎麼加上這些Sprite樹的呢?
首先,也是最重要的一步,先建一個SpriteManager
,名叫"treesManager”,圖片來源,manager的容納Sprite最大數量,Sprite 尺寸,應用的場景。
const spriteManagerTrees = new BABYLON.SpriteManager("treesManager", "textures/palm.png" /* url to sprite */, 2000, {width: 512, height: 1024}, scene);
給他迴圈去把樹長出來,並且給他個別一片空間的地方隨機長,最後會得到500棵+500棵,總共1000棵:
for (let i = 0; i < 500; i++) {
const tree = new BABYLON.Sprite("tree", spriteManagerTrees);
tree.position.x = Math.random() * (-30);
tree.position.z = Math.random() * 20 + 8;
tree.position.y = 0.5;
}
for (let i = 0; i < 500; i++) {
const tree = new BABYLON.Sprite("tree", spriteManagerTrees);
tree.position.x = Math.random() * (25) + 7;
tree.position.z = Math.random() * -35 + 8;
tree.position.y = 0.5;
}
他們看起來都像是一個3D模型,但其實都是一片紙片,高級一點就是有動畫的紙片。這個技巧也可以適時地放在XR的開發中,但要注意盡量避免像是可以由上往下看的物件,或是塞跟其他模型之間太密的地方等,因為XR的鏡頭是自由的,所以就要考量各種會因為視角轉移而破圖的狀況。
下一篇來看看,當需要生動的粒子效果的話該怎麼做呢?