Three.js--》探秘虚拟现实VR展厅的视觉盛宴

今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。

源码下载地址:地址

在线体验地址:地址

目录

项目搭建

初始化three代码

camera-controls控制器使用

添加画框

画框处理事件

添加机器人模型


项目搭建

本案例还是借助框架书写three项目,借用vite构建工具搭建vue项目,vite这个构建工具如果有不了解的朋友,可以参考我之前对其讲解的文章:vite脚手架的搭建与使用。搭建完成之后,用编辑器打开该项目,在终端执行 npm i 安装一下依赖即可。接下来对项目进行一些初始化操作:

在项目中我们都会用到一些标签,但是这些标签可能本身自带一些默认样式,这些默认样式可能会影响我们的排版布局,如果每次引用就去清除一遍默认样式有点太过繁琐,因此这里需要我们清除一下默认样式。执行如下命令安装第三方包: 

npm install reset.css --save

因为我搭建的是vue3项目,为了便于代码的可读性,所以我将three.js代码单独抽离放在一个组件当中,在App根组件中进入引入该组件。具体如下:

<template><div class="container"><Show></Show></div>
</template><script setup>
import Show from "./pages/index.vue"</script><style lang="scss">
.container {width: 100%;height: 100%;
}
</style>

初始化three代码

本次项目使用three.js代码必须要基于下面的基础代码才能实现:

导入three库

import * as THREE from 'three'

初始化场景

const scene = new THREE.Scene()

初始化相机

const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.set(5, 1, 0)
camera.lookAt(0, 0, 0)
scene.add(camera)

初始化渲染器

const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight) // 设置渲染器大小
renderer.setPixelRatio(window.devicePixelRatio) // 设置像素比

监听屏幕大小的改变,修改渲染器的宽高和相机的比例

window.addEventListener("resize",()=>{ renderer.setSize(window.innerWidth, window.innerHeight)camera.aspect = window.innerWidth / window.innerHeightcamera.updateProjectionMatrix()
})

导入轨道控制器

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true // 设置控制阻尼

设置渲染函数: 

const render = () =>{ controls.update()renderer.render(scene,camera)requestAnimationFrame(render)
}

页面加载调用

<template><div class="exhibition" ref="exhibition"></div>
</template><script setup>
import { ref, onMounted } from 'vue'// 获取dom实例
let exhibition = ref(null)onMounted(() => {exhibition.value.appendChild(renderer.domElement)render()
})
</script>

ok,写完基础代码之后,接下来开始具体的Demo实操。 

camera-controls控制器使用

因为本次项目vr展厅需要我们去进行视角的移动,采用three本身的控制器是无法满足我们的需求的,所以这里我们需要换一个新的控制器去进行视角的移动和切换,首先我们先加载好我们的场景,借助three库自带的GLTFLoader函数来加载场景,GLTFLoader函数是一个用于加载和解析 glTF(GL Transmission Format)文件的 JavaScript 库,其可以让开发人员在Web应用程序中轻松地加载和显示 glTF 格式的3D模型和场景。它提供了一种简单而有效的方式来将 glTF 文件加载到WebGL渲染器中,使开发人员能够通过JavaScript代码轻松地操作和展示3D内容。

接下来我们直接引入该库,然后加载场景,并给场景中添加环境光源:

// 加载GLTF模型
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';// 加载模型
let gltfLoader = new GLTFLoader();
gltfLoader.load("/public/assets/room1/msg.gltf", (gltf) => {scene.add(gltf.scene)
})// 添加环境光源
const ambientLight = new THREE.AmbientLight(0xffffff, 1) // 环境光
scene.add(ambientLight)

添加完成之后,我们运行我们的项目,可以看到如下场景,说明我们的场景已经加载完成:

接下来我们开始安装新的控制器,终端执行如下命令安装新的控制器,详情查看:官网

npm i camera-controls

我们在官网的案例中,随便打开一个demo,可以看到该控制器的效果还是不错的,如下:

接下来我们通过three.js 中的 Raycaster(射线投射器):一个用于在3D场景中进行射线投射和检测碰撞的工具。允许开发人员在三维空间中进行准确的拾取操作,即确定射线与场景中的对象是否相交,并获取与射线相交的对象的相关信息。来获取场景当中的模型信息,代码如下:

