Three.js第十一课-利用前十章所学知识实践

377 阅读5分钟

背景 ———— 写于2023.12.5

最近在系统性的学习Three.js,也在跟大家分享我的学习进度,当我学习完一些基础,开始试着做一些小项目时,我发现Three.js特别吃电脑的配置,刚开始做项目的时候,并不明显,但是随着模型和贴图的不断增加,我的Three.js页面也变得一卡一卡的,每次启动项目,风扇都开始呼啸,当打开资源管理器时,发现电脑的内存和CPU占用率接近爆表,无奈只能暂停项目的开发。

image.png

image.png

image.png

停更原因

因为上述背景所讲,以及本人要准备毕业设计以及毕业答辩等内容,暂时搁置了Three.js的学习分享,但是毕业设计还是有使用Three.js进行开发,开发的内容主要是酒店房间3D渲染,为用户提供远程沉浸式的看房功能。这块功能,我主要用了两种实现方式,下面我将简单的介绍一下。

第一种方式

直接引用HDR格式的全景图,对房间内容进行3D展示

优点

  1. 简单直接:使用全景图片(HDR格式/EXR格式)可以非常方便地创建一个360°的视图。
  2. 高质量:HDR(高动态范围)图片可以提供更高的颜色深度和更广泛的亮度范围,使得场景看起来更加真实。
  3. 兼容性强:全景图片格式在VR领域非常普遍,有很多现成的工具和技术可以支持它们的创建和显示。
  4. 性能较好:由于全景图片是预渲染的,因此渲染性能通常较高,尤其是在移动设备上。

缺点

  1. 交互性有限:全景图片通常只提供静态的视图,不支持复杂的交互(如开门、开关灯等)。
  2. 数据量大:HDR图片(10mb-500mb)通常比普通图片文件体积占用更大,这将会增加加载时间和带宽需求。
  3. 更新困难:如果需要修改场景中的某个部分,必须重新渲染整个全景图片。

代码实现

HTML部分

<template>
  <div class="container" ref="container"></div>
</template>

JS部分

<script setup>
// 导入相关包文件
import * as three from "three";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { ref, onMounted } from "vue";
// 创建场景,相机
const scene = new three.Scene();
const camera = new three.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

camera.position.z = 0.1;
scene.add(camera);
// 创建材质
const spheregeometry = new three.SphereGeometry(5, 32, 32);
const loader = new RGBELoader();
// 加载HDR等相关素材
loader.load("./imgs/VRhdr/HDRI_Hen-Hotel_Room_4k.hdr", (texture) => {
  const material = new three.MeshBasicMaterial({ map: texture });
  const sphere = new three.Mesh(spheregeometry, material);
  sphere.geometry.scale(1, 1, -1);
  scene.add(sphere);
});

// 初始化渲染器
const renderer = new three.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);

const container = ref(null);

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

// 挂载完毕之后获取dom
onMounted(() => {
  // 添加轨道控制器
  const controls = new OrbitControls(camera, container.value);
  controls.enableDamping = true;
  container.value.appendChild(renderer.domElement);
  render();
});
</script>

CSS部分

<style scoped>
* {
  margin: 0;
  padding: 0;
}

.container {
  height: 100vh;
  width: 100vw;
  background-color: #f0f0f0;
}
</style>

结果展示

为了节约项目内存,选的这个HDR文件比较小(12mb),所以可能不是很高清 room.gif

HDR资源分享

  1. HDRI Haven

    • 网址: hdrihaven.com/
    • 提供免费的HDRI贴图和纹理,无需注册即可下载,包含16K高清贴图。
  2. Hdrmaps

    • 网址: hdrmaps.com/freebies
    • 提供付费和免费的HDRI贴图材质,只有在“freebies”版块的资源才是免费的。
  3. Hdri-skies

    • 网址: hdri-skies.com/
    • 专业的HDRI天空贴图网站,提供2K分辨率360度HDRI天空,支持免费下载。
  4. Textures.com

    • 网址: textures.com/
    • 提供广泛的纹理、贴图和HDRI资源,不是所有资源都是免费的。
  5. Poly Haven

    • 网址: polyhaven.com/
    • 公共3D资源库,包括HDRI、Texture和3D Model三种资源,所有资源都基于CC0协议,可以免费商用。

