threejs全景图片展示

这篇博客介绍了如何基于Three.js库修改全景照片示例,特别是针对一张完整的全景图片而非多张拼接的情况。作者使用了equirectangular方法,并提供了代码示例进行初始化、更新和刷新全景图片的函数。此外,还详细展示了如何处理用户交互,包括鼠标滚轮缩放和拖动旋转视角。最后,给出了使用示例,包括容器设置、初始化、图片刷新和清除操作。
摘要由CSDN通过智能技术生成

关于代码

基于threejs的全景照片示例改了一下,并做简单封装。threejs的全景示例有cube和equirectangular两种,这里因为全景图片是一整张而不是多张图片拼合,故使用equirectangular。

关于全景图片

全景照片的来源是mapillary,访问需要梯子。
通过mapillary的#api爬取了照片文件和信息,相关教程有空发。
重点是这两个字段,一个是点位坐标一个是初始角度,可以用于照片的拍摄点定位和全景展示初始镜头朝向
在这里插入图片描述
图片本身是jpg,注意不是多张图片拼合的全景,而是一整张照片。
全景图片示例

show me the code

使用TS编写,js自行更改

import * as THREE from "three";

const Panorama = {
  scene: new THREE.Scene(),
  /**
   * 刷新图片
   * @param imgPath
   */
  updateMesh: function (imgPath: string) {
    // 这里每次都会重置参数,不想重置就把下面注释掉
    this.resetParams();
    const mesh = this.scene.getObjectByName("pano") as THREE.Mesh;
    // 刷新
    const material = mesh.material as THREE.MeshBasicMaterial;
    material.map = null;
    const texture = new THREE.TextureLoader().load(imgPath);
    material.map = texture;
  },
  /**
   * 初始化
   * @param container dom元素
   * @param imgPath 图像路径
   * @param compassAngle 照片朝向,0-360角度 正北方为0,顺时针为正
   */
  init: function (
    container: HTMLElement,
    imgPath: string,
    compassAngle: number
  ) {
    const _t = this;
    const scene = this.scene;
    // 透视投影相机
    // fov, aspect, near, far
    const camera = new THREE.PerspectiveCamera(
      95,
      container.offsetWidth / container.offsetHeight,
      1,
      1100
    );

    const texture = new THREE.TextureLoader().load(imgPath);
    // 基本材质 不响应光源
    const material = new THREE.MeshBasicMaterial({ map: texture });
    // 球体
    // radius半径, segmentsWidth经度上的切片数, segmentsHeight纬度上的切片数
    const geometry = new THREE.SphereGeometry(500, 60, 40);
    // invert the geometry on the x-axis so that all of the faces point inward
    geometry.scale(-1, 1, 1);

    // 网格
    const mesh = new THREE.Mesh(geometry, material);
    mesh.name = "pano";
    scene.add(mesh);

    const renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio(window.devicePixelRatio);
    // 设定渲染器宽高
    renderer.setSize(container.offsetWidth, container.offsetHeight);
    container.appendChild(renderer.domElement);

    container.style.touchAction = "none";

    container.addEventListener("pointerdown", onPointerDown);

    container.addEventListener("wheel", onDocumentMouseWheel);

    container.addEventListener("resize", onWindowResize);

    function onWindowResize() {
      camera.aspect = container.offsetWidth / container.offsetHeight;
      camera.updateProjectionMatrix();

      renderer.setSize(container.offsetWidth, container.offsetHeight);
    }

    function onPointerDown(event: PointerEvent) {
      if (event.isPrimary === false) return;

      _t.isUserInteracting = true;

      _t.onPointerDownMouseX = event.clientX;
      _t.onPointerDownMouseY = event.clientY;

      _t.onPointerDownLon = _t.lon;
      _t.onPointerDownLat = _t.lat;

      container.addEventListener("pointermove", onPointerMove);
      container.addEventListener("pointerup", onPointerUp);
    }

    function onPointerMove(event: PointerEvent) {
      if (event.isPrimary === false) return;
      _t.lon =
        (_t.onPointerDownMouseX - event.clientX) * 0.1 + _t.onPointerDownLon;
      _t.lat =
        (_t.onPointerDownMouseY - event.clientY) * 0.1 + _t.onPointerDownLat;
    }

    function onPointerUp(event: PointerEvent) {
      if (event.isPrimary === false) return;

      _t.isUserInteracting = false;

      container.removeEventListener("pointermove", onPointerMove);
      container.removeEventListener("pointerup", onPointerUp);
    }

    function onDocumentMouseWheel(event: WheelEvent) {
      const fov = camera.fov + event.deltaY * 0.05;

      camera.fov = THREE.MathUtils.clamp(fov, 10, 75);

      camera.updateProjectionMatrix();
    }

    function animate() {
      requestAnimationFrame(animate);
      update();
    }

    function update() {
      _t.lat = Math.max(-85, Math.min(85, _t.lat));
      _t.phi = THREE.MathUtils.degToRad(compassAngle - _t.lat);
      _t.theta = THREE.MathUtils.degToRad(_t.lon);

      const x = 500 * Math.sin(_t.phi) * Math.cos(_t.theta);
      const y = 500 * Math.cos(_t.phi);
      const z = 500 * Math.sin(_t.phi) * Math.sin(_t.theta);

      camera.lookAt(x, y, z);

      renderer.render(scene, camera);
    }

    animate();
  },
  resetParams: function () {
    this.isUserInteracting = false;
    this.onPointerDownMouseX = 0;
    this.onPointerDownMouseY = 0;
    this.lon = 0;
    this.onPointerDownLon = 0;
    this.lat = 0;
    this.onPointerDownLat = 0;
    this.phi = 0;
    this.theta = 0;
  },
  isUserInteracting: false,
  onPointerDownMouseX: 0,
  onPointerDownMouseY: 0,
  lon: 0,
  onPointerDownLon: 0,
  lat: 0,
  onPointerDownLat: 0,
  phi: 0,
  theta: 0,
};

export { Panorama };

使用示例

找个合适的地方放一个容器

<div id="pano-container"></div>

引入刚才的全景工具(ThreeExt.ts)

import { Panorama as PanoFn } from "./ThreeExt.ts";

为了节约资源,只有第一次加载的时候会初始化容器,后面更换照片会调用updateMesh方法。

初始化

const container = document.getElementById(
  "pano-container"
) as HTMLDivElement;
PanoFn.init(container, 你的照片路径, 你的照片初始角度);

刷新照片

PanoFn.updateMesh(新的照片路径);

清除

先清空场景,再清空容器元素

PanoFn.scene.clear();
const container = document.getElementById(
  "pano-container"
) as HTMLDivElement;
container.innerHTML = "";
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值