three.js学习之vR展厅

news2024/9/23 11:36:29

目标

1、需要会的知识点
	three.js的场景,摄像机,渲染器,轨道控制器,坐标轴,场景适配,渲染循环
	创建立方缓冲几何体、纹理、3d物体
实现:创建立方几何体,纹理贴图镜面反向渲染,摄像机设置在内部,通过与创建3D 标记物体交互事件切换纹理贴图,创建视频纹理实现展厅视频展示
架构:vite + js

预览图

实现

一、初始化 vite 项目

  1. 命令:npm create vite@latest

  2. 选择 Vanilla 模版

  3. 选择 JS 语法

  4. 删除多余的内容
    在这里插入图片描述

  5. 清空 main.js 和 style.css 内容

  6. index.html 中只留下核心代码
    在这里插入图片描述

新建如下untils/init.js文件填入内容

	初始化场景,摄像机,渲染器,轨道控制器,坐标轴,场景适配,渲染循环
// 目标:初始化 three.js 基础环境
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { CSS3DRenderer } from 'three/addons/renderers/CSS3DRenderer.js';
export let scene, camera, renderer, controls, css3dRenderer;

(function init() {
  scene = new THREE.Scene()
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
  camera.position.z = 0.1
  renderer = new THREE.WebGLRenderer({ antialias: true })
  renderer.setSize(window.innerWidth, window.innerHeight)
  document.body.appendChild(renderer.domElement)
})();

(function createControls() {
  controls = new OrbitControls(camera, renderer.domElement)
  controls.minPolarAngle = 0.25 * Math.PI
  controls.enableZoom = false
})();

(function createHelper() {
  // const axesHelper = new THREE.AxesHelper(5)
  // scene.add(axesHelper)
})();

(function resizeRender() {
  window.addEventListener('resize', () => {
    renderer.setSize(window.innerWidth, window.innerHeight)
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
  })
})();

(function create3dRenderer(){
  css3dRenderer = new CSS3DRenderer()
  css3dRenderer.setSize(window.innerWidth, window.innerHeight)
  css3dRenderer.domElement.style.position = 'fixed'
  css3dRenderer.domElement.style.left = '0'
  css3dRenderer.domElement.style.top = '0'
  css3dRenderer.domElement.style.pointerEvents = 'none'
  document.body.appendChild(css3dRenderer.domElement)
})();

(function renderLoop() {
  renderer.render(scene, camera)
  controls.update()
  css3dRenderer.render(scene, camera)
  requestAnimationFrame(renderLoop)
})();

main.js - 创建立方缓冲几何体

import { camera, scene } from './utils/init.js'
import * as THREE from 'three'

function createCube() {
  const geometry = new THREE.BoxGeometry(1, 1, 1)
  const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, side: THREE.DoubleSide })
  const cube = new THREE.Mesh(geometry, material)
  cube.scale.set(1, 1, -1)
  scene.add(cube)

  return cube
}

安装项目需要的所有依赖,并启动项目浏览
在这里插入图片描述

二、展厅-第一个页面内容展示

在这里插入图片描述

目标准备:
1.6 个面纹理图片(镜面翻转)
2.地上热点交互标记(借助 gui 定位位置)
const sceneInfoObj = {
  one: { // 第一个场景里数据
    publicPath: 'technology/1/',
    imgUrlArr: ['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'],
    markList: [ // 当前空间中所有标记信息对象
      {
        name: 'landMark',
        imgUrl: 'other/landmark.png',
        wh: [0.05, 0.05],
        position: [-0.46, -0.11, -0.11],
        rotation: [1.42, 0.68, 1.63],
        targetAttr: 'two'
      }
    ]
  },
}

function setMaterialCube(infoObj) {
  const { publicPath, imgUrlArr, markList } = infoObj
  const textureLoader = new THREE.TextureLoader()
  textureLoader.setPath(publicPath)

  const materialArr = imgUrlArr.map(imgStr => {
    const texture = textureLoader.load(imgStr)
    texture.colorSpace = THREE.SRGBColorSpace
    return new THREE.MeshBasicMaterial({
      map: texture,
      side: THREE.DoubleSide
    })
  })

  cubeObj.material = materialArr
    
  markList.forEach(markObj => {
    // 地板标记
    if (markObj.name === 'landMark') createLandMark(markObj)
  })

  scene.add(group)
}

function createLandMark(infoObj) {

  const { imgUrl, wh, position, rotation, targetAttr } = infoObj
  const geometry = new THREE.PlaneGeometry(...wh)
  const material = new THREE.MeshBasicMaterial({
    map: (new THREE.TextureLoader()).load(imgUrl),
    side: THREE.DoubleSide,
    transparent: true
  })
  const mesh = new THREE.Mesh(geometry, material)
  mesh.position.set(...position)
  mesh.rotation.set(...rotation)

  // 给地上热点标记添加名字-方便点击时进行区分
  mesh.name = 'mark'
  // three.js 3D 物体也可以自定义属性和值(方便后续获取绑定的这个数据)
  // 绑定这个地上热点标记,要切换到哪个场景信息对象,对应名字属性
  mesh.userData.attr = targetAttr
  group.add(mesh)
}