第二种方式

使用6张图片进行拼接,对应正方体的六个面,将相机的看向位置置于正方体中间,即可实现3D展示功能

原理图

AHMQ7F%89GJSR)X8KN%OY9I.png

qq.png

优点

  1. 灵活性高:通过将多张图片拼接在一起,可以创建复杂的场景,并支持更丰富的交互
  2. 易于更新:只需要替换或修改特定的图片,而不需要重新渲染整个场景。
  3. 文件大小可控:可以根据需要选择适当分辨率的图片,以控制文件大小和加载时间。

缺点

  1. 实现复杂:需要编写额外的代码来处理图片的拼接和视角的转换。
  2. 性能开销:在渲染时,可能需要更多的计算资源来处理多个纹理和视角转换。
  3. 可能存在接缝:如果图片拼接不当,可能会在接缝处出现明显的视觉差异。

代码实现

HTML部分

<template>
  <div class="container" ref="container"></div>
</template>

JS部分

<script setup>
// 导入相关包文件
import * as three from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { ref, onMounted } from "vue";
// 定义场景,相机,物体
let scene, camera, renderer, cube;
let ww = window.innerWidth;
let wh = window.innerHeight;
const container = ref(null);
// 创建材质
const bedroom = {
  name: "bedroom",
  picture: ["bedroom_l", "bedroom_r", "bedroom_u", "bedroom_d", "bedroom_b", "bedroom_f"],
};
// 初始化场景
scene = new three.Scene();
function init() {
  // 初始化相机
  camera = new THREE.PerspectiveCamera(75, ww / wh, 0.1, 1000);
  // 设置相机位置
  camera.position.z = 0.1;
  // 初始化渲染器
  renderer = new THREE.WebGLRenderer();
  renderer.setSize(ww, wh);
  // 保证渲染清晰度
  renderer.setPixelRatio(window.devicePixelRatio); 
}
init();

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

function addListen() {
  // 监听画面变化
  window.addEventListener("resize", () => {
    const iwidth = window.innerWidth;
    const iheigt = window.innerHeight;
    //更新相机
    camera.aspect = iwidth / iheigt;
    // 更新相机投影矩阵
    camera.updateProjectionMatrix();
    // 更新渲染器
    renderer.setSize(iwidth, iheigt);
    // 设置渲染器像素比
    renderer.setPixelRatio(window.devicePixelRatio);
  });
}

// 添加正方体
const geometry = new THREE.BoxGeometry(10, 10, 10);
// 创建物体
function addCube(arr) {
  // 创建材质数组
  const boxMaterials = [];
  arr.picture.forEach((item, index) => {
    boxMaterials.push(
      loadTexture(`./imgs/VRimg/${item}.jpg`, index)
    );
  });
  cube = new three.Mesh(geometry, boxMaterials);
  scene.add(cube);
}
addCube(bedroom);
cube.geometry.scale(1, 1, -1);
cube.rotation.y = 3.5;

// 把图片加工成材料
function loadTexture(url, index = 0) {
  let texture = new three.TextureLoader().load(url);
  let boxMaterial;
  if (index == "2" || index == "3") {
    // 如果是上和下图,则进行旋转
    texture.rotation = Math.PI;
    texture.center = new three.Vector2(0.5, 0.5);
    boxMaterial = new three.MeshBasicMaterial({
      map: texture,
    });
  } else {
    boxMaterial = new three.MeshBasicMaterial({
      map: texture,
    });
  }

  return boxMaterial;
}

onMounted(() => {
  // 添加轨道控制器
  const controls = new OrbitControls(camera, container.value);
  // 开启控制器滑动阻尼
  controls.enableDamping = true; 
  container.value.appendChild(renderer.domElement);
  render(scene);
  addListen();
});
</script>

CSS部分

<style scoped>
* {
  margin: 0;
  padding: 0;
}

.container {
  height: 100vh;
  width: 100vw;
  background-color: #f0f0f0;
}
</style>

结果展示

room2.gif

原理验证

room3.gif

写在最后

本系列将持续更新Three.js以及WebGL相关的知识分享,喜欢的同学可以关注或者订阅一下本系列,你们的支持就是我更新的动力噢~