Three.js案例-360° VR看房

在 360° 看房功能中,我们需要在浏览器中创建一个类似虚拟现实的场景,使得用户能够查看环境的每一个角落。这一功能的实现本质上是利用 球体映射技术,即通过将全景图作为纹理贴图映射到一个反向的球体上,用户可以通过旋转视角来“环顾四周”。

我们先来看一下效果
请添加图片描述

步骤拆解

创建球体模型

为了让用户能够查看 360° 环境,我们需要构建一个大球体。通过将球体的内表面作为视图区域,用户的相机将被放置在球体的中心。

球体内表面可见:通过将球体的外表面设置为不可见,而把全景图像贴图到球体的内侧,使得相机站在球体的中心时,所有视野范围都被全景图包围。

全景图像作为纹理贴图

全景图是一种特殊的图像格式,通常是水平或垂直方向无缝拼接的图像,通常为环状。我们可以将全景图作为纹理,映射到球体的内表面。

映射全景图:全景图的每个像素会映射到球体表面的某个位置,形成一个 3D 场景,用户通过旋转相机来查看不同的部分。

相机位置与控制

为了让用户从球体的中心观察全景图,必须将相机的位置设置在球体的内部。同时,我们需要监听用户的输入(如鼠标或触控)来旋转视角。

相机控制:通过旋转相机的方位角(通常是使用 lon 和 lat)来改变用户的视角,从而模拟 360° 环视的效果。

球体的反向显示

由于默认情况下,Three.js 的球体表面是朝外的,因此我们需要将球体反转,让其内表面可见。这通过 geometry.scale(-1, 1, 1) 来实现。

创建 360° 看房 Demo

下面,我们将按照以下步骤一步步实现一个简易的 360° 看房功能。

项目准备

全景图下载

可以通过网站 Poly Haven 下载,这是一个提供免费高质量纹理的网站。我们案例通过
https://polyhaven.com/a/hotel_room 进行下载
请添加图片描述

安装和引入依赖

在项目中,你需要通过 npm 安装 Three.js,并且确保你的项目支持使用 ES Modules(现代 JavaScript 模块)。

npm install three

然后在你的 JavaScript 文件中引入 Three.js 和 EXRLoader:

import * as THREE from "three";
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
import "./style.css"; // 样式文件

在 style 中,我们就写了很简单的 css。只需要把默认的 margin 和 padding 去掉就行

* {margin: 0;padding: 0;
}

创建场景和相机

在 Three.js 中,所有的图形和模型都需要添加到 场景 中,而 相机 用来决定我们看到场景的视角。这里我们创建一个简单的 透视相机,并设置视野为 75 度,近裁剪面为 0.1,远裁剪面为 1000。

// 场景
const scene = new THREE.Scene();// 相机:视角设置为 75 度,近裁剪面 0.1,远裁剪面 1000
const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000
);
camera.position.set(0, 0, 0); // 将相机位置设置在中心点
  • THREE.Scene():创建一个 3D 场景容器,所有的物体、光源、相机都会添加到其中。
  • THREE.PerspectiveCamera():创建一个透视相机,设置视角为 75 度。该相机会模拟人眼的视觉效果,适合于大多数场景。
  • THREE.WebGLRenderer():创建 WebGL 渲染器,用来将 Three.js 场景渲染到浏览器的 canvas 元素中。

创建渲染器

使用 THREE.WebGLRenderer 渲染器来渲染三维场景,并将其显示在浏览器窗口中。你还需要设置渲染器的大小,并将其挂载到 HTML 页面上。

// 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

创建球体并应用全景图纹理

我们将使用一个球体(THREE.SphereGeometry)来显示全景图。需要特别注意的是,为了将全景图映射到球体的内表面,我们需要将球体的几何体进行反向缩放,即 geometry.scale(-1, 1, 1)。

然后,使用 EXRLoader 加载 .exr 格式的全景图,并将其应用到球体的材质中。

创建球体
const geometry = new THREE.SphereGeometry(500, 60, 40); // 半径 500,60 段宽度,40 段高度
球体反向

为了将全景图贴到球体内部,我们需要将球体反向显示。这是因为 全景图是通过球体的内表面来展示的,而相机需要位于球体内部。

geometry.scale(-1, 1, 1); // 反向缩放球体,使其内表面可见

这行代码将球体的 x 轴 缩放为 -1,使球体的内表面朝向相机。这样,相机就位于球体内部,可以查看到全景图的内容。

加载全景图纹理