setMaterialCube(sceneInfoObj.one) // 默认先渲染第一个场景信息

三、展厅第二个页面
在这里插入图片描述

准备
1.准备第二个场景相关数据
2.与 3D 物体交互事件绑定
3.准备清除当前场景热点标记函数
4.切换纹理,重新创建当下场景热点标记
two: {
publicPath: 'technology/2/',
imgUrlArr: ['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'],
markList: [
  {
    name: 'landMark',
    imgUrl: 'other/landmark.png',
    wh: [0.05, 0.05],
    position: [0.47, -0.2, 0],
    rotation: [1.48, 0.26, -1.78],
    targetAttr: 'one' // 目标场景信息对象属性
  }, {
    name: 'landMark',
    imgUrl: 'other/landmark.png',
    wh: [0.05, 0.05],
    position: [-0.46, -0.16, -0.3],
    rotation: [1.21, 0.78, 0],
    targetAttr: 'three' // 目标场景信息对象属性
  }
]
},
function clear() {
  // 清除组内物体
  const list = [...group.children]
  list.forEach(obj => {
    if (!obj.isCSS3DObject) {
      obj.geometry.dispose()
      obj.material.dispose()
    }
    group.remove(obj)
  })
}

// 在 setMaterialCube 里先调用 clear 清除当下场景空间中的物体标记(都在 Group 组中)

function bindClick() {
  const rayCaster = new THREE.Raycaster()
  const pointer = new THREE.Vector2()
  // 5.2 与 3D 物体交互事件绑定
  window.addEventListener('click', e => {
    pointer.x = (e.clientX / window.innerWidth) * 2 - 1
    pointer.y = -(e.clientY / window.innerHeight) * 2 + 1

    rayCaster.setFromCamera(pointer, camera)
    const list = rayCaster.intersectObjects(scene.children)
    // 查找到我点击的热点标记物体
    const obj = list.find(obj => obj.object.name === 'mark')
    if (obj) {
      // 提取物体上绑定的自定义属性,切换场景
      // 5.4 切换纹理,重新创建当前场景下的热点标记
      const infoObj = sceneInfoObj[obj.object.userData.attr]

      setMaterialCube(infoObj)
    }
  })
}

四、展厅 第三个页面

在这里插入图片描述

1.准备第三个场景相关数据
2.与 3D 物体交互事件绑定
3.准备清除当前场景热点标记函数
4.切换纹理,重新创建当下场景热点标记
ps:因为前面流程代码已经准备好了,切换点击->关联场景属性 key 名 -> 清空当下空间物体 -> 重新创建新空间物体和纹理贴图,所以准备好数据就可以切换空间了
three: {
publicPath: 'technology/3/',
imgUrlArr: ['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'],
markList: [
  {
    name: 'landMark',
    imgUrl: 'other/landmark.png',
    wh: [0.05, 0.05],
    position: [0.4, -0.18, 0.32],
    rotation: [-1.53, -0.04, -1.26],
    targetAttr: 'two' // 目标场景信息对象属性
  }, {
    name: 'landMark',
    imgUrl: 'other/landmark.png',
    wh: [0.05, 0.05],
    position: [0.32, -0.16, -0.33],
    rotation: [1.46, 0.1, -0.17],
    targetAttr: 'four' // 目标场景信息对象属性
  }
]
},

五、展厅-第四个页面
在这里插入图片描述

准备:
1.准备第四个场景相关数据
2.准备创建 DOM 的热点标记函数
(这里采用 DOM 的热点标记,使用 CSS3D 技术)
four: {
publicPath: 'technology/4/',
imgUrlArr: ['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'],
markList: [
  {
    name: 'landMark',
    imgUrl: 'other/landmark.png',
    wh: [0.05, 0.05],
    position: [-0.35, -0.22, 0.4],
    rotation: [-0.85, -0.45, -1.8],
    targetAttr: 'three' // 目标场景信息对象属性
  },
  {
    name: 'dom',
    position: [0.49, 0, 0],
    rotation: [0, -0.5 * Math.PI, 0],
    targetAttr: 'five', // 目标场景信息对象属性
    active(e) {
      setMaterialCube(sceneInfoObj.five)
    }
  }
]
},
function createDomMark(infoObj) {
  const { position, rotation, active } = infoObj
  const tag = document.createElement('span')
  tag.className = 'mark-style'
  tag.innerHTML = '前进'
  tag.style.pointerEvents = 'all'
  tag.addEventListener('click', e => {
    // 为了保证这个函数通用,回调数据对象中的函数代码
    active(e)
  })

  // DOM -> 3D 物体
  const tag3d = new CSS3DObject(tag)
  tag3d.scale.set(1 / 800, 1 / 800, 1 / 800)
  tag3d.position.set(...position)
  tag3d.rotation.set(...rotation)
  group.add(tag3d)
}
// 修改 setMaterialCube 内代码
markList.forEach(markObj => {
// 地板标记
if (markObj.name === 'landMark') createLandMark(markObj)
// 原生 DOM 标记
else if (markObj.name === 'dom') createDomMark(markObj)
})