// 获取容器div点击事件
const handleClick = (e) => {// 获取鼠标位置mouse.x = (e.offsetX / window.innerWidth) * 2 - 1 mouse.y = -(e.offsetY / window.innerHeight) * 2 + 1 // 计算射线坐标raycaster.setFromCamera(mouse, camera)// 计算物体和射线的焦点const intersects = raycaster.intersectObjects(scene.children)// 判断是否有焦点if (intersects.length > 0) {console.log(intersects[0].object.name)}
}

呈现的效果如下所示:

点击事件肯定只适用展厅中的内容,为了防止点击其他模型触发点击事件,我们需要给计算物体和射线的焦点处设置展厅场景内容,具体修改如下:

接下来我们开始引入camera-controls库中的内容:

import * as THREE from 'three'
import CameraControls from 'camera-controls';
CameraControls.install( { THREE: THREE } );

在html中,这里我给了场景容器的div设置了点击事件,鼠标按下和抬起事件,三种事件,点击事件很容易理解,鼠标的按下和抬起事件合并起来就是鼠标的拖动事件:

<template><div class="exhibition" ref="exhibition" @click="handleClick"@mousedown="handleMouseDown" @mouseup="handleMouseUp"></div>
</template>

接下来就是对这三个事件进行处理了,判断用户是执行了点击事件还是拖动事件:

let isDragging = false // 判断是否拖动
// 获取容器div点击事件
const handleClick = (e) => {// 如果发生了拖动,则不执行点击事件if (isDragging) return// 获取鼠标位置mouse.x = (e.offsetX / window.innerWidth) * 2 - 1 mouse.y = -(e.offsetY / window.innerHeight) * 2 + 1 // 计算射线坐标raycaster.setFromCamera(mouse, camera)// 计算物体和射线的焦点const intersects = raycaster.intersectObjects(eventMeshs)// 判断是否有焦点const mesh = intersects[0]if (mesh) {const v3 = mesh.point // 获取焦点位置if (mesh.object.name === 'meishu01') {cameraControls.moveTo(v3.x, 1, v3.z, true)}}
}let startXY
// 获取容器div鼠标按下事件
const handleMouseDown = (e) => {// 获取鼠标位置startXY = [e.offsetX, e.offsetY]
}// 获取容器div鼠标抬起事件
const handleMouseUp = (e) => {// 获取鼠标位置const [ endX, endY ] = startXYif (Math.abs(e.offsetX - endX) > 3 || Math.abs(endY - e.offsetY) > 3) {// 标记发生了拖动isDragging = true} else {// 标记未发生拖动isDragging = false}
}

最终呈现的效果如下:

添加画框

接下来开始编写相应的函数给展厅场景中添加对应的图片了,如下:

// 添加画框
const loadItem = (items, deepth) => {items.forEach(async (item) => {// 加入到画布当中const { id, url, position, scale, rotation } = item// 绘制画框,贴图const texture = await new THREE.TextureLoader().loadAsync(url)let width, heightlet originwidth = texture.image.width // 获取图片原始宽度let originheight = texture.image.height // 获取图片原始高度let maxSize = 10 // 最大尺寸if (width > maxSize) {width = maxSizeheight = (maxSize / originwidth) * originheight} else {height = maxSizewidth = (maxSize / originheight) * originwidth}const geometry = new THREE.BoxGeometry(width, height, deepth) // 创建画框const material = new THREE.MeshBasicMaterial({ color: 0xffffff }) // 创建贴图const imgMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff,map: texture})const mesh = new THREE.Mesh(geometry, [ material, material, material, material, material, imgMaterial ]) // 创建画框scene.add(mesh)})
}

执行如下函数,给图片添加对应的信息,函数如下:

loadItem([{ url: "/public/assets/pictures2/1.jpg",name: "名称",desc: "信息描述",scale: { x: 0.1, y: 0.1, z: 0.1 },position: { x: 24.23375412142995, y: 2.3, z: 10.729648829537796 },view: { x: 24.011, y: 2.1, z: 4.379 },id: "1",rotation: { x: 0, y: 0, z: 0 },type: "picture",}
], 0.1)

最终呈现的效果如下,总体来说还是不错的,现在的问题就是将图片铁道场景的墙壁上:

如何把画框贴到墙壁上,换句话说如何知道画框与墙壁之间的具体位置呢?这里我们需要借助three给我们提供的TransformControls库,使用TransformControls可以为用户提供更直观、友好的界面,使他们能够轻松地在 3D 场景中进行对象的编辑和操作,代码如下:

import { TransformControls } from 'three/examples/jsm/controls/TransformControls';// 实例化TransformControls
const transformControls = new TransformControls(camera, renderer.domElement)
transformControls.setSpace('local') // 设置空间
transformControls.addEventListener('mousedownn', () => {controls.enabled = false
})
transformControls.addEventListener('mouseup', () => {controls.enabled = true
})
transformControls.addEventListener('objectChange', () => {const { position, scale, rotation } = transformControls.objectconsole.log(JSON.stringify({ position, scale, rotation: { x: rotation.x, y: rotation.y, z: rotation.z } }))
})
scene.add(transformControls)

我们通过 TransformControls控制器移动画框到墙壁上,并通过监听事件拿到对应的位置数据:

将数据复制到外部存放图片相关信息资源的js文件当中:

export const items = [{id: 1,url: "./assets/pictures/01.jpg",position: { x: 54.44612606517201, y: 3.679549096713978, z: 50.93531019361985 }, scale: { x: 1, y: 1, z: 1 }, rotation: { x: 0, y: 0, z: 0 },},{id: 1,url: "./assets/pictures/02.jpg",position: { x: 42.53326413580169, y: 3.679549096713978, z: 50.93531019361985 }, scale: { x: 1, y: 1, z: 1 }, rotation: { x: 0, y: 0, z: 0 },},{id: 1,url: "./assets/pictures/03.jpg",position: { x: 29.007956809298168, y: 3.679549096713978, z: 50.93531019361985 }, scale: { x: 1, y: 1, z: 1 }, rotation: { x: 0, y: 0, z: 0 },},
];

通过手动的修改,将图片全部铁道墙壁上,最终达到的效果如下,还是很完美的:

画框处理事件

在上文讲述贴好图片之后,接下来我们需要给图片设计一个点击事件,拿到当前点击图片的相关讯息进行进一步的处理,首先我们先思考一下,该如何设计点击事件拿到相关讯息呢?步骤如下:

在展厅中当我们点击相应的图片的时候,控制台会给出对应的信息:

ok,拿到相应的图片信息之后,接下来就是如何展示数据了,这里用到一款插件,安装命令如下:

npm i zoomtastic

当然我们也可以在npm平台找到这个对应的包,可以看看相关的使用教程:地址

我们这里就不再具体讲解该包的使用了,这里直接拿来用,上展示:

// 导入第三方库
import Zoomtastic from 'zoomtastic';
// 挂载
Zoomtastic.mount();// 设置画框点击事件
const handleClickPicture = (item) => {// 展示当前的图片Zoomtastic.show(item.url);
}

现在当我点击对应的图片之后,得到如下结果:

添加机器人模型

接下来给场景中添加两个机器人模型并设置一下动画效果,代码如下:

// 加载机器人模型
let robotLoader = new GLTFLoader();
robotLoader.load("/public/assets/robot/robot.glb", (gltf) => {gltf.scene.scale.set(5, 5, 5)gltf.scene.position.set(0.1324808945523861, -10.232245896556929, -30.95853005109946)eventMeshs.push(gltf.scene)gltf.scene.odata = { id: "robot" }const mixer = new THREE.AnimationMixer(gltf.scene) // 创建动画控制器const ani = gltf.animations[0] // 获取动画mixer.clipAction(ani).setDuration(5).play() // 播放动画mixer.update(0) // 更新动画animateFuns.push(d => mixer.update(d))scene.add(gltf.scene)
})

在渲染函数的时候调用数组当中的动画数据:

let animateFuns = []
const clock = new THREE.Clock();
// 设置渲染函数
const render = () =>{ const delta = clock.getDelta();controls.update( delta );renderer.render(scene,camera)animateFuns.forEach((fun) => {fun(delta)})requestAnimationFrame(render)
}

效果如下:

接下来再在场景中添加一个机器人模型:

let robotLoader1 = new GLTFLoader();
robotLoader1.load("/public/assets/robot/robot1.gltf", (gltf) => {gltf.scene.scale.set(5, 5, 5)gltf.scene.position.set(0.25734022000060963, -10.237542382614008, 30.602748751354614)gltf.scene.rotation.set(-3.1353226226906985, -0.014796136198272362, -3.141104770940116)eventMeshs.push(gltf.scene)gltf.scene.odata = { id: "robot1" }const mixer = new THREE.AnimationMixer(gltf.scene) // 创建动画控制器const ani = gltf.animations[0] // 获取动画mixer.clipAction(ani).setDuration(5).play() // 播放动画mixer.update(0) // 更新动画animateFuns.push(d => mixer.update(d))scene.add(gltf.scene)
})

最终呈现的效果如下:

ok,后面展厅添加的图片和上文讲解的原理一样,这里就不再赘述了,demo写完给个赞吧!

源码下载地址:地址

在线体验地址:地址

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

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

相关文章

分布式系统事务一致性解决方案(基于事务消息)

参考&#xff1a;https://rocketmq.apache.org/zh/docs/featureBehavior/04transactionmessage/ 文章目录 概要错误的方案方案一&#xff1a;业务方自己实现方案二&#xff1a;RocketMQ 事务消息什么是事务消息事务消息处理流程事务消息生命周期使用限制使用示例使用建议 概要 …

单纯形投影算法

目录 一&#xff0c;任意点到平移坐标轴面的投影 1&#xff0c;求解目标 2&#xff0c;转换变量 3&#xff0c;求解结果 4&#xff0c;f(t)的导数 5&#xff0c;f(t)的最小值 二&#xff0c;任意点到标准单纯形的投影 1&#xff0c;求解目标 2&#xff0c;公式变形 3…

制作一个RISC-V的操作系统十四-任务同步和锁

文章目录 并发与同步临界区和锁锁死锁解决死锁自旋锁&#xff08;spin lock&#xff09;原子性问题原子操作实现amoswap.w.aq例子 另一种方法自旋锁的注意事项代码其他同步技术 并发与同步 控制流&#xff1a;可理解为任务或进程 中断也可以理解为一个切换到另一个任务&#…

LeetCode 226.翻转二叉树

题目描述 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1]示例 2&#xff1a; 输入&#xff1a;root [2,1,3] 输出&#xff1a;[2,3,1]示例…

CMake+qt+Visual Studio

#使用qt Creator 创建Cmake 项目,使用Cmake Gui 生成sln 工程&#xff0c;使用Visual Studio 开发 ##使用qt Creator 创建CMake项目 和创建pro工程的步骤一致&#xff0c;只是在选择构建系统的步骤上选择CMake,接下来步骤完全相同 工程新建完成之后&#xff0c;构建cmake 项…

想冲宇宙厂,直接挂了。。。

宇宙厂实际是字节&#xff0c;这个称呼是因为字节跳动主宰了宇宙内一切App&#xff0c;有点家大业大的意思。 今天分享一位字节春招凉经&#xff0c;问了一些数据库和Java八股&#xff0c;没出算法题&#xff0c;直接挂了&#xff0c;竟然最喜欢出算法题的字节&#xff0c;这次…

刷代码随想录有感(49):找树左下角的值

题干&#xff1a; 用层序遍历方便些&#xff0c;因为只需要把res不断替换成每一层第一个节点值即可&#xff0c;代码如下&#xff1a; class Solution { public:int findBottomLeftValue(TreeNode* root) {queue<TreeNode*>que;if(root ! NULL)que.push(root);int res …

C++中的数据结构与算法

随处可见的红黑树 一般会用到[key,value]。 例如github中这个例子&#xff0c;第一个是访问网站&#xff0c;第二个是访问次数&#xff0c;但是这个不是静态的&#xff0c;这有个动态排序&#xff0c;并且当我们需要让相应的访问次数加1的时候&#xff0c;我们用红黑树查找的时…

C++每日一练——杨辉三角

给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRows 1 输出: [[1]]提示: 1 <…

Jsoncpp搭建交叉编译环境(移植到arm)

1. 官网下载源码 github地址&#xff1a;GitHub - open-source-parsers/jsoncpp at update 2. 交叉编译环境 当前平台/开发平台-编译环境&#xff1a; [rootlocalroot ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootlocalroot ~]# uname -a Lin…

ELK创建仪表盘

创建仪表盘步骤&#xff1a; 一、保存search二、生成饼图三、创建仪表盘 一、保存search 首先保存一段时间内的search&#xff0c;可以添加想要的字段&#xff0c;并保存这个search方便下次直接打开该search&#xff0c;并方便在可视化和仪表盘中使用该search. 二、生成饼图…

Mac安装telnet

一、安装Homebrew 1、打开官网&#xff1a;Homebrew — The Missing Package Manager for macOS (or Linux) 2、打开终端输入&#xff1a; /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 二、安装Telnet bre…

数字文旅重塑旅游发展新格局:以数字化转型为突破口,提升旅游服务的智能化水平,为游客带来全新的旅游体验

随着信息技术的迅猛发展&#xff0c;数字化已成为推动各行各业创新发展的重要力量。在旅游业领域&#xff0c;数字文旅的兴起正以其强大的驱动力&#xff0c;重塑旅游发展的新格局。数字文旅以数字化转型为突破口&#xff0c;通过提升旅游服务的智能化水平&#xff0c;为游客带…

急急急!微信朋友圈删除了怎么恢复?

微信朋友圈是我们与朋友分享生活点滴的重要平台&#xff0c;但有时候微信出现异常&#xff0c;导致我们编辑好的朋友圈被删除了&#xff0c;这时候该怎么办呢&#xff1f; 幸运的是&#xff0c;微信提供了一种简单的方式来恢复已删除的朋友圈内容。微信朋友圈删除了怎么恢复&a…

四信数字孪生水库解决方案,加快构建现代化水库运行管理矩阵

近年&#xff0c;水利部先后出台《关于加快构建现代化水库运行管理矩阵的指导意见》与《构建现代化水库运行管理矩阵先行先试工作方案》等文件&#xff0c;明确总体要求及试点水库、先行区域建设技术要求等&#xff0c;为全面推进现代化水库运行管理矩阵建设工作提供依据。 《2…

【Elasticsearch<一>✈️✈️】简单安装使用以及各种踩坑

目录 &#x1f378;前言 &#x1f37b;一、软件安装&#xff08;Windows版&#xff09; 1.1、Elasticsearch 下载 2.1 安装浏览器插件 3.1、安装可视化工具 Kibana 4.1、集成 IK 分词器 &#x1f37a;二、安装问题 &#x1f379;三、测试 IK 分词器 ​&#x1f377; 四、章…

spi接口的基本概念、引脚定义及注意事项

目录 基本概念 引脚定义 注意事项 SPI&#xff08;Serial Peripheral Interface&#xff09;是一种同步串行接口技术&#xff0c;广泛应用于微控制器和各种外围设备之间的短距离通信。 基本概念 SPI接口允许微控制器以串行方式与一个或多个外围设备进行通信。它是一种高速、…

统信UOS下如何配置全局搜索

原文链接&#xff1a;统信UOS下如何配置全局搜索 Hello&#xff0c;大家好啊&#xff01;全局搜索是操作系统中非常实用的一个功能&#xff0c;它可以帮助我们快速定位文件、程序和其他资源。今天&#xff0c;我要给大家介绍如何在统信UOS桌面操作系统上配置全局搜索&#xff0…

IT廉连看——UniApp——样式绑定

IT廉连看——UniApp——样式绑定 一、样式绑定 两种添加样式的方法&#xff1a; 1、第一种写法 写一个class属性&#xff0c;然后将css样式写在style中。 2、第二种写法 直接把style写在class后面 添加一些效果&#xff1a;字体大小 查看效果 证明这样添加样式是没有问题的…

基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度(matlab代码)

目录 1 主要内容 系统结构图 P2G-CCS 耦合模型 系统掺氢分析 其他算例对比 2 部分代码 3 下载链接 1 主要内容 该程序复现《基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度》模型&#xff0c;以碳交易和碳封存成本、燃煤机组启停和煤耗成本、弃风成本、购…