在 Three.js 中,纹理是通过 Loader 加载的。由于我们加载的是 .exr 格式的全景图,我们需要使用 EXRLoader 来加载纹理。

const texture = new EXRLoader().load("hotel_room_4k.exr");

这里,"hotel_room_4k.exr" 是我们房屋的全景图,将其放到 public 文件夹下面,这张图片也是就是我们刚才下载的那张图。你可以根据需要替换为其他全景图文件。加载纹理后,Three.js 会自动将纹理映射到球体的内表面。

创建材质并将纹理应用到球体

在 Three.js 中,材质是与几何体一起使用的,它定义了物体表面的外观。在这里,我们使用 THREE.MeshBasicMaterial 来创建一个基本材质,并将加载的纹理应用到材质上。

const material = new THREE.MeshBasicMaterial({ map: texture });

THREE.MeshBasicMaterial 是一种不受光照影响的材质,适合用于全景图等无需光照的场景。map 属性用于设置纹理,我们把加载的 texture 赋值给了它。

创建球体并加入场景

接下来,我们将创建一个 网格(Mesh),它是由几何体和材质组成的。我们用球体几何体和材质创建网格,并将其添加到场景中。

const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);

通过这行代码,球体与材质组合成了一个网格,并且这个网格被添加到场景中。这样,球体就成为了我们展示全景图的基础物体。

我们现临时增加这段代码,用于阶段性的测试。将上面的内容渲染到页面上。

function animate() {requestAnimationFrame(animate);renderer.render(scene, camera);
}animate();

请添加图片描述

相机控制与交互

为了让用户能够通过鼠标拖动来查看全景图的不同角度,我们需要实现 相机控制与交互。用户通过鼠标的拖动操作来改变相机的视角,我们会根据鼠标的移动来更新相机的经度(lon)和纬度(lat),然后重新计算相机的位置。

记录鼠标按下位置

首先,我们需要记录鼠标按下的初始位置。当用户按下鼠标时,我们需要记录下鼠标当前位置的 X 坐标Y 坐标,以及当前相机的 经度(lon)纬度(lat)。这些值将用于计算相机在鼠标拖动过程中应该旋转的角度。

let lon = 0,lat = 0; // 初始化经度和纬度
let isUserInteracting = false; // 标记是否正在进行交互
let onPointerDownPointerX = 0,onPointerDownPointerY = 0;
let onPointerDownLon = 0,onPointerDownLat = 0;// 鼠标按下事件
document.addEventListener("pointerdown", (event) => {isUserInteracting = true; // 用户正在交互onPointerDownPointerX = event.clientX; // 记录鼠标按下时的X坐标onPointerDownPointerY = event.clientY; // 记录鼠标按下时的Y坐标onPointerDownLon = lon; // 记录当前经度onPointerDownLat = lat; // 记录当前纬度
});
计算鼠标移动

在鼠标拖动的过程中,我们需要根据鼠标移动的距离来更新相机的 经度(lon)纬度(lat)。通过比较当前鼠标的位置和按下时的位置的差异,我们可以计算出相机应该旋转的角度。

document.addEventListener("pointermove", (event) => {if (isUserInteracting) {lon = (onPointerDownPointerX - event.clientX) * 0.1 + onPointerDownLon;lat = (event.clientY - onPointerDownPointerY) * 0.1 + onPointerDownLat;}
});
  • lon 是经度,表示水平旋转的角度。
  • lat 是纬度,表示垂直旋转的角度。

通过这段代码,我们根据鼠标移动的水平和垂直距离,更新经度和纬度的值,从而改变相机的朝向。

限制纬度范围

为了避免用户将视角旋转到不合理的位置,我们需要限制 纬度(lat) 的范围。纬度的范围应该在 -85° 到 85° 之间,以防用户将视角转到地球的极端位置(超过 85° 或 -85° 会导致不可见的效果)。

lat = Math.max(-85, Math.min(85, lat)); // 限制纬度的范围
更新相机的位置

当用户拖动鼠标时,我们根据经度和纬度来计算相机的新位置。相机的位置应该与球体中心(即场景的原点)保持一定的距离,并朝向球体的中心。

