当前位置:网站首页>第三课threejs全景预览房间案例
第三课threejs全景预览房间案例
2022-07-29 05:09:00 【小豆包3D世界】
背景: 如何在网页中预览房间每个角度? 如全景看房
功能实现思路
- 创建threejs场景
- 创建球体
- 创建鱼眼全景图片
- 翻转球体将鱼眼图贴在球体内部
- 相机设置在球体中心,循环更好相机拍摄目标位置
第一步
创建盛放场景盒子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>
扫码访问小豆包

扫码关注小豆包公众号

边栏推荐
- C语言 一维数组
- Redirection and files
- 阿里云架构师细说游戏行业九大趋势
- 【C语言系列】— 把同学弄糊涂的 “常量” 与 “变量”
- 实现简单的数据库查询(不完整)
- C语言数组典型应用代码详细讲解—高手误入(逐步代码详解)
- 321, Jingdong Yanxi × Nlpcc 2022 challenge starts!
- ANSI C类型限定符
- Allocate memory: malloc() and free()
- Live broadcast preview | how to improve enterprise immunity through "intelligent edge security"?
猜你喜欢
随机推荐
省市区三级联动(简单又完美)
vim编辑器使用
Best practices for elastic computing in the game industry
Differences between texture2d and texture2dproj under webgl1.0
C语言 一维数组
C language first level pointer
【C语言系列】—深度解剖数据在内存中的存储(二)-浮点型
The road to success in R & D efficiency of 1000 person Internet companies
Day 1
Alibaba cloud and Dingjie software released the cloud digital factory solution to realize the localized deployment of cloud MES system
QML type: state state
递归的基本原理
VIM editor use
One dimensional array exercise
PyQt5:第一章第1节:使用Qt组件创建一个用户界面-介绍
Day 2
浅谈Servlet
ClickHouse学习(八)物化视图
无重复字符的最长字串
Come on! See how Clickhouse, which has risen 16 places a year, can be implemented in jd.com









