第三课threejs全景预览房间案例

背景: 如何在网页中预览房间每个角度? 如全景看房

功能实现思路

  1. 创建threejs场景
  2. 创建球体
  3. 创建鱼眼全景图片
  4. 翻转球体将鱼眼图贴在球体内部
  5. 相机设置在球体中心,循环更好相机拍摄目标位置

第一步

创建盛放场景盒子div

<template>
  <div class="three-box-one">
    <div id="three" />
  </div>
</template>

第二步

引入vue, threejs


import Vue from 'vue';
import * as THREE from 'three';

第三步

创建基础Data数据

data (): DataType {
    return {
      id: null,
      domW: 0,
      domH: 0,
      scene: THREE.Scene,
      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
    }
  },

第四步

初始运行创建场景,获取场景宽度高度

mounted () {
    this.id = document.getElementById('three')
    this.domW = this.id.offsetWidth
    this.domH = this.id.offsetHeight
    this.init()
  },

第五步

init 函数讲解


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


第五步

添加灯光

addLight () {
      // 设置环境光
      const ambientLight = new THREE.AmbientLight('#ffffff')
      this.scene.add(ambientLight)
      // 设置平行光
      const light = new THREE.DirectionalLight('#ffffff')
      this.scene.add(light)
      // 设置点光源
      const pointLight = new THREE.PointLight('#ffffff', 0.1, 1000)
      pointLight.position.set(300, 300, 300)
      this.scene.add(pointLight)
    },

第六步

添加空间辅助线

axisHelper () {
      const axes:THREE.AxesHelper = new THREE.AxesHelper(800)
      this.scene.add(axes)
    },

第七步

创建圆球盛放鱼眼材质图

知识点:

`SphereBufferGeometry(radius : Float, widthSegments : Integer, heightSegments : Integer, phiStart : Float, phiLength : Float, thetaStart : Float, thetaLength : Float)`

* radius — 球体半径,默认为1。
* widthSegments — 水平分段数(沿着经线分段),最小值为3,默认值为8。
* heightSegments — 垂直分段数(沿着纬线分段),最小值为2,默认值为6。
* phiStart — 指定水平(经线)起始角度,默认值为0。。
* phiLength — 指定水平(经线)扫描角度的大小,默认值为 Math.PI * 2。
* thetaStart — 指定垂直(纬线)起始角度,默认值为0。
* thetaLength — 指定垂直(纬线)扫描角度大小,默认值为 Math.PI。
* 该几何体是通过扫描并计算围绕着Y轴(水平扫描)和X轴(垂直扫描)的顶点来创建的。 因此,不完整的球体(类似球形切片)可以通过为phiStart,phiLength,thetaStart和thetaLength设置不同的值来创建, 以定义我们开始(或结束)计算这些顶点的起点(或终点)。
*
*

initSphereGeometry () {
      // 创建半径500的球体 (球缓冲几何体)
      const geometry = new THREE.SphereBufferGeometry(500, 60, 40)
      geometry.scale(-1, 1, 1)
      // 创建材质获取材质图片鱼眼图
      this.material = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('https://www.douchuanwei.com/api/files/img/a.jpg')
      })
      this.mesh = new THREE.Mesh(geometry, this.material)
      // 将球体添加到场景
      this.scene.add(this.mesh)
    },

第八步

添加document 事件监听

addEventListenFn () {
      const _this:any = this
      // 鼠标按下获取鼠标xy坐标转换成球体经纬度
      document.addEventListener('mousedown', this.onPointerStart, false)
      // 鼠标移动计算变化后的球体经纬度
      document.addEventListener('mousemove', this.onPointerMove, false)
      // 鼠标抬起停止跟随
      document.addEventListener('mouseup', this.onPointerUp, false)
      // 鼠标滚轮放大缩小摄像机目标距离
      document.addEventListener('wheel', this.onDocumentMouseWheel, false)
      // 移动端手指移上获取鼠标xy坐标转换成球体经纬度
      document.addEventListener('touchstart', this.onPointerStart, false)
      // 移动端手指移动计算变化后的球体经纬度
      document.addEventListener('touchmove', this.onPointerMove, false)
      // 移动端手指抬起停止跟随
      document.addEventListener('touchend', this.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)
    },

