背景 ———— 写于2023.12.5
最近在系统性的学习Three.js,也在跟大家分享我的学习进度,当我学习完一些基础,开始试着做一些小项目时,我发现Three.js特别吃电脑的配置,刚开始做项目的时候,并不明显,但是随着模型和贴图的不断增加,我的Three.js页面也变得一卡一卡的,每次启动项目,风扇都开始呼啸,当打开资源管理器时,发现电脑的内存和CPU占用率接近爆表,无奈只能暂停项目的开发。
停更原因
因为上述背景所讲,以及本人要准备毕业设计以及毕业答辩等内容,暂时搁置了Three.js的学习分享,但是毕业设计还是有使用Three.js进行开发,开发的内容主要是酒店房间3D渲染,为用户提供远程沉浸式的看房功能。这块功能,我主要用了两种实现方式,下面我将简单的介绍一下。
第一种方式
直接引用HDR格式的全景图,对房间内容进行3D展示
优点
- 简单直接:使用全景图片(HDR格式/EXR格式)可以非常方便地创建一个360°的视图。
- 高质量:HDR(高动态范围)图片可以提供更高的颜色深度和更广泛的亮度范围,使得场景看起来更加真实。
- 兼容性强:全景图片格式在VR领域非常普遍,有很多现成的工具和技术可以支持它们的创建和显示。
- 性能较好:由于全景图片是预渲染的,因此渲染性能通常较高,尤其是在移动设备上。
缺点
- 交互性有限:全景图片通常只提供静态的视图,不支持复杂的交互(如开门、开关灯等)。
- 数据量大:HDR图片(10mb-500mb)通常比普通图片文件体积占用更大,这将会增加加载时间和带宽需求。
- 更新困难:如果需要修改场景中的某个部分,必须重新渲染整个全景图片。
代码实现
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),所以可能不是很高清
HDR资源分享
-
HDRI Haven
- 网址: hdrihaven.com/
- 提供免费的HDRI贴图和纹理,无需注册即可下载,包含16K高清贴图。
-
Hdrmaps
- 网址: hdrmaps.com/freebies
- 提供付费和免费的HDRI贴图材质,只有在“freebies”版块的资源才是免费的。
-
Hdri-skies
- 网址: hdri-skies.com/
- 专业的HDRI天空贴图网站,提供2K分辨率360度HDRI天空,支持免费下载。
-
Textures.com
- 网址: textures.com/
- 提供广泛的纹理、贴图和HDRI资源,不是所有资源都是免费的。
-
Poly Haven
- 网址: polyhaven.com/
- 公共3D资源库,包括HDRI、Texture和3D Model三种资源,所有资源都基于CC0协议,可以免费商用。
第二种方式
使用6张图片进行拼接,对应正方体的六个面,将相机的看向位置置于正方体中间,即可实现3D展示功能
原理图
优点
- 灵活性高:通过将多张图片拼接在一起,可以创建复杂的场景,并支持更丰富的交互
- 易于更新:只需要替换或修改特定的图片,而不需要重新渲染整个场景。
- 文件大小可控:可以根据需要选择适当分辨率的图片,以控制文件大小和加载时间。
缺点
- 实现复杂:需要编写额外的代码来处理图片的拼接和视角的转换。
- 性能开销:在渲染时,可能需要更多的计算资源来处理多个纹理和视角转换。
- 可能存在接缝:如果图片拼接不当,可能会在接缝处出现明显的视觉差异。
代码实现
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>
结果展示
原理验证
写在最后
本系列将持续更新Three.js以及WebGL相关的知识分享,喜欢的同学可以关注或者订阅一下本系列,你们的支持就是我更新的动力噢~