贝壳如视三维渲染框架
Five是 贝壳如视(realsee.com) 提供的在浏览器中运行的三维空间渲染 Javascript SDK。您可以通过如视开发者中心的数据服务,并结合Five,制作丰富多彩的三维空间应用。
并且Five提供了一系列的方法、事件、生命周期函数。您可以方便地基于Five进行二次开发,并结合到您自身的项目中,为您的项目添砖加瓦。
Five通过 TypeScript 编写,保证开发的质量以及编程体验,推荐通过 Visual Studio Code、WebStorm 等现代源代码编辑器,您将可以体验到友好的代码提示以及自动补全。
Five还提供了完善的 React Hooks API ,可以方便的通过 React 开发复杂的响应式数据应用。当然直接使用Five开发也是没有问题的。
Five提供通过 npm 的方式安装。Five基于 Three.js, 所以同时您需要安装相关依赖。
目前依赖的three版本为 115 ~ 117 的版本
npm install @realsee/five three@0.117.1
如果您使用React Hooks API,那么也请同时安装React的相关依赖。
npm install @realsee/five three@0.117.1 react react-dom @types/react @types/react-dom
接下来便可以在您的项目使用Five了。
import { Five } from "@realsee/five";
// 如果您使用 React Hoos API
import { useFiveState } from "@realsee/five/react";
Safari | Safari on iOS | Chrome | Chrome for Android | Edge | Firefox |
---|---|---|---|---|---|
>= 9 | >= 9 | >= 49 | >= 93 | >= 13 | >= 45 |
Five提供了快速上手体验的项目生成工具,您可以通过他熟悉Five的功能以及尝试基于Five开发功能。
- 先创建一个文件夹
five-quick-start
, 作为项目根目录并且使用npm init
命令初始化一个工程。
mkdir five-quick-start && cd five-quick-start && npm init -y
- 安装Five
npm install @realsee/five
- 通过Five的内置的
five-quick-start-init
脚本来快速补完当前项目
npx five-quick-start-init
项目的文件结构如下
.
├── README.md
├── assets 静态文件(测试数据)
│ ├── data0.json
│ └── data1.json
├── index.html 页面模版
├── index.tsx 逻辑代码
├── package.json npm 包管理描述
├── tsconfig.json typescript 配置
├── webpack.config.js webpack 开发环境配置
└── webpack.production.js webpack 生产配置
通npm script运行测试环境
npm run dev
默认将会在 port: 3000
开启 webpack dev server
。您也可以在 webpack.config.js
中修改配置。
- 现在可以打来浏览器
http://0.0.0.0:3000
来看看项目初始化的效果了。您可以修改代码来体验一下如何使用Five来二次开发。
Five的简单使用样例:
import { Five } from "@realsee/five";
// 构造函数的具体参数见文档
const five = new Five();
// 将渲染视图的 canvas 添加到 DOM 中
five.appendTo(document.getElementById('app')!);
// 如果显示区域需要变动,在变动时请调用 refresh 重置显示参数
window.addEventListener('resize', () => five.refresh());
// 获取三维空间的 Work 数据,加载进来
// Work 数据可以通过如视开发者中心获取
fetch(`./work.json`)
.then(res => res.json())
.then(work => five.load(work));
其他的 Five API 待补充
在React框架中使用Five的简单使用样例:
import * as React from "react";
import * as ReactDOM from "react-dom";
import {Five, Work, parseWork} from "@realsee/five";
import {createFiveProvider, FiveCanvas} from "@realsee/five/react";
// 创建 Provider, 参数与 new Five 参数类似
// 构造函数的具体参数见文档
const FiveProvider = createFiveProvider();
const App: React.FC = () => {
// 声明 State: Work
const [work, setWork] = React.useState<Work | null>(null);
// 声明 State: Size<{width, height}>
const [size, setSize] = React.useState({
width: window.innerWidth,
height: window.innerHeight
})
// 获取三维空间的 Work 数据,加载进来
const loadWork = React.useCallback((url: string) => {
fetch(url)
.then(res => res.json())
.then(data => setWork(parseWork(data)));
}, []);
React.useEffect(() => {
loadWork("./data0.json");
}, []);
// 如果显示区域需要变动,在变动时请调用 FiveCanvas 的 size
React.useEffect(() => {
const onResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener("resize", onResize, false);
return () => window.removeEventListener("resize", onResize, false);
});
if (work) {
{/*
在 FiveProvider 内承载一个 Five 实例,
在他内部的组件可以使用 Five React Hook API 来获取/设置/操作 他
一个页面中也可以有多个 FiveProvider
*/}
return <FiveProvider initialWork={work}>
{/*
将渲染视图的 canvas 添加到 DOM 中
FiveCanvas 需要出现在 FiveProvider 内,他将渲染 FiveProvider 的视图
这样的设计可以实现不同的 DOM 层级结构
在 FiveProvider 内的其他组件也可以使用 Five React Hook API 来获取/设置/操作 他外部的 FiveProvider
*/}
<FiveCanvas width={size.width} height={size.height}/>
</FiveProvider>
}
return null;
}
ReactDOM.render(<App></App>, document.getElementById("app"));
其他的Five React Hook API文档建设中...
欢迎查看 Five API 文档 。
该文档由 TypeDoc 生成,您可以详细查看 api 使用方式,调用参数,数据结构。
Work是如视开发者中心 提供的对于一个三维空间的描述。 是通过如视硬件设备(如视扫描仪 、如视Lite全景相机 、如视VR App )扫描并处理之后用于三维空间展示的数据。
Work以JSON
作为数据格式Five框架可以解析Work数据并展示。一个Five实例每次可以载入并展示一个Work。并且也可以在不同的Work之间动态切换。
Work的数据样例如下
{
"initial": {
"mode": "Panorama",
"pano_index": 6,
"longitude": 2.6869287662553916,
"latitude": 0,
"fov": 95
},
"model": {
"file_url": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/model\/auto3d-DJaa08PIzN4JYluXQ1j2VS.at3d",
"material_textures": [
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_0.jpg",
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_1.jpg",
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_2.jpg",
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_3.jpg",
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_4.jpg",
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_5.jpg",
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_6.jpg",
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_7.jpg",
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_8.jpg",
"https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/materials\/texture_9.jpg"
]
},
"panorama": {
"list": [
{
"up": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/0\/2257f0f0b29d5b00ff01934ce51aaa35\/0_u.jpg",
"down": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/0\/2257f0f0b29d5b00ff01934ce51aaa35\/0_d.jpg",
"left": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/0\/2257f0f0b29d5b00ff01934ce51aaa35\/0_l.jpg",
"right": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/0\/2257f0f0b29d5b00ff01934ce51aaa35\/0_r.jpg",
"front": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/0\/2257f0f0b29d5b00ff01934ce51aaa35\/0_f.jpg",
"back": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/0\/2257f0f0b29d5b00ff01934ce51aaa35\/0_b.jpg"
},
{
"up": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/1\/ecb554bb1c122fa90186d176ccfecde4\/1_u.jpg",
"down": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/1\/ecb554bb1c122fa90186d176ccfecde4\/1_d.jpg",
"left": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/1\/ecb554bb1c122fa90186d176ccfecde4\/1_l.jpg",
"right": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/1\/ecb554bb1c122fa90186d176ccfecde4\/1_r.jpg",
"front": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/1\/ecb554bb1c122fa90186d176ccfecde4\/1_f.jpg",
"back": "https:\/\/vrlab-public.ljcdn.com\/release\/auto3dhd\/a62e1ebf7d013f7df117551a14af79fc\/images\/cube_2048\/1\/ecb554bb1c122fa90186d176ccfecde4\/1_b.jpg"
}
],
},
"observers": [
{
"visible_nodes": [ 1 ],
"accessible_nodes": [ 1 ],
"quaternion": {
"w": 0.45076583925142194,
"x": 0.010070951976936936,
"y": -0.8925839597148215,
"z": -0.0016154299986102319
},
"standing_position": [
-6.956049919128418,
-1.3924440682333898,
1.6591600179672241
],
"position": [
-6.956049919128418,
-0.10312400013208389,
1.6591600179672241
],
"floor_index": 0
},
{
"visible_nodes": [ 0 ],
"accessible_nodes": [ 0 ],
"index": 1,
"quaternion": {
"w": -0.9884643083591809,
"x": -0.0038900979633806664,
"y": 0.1512670435365699,
"z": -0.006439990839033269
},
"standing_position": [
-6.176340103149414,
-1.380554749576384,
2.179759979248047
],
"position": [
-6.176340103149414,
-0.10025200247764587,
2.179759979248047
],
"floor_index": 0,
}
]
}
Work 的数据说明
-
initial
: 初始化数据,是一个State
数据。描述Work被加载初始状态的位姿,也叫做VR的初始视角- mode: 模态
- pano_index: 初始化点位
- longitude: 相机的水平角
- latitude: 相机的偏航角
- fov: 相机垂直方向的可视角度
-
model
: 三维模型- file_url: 三维模型的资源地址,文件为
.at3d
为如视定制的模型格式 - material_textures: 三维模型的贴图资源地址
- file_url: 三维模型的资源地址,文件为
-
panorama
: 全景彩色信息- list:
- up / down / left / right / front / back: 全景彩色信息以 cubemap 方式存储和使用。
- list:
-
observers
: 采集点信息- visible_nodes: 采集点之间的可见性列表
- accessible_nodes: 采集点之间的连通性列表
- quaternion: 采集点与模型坐标的旋转偏移量
- standing_position: 采集点地面坐标
- position: 采集点坐标
- floor_index: 采集点楼层
State是Five框架用于描述状态的数据结构。他包含了模态、位于的采集点位、相机的方向、相机可视角度的信息。 您可以使用State来操作Five或者获取Five当前的状态。
interface State {
"mode": Five.Mode,
"panoIndex": number,
"longitude": number,
"latitude": number,
"fov": number,
"distanace": number,
"offset": THREE.Vector3,
}
State的数据描述
-
mode
: 当前的模态 Five 常用有 5 种模态,可以使用Five.Mode
获得- Panorama: 全景游走模态,该模态下视图将在采集点间游走,手势操作可以旋转/放大视角/切换采集点,适合查看采集的全景信息。
- Floorplan: 空间总览模态, 该模态下视图以模型为中心,手势操作可以旋转/放大模型/切换楼层,适合查看模型的整体效果。
- Topview: 户型图模态,该模态下视图以模型为中心,垂直俯视模型,手势操作可以平移/放大模型/切换楼层,适合查看模型平面结构。
- Model: 模型游走模态,该模态下视图将在模型中自由游走,手势操作可以旋转/放大视角/位移,适合查看模型的细节,做一些定位操作。
- Mapview: 地图模态, 该模态下视图将类似三维地图,手势操作可以旋转/放大视角/位移,适合查看模型的细节。适合展示小区,工厂等较大的模型。
- VRPanorama: VR 眼镜模态,该模态下可以使用 Cardboard 眼镜 或者他的第三方衍生产品,实现 VR 虚拟显示效果。
- XRPanorama: XR 眼镜模态,该模态下可以使用头戴显示设备比如 Pico, Meta Quest 等,实现沉浸式体验。
-
panoIndex
: 采集点位 -
longitude
/latitude
: 相机的水平角 / 相机的偏航角(弧度),我们使用类似经纬度的方式描述相机位置。 -
fov
: 相机垂直方向的可视角度 (角度) -
distance
: 相机距 offset 的距离 -
offset
: 相机看向的目标
目前涉及到的模型类型 at3d / domez / 3d-tile(b3dm, pnts, glb)
-
world
: five.scene 场景下的坐标。属于 view 下的绝对坐标。坐标的上方向是 Y 轴,单位米。 -
local
: 单个模型的相对坐标。坐标的上方向是 Y 轴,单位米。 单个模型受到work.transform
的影响会产生一定的位姿变化进行项目拼接(比如沙盘项目)。这种情况下,会与world
有一定偏转。 -
enu
: 模型描述坐标(East-North-Up)。坐标的上方向是 Z 轴,单位米。需要在算法模型输入/输出时使用这个坐标系。 -
ecef
: 模型在地球的坐标系(Earth-centered, Earth-fixed)。坐标的上方向是 Z 轴并且固定是北,单位米。 -
lla
: 模型在地球的经纬高度 (Latitude-Longitude-Altitude)。 经纬度单位是弧度,高度单位是米。
转换:
-
local
与world
的转换依赖work.transform
-
local
与enu
的转换是固定的 yUp 转 zUp -
enu
与ecef
依赖tileset.json
中rootMeta.coordinate.pose_ecef_to_enu
/rootMeta.coordinate.pose_enu_to_ecef
。 如果缺损,则使用a=6378137.0;invf=298.257223563
的椭球近似计算 -
lla
通过a=6378137.0;invf=298.257223563
的椭球近似计算
椭球近似计算: (enu
+ lla
) 与 ecef
可以相互推导。
- 在 five 环境下,通过 viewLayer 对象,即 five.models[number].viewLayer[number].scene 转化。提供对应方法。
localToWorld(vector: THREE.Vector3): THREE.Vector3
worldToLocal(vector: THREE.Vector3): THREE.Vector3
localToEnu(vector: THREE.Vector3): THREE.Vector3
enuToLocal(vector: THREE.Vector3): THREE.Vector3
localToEcef(vector: THREE.Vector3): THREE.Vector3
ecefToLocal(vector: THREE.Vector3): THREE.Vector3
localToLla(vector: THREE.Vector3): THREE.Vector3
llaToLocal(vector: THREE.Vector3): THREE.Vector3
worldToEnu(vector: THREE.Vector3): THREE.Vector3
enuToWorld(vector: THREE.Vector3): THREE.Vector3
worldToEcef(vector: THREE.Vector3): THREE.Vector3
ecefToWorld(vector: THREE.Vector3): THREE.Vector3
worldToLla(vector: THREE.Vector3): THREE.Vector3
llaToWorld(vector: THREE.Vector3): THREE.Vector3
这些函数均为直接修改传入 vector
。 如果要保持原先的数据,请实现 vector.clone()
。
通过 intersectRaycaster
方法得到的 Intersection
在 world
坐标系下, 并返回对应的 model
与 viewLayer
。
目前约定为,算法的模型输入输出均使用 enu
坐标系。