实例三维模型
贡献者
Sean Lilley, \@lilleyse
Patrick Cozzi, \@pjcozzi
翻译
- ChrisWang, @ecnuzlwang
综述
实例三维模型 是用于高效流化和渲染大量三维模型(称为实例,模型通常很少有变化)的瓦片格式。最简单的例子就是不同地点的树木实例。每一个实例都索引同一个模型,但却拥有不同的属性,比如位置。在使用核心3D Tiles标准语言时,每一个实例就是一个要素。
除了树木,实例三维模型也适用于外部的要素,比如消防栓、窨井盖、路灯和交通灯等地物。也可以实例化内部要素,比如螺钉、阀门和电气外线出口
组合瓦片可以包含不同类型的实例模型,比如同时包含树木和交通灯等。
实例三维模型能很好的映射到ANGLE_instanced_arrays 拓展模块,用于WebGL的高效渲染。
结构
一个瓦片有文件头和紧随其后的文件主体部分构成。
图1: 实例三维模型结构(虚线代表可选填字段)
Header
头部有28字节,包含下列字段:
字段名 |
数据类型 |
描述 |
|
4-byte ANSI string |
"i3dm". 用来指定arraybuffer为实例三维模型瓦片 |
|
|
实例三维模型的版本号。目前为1 |
|
|
整体瓦片的大小,以字节为单位 |
|
|
JSON格式的要素表格的长度,以字节为单位 |
|
|
二进制要素表格的字节长度,如果 |
|
JSON格式的批表格的长度,以字节为单位。0代表没有批表格 |
|
|
|
二进制批表格的字节长度,如果 |
|
说明文件体内glTF字段的格式。0代表url;1代表嵌入二进制glTF。参考下文的glTF部分。 |
featureTableJSONByteLength为0,或者没有glTF,则瓦片就不会被渲染。
文件体紧随文件头之后,包含三个字段:Feature Table, Batch Table和glTF.
要素表格
包含用于生成实例模型i3dm 语义的值
语法
实例语法
这类语法映射到用于生成实例要素的数组。这些数组的长度必须相等,而且与实例数目相同
如果一个语义A依赖另一个语义B,那么语义B必须定义。如果SCALE 和 SCALE_NON_UNIFORM两者都定义了,那么两者都会被应用。如果POSITION 和 POSITION_QUANTIZED 在实例中都定义了,会使用较高精度的POSITION。如果NORMAL_UP,NORMAL_RIGHT,NORMAL_UP_OCT32P和NORMAL_RIGHT_OCT32P在实例中都定义了,更高精度的NORMAL_UP和NORMAL_RIGHT会被使用。
语义 |
数据类型 |
描述 |
是否必填 |
|
|
包含实例位置x,y,z笛卡尔坐标的三元素数组 |
|
|
|
包含实例位置x,y,z网格化笛卡尔坐标的三元素数组 |
|
|
|
定义实例上方向的单位向量 |
|
|
|
定义实例右方向的单位向量 |
|
|
|
定义32位精度的实例上方向单位向量(oct-encoded) |
|
|
|
定义32位精度的实例上方向单位向量(oct-encoded,将Cartesian3变成Cartesian2),必须与上方向正交 |
|
|
|
定义缩放比例的数字(每个轴都相同) |
|
|
|
定义缩放比例的三元数组(三个轴不同) |
|
|
|
|
|
全局语义
全局语义应用于所有实例
语义 |
数据类型 |
描述 |
是否必填 |
|
|
实例的数目,用于生成实例属性中的数组对象 |
|
|
|
定义网格体的( |
|
|
|
定义网格体的( |
|
实例方向
实例的方向由一组标准正交基(上方向和右方向)的单位向量定义。如果NORMAL_UP和NORMAL_RIGHT,或者NORMAL_UP_OCT32P和NORMAL_RIGHT_OCT32P 没有定义,实例默认会根据tileset的transform属性和实例的WGS84经纬度坐标,使用东北上参考系的方向。法向量就是tileset中transform属性的逆转置矩阵。
标准基中的x 向量映射为转换后基的右向量。y向量则是映射为上向量。z 向量为前向量。但是前向量通常都是上向量和右向量的叉积,所以此处省略。
图 2:标准基中的盒子模型
图 3:旋转后的盒子模型
Oct-encoded法向量
如果实例没有定义NORMAL_UP 和 NORMAL_RIGHT,它的方向可以定义在oct-encoded法向量,NORMAL_UP_OCT32P 和 NORMAL_RIGHT_OCT32P之中。这种用oct-encoding定义上方向和右方向的方法在 A
Survey of Efficient Representations of Independent Unit Vectors by Cigolle et
al.中详细描述了。在Cesium的AttributeCompression 模块可以找到这种编码和解码的应用实例代码。
实例位置
在应用其他transform矩阵之前,POSITION 定义了实例的位置。
量化位置
如果实例没有POSITION 属性,它的位置也可以定义在POSITION_QUANTIZED ,该属性定义了相对于网格体的位置信息。 如果POSITION 和 POSITION_QUANTIZED 都没有定义,那就不会创建实例。
网格体由offset 和 scale 定义,用于将网格化的位置映射到模型空间中。
图 4:基于offset 和 scale 的网格体
Offset存储在全局语义QUANTIZED_VOLUME_OFFSET中,scale 存储在全局语义QUANTIZED_VOLUME_SCALE中。如果两者没有定义,那POSITION_QUANTIZED 不能使用。
网格化位置可以通过以下公式映射到模型空间:
POSITION = POSITION_QUANTIZED * QUANTIZED_VOLUME_SCALE +
QUANTIZED_VOLUME_OFFSET
实例缩放
通过SCALE 和 SCALE_NON_UNIFORM 语义可以实现缩放。 SCALE 用于各轴缩放比例相等的缩放,而SCALE_NON_UNIFORM 用于x, y, 和 z 轴缩放比例相互独立的缩放。
例子
下面的例子展示了如果为要素表格生成JSON和二进制buffer。
只有位置信息
在这个小例子中,我们在单位长度的方格四个顶点各放置了一个实例,方向默认。
var featureTableJSON = {
INSTANCES\_LENGTH : 4,
POSITION : {
byteOffset : 0
}
};
var featureTableBinary = new Buffer(new Float32Array([
0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 0.0, 1.0,
1.0, 0.0, 1.0
]).buffer);
网格化位置和 Oct-Encoded法向量
在这个例子中,四个实例的朝向由oct-encoded的上向量 [0.0, 1.0, 0.0] 和右向量 [1.0, 0.0, 0.0]定义,被放置在x,z轴范围为-250到250的网格体中
var featureTableJSON = {
INSTANCES_LENGTH : 4,
QUANTIZED_VOLUME_OFFSET : [-250.0, 0.0, -250.0],
QUANTIZED_VOLUME_SCALE : [500.0, 0.0, 500.0],
POSITION_QUANTIZED : {
byteOffset : 0
},
NORMAL_UP_OCT32P : {
byteOffset : 24
},
NORMAL_RIGHT_OCT32P : {
byteOffset : 40
}
};
var positionQuantizedBinary = new Buffer(new Uint16Array([
0, 0, 0,
65535, 0, 0,
0, 0, 65535,
65535, 0, 65535
]).buffer);
var normalUpOct32PBinary = new Buffer(new Uint16Array([
32768, 65535,
32768, 65535,
32768, 65535,
32768, 65535
]).buffer);
var normalRightOct32PBinary = new Buffer(new Uint16Array([
65535, 32768,
65535, 32768,
65535, 32768,
65535, 32768
]).buffer);
var featureTableBinary = Buffer.concat([positionQuantizedBinary, normalUpOct32PBinary, normalRightOct32PBinary]);
批表格Batch Table
批表格是一个包含JSON的
UTF-8格式字符串。它紧跟着文件,可以用TextDecoder JavaScript
API从arraybuffer中提取出来,然后用 JSON.parse转换成JavaScript对象。
对象中每个属性都是一个数组,数组长度与header.batchLength相等。数组元素可以是任意JSON数据类型,包括对象和数组。元素也可以是null。
一个实例的batchId 用于获取每个数组中对应元素的属性。比如,下面实例中包含两个实例的属性:
{
"id" : ["unique id", "another unique id"],
"displayName" : ["Tree species", "Another tree species"],
"yearPlanted" : [1999, 2003],
"location" : [{"x" : 1, "y" : 2}, {"x" : 3, "y" : 4}]
}
batchId = 0 的实例,属性如下:
id[0] = 'unique id';
displayName[0] = 'Tree species';
yearBuilt[0] = 1999;
location[0] = {x : 1, y : 2};
batchId = 1 的属性为:
id[1] = 'another unique id';
displayName[1] = 'Another tree species';
yearBuilt[1] = 2003;
location[1] = {x : 3, y : 4};
glTF
glTF字段写在batch table之后(如果header.batchTableByteLength为0,则在文件头之后)。
glTF 是WebGL的实时数据格式,Binary
glTF 是glTF的拓展,为glTF定义了一个二进制容器。实例三维模型使用glTF
1.0标准和KHR_binary_glTF 拓展.
header.gltfFormat 定义glTF字段的格式。0代表glTF字段是
- 一个utf-8字符串,包含glTF模型的url
当header.gltfFormat是1时,glTF字段为
- 包含二进制glTF的二进制大对象
以上两个实例中,header.gltfByteLength 内包含的都是glTF字段的字节长度。
文件拓展名
.i3dm
MIME类型
application/octet-stream
资源
A Survey of Efficient Representations of Independent Unit Vectors by
Cigolle et al.Mesh Geometry Compression for Mobile Graphics by Jongseok Lee et
al.Cesium AttributeCompression module
for oct-encoding
应用实例
Cesium
由经纬度和高度生成方向向量
var position = Cartesian3.fromRadians(longitude, latitude, height);
// Compute the up vector
var up = new Cartesian3();
var transform = Transforms.eastNorthUpToFixedFrame(position);
var rotation = new Matrix3();
Matrix4.getRotation(transform, rotation);
// In east-north-up, the up vector is stored in the z component
Matrix3.multiplyByVector(rotation, Cartesian3.UNIT\_Z, up);
// Compute the right and forward vectors
var right = new Cartesian3();
var forward = Cartesian3.clone(Cartesian3.UNIT\_Z);
// You can change the orientation of a model by rotating about the y-axis first
var orient = new Matrix3();
var orientAngle = CesiumMath.fromDegrees(90.0);
Matrix3.fromRotationY(orientAngle, orient);
Matrix3.multiplyByVector(orient, forward, forward);
// Cross up and forward to get right
Cartesian3.cross(up, forward, right);
Cartesian3.normalize(right, right);
为实例构建一个模型矩阵
// Cross right and up to get forward
var forward = new Cartesian3();
Cartesian3.cross(right, up, forward);
// Place the basis into a rotation matrix
var rotation = new Matrix3();
Matrix3.setColumn(rotation, 0, right, rotation);
Matrix3.setColumn(rotation, 1, up, rotation);
Matrix3.setColumn(rotation, 2, forward, rotation);
// Get the scale
var scale = Matrix3.IDENTITY.clone();
if (defined(uniformScale)) {
Matrix3.multiplyByScalar(scale, uniformScale, scale);
}
if (defined(nonUniformScale)) {
Matrix3.multiplyByVector(scale, nonUniformScale, scale);
}
// Generate the model matrix
var modelMatrix = new Matrix4();
Matrix4.fromTranslationRotationScale(new TranslationRotationScale(position, rotation, scale),modelMatrix);