const phi = THREE.MathUtils.degToRad(90 - lat); // 将纬度转为弧度
const theta = THREE.MathUtils.degToRad(lon); // 将经度转为弧度// 计算相机的位置
const lookAtX = 500 * Math.sin(phi) * Math.cos(theta);
const lookAtY = 500 * Math.cos(phi);
const lookAtZ = 500 * Math.sin(phi) * Math.sin(theta);// 使相机始终朝向场景的中心
camera.lookAt(lookAtX, lookAtY, lookAtZ);
  • phi 是纬度角度转化成的弧度,theta 是经度角度转化成的弧度。
  • 我们通过这些角度来计算相机的 x, y, z 位置,确保相机始终朝向球体的中心(即全景图的中心)。

动画与渲染

通过 requestAnimationFrame 来创建动画循环,不断更新相机的朝向并渲染场景。每帧都需要计算相机的朝向,并确保用户不会在纬度方向上超出可视范围(-85 到 85)。

// 动画循环
function animate() {requestAnimationFrame(animate);// 限制角度范围,防止旋转过度lat = Math.max(-85, Math.min(85, lat)); // 只限制lat的范围const phi = THREE.MathUtils.degToRad(90 - lat); // 使用 Three.js 的角度转弧度方法const theta = THREE.MathUtils.degToRad(lon);// 计算相机的朝向const lookAtX = 500 * Math.sin(phi) * Math.cos(theta);const lookAtY = 500 * Math.cos(phi);const lookAtZ = 500 * Math.sin(phi) * Math.sin(theta);camera.lookAt(lookAtX, lookAtY, lookAtZ);renderer.render(scene, camera);
}// 启动动画
animate();

窗口大小调整

为了确保在用户调整浏览器窗口大小时,场景能够自动适配新的大小,我们监听了窗口的 resize 事件,并更新渲染器的尺寸与相机的纵横比。

// 窗口大小调整事件
window.addEventListener("resize", () => {renderer.setSize(window.innerWidth, window.innerHeight);camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();
});

到此为止,你就可以看到一个完整的 360° 看房功能。用户可以通过鼠标拖动来查看全景图的不同角度,实现了一个简单的 360° 看房效果。

效果如我们一开始所展示的那样,用户可以通过鼠标拖动来查看房屋的各个角落。
请添加图片描述

代码

gitee

https://gitee.com/calmound/threejs-demo/tree/main/360vr

github

https://github.com/calmound/threejs-demo/tree/main/360vr

Three.js学习

https://threejs3d.com/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/490599.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何制作一款电子桌宠小狗(硬件部分)开源

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 在科技与创意的交融中,我开启了一段用 STM32 单片机制作电子桌宠小狗的有趣之旅。在这个项目中,我们将看到各种硬件组件的巧妙结合,共同打造出一个可爱又灵动的小宠物。 一、STM32…

期末速成C++【初识C++】

目录 1.英文单词 2.C的特点 3.C对C语言的补充 3.1命名空间和域操作符 🎇3.2控制台输入输出 3.3类型增强 3.3.1const常变量 3.3.2const与指针 3.3.3布尔类型与枚举类型 3.4默认参数 🎇3.5函数重载 🎇引用 3.6.1引用做函数参数 …

《数据结构》(408代码题)

2009 单链表(双指针) 分析:首先呢,给我们的数据结构是一个带有表头结点的单链表,也不允许我们改变链表的结构。链表的长度不是直接给出的啊,所以这个倒数也很棘手。那我们该如何解决这个“k”呢&#xff0c…

在 Visual Studio Code 中编译、调试和执行 Makefile 工程 llama2.c

在 Visual Studio Code 中编译、调试和执行 Makefile 工程 llama2.c 1. Installing the extension (在 Visual Studio Code 中安装插件)1.1. Extensions for Visual Studio Code1.2. C/C1.2.1. Pre-requisites 1.3. Makefile Tools 2. Configuring your project (配置项目)2.1.…

ur机器人ros-urdf

新建工作空间 mkdir -p ~/catkin_ws/src cd catkin_ws_ur5/src git clone -b melodic-devel https://github.com/ros-industrial/universal_robot.git cd .. rosdep update rosdep install --rosdistro melodic --ignore-src --from-paths src catkin_make source ~/catkin_ws…

Leonardo.Ai丨一键生成图片(AI绘图)

随着人工智能技术的迅速发展,AI在各个领域的应用越来越广泛,特别是在图像生成方面。AI艺术创作的崛起,不仅让艺术创作变得更加便捷和创新,也为设计师、艺术家及普通用户提供了全新的工具。Leonardo.Ai作为一款基于人工智能的图像生成工具,通过简洁的操作和强大的功能,成功…

【前端开发】HTML+CSS网页,可以拿来当作业(免费开源)

