最近入了three.js的坑,想用three.js作一些demo以便鞏固本身最近所掌握的知識點,並且正好遇上國慶放假,隨,有了這篇~html
預覽地址: Three.js之上海外灘git
Tip1: 打開後瀏覽器一直轉圈 建築物的貼圖不顯示 是網絡問題 等一下子就好 畢竟是github...
Tip2: 若是打開後幀數太低 比較卡的話,能夠調整代碼中的SHADOW_MAPSIZE大小 經過調整鋸齒感來優化性能
github
代碼地址: Three.js之上海外灘web
以爲不錯的話麻煩點個star 謝謝 👏算法
本篇雖是關於Three.js入門的文章, 可是太過入門的就不講了,沒意義,網上不少相關入門知識。本篇主要是把本身在寫demo時候遇到的坑點給記錄下來, 有什麼不懂的直接去查閱文檔或者網上搜,這裏提一下:Three.js的官方文檔和例子對開發者也是挺友好的(有中文版!)api
廢話很少, 先看下效果吧:數組
代碼比較多,就不一一講解了,本篇主要分爲如下幾個部分:瀏覽器
THREE.Shape
和THREE.ExtrudeGeometry
建立不規則的幾何體」THREE.Object3D()
建立不規則的幾何體」Math.sin
規律性的改變幾何體的頂點vertices
建立不規則的幾何體」vertices
和三角形面數組faces
的方式建立不規則的幾何體」THREE.Object3D()
建立不規則的幾何體」Math.random
隨機生成幾何體」THREE.TextureLoader
的運用」requestAnimationFrame
動畫方法的運用」THREE.CubeTextureLoader
建立360度全景空間」/** 材質顏色常量 */
const MATERIAL_COLOR = "rgb(120, 120, 120)";
init();
function init() {
// 1.場景
let scene = new THREE.Scene();
let stats = new Stats();
document.body.appendChild(stats.dom);
let clock = new THREE.Clock();
let gui = new dat.GUI();
// 座標軸輔助器
let axesHelper = new THREE.AxesHelper(500);
// 網格輔助器
let gridHelper = new THREE.GridHelper(100, 100);
scene.add(axesHelper);
scene.add(gridHelper);
//經過Shape生成一個不規則等2D圖形
// 地面1
let ground1 = getGroundFront();
// 地面2
let ground2 = getGroundBehind();
scene.add(ground1);
scene.add(ground2);
// 光源
let spotLight = getSpotLight(1.2);
spotLight.position.set(100, 100, 80);
scene.add(spotLight);
// 相機
let camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
1,
1000
);
camera.position.set(0, 30, 90);
camera.lookAt(new THREE.Vector3(0, 0, 0));
// 3.渲染器
let renderer = new THREE.WebGLRenderer();
renderer.setClearColor(MATERIAL_COLOR);
renderer.shadowMap.enabled = true; // 開啓渲染器的陰影功能
renderer.shadowMap.type = THREE.PCFShadowMap; // PCF陰影類型
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById("webgl").appendChild(renderer.domElement);
// 相機軌道控制器
let controls = new THREE.OrbitControls(camera, renderer.domElement);
update(renderer, scene, camera, controls, stats);
}
function getSpotLight(intensity) {
// 生成光源
let light = new THREE.PointLight(0xffffff, intensity);
light.castShadow = true;
light.receiveShadow = true;
light.shadow.bias = 0.001;
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
return light;
}
function getGroundBehind() {
// 地面2 後半部分
let shape = new THREE.Shape();
shape.moveTo(45, 100); // moveTo( x, y )
shape.lineTo(50, 100); // lineTo( x, y ) - 線
shape.lineTo(50, 0); // lineTo( x, y ) - 線
shape.lineTo(-50, 0); // lineTo( x, y ) - 線
shape.lineTo(-50, 60); // lineTo( x, y ) - 線
// 貝塞爾曲線
shape.bezierCurveTo(5, 15, 15, 5, 45, 100);
let extrudeGeometry = new THREE.ExtrudeGeometry(shape, {
depth: 3,
steps: 2,
bevelThickness: 0,
bevelSize: 1
});
let material = new THREE.MeshLambertMaterial({ color: "gray" });
let mesh = new THREE.Mesh(extrudeGeometry, material);
mesh.receiveShadow = true;
mesh.rotation.x = Math.PI + Math.PI / 2; // 地面旋轉180度
mesh.rotation.y = Math.PI; // 地面旋轉180度
mesh.position.set(0, 0, 50);
return mesh;
}
function getGroundFront() {
// 地面1 前半部分
let shape = new THREE.Shape();
shape.moveTo(50, 0); // moveTo( x, y )
shape.lineTo(-25, 0); // lineTo( x, y ) - 線
shape.quadraticCurveTo(-10, 107, 50, 15); // 二次曲線
let extrudeGeometry = new THREE.ExtrudeGeometry(shape, {
depth: 3,
steps: 2,
bevelThickness: 0,
bevelSize: 1
});
let material = new THREE.MeshLambertMaterial({ color: "#666" });
let mesh = new THREE.Mesh(extrudeGeometry, material);
mesh.receiveShadow = true;
mesh.rotation.x = Math.PI / 2; // 地面旋轉90度
mesh.position.set(0, 0, -50);
return mesh;
}
function update(renderer, scene, camera, controls, stats) {
renderer.render(scene, camera);
// 性能監控
stats.update();
// 相機軌道控制器
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(function() {
update(renderer, scene, camera, controls, stats);
});
}
複製代碼
核心代碼在main.js
中,因此只貼出main.js
的相關代碼, 其他還有index.html
和第三方庫引用文件等能夠去github
上查看bash
該部分沒有作什麼比較核心的地方,只是一些場景 相機 渲染器 畫輔助線 性能監控 等的初始化,但花費時間稍長的是畫出兩個不規則地面,留出一道「黃浦江」的位置。網絡
咱們知道,three.js
的基礎API只能畫出規則網格(幾何體),像什麼方體 錐體 球體等等.. 像這種自定義的網格(幾何體)咱們能夠用THREE.Shap
這個API。
function getGroundFront() {
let shape = new THREE.Shape(); // 1.畫一個二維面 Shape
shape.moveTo(50, 0); // 2. 將.currentPoint 移動到 x, y
shape.lineTo(-25, 0); // 3. 在當前路徑上,從.currentPoint 鏈接一條直線到 x,y。
shape.quadraticCurveTo(-10, 107, 50, 15); // 4. 從.currentPoint 建立一條二次曲線,以(cpX,cpY)做爲控制點,並將.currentPoint 更新到 x,y。
let extrudeGeometry = new THREE.ExtrudeGeometry(shape, {
depth: 3,
steps: 2,
bevelThickness: 0,
bevelSize: 1
});// 5. 擠壓幾何體 ExtrudeGeometry
let material = new THREE.MeshLambertMaterial({ color: "#666" });
let mesh = new THREE.Mesh(extrudeGeometry, material);
mesh.receiveShadow = true; // 接收陰影
mesh.rotation.x = Math.PI / 2; // 地面旋轉90度
mesh.position.set(0, 0, -50); // 改變位置
return mesh;
}
複製代碼
其實說白就了就是 首先經過 THREE.Shap
對象,建立一個二維的圖形,而後再經過 ExtrudeGeometry
將其拉伸爲一個三維圖形。這是前半部分的地面幾何體,後半部分也是同樣的。 其餘的具體參數信息就查閱文檔:Shape 和 ExtrudeGeometry
以座標(0, 0, 0)
爲原點建立東方明珠。其實這個還蠻簡單的,不須要你本身畫出不規則的網格,利用Three.js
相關基礎的API
就能夠作到,對其進行組合排列就行了。可是相關代碼量是比較多的,這裏就不貼出了,能夠去github上翻閱 首先,咱們能夠觀察下東方明珠,不難發現,它都是有一些常見的幾何體組合而成,好比圓柱,球體,圓環等等。咱們能夠將整個東方明珠分爲三個部分,底部,中部,頂部。 首先底部由兩個疊加的圓臺(其實就是高度很小的圓柱),加上3個豎直的圓柱和3個傾斜的圓柱組成。 中部就簡單多了,兩個球,加上中間整齊排列的圓環組成。 頂部也不難,3個半徑不一樣球體和3個半徑不一樣的圓柱組成。
這個當時也是花費了一些時間的。 因爲上海中心大廈是個極其不規則的幾何體,用Three.js實現仍是略微麻煩一些的,最終的實現效果也並非特別理想,只是略像而已。
function getShanghaiTower() {
// 1. 經過 THREE.CylinderGeometry 生成一個圓柱體 注意參數
let _geometry = new THREE.CylinderGeometry(2, 3, 18, 7, 50);
// 2. 操做該圓柱的頂點, 經過正弦函數規律性的變化 使其網格發生變化
_geometry.vertices.forEach((vertex, ind) => {
// 正弦函數規律性的改變頂點座標的x軸和z軸
vertex.z = vertex.z + Math.sin((vertex.y + ind) * 0.015);
vertex.x = vertex.x + Math.sin((vertex.y + ind) * 0.01) * 1;
if (vertex.y >= 8.5) {
// 3. 這裏作了一個斜塔尖
vertex.y -= vertex.x * 0.2;
}
});
// 4. 改變頂點後別忘記了讓網格的verticesNeedUpdate等於true
_geometry.verticesNeedUpdate = true;
let _material = new THREE.MeshPhongMaterial({
color: "rgb(120, 120, 120)"
// wireframe: true
});
let tower = new THREE.Mesh(_geometry, _material);
tower.position.set(10, 17, -8); // 位置
tower.scale.set(1, 2, 0.5); // 縮放
return tower;
}
複製代碼
首先經過 THREE.CylinderGeometry
生成一個圓柱體 注意參數,上半徑和下半徑不一樣。 經過網格.vertices
,獲取該幾何體的全部頂點,經過規律性的改變每一個頂點的x和z座標,從而實現改變最終幾何體的目的。可是最終別忘了要手動設置網格.verticesNeedUpdate = true
,若是不設置的話,Three.js默認是不會改變其頂點的。
能夠看到, 環球金融中心也是一個不規則的網格幾何體。 能夠將其分爲兩部分,底部不規則網格和頂部不規則網格。此處實現不規則網格幾何體的話咱們就要經過手寫頂點座標vertices
和手寫每一個面的三角形faces
來實現自定義的不規則幾何體。
底部不規則網格:
頂部不規則網格:
底部代碼:
getGlobalFinancialCenterBottom方法:
function getGlobalFinancialCenterBottom() {
// 1. 手寫幾何體的每一個頂點座標
let vertices = [
// 底部
new THREE.Vector3(3, 0, 3), // 下標0
new THREE.Vector3(3, 0, -3), // 下標1
new THREE.Vector3(-3, 0, 3), // 下標2
new THREE.Vector3(-3, 0, -3), // 下標3
// 中部
new THREE.Vector3(3, 10, 3), // 下標4
new THREE.Vector3(-3, 10, -3), // 下標5
// 上部
new THREE.Vector3(-1.5, 30, 3), // 下標6
new THREE.Vector3(3, 30, -1.5), // 下標7
new THREE.Vector3(3, 30, -3), // 下標8
new THREE.Vector3(1.5, 30, -3), // 下標9
new THREE.Vector3(-3, 30, 1.5), // 下標10
new THREE.Vector3(-3, 30, 3) // 下標11
]; //頂點座標,一共8個頂點
let faces = [
// 底部2個三角形
new THREE.Face3(0, 1, 2),
new THREE.Face3(3, 2, 1),
// 每一個面的 3個三角形
// 1.
new THREE.Face3(6, 2, 0),
new THREE.Face3(0, 4, 6),
new THREE.Face3(11, 2, 6),
// 2.
new THREE.Face3(0, 1, 7),
new THREE.Face3(7, 4, 0),
new THREE.Face3(8, 7, 1),
// 3.
new THREE.Face3(1, 3, 9),
new THREE.Face3(9, 8, 1),
new THREE.Face3(3, 5, 9),
// 4.
new THREE.Face3(10, 3, 2),
new THREE.Face3(11, 10, 2),
new THREE.Face3(10, 5, 3),
// 頂部4個三角形
new THREE.Face3(6, 10, 11),
new THREE.Face3(7, 8, 9),
new THREE.Face3(6, 7, 10),
new THREE.Face3(7, 9, 10),
// 兩個剖面 三角形
new THREE.Face3(7, 6, 4),
new THREE.Face3(10, 9, 5)
]; //頂點索引,每個面都會根據頂點索引的順序去繪製線條
let globalGeometry_bottom = new THREE.Geometry();
globalGeometry_bottom.vertices = vertices;
globalGeometry_bottom.faces = faces;
globalGeometry_bottom.computeFaceNormals(); //計算法向量,會對光照產生影響
let _material = new THREE.MeshPhongMaterial({
color: "rgb(120, 120, 120)"
// wireframe: true
});
let globalFinancialCenter = new THREE.Mesh(globalGeometry_bottom, _material);
return globalFinancialCenter;
}
複製代碼
咱們知道,每3個點能夠肯定一個三角形面,其實說白了,在Three.js中的每一個幾何體,都是有n個三角形面組合而成的。 個人理解是 頂點 --> 三角形面 --> 幾何體面 --> 幾何體
首先定義來一個不規則幾何體的頂點數組vertices
,裏面都是幾何體每一個頂點的座標。 此處注意:若是繪製的面是朝向相機的,那這個面的頂點的書寫方式是逆時針繪製的,好比圖上模型的第一個面的添加里面書寫的是(0,1,2)。不然,你看到的幾何體的那個面就是透明的 數組的頂點座標排序無所謂,可是數組的下標很重要,由於要對應到每一個三角形面faces
。 ok,緊接着又定義了一個三角形面的數組faces
,裏面每個元素都是一個三角形,而每一個元素裏面又經過 new THREE.Face3(0, 1, 2)
來肯定一個三角形面, 其中的(0, 1, 2)
是咱們剛纔定義的頂點數組的下標,注意:必定是頂點數組下標。 好比(0, 1, 2)
對應的就是上面定義的頂點數組中的new THREE.Vector3(3, 0, 3), new THREE.Vector3(3, 0, -3) 和 new THREE.Vector3(-3, 0, 3)
。可能有人疑惑,定義三角形面的時候,爲何不直接經過寫頂點座標而非要寫頂點座標的下標呢?那樣不是更方便更直觀嗎? 其實仔細想一下不難發現,若是經過直接寫頂點座標來定義三角形的話,那麼三角形面的三個頂點確定會與其餘三角形面的某個頂點重合,簡單的幾何體還好,若是是複雜的幾何體,重複的頂點座標會極大的形成性能浪費。因此Three.js
纔會經過頂點數組的下標來肯定每一個三角形面。
這個是底部不規則幾何體的代碼, 頂部實現方式同樣的,也是經過定義頂點數組和三角形面的方式來自定義幾何體。 只不過頂部的幾何體稍微複雜一些,是兩個對稱的不規則幾何體加上一個面THREE.PlaneGeometry
組成。 環球金融中心的具體代碼請查閱github。 在建立不規則幾何體每一個頂點的時候,若是實在想不出每一個頂點的空間位置的話,推薦在稿紙上先畫出個大概的草圖,創建座標軸來進行標註。如下是我當時畫的草圖,不是很精確,只是草圖,供參考:
好比上圖,肯定好每一個頂點的額座標以後,再根據每3個頂點座標寫出每一個三角形面。
// 金茂大廈
function getJinmaoTower(x, y, z) {
let JinmaoTower = new THREE.Object3D();
let _geometry = new THREE.BoxGeometry(1, 22, 6);
let _material = new THREE.MeshPhongMaterial({
color: "rgb(120, 120, 120)"
});
// 金茂大廈中間骨架
var cube1 = new THREE.Mesh(_geometry, _material);
var cube2 = new THREE.Mesh(_geometry, _material);
cube2.rotation.set(0, Math.PI / 2, 0);
// 金茂大廈主幹
let towerBody = getJinmaoTowerBody();
// 金茂大廈頂部主體
let towerTop = getJinmaoTowerTop();
JinmaoTower.add(cube1);
JinmaoTower.add(cube2);
JinmaoTower.add(towerBody);
JinmaoTower.add(towerTop);
JinmaoTower.position.set(x, y, z);
return JinmaoTower;
}
// 金茂大廈頂部主體
function getJinmaoTowerTop() {
let towerTop = new THREE.Object3D();
let _geometry1 = new THREE.BoxGeometry(3.8, 0.5, 3.8);
let _geometry2 = new THREE.BoxGeometry(3, 0.5, 3);
let _geometry3 = new THREE.BoxGeometry(2.2, 0.5, 2.2);
let _geometry4 = new THREE.BoxGeometry(1.4, 0.5, 1.4);
let _cylinderGeometry = new THREE.CylinderGeometry(0.1, 0.5, 5, 3);
let _material = new THREE.MeshPhongMaterial({
color: "rgb(120, 120, 120)"
});
let cube1 = new THREE.Mesh(_geometry1, _material);
let cube2 = new THREE.Mesh(_geometry2, _material);
let cube3 = new THREE.Mesh(_geometry3, _material);
let cube4 = new THREE.Mesh(_geometry4, _material);
let cylinder = new THREE.Mesh(_cylinderGeometry, _material);
cube2.position.set(0, 0.5, 0);
cube3.position.set(0, 1, 0);
cube4.position.set(0, 1.5, 0);
cylinder.position.set(0, 2, 0);
towerTop.add(cube1);
towerTop.add(cube2);
towerTop.add(cube3);
towerTop.add(cube4);
towerTop.add(cylinder);
towerTop.position.set(0, 11, 0);
towerTop.rotation.set(0, Math.PI / 4, 0);
return towerTop;
}
// 金茂大廈身體主幹
function getJinmaoTowerBody() {
let towerBody = new THREE.Object3D();
let _geometry1 = new THREE.BoxGeometry(5, 7, 5);
let _geometry2 = new THREE.BoxGeometry(4.5, 5.5, 4.5);
let _geometry3 = new THREE.BoxGeometry(4, 4, 4);
let _geometry4 = new THREE.BoxGeometry(3.5, 3, 3.5);
let _geometry5 = new THREE.BoxGeometry(3, 2, 3);
let _geometry6 = new THREE.BoxGeometry(2.5, 1.5, 2.5);
let _geometry7 = new THREE.BoxGeometry(2, 1.3, 2);
let _geometry8 = new THREE.BoxGeometry(1.5, 1, 1.5);
let _material = new THREE.MeshPhongMaterial({
color: "rgb(120, 120, 120)"
});
let cube1 = new THREE.Mesh(_geometry1, _material);
let cube2 = new THREE.Mesh(_geometry2, _material);
let cube3 = new THREE.Mesh(_geometry3, _material);
let cube4 = new THREE.Mesh(_geometry4, _material);
let cube5 = new THREE.Mesh(_geometry5, _material);
let cube6 = new THREE.Mesh(_geometry6, _material);
let cube7 = new THREE.Mesh(_geometry7, _material);
let cube8 = new THREE.Mesh(_geometry8, _material);
cube2.position.set(0, 5.5, 0);
cube3.position.set(0, 9.5, 0);
cube4.position.set(0, 12.5, 0);
cube5.position.set(0, 14.5, 0);
cube6.position.set(0, 16, 0);
cube7.position.set(0, 17.3, 0);
cube8.position.set(0, 18.3, 0);
towerBody.add(cube1);
towerBody.add(cube2);
towerBody.add(cube3);
towerBody.add(cube4);
towerBody.add(cube5);
towerBody.add(cube6);
towerBody.add(cube7);
towerBody.add(cube8);
towerBody.position.set(0, -8, 0);
return towerBody;
}
複製代碼
搭建金茂大廈也是比較簡單的,由於不用本身手寫不規則幾何體,經過Three.js現有的幾何體就能夠完成。 大體步驟是:首先生成一個十字的骨架,而後經過底部累加多個方形幾何體 頂部也是相似。用到的方法在以前已經講解過,此處就不過多贅述,有興趣能夠去github上查閱代碼。
由於只寫了4個外灘的標誌性建築:東方明珠,中心大廈,環球金融中心和金茂大廈。另外其餘的建築物我選擇經過隨機生成建築物的形式來實現。
function getBuilding(scene) {
// 1. 定義了隨機建築物的位置座標數組
let positionsArr = [
{ x: -13, y: 0, z: -15 },
{ x: -7, y: 0, z: -13 },
{ x: -1, y: 0, z: -16 },
{ x: -2, y: 0, z: -10 },
{ x: -10, y: 0, z: -5 },
{ x: 5, y: 0, z: -25 },
{ x: -3, y: 0, z: -18 },
{ x: -8, y: 0, z: -18 },
{ x: -18, y: 0, z: -25 },
{ x: -6, y: 0, z: -25 },
{ x: -3, y: 0, z: -30 },
{ x: -10, y: 0, z: -30 },
{ x: -17, y: 0, z: -30 },
{ x: -3, y: 0, z: -35 },
{ x: -12, y: 0, z: -35 },
{ x: -20, y: 0, z: -35 },
{ x: -3, y: 0, z: -40 },
{ x: -16, y: 0, z: -40 },
{ x: 16, y: 0, z: -40 },
{ x: 18, y: 0, z: -38 },
{ x: 16, y: 0, z: -40 },
{ x: 30, y: 0, z: -40 },
{ x: 32, y: 0, z: -40 },
{ x: 16, y: 0, z: -35 },
{ x: 36, y: 0, z: -38 },
{ x: 42, y: 0, z: -32 },
{ x: 42, y: 0, z: -26 },
{ x: 35, y: 0, z: -20 },
{ x: 36, y: 0, z: -32 },
{ x: 25, y: 0, z: -22 },
{ x: 26, y: 0, z: -20 },
{ x: 19, y: 0, z: -8 },
{ x: 30, y: 0, z: -18 },
{ x: 25, y: 0, z: -15 },
{ x: 9, y: 0, z: -10 },
{ x: 1, y: 0, z: -9 },
{ x: 1, y: 0, z: -30 },
{ x: 0, y: 0, z: -35 },
{ x: 1, y: 0, z: -32 },
{ x: 8, y: 0, z: -5 },
{ x: 15, y: 0, z: -6 },
{ x: 5, y: 0, z: -40 },
{ x: 9, y: 0, z: -40 }
];
let defauleLength = 16;
// 2. 循環數組,生成每一個幾何體
for (let i = 0; i < positionsArr.length; i++) {
// 3. 經過隨機數Math.random,隨機生成每一個幾何體的長 寬 高
let w = Math.random() * 3 + 2; // 隨機數(2, 5);
let d = Math.random() * 3 + 2; // 隨機數(2, 5);
let h = Math.random() * defauleLength + 5; // 隨機數(0, 20);
let geometry = new THREE.BoxGeometry(w, h, d);
let material = new THREE.MeshPhongMaterial({ color: "rgb(120, 120, 120)" });
// 4. 生成每一個幾何體
let mesh = new THREE.Mesh(geometry, material);
// 5. 每一個幾何體的位置
mesh.position.set(
positionsArr[i].x,
positionsArr[i].y + h / 2,
positionsArr[i].z
);
// 6. 顯示陰影
mesh.castShadow = true;
// 7. 將每一個幾何體添加到場景中
scene.add(mesh);
}
}
複製代碼
這裏的隨機生成的幾何體,除了位置座標肯定外,長度 寬度 高度包括以後要作的貼圖都是隨機的
給建築物添加貼圖,使其看起來更加美觀一些。這裏東方明珠和環球金融中心沒有找到合適的貼圖,因此沒有作貼圖處理。具體代碼不復雜,有興趣能夠去GitHub上查閱代碼
以前咱們爲黃浦江留出了空位,如今就要把它「填」上。 需求是要做出水流的感受,原理不難,首先定一個平面,並對它進行貼圖,使之更像江水的顏色。 這裏用了MeshStandardMaterial
材質,該材質是標準網絡材質。該材質提供了比MeshLambertMaterial
或 MeshPhongMaterial
更精確和逼真的結果,但代價是計算成本更高。 由於這個江水的波浪須要動態的變化,因此江水的代碼須要在兩個地方書寫。 首先經過getRiver定義好江水的基本樣式,其次給江水設置一個名稱river.name = 'river'
,而後在update方法中動態的改變其頂點
getRiver方法:
function getRiver() {
let material = new THREE.MeshStandardMaterial({
color: MATERIAL_COLOR
});
//顏色貼圖
material.map = loader.load("../assets/textures/river.jpg");
//凹凸貼圖
material.bumpMap = loader.load("../assets/textures/river.jpg");
// 粗糙貼圖 使紋理反光起伏變化
material.roughnessMap = loader.load("../assets/textures/river.jpg");
// bumpScale:凹凸參數 控制貼圖平面的平整度
material.bumpScale = 0.01;
// metalness:金屬質感 範圍(0,1)
material.metalness = 0.1;
// roughness:反光強度/粗糙度 取值範圍(0,1)
material.roughness = 0.7;
// 調整透明度 使之更加靠近江水的顏色
material.transparent = true;
material.opacity = 0.85;
let geometry = new THREE.PlaneGeometry(73, 100, 60, 60);
// 平面要定義side爲THREE.DoubleSide 否則只能展現出一個面
material.side = THREE.DoubleSide;
let river = new THREE.Mesh(geometry, material);
river.receiveShadow = true;
// river.castShadow = true;
river.rotation.x = Math.PI / 2;
river.rotation.z = Math.PI / 2;
river.position.z = -14.5;
river.position.y = -2;
return river;
}
複製代碼
init方法:
...
// 黃浦江
let river = getRiver();
river.name = "river";
scene.add(river);
...
複製代碼
update方法:
function update(renderer, scene, camera, controls, stats, clock) {
renderer.render(scene, camera);
// 性能監控
stats.update();
// 相機軌道控制器
controls.update();
// 獲取時間
let elapsedTime = clock.getElapsedTime();
// 獲取到江水river
let plane = scene.getObjectByName("river");
// 拿到geometry
let planeGeo = plane.geometry;
// 使其按照Math.sin規律性的動態改變江水的頂點
planeGeo.vertices.forEach((vertex, ind) => {
vertex.z = Math.sin(elapsedTime + ind * 0.3) * 0.5;
});
// 改變頂點後設置verticesNeedUpdate爲true纔有效果
planeGeo.verticesNeedUpdate = true;
renderer.render(scene, camera);
requestAnimationFrame(function() {
update(renderer, scene, camera, controls, stats, clock);
});
}
複製代碼
在update
方法中,獲取到剛纔定義的name爲river
的幾何體(黃浦江),並經過clock.getElapsedTime
來獲取自時鐘啓動後的秒數和Math.sin
並循環調用requestAnimationFrame
方法 來動態的改變該平面幾何體的頂點,使其更像水流的波浪。
能夠看到,代碼寫到這裏,上海外灘已經初具模樣。燈光,建築物,貼圖,河流等都有了。可是 場景中的整個背景仍是灰色的。接下來,咱們就開始着手優化這個。 經過 THREE.CubeTextureLoader
來實現一個360度的背景。
首先,須要6個圖片。其實就是把一個全景照片分紅6份,將其每一個圖片路徑放到一個數組中。最終經過new THREE.CubeTextureLoader().load(數組);
來加載這個圖片數組便可。 相關貼圖和背景圖片在github中,須要能夠拿去
getReflectionCube方法:
function getReflectionCube() {
let path = "/assets/cubemap/";
let format = ".jpg";
// 定義好包含6長全景圖片的數組urls
let urls = [
`${path}px${format}`,
`${path}nx${format}`,
`${path}py${format}`,
`${path}ny${format}`,
`${path}pz${format}`,
`${path}nz${format}`
];
let reflectionCube = new THREE.CubeTextureLoader().load(urls);
reflectionCube.format = THREE.RGBFormat;
return reflectionCube;
}
複製代碼
init方法中:
...
// 全景視圖
scene.background = getReflectionCube();
...
複製代碼
至此,就大概實現了3D版的上海外灘。其實還有許多地方沒有進行優化,好比說貼圖太醜,優化總體燈光,江水反射效果等。根本緣由仍是時間不夠,最近給本身挖了很多的坑,出來混老是要還了,須要本身慢慢去填上。加上公司項目也是比較緊,沒有過多的時間去作一些優化。不過經過該項目本身仍是在three.js中獲得了很多鍛鍊和提高,算是入了個門了吧😄。可是在three.js中的一些高級方法本身仍是掌握的不是很到位,須要繼續深刻研究。還有就是手寫模型是真的累啊...考慮複雜模型仍是要經過3D建模軟件畫出理想的模型,在three.js中直接引入模型就行了,就不須要本身手寫複雜模型了。不說了,我要去練習blender了...
轉載請註明出處 謝謝🙏