最近入了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了...
转载请注明出处 谢谢🙏