前言
本篇文章将手把手带大家利用threejs和请求数据渲染全景房
初始化项目
创建vite-vue项目
1 | npm init vite@latest vr-room -- --template vue |
安装依赖
1 | npm i three gsap |
设置全局样式
1 | html,body { |
测试Three是否可用
将App.vue组件改装
1 | <script setup> |
添加全景图
图片放在public/assets文件下
1 | // 加载全景图 |

请求测试数据
1 | // 获取请求数据 |

生成数据
生成房间顶部,底部数据
首先我们根据测试点位数据中的objData的roomList进行生成
我们先写一个创建多边形的类,代码如下:
1 | import * as THREE from "three"; |
核心逻辑就是使用数据中给的点位生成多边形,因为给的数据y轴是朝上的,因此需要旋转几何体顶点90度,然后,我们需要两面,一个底面,一个顶面,而顶面不需要两边看到,就有了上面这段代码,通过这个类,我们可以生成对应的面
1 | // 导入生成房间顶,底面的类 |

生成房间顶部底部映射数据


写一个映射的类,代码如下:
1 | import * as THREE from "three"; |
代码解释1:
1 | let point = panarama.point[0]; |
这一段代码是用返回字段中的图片地址信息,当然这些贴图我都已经提前放在了本地,使用这张贴图的意思.
- flipY:翻转图像的Y轴以匹配WebGL纹理坐标空间需要关闭,不然贴图就会朝着我们这一面,
- minFilter:该属性定义当一个纹理单元(texel)不足以覆盖单个像素点时纹理如何采样,设置为NearestFilter为了性能
- magFilter:该属性定义当一个纹理单元(texel)覆盖多个像素点时纹理如何采样。设置为NearestFilter为了性能
- wrapS,wrapT:都设置为平铺重复。表示边缘被夹到纹理单元(texels)的外边界。THREE.ClampToEdgeWrapping:夹边。超过1.0的值被固定为1.0。超过1.0的其它地方的纹理,沿用最后像素的纹理。用于当叠加过滤时,需要从0.0到1.0精确覆盖且没有模糊边界的纹理。THREE.RepeatWrapping:平铺重复。超过1.0的值都被置为0.0。纹理被重复一次。在渲染具有诸如砖墙之类纹理的物体时,如果使用包含一整张砖墙的纹理贴图会占用较多的内存,通常只需载入一张具有一块或多块砖瓦的较小的纹理贴图,再把它按照重叠纹理寻址模式在物体表面映射多次,就可以达到和使用整张砖墙贴图同样的效果。
THREE.MirroredRepeatWrapping:镜像重复。每到边界处纹理翻转,意思就是每个1.0 u或者v处纹理被镜像翻转。
代码解释2:
1 | let center = new THREE.Vector3(point.x / 100, point.z / 100, point.y / 100); |
之前说过,因为yz轴给的数据和three中的是相反的,所以换成three的坐标系需要进行相应的切换
代码解释3:
1 | return new THREE.ShaderMaterial({ |
首先是THREE.ShaderMaterial大家都知道是用来返回自定义的webgl的材质,定义全局参数,将纹理贴图和中心点坐标传下去,能够在片元着色器和顶点着色器中使用。
1 | uniforms: { |
片元着色器
1 | vertexShader: ` |
这是一段很常规的顶点着色器代码,将webgl中的顶点坐标转换成屏幕坐标,然后将uv坐标传递给片元着色器
1 | fragmentShader: ` |
代码中的变量和函数解释如下:
varying vec2 vUv;:插值后的纹理坐标,在顶点着色器中传递给片元着色器。
uniform sampler2D uPanorama;:纹理采样器,用于从纹理单元中获取纹理颜色。
uniform vec3 uCenter;:用于指定球心位置的坐标。
varying vec3 vPosition;:插值后的片元位置坐标,在顶点着色器中传递给片元着色器。
const float PI = 3.14159265359;:表示圆周率 π 的常量。
接下来是 main() 函数,它定义了每个片元的颜色计算逻辑:
vec3 nPos = normalize(vPosition - uCenter);:计算从球心到当前片元位置的单位向量。
float theta = acos(nPos.y)/PI;:根据单位向量的 y 分量计算球面上的纬度角度(theta)。
float phi = (atan(nPos.z, nPos.x)+PI)/(2.0PI);:根据单位向量的 z 和 x 分量计算球面上的经度角度(phi)。
phi += 0.75;:对经度角度进行偏移,以调整纹理的起始位置。
vec4 pColor = texture2D(uPanorama, vec2(phi, theta));:从纹理单元中采样纹素颜色,并将结果赋值给 pColor。
gl_FragColor = pColor;:设置当前片元的颜色为 pColor。
if(nPos.z<0.003&&nPos.z>-0.003 && nPos.x<0.0):检查当前片元在球面上是否处于特定区域。
phi = (atan(0.003, nPos.x)+PI)/(2.0PI);:根据单位向量的 x 分量计算特定区域内的经度角度。
phi += 0.75;:对经度角度进行偏移,以调整纹理的起始位置。
gl_FragColor = texture2D(uPanorama, vec2(phi, theta));:根据特定区域重新采样纹素颜色,并覆盖原有的颜色。
使用映射类
根据id进行对应的内容映射,将多边形和贴图对应起来
1 | // 导入映射类 |

生成墙体数据

创建墙数据
1 | // 创建墙 |
定义墙体类
1 | import * as THREE from "three"; |
代码解释1:
1 | constructor(wallPoints, faceRelation) { |
传进来墙的点位信息和朝向信息
代码解释2:
1 | let wallPoints = this.wallPoints; |
点位信息重置,将点的xyz切换成我们要使用的坐标系
代码解释3:
1 | let faceIndexs = [ |
这段代码我可以给大家画个图就可以理解了
代码注释4:
接下来部分的代码,根据 faceRelation 中的数据,确定每个面对应的全景图和材质。遍历 faceRelation 数组,找到与当前面索引匹配的关联项,并将关联项的全景图索引添加到 mIndex 数组中。
然后,定义了一个包含了所有面的顶点坐标、UV 坐标、顶点索引和法线的数组。通过遍历 faceIndexs 数组,获取每个面的顶点坐标,并将它们添加到 positions 数组中。设置 UV 坐标为默认值 [0, 0, 1, 0, 1, 1, 0, 1],将其添加到 uvs 数组中。设置顶点索引,并将索引添加到 indices 数组中。设置法线信息,并将法线添加到 normals 数组中。
效果图

添加房间切换
修改div
1 | <div> |
调整样式
1 | <style> |
添加切换房间逻辑
按照指定的房间顺序依次切换房间,如果到最后一个房间,就从第一个房间开始切换,dir适当调整一下镜头,让房间切换不至于在边上
1 | let roomIndex = 0; |
本文就到这里,下次文章再见。本文代码放在了我的git仓库里面,地址为https://gitee.com/guJyang/vr-room