第九步

获取鼠标 x y 坐标,设置经纬度

onPointerStart (e) {
      this.isUserInteracting = true
      // 获取鼠标x y 坐标
      const clientX = e.clientX || e.touches[0].clientX
      const clientY = e.clientY || e.touches[0].clientY
      this.onMouseDownMouseX = clientX
      this.onMouseDownMouseY = clientY
      // 设置经纬度
      this.onMouseDownLon = this.lon
      this.onMouseDownLat = this.lat
    },

第十步

鼠标移动转换移动坐标到经纬度

onPointerMove (e) {
      if (this.isUserInteracting) {
        const clientX = e.clientX || e.touches[0].clientX
        const clientY = e.clientY || e.touches[0].clientY
        this.lon = (this.onMouseDownMouseX - clientX) * 0.1 + this.onMouseDownLon
        this.lat = (clientY - this.onMouseDownMouseY) * 0.1 + this.onMouseDownLat
      }
    },

第十一步

鼠标离开停止经纬度转换

onPointerUp () {
      this.isUserInteracting = false
    },

第十二步

鼠标滚轮放大缩小场景,设置相机拍摄目标位置

onDocumentMouseWheel (e) {
    const fov = this.camera.fov + e.deltaY * 0.05
    this.camera.fov = THREE.MathUtils.clamp(fov, 10, 75)
    this.camera.updateProjectionMatrix()
},

第十二步

刷帧函数讲解

animate () {
    window.requestAnimationFrame(this.animate)
    // 更新相机 旋转场景空间
    this.updateFn()
}

第十三步

更新场景函数

 updateFn () {
      if (!this.isUserInteracting) {
          // 经度每帧更新0.1 场景自动旋转起来
        this.lon += 0.1
      }
      this.lat = Math.max(-85, Math.min(85, this.lat))
      this.phi = THREE.MathUtils.degToRad(90 - this.lat)
      this.theta = THREE.MathUtils.degToRad(this.lon)
      // 更新相机 目标 x y z 位置
      this.camera.target.x = 500 * Math.sin(this.phi) * Math.cos(this.theta)
      this.camera.target.y = 500 * Math.cos(this.phi)
      this.camera.target.z = 500 * Math.sin(this.phi) * Math.sin(this.theta)
      // 相机拍摄目标(始终拍摄这里)
      this.camera.lookAt(this.camera.target)
      this.renderer.render(this.scene, this.camera)
    },

第十四步

防止浏览器窗口变化随时响应场景大小


onWindowResize () {
      window.onresize = () => {
        this.domH = this.id.offsetHeight
        this.domW = this.id.offsetWidth
        this.camera.aspect = this.domW / this.domH
        this.camera.updateProjectionMatrix()
        this.renderer.setSize(this.domW, this.domH)
      }
    },

完整示例:

<template>
  <div class="three-box-one">
    <div id="three" />
  </div>
</template>