HTML代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content_lizhongyu"widthdevice-width, initial-scale1.0"><title>小兔鲜儿-新鲜、惠民、快捷<…

AI学习记录 - 依据 minimind 项目入门

想学习AI&#xff0c;还是需要从头到尾跑一边流程&#xff0c;最近看到这个项目 minimind, 我也记录下学习到的东西&#xff0c;需要结合项目的readme看。 1、github链接 https://github.com/jingyaogong/minimind?tabreadme-ov-file 2、硬件环境&#xff1a;英伟达4070ti …

【STM32】RTT-Studio中HAL库开发教程九:FLASH中的OPT

文章目录 一、概要二、内部FLASH排布三、内部FLASH主要特色四、OTP函数介绍五、测试验证 一、概要 STM32系列是一款强大而灵活的微控制器&#xff0c;它的片内Flash存储器可以用来存储有关代码和数据&#xff0c;在实际应用中&#xff0c;我们也需要对这个存储器进行读写操作。…

长安大学《2024年812自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《长安大学812自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题

说说你对java lambda表达式的理解?

大家好&#xff0c;我是锋哥。今天分享关于【说说你对java lambda表达式的理解?】面试题。希望对大家有帮助&#xff1b; 说说你对java lambda表达式的理解? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Java Lambda 表达式是 Java 8 引入的一项重要特性&#…

【PostgreSQL使用】最新功能逻辑复制槽的failover,大数据下高可用再添利器

逻辑复制的failover ​专栏内容&#xff1a; postgresql入门到进阶手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. ✅ &#x1f52…

【leetcode100】环形链表Ⅱ

1、题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统…

决策曲线分析(DCA)中平均净收益用于评价模型算法(R自定义函数)

决策曲线分析&#xff08;DCA&#xff09;中平均净收益用于评价模型算法 DCA分析虽然不强调用来评价模型算法或者变量组合的优劣&#xff0c;但是实际应用过程中感觉DCA曲线的走势和模型的效能具有良好的一致性&#xff0c;其实这种一致性也可以找到内在的联系&#xff0c;比如…

【人工智能】OpenAI O1模型:超越GPT-4的长上下文RAG性能详解与优化指南

在人工智能&#xff08;AI&#xff09;领域&#xff0c;长上下文生成与检索&#xff08;RAG&#xff09; 已成为提升自然语言处理&#xff08;NLP&#xff09;模型性能的关键技术之一。随着数据规模与应用场景的不断扩展&#xff0c;如何高效地处理海量上下文信息&#xff0c;成…

基于智能电能表的智能家居能源管理系统设计

目录 引言系统设计 硬件设计软件设计系统功能模块 电能测量模块数据传输模块能源管理模块控制算法 数据采集与处理算法能源优化算法代码实现 电能测量模块实现数据传输模块实现系统调试与优化结论与展望 1. 引言 随着智能家居的发展&#xff0c;电能管理成为智能家居系统中的…

GLM-4-Plus初体验

引言&#xff1a;为什么高效的内容创作如此重要&#xff1f; 在当前竞争激烈的市场环境中&#xff0c;内容创作已成为品牌成功的重要支柱。无论是撰写营销文案、博客文章、社交媒体帖子&#xff0c;还是制作广告&#xff0c;优质的内容不仅能够帮助品牌吸引目标受众的注意力&a…

软考高级架构 - 10.5 软件架构演化评估方法

10.4 软件架构演化原则总结 本节提出了18条架构演化的核心原则&#xff0c;并为每条原则设计了简单而有效的度量方法&#xff0c;用于从系统整体层面提供实用信息&#xff0c;帮助评估和指导架构演化。 演化成本控制&#xff1a;成本小于重新开发成本&#xff0c;经济高效。进…

科研笔记:ARR 与 ACL rolling

1 ARR 介绍 ARR 提供 评审服务 —— 仅限评审 —— 对于提交的论文。评审不会针对特定会议/场所&#xff0c;但评审标准与传统会议的主会场长文或短文提交要求相同&#xff08;如 ACL 或其他由 ACL 主办的重要会议&#xff09; 2 提交论文进行 ARR 评审 提交截止日期 每两个…

9_less教程 --[CSS预处理]

LESS&#xff08;Leaner Style Sheets&#xff09;是一种CSS预处理器&#xff0c;它扩展了CSS语言&#xff0c;增加了变量、嵌套规则、混合&#xff08;mixins&#xff09;、函数等功能&#xff0c;使得样式表的编写更加灵活和易于维护。下面是一些LESS的基础教程内容&#xff…