细数实现全景图VR的几种方式(panorama/cubemap/eac)
- Three.js系列: 在元宇宙看电影,享受 VR 视觉盛宴
- Three.js系列: 造个海洋球池来学习物理引擎
- Three.js系列: 游戏中的第一、三人称视角
- Three.js系列: 数实现全景图VR的几种方式(panorama/cubemap/eac)
本篇是 Three.js 系列的第四篇内容,想看其他内容可以看上方️,今天就给大家来介绍介绍全景图相关的知识,我们知道因为最近疫情的影响,大家都没办法出门,很多全景的项目火了,比如各个著名的风景区都开放了VR。
除此之外,VR 设备也是非常的火热,在我国 2022年上半年,VR市场销售额突破了 8亿元,同比增长了 81%
而在国外呢,截至 2022 年 Q1 ,已经卖出了1480万台!
因为我们学习制作 VR 技术就是顺势而为,毕竟雷布斯说过,“站在风口上
,猪都可以飞起来”。
接下来我就谈谈目前展示主要有几种形式。目前展示 VR 主要有 3种 主流方式,分别为 建模 、建模 + 全景图 和 全景图
建模 | 建模+全景图 | 全景图 | |
---|---|---|---|
代表作品 | VR游戏 | 贝壳系列看房 | 普通云游览、云游览 |
体验 | 极好 | 好 | 中等 |
我们来实际体验一下他们的差异
以上为 VR游戏《雇佣战士》的体验,视角切换非常的流畅,且场景非常的大,玩过3D类型游戏的朋友就能明白。这种场景都是通过建模来完成,利用 blender、3D Max、maya 等建模软件,再使用Unity、UE 等游戏开发平台,各种效果可以说非常的好。
而到了贝壳这种呢,则是通过建模加上全景图两种方式结合使用,模型和全景图是通过线下采集得到,但是对于这种看房页面,要在 Web 上渲染精细的模型资源消耗是巨大的,因此他们采取了一个折中的方案,就是粗糙模型 + 全景图,通过模型来补间场景切换的突变感,变化过程中明显能感受的掉帧的感觉。虽然效果不如纯手动建模来的精细,但是总的来说体验也非常不错了。
最后这种云游览,则是直接通过两张全景图直接切换得到的,这种方式最为简单,当然效果远远前面两种,但是单张图片的全景视角比起静态的图片而言,也是增加了空间感。
用表格总结起来就是以下:
建模 | 建模+全景图 | 全景图 | |
---|---|---|---|
代表作品 | VR游戏《雇佣战士》 | 贝壳系列看房 | 普通云游览、云游览 |
实现难度 | 很难 | 难 | 简单 |
过渡效果 | 极度真实 | 好 | 一般 |
模型 | Blender、3D Max、maya | 带有光学传感相机 | 普通360相机 |
由于全景图是通过一个个点位拍摄而得到的。因此它无法拥有位置信息,也就是各个点位的依赖关系,因此当在切换场景的时候,我们无法得到沉浸式的过渡效果;而贝壳则是通过利用了模型的补间来改善过渡;VR游戏《雇佣战士》则是纯手动建模,因此效果非常好。
今天我们主要讲解的就是全景图模式(因为它比较简单),当然也不是想象中那么简单,只是相比前两种方式而言,难度是下降了一个坡度,毕竟学习都是从兴趣开始的,一开始来个高难度的,简直就是劝退了。
首先我们先来了解一些前置知识,目前主流全景图都有什么格式?
我翻阅总结后目前最常用的大约为以下三种
- 等距柱状投影格式(Equirectangular)
- 等角度立方体贴图格式(Equi-Angular Cubemap)
- 立方体贴图(Cube Map )
等距柱状投影
也就是最常见的世界地图的投影方式,做法是将经线和纬线等距地(或有疏密地)投影到一个矩形平面上。
这种格式的优点是比较直观,并且投影是矩形的。缺点也很明显,球体的上下两极投影出来的像素数很多,而细节内容比较丰富的赤道区域相比来说像素数就很少,导致还原时清晰度比较糟糕。另外,这种格式的画面在未渲染的情况下扭曲比较明显。
立方体贴图
是另一种全景画面的储存格式,做法是将球体上的内容向外投影到一个立方体上,然后展开,而它对比等距柱状投影的优势是,在相同分辨率下,它的图片体积更小,约为 等距柱状投影 的 1/3
等角度立方体贴图
是谷歌所提出的进一步优化的格式,方法是更改优化投影时的采样点位置,使得边角与中心的像素密度相等。
这样做的好处就是在相同的源视频分辨率下可以提高细节部分的清晰度。排版如下:
我们简单总结一下:
等距柱状投影 | 立方体贴图 | 等角度立方体贴图 | |
---|---|---|---|
图源 | 简单 | 一般 | 难 |
技术实现 | 简单 | 简单 | 一般 |
图片体积 | V | 1/3 V | 1/3 ~ 1/4 V |
图片清晰度 | 一般 | 好 | 更好 |
v为基准体积
接下来就到了我们使用 Three.js 来实现以上效果的时刻了。
等距柱状投影
这种方式实现起来比较简单。首先我们在 https://www.flickr.com/ 找一张全景图。
在前面的介绍中我们可以得到 2:1 的等距投影全景图对应的几何体为球形,还记得我们在前《造一个海洋球》学过,如何来创建一个球体,没错就是 **SphereGeometry**
。
... 省略场景初始化等代码
// 创建一个球体
const geometry = new THREE.SphereGeometry(30, 64, 32);
// 创建贴图, 并设置为红色
const material = new THREE.MeshBasicMaterial({
color: "red",
});
// 创建对象
const skyBox = new THREE.Mesh(geometry, material);
// 添加对象到场景中
scene.add(skyBox);
// 设置在远处观看
camera.position.z = 100
...
然后我们就得到了一个小红球:
嗯,现在为止你已经学会了如果创建一个小红球,接下来还有2个步骤加油!
接下来呢,我们就将我们的2:1的全景图贴到我们的球体上
const material = new THREE.MeshBasicMaterial({
- //color: "red",
+ map: new THREE.TextureLoader().load('./images/panorama/example.jpg')
});
我们就得到了一个类似地球仪的球体。
现在我们要做的,就是我们不想在远处观看这些内容,而是要“身临其境”!
所以我们需要把相机移动到球体的内部,而不是在远处观看
- camera.position.z = 100
+ camera.position.z = 0.01
这个时候我们发现,突然漆黑一篇。
小问题,这是由于在 3d 渲染中,默认物体只会渲染一个面,这也是为了节省性能。当然我们可以也通过让物体默认只渲染内部,这就需要通过声明贴图的法线方向了,过程不是本节课的讨论范围这里只提供一个思路。幸好 Three.js 给我们提供了一个简单的方法 THREE.DoubleSide
,通过这个方法,就能让我们的物体渲染两个面。这样我们即使在物体内部也能看到贴图啦。
const material = new THREE.MeshBasicMaterial({
map: new THREE.TextureLoader().load('./images/panorama/example.jpg'),
+ side: THREE.DoubleSide,
});
现在我们只用了 **SphereGeometry**
球体快速实现了全景的效果。
立方体贴图
立方体贴图就和它的名字一样,我们只需要使用一个立方体就能渲染出来一个全景效果,但是2:1 的全景图肯定是不能直接使用的,我们首先需要通过 工具来进行转化,目前有两种比较方便的方式。
- https://jaxry.github.io/panorama-to-cubemap/
- ffmpeg 5.x 使用命令
ffmpeg -i example.jpg -vf v360=input=equirect:output=c3x2 example-cube.jpg
最终我们可以得到以下6张图
开始来写我们的代码
... 省略场景初始化等代码
// 创建立方体
const box = new THREE.BoxGeometry(1, 1, 1);
// 创建贴图
function getTexturesFromAtlasFile(atlasImgUrl, tilesNum) {
const textures = [];
for (let i = 0; i < tilesNum; i++) {
textures[i] = new THREE.Texture();
}
new THREE.ImageLoader()
.load(atlasImgUrl, (image) => {
let canvas, context;
const tileWidth = image.height;
for (let i = 0; i < textures.length; i++) {
canvas = document.createElement('canvas');
context = canvas.getContext('2d');
canvas.height = tileWidth;
canvas.width = tileWidth;
context.drawImage(image, tileWidth * i, 0, tileWidth, tileWidth, 0, 0, tileWidth, tileWidth);
textures[i].image = canvas;
textures[i].needsUpdate = true;
}
});
return textures;
}
const textures = getTexturesFromAtlasFile( './images/cube/example-cube.jpg', 6 );
const materials = [];
for ( let i = 0; i < 6; i ++ ) {
materials.push( new THREE.MeshBasicMaterial( {
map: textures[ i ],
side: THREE.DoubleSide
} ) );
}
const skyBox = new THREE.Mesh(box, materials);
scene.add(skyBox);
...
这里有一个注意点,就是在 Three.js 中如果有多张贴图,是支持以数组形式传入的,例如此例子中,传入的顺序为 “左右上下前后”
此时我们也得到了上方一样的效果。
等角度立方体贴图
这里也和 cubemap 一样,我们需要通过工具转化才能得到对应格式的图片。这里只需要了 5.x 的 ffmpeg,因为它自带一个 360 filter ,能够处理 EAC 的转化。首先通过以下命令得到一张 eac 的图。
ffmpeg -i example.jpg -vf v360=input=equirect:output=eac example-eac.jpg
这里由于 Three.js 默认不支持 EAC 的渲染,因此我们使用了一个 egjs-view360
来进行渲染 ,原理为自己手写一个 shader 来处理 EAC 这种情况,这里暂时先不展开讲解,过程比较枯燥,后续单开一个章节来说明。
使用 egjs-view360
来渲染 EAC 图,整体比较简单
...省略依赖库
<div class="viewer" id="myPanoViewer">
</div>
<script>
var PanoViewer = eg.view360.PanoViewer;
var container = document.getElementById("myPanoViewer");
var panoViewer = new PanoViewer(container, {
image: "./images/eac/example-eac.jpg",
projectionType: "cubestrip",
cubemapConfig: {
order: "BLFDRU",
tileConfig: [{ rotation: 0 }, { rotation: 0 }, { rotation: 0 }, { rotation: 0 }, { rotation: -90 }, { rotation: 180 }],
trim: 3
}
});
PanoControls.init(container, panoViewer);
PanoControls.showLoading();
</script>
我们最终也能得到以上的结果。
这里再给一组文件体积的数据:(所有图片统一使用了 tinypng 进行了压缩去除无效信息)
最终得出了一个这样的排名:
体验: EAC > CubeMap > Equirectangular
文件体积:CubeMap < EAC < Equirectangular
上手难度 :EAC < CubeMap < Equirectangular
所以如果你想要高体验高画质,那么你就选择 EAC,如果想要带宽小,那么就选择 CubeMap,如果你是个初学者想要快速实现效果,那么就使用 Equirectangular !
以上所有代码均在:https://github.com/hua1995116/Fly-Three.js 中可以找到。
这里最后补充一个小提示,球形贴图的一个好处就是天然地可以作为小行星的展示,例如这种特效。
本期我们通过了从VR的发展现状、VR的几种实现方式,再到通过 Equirectangular、CubeMap、Equi-Angular Cubemap三种全景图来实现 VR 进行了讲解,希望对你有所帮助,我们下期再见。
参考资料
https://www.bilibili.com/read/cv788511
https://www.trekview.org/blog/2021/projection-type-360-photography/
https://jiras.se/ffmpeg/equiangular.html
https://blog.google/products/google-ar-vr/bringing-pixels-front-and-center-vr-video/
https://jiras.se/ffmpeg/mono.html
https://ffmpeg.org/ffmpeg-filters.html#v360
https://blog.csdn.net/qiutiantxwd/article/details/107283224
细数实现全景图VR的几种方式(panorama/cubemap/eac)的更多相关文章
- 刚開始学习的人非常有用:struts2中将jsp数据传到action的几种方式
先给上struts.xml代码: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE strut ...
- 细数iOS上的那些安全防护
细数iOS上的那些安全防护 龙磊,黑雪,蒸米 @阿里巴巴移动安全 0x00 序 随着苹果对iOS系统多年的研发,iOS上的安全防护机制也是越来越多,越来越复杂.这对于刚接触iOS安全的研究人员来说非 ...
- 细数.NET 中那些ORM框架 —— 谈谈这些天的收获之一
细数.NET 中那些ORM框架 —— 谈谈这些天的收获之一(转) ADO.NET Entity Framework ADO.NET Entity Framework 是微软以 ADO.N ...
- 细数Python Flask微信公众号开发中遇到的那些坑
最近两三个月的时间,断断续续边学边做完成了一个微信公众号页面的开发工作.这是一个快递系统,主要功能有用户管理.寄收件地址管理.用户下单,订单管理,订单查询及一些宣传页面等.本文主要细数下开发过程中遇到 ...
- 细数 Windows Phone 灭亡的七宗罪(过程很详细,评论很精彩,但主要还是因为太慢了,生态跟不上,太贪了,厂商不愿意推广)
曾梦想仗剑走天涯,看一看世界的繁华 年少的心有些轻狂,如今你四海为家 曾让你心疼的姑娘,如今已悄然无踪影 犹记得上大学攒钱买了第一台智能手机Lumia 520时,下载的第一首歌曲<曾经的你> ...
- 细数Qt开发的各种坑(欢迎围观)
1:Qt的版本多到你数都数不清,多到你开始怀疑人生.从4.6开始到5.8,从MSVC编译器到MINGW编译器,从32位到64位,从Windows到Linux到MAC.MSVC版本还必须安装对应的VS2 ...
- 迄今最安全的MySQL?细数5.7那些惊艳与鸡肋的新特性(上)【转载】
转自: DBAplus社群 http://www.toutiao.com/m5762164771/ 迄今最安全的MySQL?细数5.7那些惊艳与鸡肋的新特性(上) - 今日头条(TouTiao.com ...
- android企业级商城源码、360°全景图VR源码、全民直播源码等
Android精选源码 [新版]Android技术博客精华汇总 开源了:乐乐音乐5.0-Android音乐播放器 android实现仿真水波纹效果源码 360°全景图VR,这是一个值得把玩的APP a ...
- 180724-统计JVM进程中线程数两种方式小记
I. 统计进程中的线程数 相关系列博文推荐: 180711-JVM定位分析CPU性能消耗 180704-JDK常用监控参数 jvm调优的工具介绍 1. proc查询 /proc 目录以可读文本文件形式 ...
随机推荐
- JVM学习笔记-从底层了解程序运行(一)
1:JVM基础知识 什么是JVM 1. java虚拟机,跨语言的平台,实现java跨平台 2. 可以实现多种语言跨平台,只要该语言可以编译成.class文件 3. 解释执行.class文件 java是 ...
- 微信0day复现
由于微信采用的是google内核,前些日子google爆出0day远程代码执行漏洞,但是需要关闭沙箱,而微信采用的是老版本google内核,默认关闭沙箱,因此只要微信用户点击恶意连接,可直接获取该PC ...
- 【故障公告】取代 memcached 的 redis 出现问题造成网站故障
6月19日开始,我们将博客站点的缓存服务器从 memcached 换成了 redis,稳定运行了3天,今天上午访问高峰突然出现问题,在 11:00-12:30 期间影响了网站的正常访问,由此给您带来麻 ...
- sql-DDL-操作数据库与表
1. 操作数据库:CRUD oracle应该是没有操作数据库的SQL oracl创建数据库通过数据库提供的工具来新建数据库 windows版oracle新建数据库 C(Create):创建 creat ...
- 实现一个Prometheus exporter
Prometheus 官方和社区提供了非常多的exporter,涵盖数据库.中间件.OS.存储.硬件设备等,具体可查看exporters.exporterhub.io,通过这些 exporter 基本 ...
- RT-Thread 组件 FinSH 使用时遇到的问题
一.FinSH 的移植与使用问题 FinSH组件输入无反应的问题 现象:当打开 finsh 组件后,控制台会打相应的信息,如下图说是: \ | / - RT - Thread Operating Sy ...
- (一)java基础篇---第一个程序
先认识java的基础知识 1.变量命名规则 :1)变量名由数字字母下划线组成,2)不能使用java的关键字,比如public这种,3)遵循小驼峰命名法 2.数据类型 2.1基本数据类型有8种 其中分为 ...
- 函数式接口的概念&函数式接口的定义和函数式接口的使用
函数式接口概念 函数式接口在Java中是指:有且仅有一个抽象方法的接口. 函数式接口,即适用于函数式编程场景的接口.而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambd ...
- [HNOI2009]【一本通提高组合数学】有趣的数列
[HNOI2009]有趣的数列 题目描述 我们称一个长度为 2 n 2n 2n 的数列是有趣的,当且仅当该数列满足以下三个条件: 它是从 1 ∼ 2 n 1 \sim 2n 1∼2n 共 2 n 2n ...
- Trie树模板1字符串统计
Trie树模板1字符串统计 我们首先来了解一下字典树,首先看一下一张字典树的图片 字典树就是一个可以高效存储.查找字符串的树,比如上面这个字典树就是存储abc,acb,bac的字典树. 1.插入操作( ...