预览地址: 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了...
转载请注明出处 谢谢🙏