六、展厅-第五个页面

在这里插入图片描述

1.准备第五个场景相关数据
2.准备创建 Video 的物体函数
(这里采用视频转 3D 物体技术)
(浏览器要求当前页面自动播放的视频是静音的,我们可以后续加上声音标签dom或者声音模型
导入进行控制点击声音播放)
3.控制轨道控制器拉动,旋转
five: {
    publicPath: 'technology/5/',
    imgUrlArr: ['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'],
    markList: [
      {
        name: 'landMark',
        imgUrl: 'other/landmark.png',
        wh: [0.03, 0.03],
        position: [-0.05, -0.05, 0.4],
        rotation: [1.21, -0.15, -0.69],
        targetAttr: 'four' // 目标场景信息对象属性
      },
      {
        name: 'video',
        imgUrl: 'video/movie.mp4',
        wh: [0.2, 0.1],
        position: [0.49, 0.04, 0.045],
        rotation: [0, -0.5 * Math.PI, 0]
      }
    ]
  }
function createVideoMark(infoObj) {
const { imgUrl, wh, position, rotation } = infoObj
// 原生 video 承载视频
const video = document.createElement('video')
video.src = imgUrl
video.muted = true
video.addEventListener('loadedmetadata', () => {
video.play()
})

const plane = new THREE.PlaneGeometry(...wh)
const material = new THREE.MeshBasicMaterial({
map: (new THREE.VideoTexture(video))
})
const mesh = new THREE.Mesh(plane, material)
mesh.position.set(...position)
mesh.rotation.set(...rotation)
group.add(mesh)
}
markList.forEach(markObj => {
// 地板标记
if (markObj.name === 'landMark') createLandMark(markObj)
// 原生 DOM 标记
else if (markObj.name === 'dom') createDomMark(markObj)
// Video 标记
else if (markObj.name === 'video') createVideoMark(markObj)
})

git项目地址

https://github.com/geyixia/vr-memorial-hall
注意:video文件超过100M, git push 不上去,我没加git扩展
所以clone下来项目后需要你自己加上一个video
在这里插入图片描述

鸣谢-广告

学程序上黑马,黑马程序员成就IT黑马,感谢黑马讲师的视频课程

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

空转旋转 seurat spatial rotate 图片 翻转 数据结构 对象 seurat的空转数据存储

1seurat 取子集操作 3. 对象操作 ① 通过结构图上的,$符号依次取 ② 两个中括号操作,pbmc[[ ]]。 教程中,pbmc[[percent.MT]]向meta.data添加 percent.MT 这一列。 pbmc[[]],中括号取的是上面结构图中的二级数据名称以上两种方法的区别是&am…

《Unity Shader入门精要》笔记06

基础纹理 单张纹理纹理的属性Alpha SourceWrap ModeFilter Mode 凹凸映射高度纹理法线纹理实践在切线空间下计算在世界空间下计算 Unity中的法线纹理类型Create from Grayscale 渐变纹理遮罩纹理其他遮罩处理 单张纹理 我们通常会使用一张纹理来代替物体的漫反射颜色 Shader …

[SRT]1.协议简介

1.简介 ​ 安全可靠传输协议(Secure Reliable Transport)简称SRT,是一种基于UDT协议的开源互联网传输协议,Haivision和Wowza合作成立SRT联盟,管理和支持SRT协议开源应用的组织,这个组织致力于促进视频流解决方案的互通性&a…

041:mapboxGL移动到到某Layer上,更换鼠标形状

第041个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中通过鼠标hover的方式来更换鼠标形状。 通过mouseenter和mouseleave的方法,经过某个图层上的时候,更换鼠标的形状,从default到pointer。 离开后从pointer到default。 直接复制下面的 vue+mapbox源代码,操…

安信证券携手共议量化行业的赋能发展

9月22日,安信证券上海浦西分公司携手非凸科技、通联数据在上海共同开展“量化私募闭门交流会”,与资方管理人就如何赋能量化私募可持续发展,给出了精彩纷呈的见解与讨论。 安信证券以“打造数字化券商”为目标,加强科技创新与业务…

数据库常见面试题--MySQL

