
이전 포스팅에서는 물체를 Octahedral 좌표계로 변환하고, 각 방향에서 렌더링한 결과를 하나의 Atlas로 구성했다.

이제 현재 카메라 방향에 맞는 Atlas의 프레임을 선택하도록 만들면 Impostor가 된다.
그러기 위해서 우선 카메라의 방향 벡터를 Octahedral 공간으로 변환한다.
FVector2f AOctahedralImpostorActor::DirectionToOctahedralUV(const FVector& Dir) const
{
FVector N = Dir.GetSafeNormal();
const float Denom = FMath::Abs(N.X) + FMath::Abs(N.Y) + FMath::Abs(N.Z);
if (Denom < KINDA_SMALL_NUMBER)
{
return FVector2f::ZeroVector;
}
// L1 normalization
N /= Denom;
FVector2f UV(N.X, N.Y);
if (N.Z < 0.0f)
{
const float OldX = UV.X;
UV.X = FMath::Sign(OldX) * (1.0f - FMath::Abs(UV.Y));
UV.Y = FMath::Sign(UV.Y) * (1.0f - FMath::Abs(OldX));
}
return UV; // [-1, 1] range
}
Octahedral UV는 [-1, 1] 범위를 가지므로 이를 [0, 1]로 변환한 뒤, Atlas 해상도에 맞게 가장 가까운 프레임 좌표를 구한다.
FIntPoint AOctahedralImpostorActor::GetNearestFrameIndexFromViewDir(const FVector& ViewDir) const
{
const FVector2f OctUV = DirectionToOctahedralUV(ViewDir);
// [-1,1] → [0,1]
const FVector2f UV01 = (OctUV + FVector2f(1.0f, 1.0f)) * 0.5f;
const int32 X = FMath::Clamp(
FMath::RoundToInt(UV01.X * (AtlasResolution - 1)),
0,
AtlasResolution - 1
);
const int32 Y = FMath::Clamp(
FMath::RoundToInt(UV01.Y * (AtlasResolution - 1)),
0,
AtlasResolution - 1
);
return FIntPoint(X, Y);
}
현재 카메라 위치를 기준으로 방향 벡터를 계산하고, 그에 맞는 Atlas 프레임을 Material에 전달한다.
void AOctahedralImpostorActor::UpdateImpostorFrame()
{
if (!ImpostorMID || !GetWorld())
{
return;
}
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (!PC || !PC->PlayerCameraManager)
{
return;
}
const FVector TargetCenter = GetTargetCenter();
const FVector CameraPos = PC->PlayerCameraManager->GetCameraLocation();
// View direction
const FVector ViewDir = (CameraPos - TargetCenter).GetSafeNormal();
const FIntPoint FrameCoord = GetNearestFrameIndexFromViewDir(ViewDir);
ImpostorMID->SetScalarParameterValue(TEXT("FrameCoordX"), FrameCoord.X);
ImpostorMID->SetScalarParameterValue(TEXT("FrameCoordY"), FrameCoord.Y);
ImpostorMID->SetScalarParameterValue(TEXT("FramesX"), AtlasResolution);
ImpostorMID->SetScalarParameterValue(TEXT("FramesY"), AtlasResolution);
ImpostorMID->SetTextureParameterValue(TEXT("AtlasTexture"), AtlasRT);
}
Impostor는 항상 카메라를 바라보도록 회전시켜야 한다.
void AOctahedralImpostorActor::UpdateImpostorBillboard()
{
if (!ImpostorPlaneComp || !GetWorld())
{
return;
}
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (!PC || !PC->PlayerCameraManager)
{
return;
}
const FVector PlanePos = GetTargetCenter();
const FVector CameraPos = PC->PlayerCameraManager->GetCameraLocation();
FRotator FaceRot = (CameraPos - PlanePos).Rotation();
// Plane 보정
FaceRot += FRotator(-90.0f, 0.0f, 0.0f);
ImpostorPlaneComp->SetWorldLocation(PlanePos);
ImpostorPlaneComp->SetWorldRotation(FaceRot);
}
Octahedral Impostor를 적용한 결과, 카메라의 위치가 변화함에 따라 Atlas에서 적절한 방향의 이미지가 선택되며, 실제 3D 모델이 회전하는 것처럼 자연스러운 시점 변화가 표현된다.
단순한 Billboard 방식과 달리 여러 방향에서 미리 캡처된 이미지를 기반으로 하기 때문에 측면이나 후면에서도 보다 입체적인 느낌을 유지할 수 있으며, 실제 메시를 렌더링하지 않고 텍스처만 샘플링하기 때문에 성능 비용 또한 매우 낮다. 특히 멀리 있는 오브젝트나 대량 배치가 필요한 환경에서 효율적인 LOD 기법으로 활용할 수 있다.

다만 현재 방식은 가장 가까운 프레임을 선택하는 구조이기 때문에 특정 각도에서 이미지가 급격히 전환되는 popping 현상이 발생할 수 있다.


현재는 단일 프레임만을 선택해서 보여주기 때문에 Popping 현상이 발생한다.
이를 없애고 자연스럽게 3D 물체처럼 보이기 위해서는 다음과 같은 개선들을 거치면 된다.
- 가장 인접한 3개의 프레임을 기반으로 무게중심 보간
- 프레임마다 개별적인 투영(가상 프레임 투영)을 수행
- 카메라 중심 기준이 아닌 픽셀 단위 시점 방향과 경계 구 교차를 활용한 방향 계산으로 샘플링 정확도를 높임.
- 단일 프레임 기반 시차 오프셋(parallax offset)을 적용해 깊이감을 보정
- 알베도·노멀·머티리얼 정보를 활용한 PBR 기반 라이팅을 도입
출처 : hemi-octahedral impostors