上篇:3dTile技术研究-概念详述(1)
为了支持局部坐标系,比如,这样位于一个城市tileset中的一个建筑的tileset就可以定义在它自己的局部坐标系中,同样在建筑云中的点云tile可以定义在其自己的坐标系中。每个tile都有一个可选的transform
属性。
transform
属性是一个4x4的仿射矩阵,以列优先顺序存储,它从tile的局部坐标系转换到tile的parent坐标系中,而对于root tile来说就是转换到tileset的坐标系中。
需要将transform
应用于
tile.content
transform
to account for correct vector transforms when scale is used.content.boundingVolume
, except when content.boundingVolume.region
is defined, which is explicitly in EPSG:4979 coordinates.tile.boundingVolume
, except when tile.boundingVolume.region
is defined, which is explicitly in EPSG:4979 coordinates.tile.viewerRequestVolume
, except when tile.viewerRequestVolume.region
is defined, which is explicitly in EPSG:4979 coordinates.如是前面博客说的那样,feature、model更多的指基本对象,比如构件。
transform
属性不需要作用于geometricError
,也就是transform
中的缩放因子不需要作用于geometricError
,geometricError
总以米定义。
当没有定义 transform
时,它默认是单位矩阵:
[
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0
]
从每个tile的局部坐标系到tileset的全局坐标系的转换是通过对tileset进行自上而下的遍历,以及通过将child的transform
与其parent的进行后乘运算来计算的,就像计算机图形学中的传统场景图或节点层次结构那样。
图形学中的矩阵运算一般都是后乘,即m1 × m2 × point 意味着先将point通过m2进行一次转换,再对结果(仍是一个point)通过m1进行转换,
换一种角度(m1 × m2)× point 并不意味着先将m1作用于point,实际上表达的是先将m2作用于point,再对将m1作用于前述运算的结果。只不过对于数学运算来说是后乘的。
可参考博主的其他文章
两种方法推导二维旋转矩阵
三维旋转矩阵推导
[缩放矩阵]缩放矩阵推导
Batched 3D Model 和 Instanced 3D Model tiles 嵌入了glTF,glTF定义了它自己的节点层次结构并且使用Y-UP的坐标系。所有针对tile格式的转换矩阵包括tile.transform
属性须在glTF的矩阵计算后才能应用。
首先,glTF节点层次矩阵通过 glTF specification所述的那样被应用。
其次,对于3D Tiles与z-up一致的坐标系来说,在运行时必须将glTF从y-up 转换到 z-up。这可以通过将模型绕着x轴旋转π/2弧度来完成。同等的,应用如下的转换矩阵(以行优先方式展示):
[
1.0, 0.0, 0.0, 0.0,
0.0, 0.0, -1.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0
]
更广泛的说,转换矩阵的次数是:
RTC_CENTER
which is used to translate model vertices.实现注意项: 当使用本身就是z-up的源数据,比如在 WGS 84 坐标系或是一个局部的z-up坐标系的数据,一个通用的工作流程是:
- Mesh 数据, 包括 positions 和 normals, 不需要修改 - 它们已是z-up的。
- 将root节点的矩阵指定为列优先的从z-up转换到y-up的。这将源数据转换到glTF要求的y-up坐标系中。
- 在运行时,通过上述的矩阵将glTF从y-up转换回z-up。上面的矩阵就被有效的抵消了。
Example glTF root node:
"nodes": [ { "matrix": [1,0,0,0,0,0,-1,0,0,1,0,0,0,0,0,1], "mesh": 0, "name": "rootNode" } ]
有关tileset的计算转换的示例(在上面的代码中的transformToRoot
),请考虑:
每个tile的计算转换矩阵为:
TO
: [T0]
T1
: [T0][T1]
T2
: [T0][T2]
T3
: [T0][T1][T3]
T4
: [T0][T1][T4]
在tile内容中的positions 和 normals也可能有特定于tile的转换矩阵应用于它们,在应用tile的转换矩阵之前(前面提到的对于仿射变换要后乘)。一些例子是:
b3dm
和 i3dm
tile中嵌入glTF,后者定义了自己的节点层次和坐标系。tile.transform
在这些转换计算后再应用。请参阅glTF转换。i3dm
的 Feature Table定义了每个实例的 position, normals, 和 scales。这些用于创建按实例的4x4仿射变换矩阵,该矩阵在应用tile.transform
之前应用于每个实例。i3dm
和pnts
的Feature Tables中的POSITION_QUANTIZED
,在pnts中的NORMAL_OCT16P,
在任何其他变换矩阵之前需要被解压缩。因此,以上示例的完整计算转换为:
TO
: [T0]
T1
: [T0][T1]
T2
: [T0][T2][pnts-specific transform, including RTC_CENTER (if defined)]
T3
: [T0][T1][T3][b3dm-specific transform, including RTC_CENTER (if defined), coordinate system transform, and glTF node hierarchy]
T4
: [T0][T1][T4][i3dm-specific transform, including per-instance transform, coordinate system transform, and glTF node hierarchy]
实现的例子:
本章节是非规范性的
下面的JavaScript代码展示了如何使用Cesium的Matrix4和Matrix3去计算这些。
function computeTransforms(tileset) {
var t = tileset.root;
var transformToRoot = defined(t.transform) ? Matrix4.fromArray(t.transform) : Matrix4.IDENTITY;
computeTransform(t, transformToRoot);
}
function computeTransform(tile, transformToRoot) {
// Apply 4x4 transformToRoot to this tile's positions and bounding volumes
var inverseTransform = Matrix4.inverse(transformToRoot, new Matrix4());
var normalTransform = Matrix4.getRotation(inverseTransform, new Matrix3());
normalTransform = Matrix3.transpose(normalTransform, normalTransform);
// Apply 3x3 normalTransform to this tile's normals
var children = tile.children;
var length = children.length;
for (var i = 0; i < length; ++i) {
var child = children[i];
var childToRoot = defined(child.transform) ? Matrix4.fromArray(child.transform) : Matrix4.clone(Matrix4.IDENTITY);
childToRoot = Matrix4.multiplyTransformation(transformToRoot, childToRoot, childToRoot);
computeTransform(child, childToRoot);
}
}
虽然关于矩阵的这部分内容较多,概念和细节描述也较多,但整体上逻辑相对清晰简洁,需要注意理解。
传送:
3dTile技术研究-开篇
3dTile技术研究-概述
3dTile技术研究-概念详述(1)