由于单精度浮点数的精度问题,使得相机在近距离浏览离世界原点非常远的物体时会发生抖动现象。
目前的解决方案是:通过GLM库的双精度数据类型来保存原始的高精度坐标数值,适时改变UE4中世界原点的位置,重新计算在新的原点下的相对位置。
Camera has moved too far from the origin, move the origin.
当相机距离原点很远的时候,重新设置原点。
ACesiumGeoreference
:提供场景内坐标参考的类,关联所有具有地理参考的Actor。初始化世界原点,并通过判断和相机的距离重新更新世界原点。
void ACesiumGeoreference::_performOriginRebasing() {
bool isGame = this->GetWorld()->IsGameWorld();
const FIntVector& originLocation = this->GetWorld()->OriginLocation;
if (isGame && this->WorldOriginCamera) {
const FMinimalViewInfo& pov = this->WorldOriginCamera->ViewTarget.POV;
const FVector& cameraLocation = pov.Location;
if (this->KeepWorldOriginNearCamera &&
(!this->_insideSublevel || this->OriginRebaseInsideSublevels) &&
!cameraLocation.Equals(
FVector(0.0f, 0.0f, 0.0f),
this->MaximumWorldOriginDistanceFromCamera))
{
// Camera has moved too far from the origin, move the origin.
this->GetWorld()->SetNewWorldOrigin(FIntVector(
static_cast<int32>(cameraLocation.X) +
static_cast<int32>(originLocation.X),
static_cast<int32>(cameraLocation.Y) +
static_cast<int32>(originLocation.Y),
static_cast<int32>(cameraLocation.Z) +
static_cast<int32>(originLocation.Z)));
}
}
}
新的世界原点更新后,对场景的两类物体应用世界偏移(WorldOffset)。一类是地形数据的Mesh(UCesium3DTilesetRoot),另一类是放置在场景中的带地理参考的模型Mesh(UCesiumGeoreferenceComponent)。
virtual void ApplyWorldOffset(const FVector& InOffset, bool bWorldShift) override;
UCesium3DTilesetRoot::ApplyWorldOffset(const FVector& InOffset,bool bWorldShift);
UCesiumGeoreferenceComponent::ApplyWorldOffset(const FVector& InOffset,bool bWorldShift);
void UCesium3DTilesetRoot::ApplyWorldOffset(
const FVector& InOffset,
bool bWorldShift) {
USceneComponent::ApplyWorldOffset(InOffset, bWorldShift);
const FIntVector& oldOrigin = this->GetWorld()->OriginLocation;
this->_worldOriginLocation = glm::dvec3(
static_cast<double>(oldOrigin.X) - static_cast<double>(InOffset.X),
static_cast<double>(oldOrigin.Y) - static_cast<double>(InOffset.Y),
static_cast<double>(oldOrigin.Z) - static_cast<double>(InOffset.Z));
// Do _not_ call _updateAbsoluteLocation. The absolute position doesn't change
// with an origin rebase, and we'll lose precision if we update the absolute
// location here.
this->_updateTilesetToUnrealRelativeWorldTransform();
}
根据新的UE4 世界原点 ,计算物体的ECEF坐标转换到UE4相对坐标的转换矩阵。
void UCesium3DTilesetRoot::_updateTilesetToUnrealRelativeWorldTransform() {
ACesium3DTileset* pTileset = this->GetOwner<ACesium3DTileset>();
if (!IsValid(pTileset->Georeference)) {
this->_tilesetToUnrealRelativeWorld = glm::dmat4(1.0);
this->_isDirty = true;
return;
}
const glm::dmat4& ellipsoidCenteredToUnrealWorld =
pTileset->Georeference->GetEllipsoidCenteredToUnrealWorldTransform();
glm::dvec3 relativeLocation =
this->_absoluteLocation - this->_worldOriginLocation;
FMatrix tilesetActorToUeLocal =
this->GetComponentToWorld().ToMatrixWithScale();
glm::dmat4 ueAbsoluteToUeLocal = glm::dmat4(
glm::dvec4(
tilesetActorToUeLocal.M[0][0],
tilesetActorToUeLocal.M[0][1],
tilesetActorToUeLocal.M[0][2],
tilesetActorToUeLocal.M[0][3]),
glm::dvec4(
tilesetActorToUeLocal.M[1][0],
tilesetActorToUeLocal.M[1][1],
tilesetActorToUeLocal.M[1][2],
tilesetActorToUeLocal.M[1][3]),
glm::dvec4(
tilesetActorToUeLocal.M[2][0],
tilesetActorToUeLocal.M[2][1],
tilesetActorToUeLocal.M[2][2],
tilesetActorToUeLocal.M[2][3]),
glm::dvec4(relativeLocation, 1.0));
this->_tilesetToUnrealRelativeWorld =
ueAbsoluteToUeLocal * ellipsoidCenteredToUnrealWorld;
this->_isDirty = true;
}
最后对GltfMeshComponent
作RelativeTransform
,以保持场景的物体的相对位置关系。
// Called every frame
void ACesium3DTileset::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
UCesium3DTilesetRoot* pRoot = Cast<UCesium3DTilesetRoot>(this->RootComponent);
if (!pRoot) {
return;
}
if (pRoot->IsTransformChanged()) {
// 是否更新的相对位置
this->UpdateTransformFromCesium(
this->GetCesiumTilesetToUnrealRelativeWorldTransform());
pRoot->MarkTransformUnchanged();
}
///
......
///
}
void UCesiumGltfPrimitiveComponent::UpdateTransformFromCesium(
const glm::dmat4& CesiumToUnrealTransform) {
this->SetUsingAbsoluteLocation(true);
this->SetUsingAbsoluteRotation(true);
this->SetUsingAbsoluteScale(true);
const glm::dmat4x4& transform =
CesiumToUnrealTransform * this->HighPrecisionNodeTransform;
/**
void ACesium3DTileset::Tick(float DeltaTime){
...
...
...
if (Gltf->GetAttachParent() == nullptr)
{
Gltf->AttachToComponent(
this->RootComponent,
FAttachmentTransformRules::KeepRelativeTransform);
}
...
...
...
}
*/
// 规则为相对位置
this->SetRelativeTransform(FTransform(FMatrix(
FVector(transform[0].x, transform[0].y, transform[0].z),
FVector(transform[1].x, transform[1].y, transform[1].z),
FVector(transform[2].x, transform[2].y, transform[2].z),
FVector(transform[3].x, transform[3].y, transform[3].z))));
}
void UCesiumGeoreferenceComponent::ApplyWorldOffset(
const FVector& InOffset,
bool bWorldShift) {
// USceneComponent::ApplyWorldOffset will call OnUpdateTransform, we want to
// ignore it since we don't have to recompute everything on origin rebase.
this->_ignoreOnUpdateTransform = true;
USceneComponent::ApplyWorldOffset(InOffset, bWorldShift);
const FIntVector& oldOrigin = this->GetWorld()->OriginLocation;
this->_worldOriginLocation = glm::dvec3(
static_cast<double>(oldOrigin.X) - static_cast<double>(InOffset.X),
static_cast<double>(oldOrigin.Y) - static_cast<double>(InOffset.Y),
static_cast<double>(oldOrigin.Z) - static_cast<double>(InOffset.Z));
// Do _not_ call _updateAbsoluteLocation. The absolute position doesn't change
// with an origin rebase, and we'll lose precision if we update the absolute
// location here.
this->_updateRelativeLocation();
this->_updateActorToUnrealRelativeWorldTransform();
if (this->FixTransformOnOriginRebase) {
this->_setTransform(this->_actorToUnrealRelativeWorld);
}
}
//计算ECEF坐标到UE4相对坐标的变换矩阵
void UCesiumGeoreferenceComponent::
_updateActorToUnrealRelativeWorldTransform() {
if (!this->Georeference) {
return;
}
const glm::dmat4& ecefToUnrealWorld =
this->Georeference->GetEllipsoidCenteredToUnrealWorldTransform();
glm::dmat4 absoluteToRelativeWorld(
glm::dvec4(1.0, 0.0, 0.0, 0.0),
glm::dvec4(0.0, 1.0, 0.0, 0.0),
glm::dvec4(0.0, 0.0, 1.0, 0.0),
glm::dvec4(-this->_worldOriginLocation, 1.0));
this->_actorToUnrealRelativeWorld =
absoluteToRelativeWorld * ecefToUnrealWorld * this->_actorToECEF;
}
// 应用变换
void UCesiumGeoreferenceComponent::_setTransform(const glm::dmat4& transform) {
if (!this->GetWorld()) {
return;
}
// We are about to get an OnUpdateTransform callback for this, so we
// preemptively mark down to ignore it.
_ignoreOnUpdateTransform = true;
this->_ownerRoot->SetWorldTransform(
FTransform(FMatrix(
FVector(transform[0].x, transform[0].y, transform[0].z),
FVector(transform[1].x, transform[1].y, transform[1].z),
FVector(transform[2].x, transform[2].y, transform[2].z),
FVector(transform[3].x, transform[3].y, transform[3].z))),
false,
nullptr,
TeleportWhenUpdatingTransform ? ETeleportType::TeleportPhysics
: ETeleportType::None);
// TODO: try direct setting of transformation, may work for static objects on
// origin rebase
/*
this->_ownerRoot->SetRelativeLocation_Direct(
FVector(transform[3].x, transform[3].y, transform[3].z));
this->_ownerRoot->SetRelativeRotation_Direct(FMatrix(
FVector(transform[0].x, transform[0].y, transform[0].z),
FVector(transform[1].x, transform[1].y, transform[1].z),
FVector(transform[2].x, transform[2].y, transform[2].z),
FVector(0.0, 0.0, 0.0)
).Rotator());
this->_ownerRoot->SetComponentToWorld(this->_ownerRoot->GetRelativeTransform());
*/
}