three-入门学习-3D房屋全景实现


title: three-入门学习-3D房屋全景实现
date: 2023-04-06 16:14:00
categories: 入门学习
tags: Three


three-3D 房屋全景实现

1. 安装 three.js

根据之前的粗略学习,突发想做个全景图网上学习一波

3D 效果是前端绕不过的发展趋势,未来会有更多的 3D 场景的需要。

<template>
  <div>
    3D

    <div class="three-box-one">
      <div id="three" />
    </div>
  </div>
</template>
<script setup lang="ts">
import * as THREE from 'three';
import { reactive, onMounted } from 'vue';
import house from './assets/panorama-house.webp';
let scene: THREE.Scene, mesh;
interface DataType {
  id: HTMLElement | any;
  domW: Number | any;
  domH: Number | any;
  camera: THREE.PerspectiveCamera | any;
  renderer: THREE.WebGLRenderer | any;
  mesh: THREE.Mesh | any;
  material: THREE.MeshBasicMaterial;
  // controls?: OrbitControls | any;
  onMouseDownMouseX: number;
  onMouseDownMouseY: number;
  lon: number;
  lat: number;
  onMouseDownLon: number;
  onMouseDownLat: number;
  phi: number;
  theta: number;
  isUserInteracting: Boolean;
}

const dataType: DataType = {
  id: null,
  domW: 0,
  domH: 0,
  camera: THREE.PerspectiveCamera,
  renderer: THREE.WebGLRenderer,
  mesh: THREE.Mesh,
  material: new THREE.MeshBasicMaterial(),
  // controls: OrbitControls,
  onMouseDownMouseX: 0,
  onMouseDownMouseY: 0,
  lon: 0,
  lat: 0,
  onMouseDownLon: 0,
  onMouseDownLat: 0,
  phi: 0,
  theta: 0,
  isUserInteracting: false,
};
const data = reactive(dataType);
onMounted(() => {
  data.id = document.getElementById('three');
  data.domW = window.innerWidth;
  data.domH = window.innerHeight;

  init();
});
const init = () => {
  // 构建器
  scene = new THREE.Scene();
  // 创建近大远小(透视投影)相机
  data.camera = new THREE.PerspectiveCamera(
    75,
    data.domW / data.domH,
    0.01,
    1100
  );
  // 返回一个能够表示当前摄像机所正视(拍摄)的世界空间方向的Vector3对象
  data.camera.target = new THREE.Vector3(0, 0, 0);
  // data.camera.position.z = 5;
  // 创建渲染函数
  data.renderer = new THREE.WebGLRenderer({
    antialias: true, // 模型抗锯齿
    alpha: true, // 开启背景透明
  });
  data.renderer.setClearColor(new THREE.Color(0x000000));
  data.renderer.setPixelRatio(window.devicePixelRatio);
  // 设置渲染场景大小
  data.renderer.setSize(data.domW, data.domH);
  // 将场景添加到div标签
  data.id.appendChild(data.renderer.domElement);
  // 添加灯光
  addLight();
  // 添加场景辅助线
  axisHelper();
  // 添加球体设置材质
  initSphereGeometry();
  // 添加事件监听器,配合鼠标做不同的位置变换
  addEventListenFn();
  // 刷帧渲染动画
  animate();
  // 响应屏幕改变大小函数
  onWindowResize();
};

const addLight = () => {
  // 设置环境光 环境光会均匀的照亮场景中的所有物体。
  const ambientLight = new THREE.AmbientLight('#ffffff');
  scene.add(ambientLight);
  // 设置平行光
  const light = new THREE.DirectionalLight('#ffffff');
  scene.add(light);
  // 设置点光源
  const pointLight = new THREE.PointLight('#ffffff', 0.1, 1000);
  pointLight.position.set(300, 300, 300);
  scene.add(pointLight);
};