梳理面试过程中数据库相关的常见问题,需要说明的是,这篇文章主要是基于MySQL数据库,其他类型的数据库还请自行参考使用。 数据库概述 为什么使用数据库 1、数据库增删改查更方便 2、提供了事务的能力 本质是更好的管理数据。 数据库体系结…

Android rtmp 低延迟直播方案:简介

Android rtmp 低延迟直播方案:简介 Android RTMP 低延迟直播方案:使用 RTMP 推送至 ZLMediaKit,通过 WebRTC 进行拉流。

会展购票系统有哪些特点?如何选择好的会展购票系统开发公司

会展购票系统是会展行业的重要组成部分,它具有提高会展业务流程运转效率、业务操作方便快捷、降低人工成本等优势。如开利网络自主研发的会展购票系统就有以下几个特点: 1. 拥有多端开发能力。除了电脑端外,还可以开发微信小程序端、公众号端…

不说废话,推荐一款超实用免费配音软件~

短视频发展如火如荼,在制作编辑视频的同时,文字转语音的需求也越来越大,例如解说配音、旁白配音、vlog配音等,今天就给大家推荐一款超实用的免费配音软件,感兴趣的小伙伴请接着往下看! 一、悦音AI配音&…

语音芯片的“等级”之分

语音芯片,你或许不晓得这个“芯”也是有高低之分,你可能听说过手机“发烧级”高性能芯片,同样在语音芯片中存在着性能不同等级的语音芯片。一般我们将普通芯片主要分为3个等级:商业级(又称民用级)、工业级和…

在Scrum敏捷开发中,开发人员(Developers)的职责

在Scrum敏捷开发中,开发人员(Developers)是Scrum团队中最重要的角色之一,负责产品的开发和交付,其重要性不言而喻。 那开发人员的职责和需要参加的活动是什么呢? Developers核心职责: 承诺并完…

【外汇天眼】不工作只做交易?探索不平凡的生活

金融界曾有一句广为流传的名言——"富人做债券,中产做股票,穷人做期货外汇"。这并非因为穷人特别热爱期货和外汇交易,而是因为他们更渴望通过杠杆交易在短期内实现财富梦想。如果一个人不愿意或没有技能去工作,但精通交…

Lambda 表达式使用详解,一篇文章手把手教会你

目录 1. Lambda 表达式有什么用? 2. 匿名内部类举例 3. Lambda 表达式的标准格式与使用 4. Lambda 表达式使用注意点 5. 什么是函数式接口? 6. Lambda 表达式的省略写法 7. Lambda 表达式省略写法简单展示 1. Lambda 表达式有什么用? …

LeetCode【394】字符串解码

题目&#xff1a; 思路&#xff1a; 参考&#xff1a;https://blog.csdn.net/xushiyu1996818/article/details/107973300 代码&#xff1a; public String decodeString(String s){Deque<Character> stack new ArrayDeque<>();for(char c :s.toCharArray()){if…

第一章:Android开发技能入门指南

Android 从入门到出门第一章&#xff1a;Android开发技能入门指南第二章&#xff1a;使用声明式UI创建屏幕并探索组合原则第三章&#xff1a;使用Hilt处理Jetpack Compose UI状态第四章&#xff1a;现代Android开发中的导航第五章&#xff1a;使用DataStore存储数据和测试第六章…

软考之系统架构师-01什么是系统架构师?什么是系统架构?

第一章 绪论 1. 什么是系统架构师&#xff1f; 系统架构设计师(System Architecture Designer&#xff09;是项目开发活动中的关键角色之一。系统架构是系统的一种整体的高层次的结构表示&#xff0c;是系统的骨架和根基&#xff0c;其决定了系统的健壮性和生命周期的长短。 …

系统架构师--面向对象选择题

系统架构师--面向对象选择题 面向对象考题&#xff08;将设计模式删减了&#xff0c;主要考察uml&#xff09;

IDEA使用模板创建webapp时,web.xml文件版本过低的一种解决方法

创建完成后的web.xml 文件&#xff0c;版本太低 <!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Appl…

关于优先队列的一点细节

在使用优先队列PriorityQueue时&#xff0c;默认的是升序排列&#xff0c;自己可以指定比较器改为降序排列&#xff0c;例如Collections.reverseOrder()等。 但是在我做力扣的过程中&#xff0c;简单的用一个list的addAll方法添加了优先队列里边所有元素&#xff0c;结果发现添…

浅谈智能照明控制系统在地铁照明中的应用

摘要&#xff1a;随着我国经济建设的加速发展&#xff0c;城市轨道交通越来越获得社会的青睐。车站照明关系到轨道交通的服务质量、运营成本、运营成本等多个方面&#xff0c;在既要保证运营成本又要满足国家“节能”要求的背景下&#xff0c;智能照明控制系统应运而生。 轨道…