預覽地址: Three.js之上海外灘html
Tip1: 打開後瀏覽器一直轉圈 建築物的貼圖不顯示 是網絡問題 等一下子就好 畢竟是github... Tip2: 打開後幀數太低 比較卡的話,能夠調整代碼中的SHADOW_MAPSIZE大小 經過調整鋸齒感來優化性能
本篇雖是關於Three.js入門的文章, 可是太過入門的就不講了,沒意義,網上不少相關入門知識。本篇主要是把本身在寫demo時候遇到的坑點給記錄下來, 有什麼不懂的直接去查閱文檔或者網上搜,這裏提一下:Three.js的官方文檔和例子對開發者也是挺友好的(有中文版!)git
廢話很少, 先看下效果吧:github
代碼比較多,就不一一講解了,本篇主要分爲如下幾個部分:web
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
上查看算法
該部分沒有作什麼比較核心的地方,只是一些場景 相機 渲染器 畫輔助線 性能監控 等的初始化,但花費時間稍長的是畫出兩個不規則地面,留出一道「黃浦江」的位置。api
咱們知道,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實現仍是略微麻煩一些的,最終的實現效果也並非特別理想,只是略像而已。app
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了...
轉載請註明出處 謝謝🙏