ThreeJs, подарок к Национальному дню, реализует трехмерный панорамный вид на Шанхайскую набережную.

three.js

ThreeJs, подарок к Национальному дню, реализует трехмерный панорамный вид на Шанхайскую набережную.

  • Я недавно вошел в яму three.js и хотел использовать three.js, чтобы сделать несколько демонстраций, чтобы закрепить точки знаний, которые я недавно освоил, и как раз к празднику Национального дня, так что у меня есть это ~

  • Адрес предварительного просмотра:Three.js Шанхай Бунд

    Tip1: 打开后浏览器一直转圈 建筑物的贴图不显示 是网络问题 等一会儿就好 毕竟是github... Tip2: 如果打开后帧数过低 比较卡的话,可以调整代码中的SHADOW_MAPSIZE大小 通过调整锯齿感来优化性能

  • Кодовый адрес:Three.js Шанхайская набережная

  • Если вы думаете, что это хорошо, пожалуйста, нажмите звездочку, спасибо 👏


Хоть эта статья и посвящена началу работы с Three.js, но я не буду о ней рассказывать, если она слишком ознакомительная. Это бессмысленно. В интернете много сопутствующих вводных знаний. В этой статье в основном описаны подводные камни, с которыми я столкнулся при написании демо. Если вы ничего не понимаете, перейдите к документации или поищите в Интернете. Вот упоминание: официальная документация и примеры Three.js также очень дружелюбны к разработчикам. (есть китайская версия!)


Не много ерунды, давайте посмотрим на эффект:

Кодов очень много, поэтому я не буду расписывать их по одному, статья в основном разделена на следующие части:

  1. Инициализировать код, построить неправильную геометрию земли и т. д."Знание: использоватьTHREE.Shapeа такжеTHREE.ExtrudeGeometryСоздать неправильную геометрию"
  2. Постройте жемчужину Востока«Ключевой момент: использование различных геометрических комбинацийTHREE.Object3D()Создать неправильную геометрию"
  3. Строительство Шанхайской башни"Ключевой момент: использование функции синусаMath.sinРегулярно меняйте вершины геометрииverticesСоздать неправильную геометрию"
  4. Создание глобального финансового центра"Знание: использование рукописного массива вершинverticesи массив треугольных гранейfacesспособ создания неправильной геометрии"
  5. Здание Цзиньмао«Ключевой момент: использование различных геометрических комбинацийTHREE.Object3D()Создать неправильную геометрию"
  6. Случайный алгоритм для постройки других зданий"Очки знаний:Math.randomСлучайно сгенерированная геометрия"
  7. Оптимизация текстуры для всех зданий"Очки знаний:THREE.TextureLoaderиспользовать
  8. Строительство реки Хуанпу"Очки знаний:requestAnimationFrameИспользование анимационных методов»
  9. Создайте панорамное пространство 360°"Очки знаний:THREE.CubeTextureLoaderСоздайте панорамное пространство на 360 градусов».

Инициализируйте код, создавайте сцены, камеры, рендереры, источники света, стройте неправильную геометрию земли и т. д.

  • main.js:
/** 材质颜色常量 */
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Смотреть на

Этот раздел не является более центральным местом для того, чтобы инициализировать некоторые сцены Renderer Camera рисуют вспомогательный мониторинг производительности линии, но требуется немного больше времени, чтобы нарисовать две нерегулярные земли, оставив позицию «реки Huangpu».

мы знаем,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Вытяните его в 3D-форму. Это геометрия грунта для первой половины и такая же для второй половины. Для получения информации о других конкретных параметрах см. документацию: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 по умолчанию не изменит свои вершины.

Создание глобального финансового центра

Как видите, WFC также имеет неправильную геометрию сетки. Его можно разделить на две части: нижнюю неравномерную сетку и верхнюю неравномерную сетку. Чтобы реализовать здесь неправильную геометрию сетки, нам нужно передать координаты вершин от руки.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.хорошо, а затем определить массив треугольных граней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В методе только что определенное имя получается какriverгеометрия (река Хуанпу) и перевалclock.getElapsedTimeчтобы получить секунды с момента запуска часов иMath.sinи вызов в циклеrequestAnimationFrameметод динамического изменения вершин геометрии плоскости, чтобы сделать ее более похожей на волны воды.

Создайте панорамное пространство 360°

Видно, что здесь написан код, и Шанхайская набережная начала обретать форму. Огни, здания, текстуры, реки и т. д. — все это есть. Но весь фон в сцене по-прежнему серый. Далее приступаем к оптимизации. пройти через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, и вам не нужно писать сложное от руки. смоделируйте себя. Не говоря уже о том, что я собираюсь попрактиковаться в блендере...

Пожалуйста, указывайте источник для перепечатки, спасибо🙏