<script lang="ts">
import Vue from 'vue';
import * as THREE from 'three';
// import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
interface DataType {
    id: HTMLElement | any;
    domW: Number | any;
    domH: Number | any;
    scene: THREE.Scene | 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;
}
export default Vue.extend({
  data (): DataType {
    return {
      id: null,
      domW: 0,
      domH: 0,
      scene: THREE.Scene,
      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
    }
  },
  mounted () {
    this.id = document.getElementById('three')
    this.domW = this.id.offsetWidth
    this.domH = this.id.offsetHeight
    this.init()
  },
  methods: {
    init () {
      this.scene = new THREE.Scene()
      this.camera = new THREE.PerspectiveCamera(75, this.domW / this.domH, 0.01, 1100)
      this.camera.target = new THREE.Vector3(0, 0, 0)
      this.renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
      })
      this.renderer.setSize(this.domW, this.domH)
      this.id.appendChild(this.renderer.domElement)
      this.addLight()
      this.axisHelper()
      this.initSphereGeometry()
      this.addEventListenFn()
      this.animate()
      this.onWindowResize()
    },
    controlsFn () {
      // this.controls = new OrbitControls(this.camera, this.renderer.domElement)
    },
    initSphereGeometry () {
      const geometry = new THREE.SphereBufferGeometry(500, 60, 40)
      geometry.scale(-1, 1, 1)
      this.material = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('https://www.douchuanwei.com/api/files/img/a.jpg')
      })
      this.mesh = new THREE.Mesh(geometry, this.material)
      this.scene.add(this.mesh)
    },
    addEventListenFn () {
      const _this:any = this
      document.addEventListener('mousedown', this.onPointerStart, false)
      document.addEventListener('mousemove', this.onPointerMove, false)
      document.addEventListener('mouseup', this.onPointerUp, false)
      document.addEventListener('wheel', this.onDocumentMouseWheel, false)
      document.addEventListener('touchstart', this.onPointerStart, false)
      document.addEventListener('touchmove', this.onPointerMove, false)
      document.addEventListener('touchend', this.onPointerUp, false)
      document.addEventListener('dragover', (e:any) => {
        e.preventDefault()
        e.dataTransfer.dropEffect = 'copy'
      }, false)
      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)
    },
    onPointerStart (e) {
      this.isUserInteracting = true
      const clientX = e.clientX || e.touches[0].clientX
      const clientY = e.clientY || e.touches[0].clientY
      this.onMouseDownMouseX = clientX
      this.onMouseDownMouseY = clientY
      this.onMouseDownLon = this.lon
      this.onMouseDownLat = this.lat
    },
    onPointerMove (e) {
      if (this.isUserInteracting) {
        const clientX = e.clientX || e.touches[0].clientX
        const clientY = e.clientY || e.touches[0].clientY
        this.lon = (this.onMouseDownMouseX - clientX) * 0.1 + this.onMouseDownLon
        this.lat = (clientY - this.onMouseDownMouseY) * 0.1 + this.onMouseDownLat
      }
    },
    onPointerUp () {
      this.isUserInteracting = false
    },
    onDocumentMouseWheel (e) {
      const fov = this.camera.fov + e.deltaY * 0.05
      this.camera.fov = THREE.MathUtils.clamp(fov, 10, 75)
      this.camera.updateProjectionMatrix()
    },
    addLight () {
      const ambientLight = new THREE.AmbientLight('#ffffff')
      this.scene.add(ambientLight)
      const light = new THREE.DirectionalLight('#ffffff')
      this.scene.add(light)
      const pointLight = new THREE.PointLight('#ffffff', 0.1, 1000)
      pointLight.position.set(300, 300, 300)
      this.scene.add(pointLight)
    },
    axisHelper () {
      const axes:THREE.AxesHelper = new THREE.AxesHelper(800)
      this.scene.add(axes)
    },
    onWindowResize () {
      window.onresize = () => {
        this.domH = this.id.offsetHeight
        this.domW = this.id.offsetWidth
        this.camera.aspect = this.domW / this.domH
        this.camera.updateProjectionMatrix()
        this.renderer.setSize(this.domW, this.domH)
      }
    },
    updateFn () {
      if (!this.isUserInteracting) {
        this.lon += 0.1
      }
      this.lat = Math.max(-85, Math.min(85, this.lat))
      this.phi = THREE.MathUtils.degToRad(90 - this.lat)
      this.theta = THREE.MathUtils.degToRad(this.lon)
      this.camera.target.x = 500 * Math.sin(this.phi) * Math.cos(this.theta)
      this.camera.target.y = 500 * Math.cos(this.phi)
      this.camera.target.z = 500 * Math.sin(this.phi) * Math.sin(this.theta)
      this.camera.lookAt(this.camera.target)
      this.renderer.render(this.scene, this.camera)
    },
    animate () {
      window.requestAnimationFrame(this.animate)
      this.updateFn()
      // this.mesh.rotation.y += 0.02
      // this.mesh.rotation.x += 0.01
      // this.renderer.render(this.scene, this.camera)
    }
  },
});
</script>



更多内容请到小豆包》

扫码访问小豆包

23_34de43370a343405db1b00359bb895cd.png

扫码关注小豆包公众号

小豆包公众号.jpg

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值