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>


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容