const axisHelper = () => {
  const axes: THREE.AxesHelper = new THREE.AxesHelper(800);
  scene.add(axes);
};
const initSphereGeometry = () => {
  // 创建半径500的球体 (球缓冲几何体)
  const geometry = new THREE.SphereGeometry(500, 32, 16);
  geometry.scale(-1, 1, 1);
  // 创建材质获取材质图片鱼眼图
  data.material = new THREE.MeshBasicMaterial({
    // 加载墙壁图纸 自己网上找一个全景图片就行了
    map: new THREE.TextureLoader().load(house),
  });
  mesh = new THREE.Mesh(geometry, data.material);
  // 将球体添加到场景
  scene.add(mesh);
};
const addEventListenFn = () => {
  const _this: any = data;
  // 鼠标按下获取鼠标xy坐标转换成球体经纬度
  document.addEventListener('mousedown', onPointerStart, false);
  // 鼠标移动计算变化后的球体经纬度
  document.addEventListener('mousemove', onPointerMove, false);
  // 鼠标抬起停止跟随
  document.addEventListener('mouseup', onPointerUp, false);
  // 鼠标滚轮放大缩小摄像机目标距离
  document.addEventListener('wheel', onDocumentMouseWheel, false);
  // 移动端手指移上获取鼠标xy坐标转换成球体经纬度
  document.addEventListener('touchstart', onPointerStart, false);
  // 移动端手指移动计算变化后的球体经纬度
  document.addEventListener('touchmove', onPointerMove, false);
  // 移动端手指抬起停止跟随
  document.addEventListener('touchend', onPointerUp, false);
  // 拖拽
  document.addEventListener(
    'dragover',
    (e: any) => {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'copy';
    },
    false
  );
  // 拓拽停止设置body 半透明
  document.addEventListener(
    'dragenter',
    () => {
      document.body.style.opacity = '0.5';
    },
    false
  );
  // 拖拽离开回归透明度
  document.addEventListener(
    'dragleave',
    () => {
      document.body.style.opacity = '1';
    },
    false
  );
  document.addEventListener(
    'drop',
    (e: any) => {
      e.preventDefault();
      // 读取图片为二进制码
      const reader = new FileReader();
      reader.addEventListener(
        'load',
        (es: any) => {
          // 更新材质
          _this.material.map.image.src = es.target.result;
          _this.material.needsUpdate = true;
        },
        false
      );
      reader.readAsDataURL(e.dataTransfer.files[0]);
      document.body.style.opacity = '1';
    },
    false
  );
};

const onPointerStart = (e) => {
  data.isUserInteracting = true;
  // 获取鼠标x y 坐标
  const clientX = e.clientX || e.touches[0].clientX;
  const clientY = e.clientY || e.touches[0].clientY;
  data.onMouseDownMouseX = clientX;
  data.onMouseDownMouseY = clientY;
  // 设置经纬度
  data.onMouseDownLon = data.lon;
  data.onMouseDownLat = data.lat;
};
const onPointerMove = (e) => {
  if (data.isUserInteracting) {
    const clientX = e.clientX || e.touches[0].clientX;
    const clientY = e.clientY || e.touches[0].clientY;
    data.lon = (data.onMouseDownMouseX - clientX) * 0.1 + data.onMouseDownLon;
    data.lat = (clientY - data.onMouseDownMouseY) * 0.1 + data.onMouseDownLat;
  }
};
const onPointerUp = () => {
  data.isUserInteracting = false;
};
const onDocumentMouseWheel = (e) => {
  const fov = data.camera.fov + e.deltaY * 0.05;
  data.camera.fov = THREE.MathUtils.clamp(fov, 10, 75);
  data.camera.updateProjectionMatrix();
};
const animate = () => {
  window.requestAnimationFrame(animate);
  // 更新相机 旋转场景空间
  updateFn();
};
const updateFn = () => {
  if (!data.isUserInteracting) {
    // 经度每帧更新0.1 场景自动旋转起来
    data.lon += 0.1;
  }
  data.lat = Math.max(-85, Math.min(85, data.lat));
  data.phi = THREE.MathUtils.degToRad(90 - data.lat);
  data.theta = THREE.MathUtils.degToRad(data.lon);
  // 更新相机 目标 x y z 位置
  data.camera.target.x = 500 * Math.sin(data.phi) * Math.cos(data.theta);
  data.camera.target.y = 500 * Math.cos(data.phi);
  data.camera.target.z = 500 * Math.sin(data.phi) * Math.sin(data.theta);
  // 相机拍摄目标(始终拍摄这里)
  data.camera.lookAt(data.camera.target);
  data.renderer.render(scene, data.camera);
};
const onWindowResize = () => {
  window.onresize = () => {
    data.domH = data.id.offsetHeight;
    data.domW = data.id.offsetWidth;
    data.camera.aspect = data.domW / data.domH;
    data.camera.updateProjectionMatrix();
    data.renderer.setSize(data.domW, data.domH);
  };
};
</script>
<style scoped></style>


©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容