C++/NDK에서 Azure Spatial Anchors를 사용하여 앵커를 만들고 찾는 방법

Azure Spatial Anchors를 사용하면 다양한 디바이스 간에 전 세계 앵커를 공유할 수 있습니다. 여러 가지 다양한 개발 환경을 지원합니다. 이 문서에서는 다음을 수행하기 위해 C++/NDK에서 Azure Spatial Anchors SDK를 사용하는 방법을 자세히 알아보겠습니다.

  • Azure Spatial Anchors 세션을 올바르게 설정하고 관리합니다.
  • 로컬 앵커에 속성을 만들고 설정합니다.
  • 클라우드에 업로드합니다.
  • 클라우드 공간 앵커를 찾아 삭제합니다.

필수 구성 요소

이 가이드를 완료하려면 다음이 필요합니다.

플랫폼 간

세션 초기화

SDK의 주요 진입점은 세션을 나타내는 클래스입니다. 일반적으로 보기 및 네이티브 AR 세션을 관리하는 클래스에서 필드를 선언합니다.

CloudSpatialAnchorSession 구조체에 대해 자세히 알아봅니다.

    std::shared_ptr<CloudSpatialAnchorSession> cloudSession_;
    // In your view handler
    cloudSession_ = std::make_shared<CloudSpatialAnchorSession>();

인증 설정

서비스에 액세스하려면 계정 키, 액세스 토큰 또는 Azure Active Directory 인증 토큰을 제공해야 합니다. 인증 개념 페이지에서 이에 대한 자세한 내용을 읽을 수도 있습니다.

계정 키

계정 키는 애플리케이션에서 Azure Spatial Anchors 서비스를 사용하여 인증할 수 있도록 하는 자격 증명입니다. 계정 키의 용도는 빨리 시작할 수 있도록 지원합니다. 특히 애플리케이션이 Azure Spatial Anchors와 통합하는 단계에서 더 필요합니다. 따라서 개발 중에 클라이언트 애플리케이션에 계정 키를 포함하여 계정 키를 사용할 수 있습니다. 개발을 넘어 진행할 때는 액세스 토큰에서 지원하는 프로덕션 수준의 인증 메커니즘이나 Azure Active Directory 사용자 인증으로 이동하는 것이 좋습니다. 개발을 위한 계정 키를 가져오려면 Azure Spatial Anchors 계정을 방문하여 "키" 탭으로 이동합니다.

SessionConfiguration 구조체에 대해 자세히 알아봅니다.

    auto configuration = cloudSession_->Configuration();
    configuration->AccountKey(R"(MyAccountKey)");

액세스 토큰

액세스 토큰은 Azure Spatial Anchors를 사용하여 인증하는 보다 강력한 방법입니다. 특히 프로덕션 배포를 위해 애플리케이션을 준비할 때 그렇습니다. 이 방법의 요약은 클라이언트 애플리케이션이 안전하게 인증할 수 있는 백 엔드 서비스를 설정하는 것입니다. 런타임 시 AAD와 Azure Spatial Anchors 보안 토큰 서비스를 사용하여 액세스 토큰을 요청하는 백 엔드 서비스 인터페이스. 그런 다음 이 토큰은 클라이언트 애플리케이션에 전달되고 SDK에서 Azure Spatial Anchors로 인증하는 데 사용됩니다.

    auto configuration = cloudSession_->Configuration();
    configuration->AccessToken(R"(MyAccessToken)");

액세스 토큰이 설정되지 않은 경우 이벤트를 처리 TokenRequired 하거나 대리자 프로토콜에서 tokenRequired 메서드를 구현해야 합니다.

이벤트 인수에서 속성을 설정하여 이벤트를 동기적으로 처리할 수 있습니다.

TokenRequiredDelegate 위임에 대해 자세히 알아봅니다.

    auto accessTokenRequiredToken = cloudSession_->TokenRequired([](auto&&, auto&& args) {
        args->AccessToken(R"(MyAccessToken)");
    });

처리기에서 비동기 작업을 실행해야 하는 경우 다음 예제와 같이 개체를 deferral 요청한 다음 완료하여 토큰 설정을 연기할 수 있습니다.

    auto accessTokenRequiredToken = cloudSession_->TokenRequired([this](auto&&, auto&& args) {
        std::shared_ptr<CloudSpatialAnchorSessionDeferral> deferral = args->GetDeferral();
        MyGetTokenAsync([&deferral, &args](std::string const& myToken) {
            if (myToken != nullptr) args->AccessToken(myToken);
            deferral->Complete();
        });
    });

Azure Active Directory 인증

또한 Azure Spatial Anchors를 사용하면 애플리케이션이 사용자 Azure AD(Active Directory) 토큰으로 인증할 수 있습니다. 예를 들어 Azure AD 토큰을 사용하여 Azure Spatial Anchors와 통합할 수 있습니다. 엔터프라이즈가 Azure AD 사용자를 유지 관리하는 경우 Azure Spatial Anchors SDK에서 사용자 Azure AD 토큰을 제공할 수 있습니다. 이렇게 하면 동일한 Azure AD 테넌트의 일부인 계정에 대해 Azure Spatial Anchors 서비스에 직접 인증할 수 있습니다.

    auto configuration = cloudSession_->Configuration();
    configuration->AuthenticationToken(R"(MyAuthenticationToken)");

액세스 토큰과 마찬가지로 Azure AD 토큰이 설정되지 않은 경우 TokenRequired 이벤트를 처리하거나 대리자 프로토콜에서 tokenRequired 메서드를 구현해야 합니다.

이벤트 인수에서 속성을 설정하여 이벤트를 동기적으로 처리할 수 있습니다.

    auto accessTokenRequiredToken = cloudSession_->AccessTokenRequired([](auto&&, auto&& args) {
        args->AuthenticationToken(R"(MyAuthenticationToken)");
    });

처리기에서 비동기 작업을 실행해야 하는 경우 다음 예제와 같이 개체를 deferral 요청한 다음 완료하여 토큰 설정을 연기할 수 있습니다.

    auto accessTokenRequiredToken = cloudSession_->TokenRequired([this](auto&&, auto&& args) {
        std::shared_ptr<CloudSpatialAnchorSessionDeferral> deferral = args->GetDeferral();
        MyGetTokenAsync([&deferral, &args](std::string const& myToken) {
            if (myToken != nullptr) args->AuthenticationToken(myToken);
            deferral->Complete();
        });
    });

세션 설정

세션에서 환경 데이터를 처리할 수 있도록 하려면 Start()를 호출합니다.

세션에서 발생한 이벤트를 처리하려면 이벤트 처리기를 연결합니다.

Start 메서드에 대해 자세히 알아봅니다.

    cloudSession_->Session(ar_session_);
    cloudSession_->Start();

세션에 프레임 제공

공간 앵커 세션은 사용자 주위의 공간을 매핑하여 작동합니다. 이렇게 하면 앵커가 배치된 위치를 확인하는 데 도움이 됩니다. 모바일 플랫폼(iOS & Android)에서는 플랫폼의 AR 라이브러리에서 프레임을 가져오려면 카메라 피드에 대한 기본 호출이 필요합니다. 반면, HoloLens는 지속적으로 환경을 검색하므로 모바일 플랫폼에서와 같은 특정 호출이 필요하지 않습니다.

ProcessFrame 메서드에 대해 자세히 알아봅니다.

    cloudSession_->ProcessFrame(ar_frame_);

사용자에게 피드백 제공

세션 업데이트 이벤트를 처리하는 코드를 작성할 수 있습니다. 이 이벤트는 세션에서 사용자 환경에 대한 이해가 향상될 때마다 발생합니다. 이렇게 하면 다음과 같은 작업을 수행할 수 있습니다.

  • 디바이스가 이동하고 세션에서 해당 환경의 이해를 업데이트할 때 UserFeedback 클래스를 사용하여 사용자에게 피드백을 제공합니다. 이 작업을 수행하려면
  • 공간 앵커를 만들기에 충분한 추적 공간 데이터가 있는 지점을 확인합니다. ReadyForCreateProgress 또는 RecommendedForCreateProgress를 사용하여 이를 확인합니다. ReadyForCreateProgress가 1을 초과하면 클라우드 공간 앵커를 저장하기에 충분한 데이터가 있지만 이 작업을 수행하려면 RecommendedForCreateProgress가 1을 초과할 때까지 대기하는 것이 좋습니다.

SessionUpdatedDelegate 위임에 대해 자세히 알아봅니다.

    auto sessionUpdatedToken = cloudSession_->SessionUpdated([this](auto&&, auto&& args) {
        auto status = args->Status();
        if (status->UserFeedback() == SessionUserFeedback::None) return;
        std::ostringstream str;
        str << std::fixed << std::setw(2) << std::setprecision(0)
            << R"(Feedback: )" << FeedbackToString(status.UserFeedback()) << R"( -)"
            << R"( Recommend Create=)" << (status->RecommendedForCreateProgress() * 100) << R"(%)";
        feedback_ = str.str();
    });

클라우드 공간 앵커 만들기

클라우드 공간 앵커를 만들려면 먼저 플랫폼의 AR 시스템에서 앵커를 만든 다음, 해당하는 클라우드를 만듭니다. CreateAnchorAsync() 메서드를 사용합니다.

CloudSpatialAnchor 구조체에 대해 자세히 알아봅니다.

    // Create a local anchor, perhaps by hit-testing and creating an ARAnchor
    ArAnchor* localAnchor;
    ArHitResultList* hit_result_list = nullptr;
    ArHitResultList_create(ar_session_, &hit_result_list);
    CHECK(hit_result_list);
    ArFrame_hitTest(ar_session_, ar_frame_, 0.5, 0.5, hit_result_list);
    int32_t hit_result_list_size = 0;
    ArHitResultList_getSize(ar_session_, hit_result_list, &hit_result_list_size);
    if (hit_result_list_size == 0) {
        ArHitResultList_destroy(hit_result_list);
        return;
    }
    ArHitResult* ar_hit = nullptr;
    ArHitResult_create(ar_session_, &ar_hit);
    // The hitTest method sorts the resulting list by increasing distance from the camera
    // The first hit result will usually be the most relevant when responding to user input
    ArHitResultList_getItem(ar_session_, hit_result_list, 0, ar_hit);
    if (ArHitResult_acquireNewAnchor(ar_session_, ar_hit, &localAnchor) != AR_SUCCESS) return;
    ArTrackingState tracking_state = AR_TRACKING_STATE_STOPPED;
    ArAnchor_getTrackingState(ar_session_, localAnchor, &tracking_state);
    if (tracking_state != AR_TRACKING_STATE_TRACKING) {
        ArAnchor_release(localAnchor);
        ArHitResult_destroy(ar_hit);
        return;
    }
    ArHitResult_destroy(ar_hit);
    ar_hit = nullptr;
    ArHitResultList_destroy(hit_result_list);
    hit_result_list = nullptr;

    // If the user is placing some application content in their environment,
    // you might show content at this anchor for a while, then save when
    // the user confirms placement.
    std::shared_ptr<CloudSpatialAnchor> cloudAnchor = std::make_shared<CloudSpatialAnchor>();
    cloudAnchor->LocalAnchor(localAnchor);
    cloudSession_->CreateAnchorAsync(cloudAnchor, [this, cloudAnchor](Status status) {
        std::ostringstream str;
        if (status != Status::OK) {
            str << "Save Failed: " << std::to_string(static_cast<uint32_t>(status));
            feedback_ = str.str();
            return;
        }
        str << R"(Created a cloud anchor with ID=)" << cloudAnchor->Identifier();
        feedback_ = str.str();
    });

앞에서 설명한 것처럼 새 클라우드 공간 앵커를 만들기 전에 캡처된 충분한 환경 데이터가 필요합니다. 즉, ReadyForCreateProgress가 1보다 커야 하지만 RecommendedForCreateProgress가 1보다 커질 때까지 기다리는 것이 좋습니다.

GetSessionStatusAsync 메서드에 대해 자세히 알아봅니다.

    cloudSession_->GetSessionStatusAsync([this](Status status, const std::shared_ptr<SessionStatus>& value) {
        if (status != Status::OK) {
            std::ostringstream str;
            str << "Session status error: " << std::to_string(static_cast<uint32_t>(status));
            feedback_ = str.str();
            return;
        }
        if (value->RecommendedForCreateProgress() < 1.0f) return;
        // Issue the creation request ...
    });

속성 설정

클라우드 공간 앵커를 저장할 때 몇 가지 속성을 추가하도록 선택할 수 있습니다. 저장되는 개체의 형식이나 상호 작용에 대해 활성화해야 하는지 여부와 같은 기본 속성입니다. 이렇게 하면 검색 시 유용할 수 있습니다. 예를 들어 빈 콘텐츠가 있는 그림 프레임과 같이 사용자에 대한 개체를 즉시 렌더링할 수 있습니다. 그런 다음, 백그라운드에서 다른 다운로드를 하면 추가 상태 세부 정보(예: 프레임에 표시할 그림)가 표시됩니다.

AppProperties 메서드에 대해 자세히 알아봅니다.

    std::shared_ptr<CloudSpatialAnchor> cloudAnchor = std::make_shared<CloudSpatialAnchor>();
    cloudAnchor->LocalAnchor(localAnchor);
    auto properties = cloudAnchor->AppProperties();
    properties->Insert(R"(model-type)", R"(frame)");
    properties->Insert(R"(label)", R"(my latest picture)");
    cloudSession_->CreateAnchorAsync(cloudAnchor, [this, cloudAnchor](Status status) {
        // ...
    });

속성 업데이트

앵커의 속성을 업데이트하려면 메서드를 UpdateAnchorProperties() 사용합니다. 두 개 이상의 디바이스가 동일한 앵커에 대한 속성을 동시에 업데이트하려고 하면 낙관적 동시성 모델을 사용합니다. 즉, 첫 번째 쓰기가 승리합니다. 다른 모든 쓰기에는 "동시성" 오류가 발생합니다. 다시 시도하기 전에 속성을 새로 고쳐야 합니다.

UpdateAnchorPropertiesAsync 메서드에 대해 자세히 알아봅니다.

    std::shared_ptr<CloudSpatialAnchor> anchor = /* locate your anchor */;
    auto properties = anchor->AppProperties();
    properties->Insert(R"(last-user-access)", R"(just now)");
    cloudSession_->UpdateAnchorPropertiesAsync(anchor, [this](Status status) {
        if (status != Status::OK) {
            std::ostringstream str;
            str << "Updating Properties Failed: " << std::to_string(static_cast<uint32_t>(status));
            feedback_ = str.str();
        }
    });

서비스에서 앵커를 만든 후에는 앵커의 위치를 업데이트할 수 없습니다. 새 앵커를 만들고 이전 앵커를 삭제하여 새 위치를 추적해야 합니다.

해당 속성을 업데이트하기 위해 앵커를 찾을 필요가 없는 경우 속성이 있는 개체를 GetAnchorPropertiesAsync() 반환하는 메서드를 CloudSpatialAnchor 사용할 수 있습니다.

GetAnchorPropertiesAsync 메서드에 대해 자세히 알아봅니다.

    cloudSession_->GetAnchorPropertiesAsync(R"(anchorId)", [this](Status status, const std::shared_ptr<CloudSpatialAnchor>& anchor) {
        if (status != Status::OK) {
            std::ostringstream str;
            str << "Getting Properties Failed: " << std::to_string(static_cast<uint32_t>(status));
            feedback_ = str.str();
            return;
        }
        if (anchor != nullptr) {
            auto properties = anchor->AppProperties();
            properties->Lookup(R"(last-user-access)") = R"(just now)";
            cloudSession_->UpdateAnchorPropertiesAsync(anchor, [this](Status status) {
                // ...
            });
        }
    });

Set expiration

나중에 지정된 날짜에 자동으로 만료되도록 앵커를 구성할 수도 있습니다. 앵커가 만료되면 더 이상 찾거나 업데이트되지 않습니다. 만료는 클라우드에 저장하기 전에 앵커가 만들어질 때만 설정할 수 있습니다. 이후에는 만료를 업데이트할 수 없습니다. 앵커를 만드는 동안 만료가 설정되지 않은 경우 앵커는 수동으로 삭제할 때만 만료됩니다.

Expiration 메서드에 대해 자세히 알아봅니다.

    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
    std::chrono::system_clock::time_point oneWeekFromNow = now + std::chrono::hours(7 * 24);
    const int64_t oneWeekFromNowUnixEpochTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(oneWeekFromNow.time_since_epoch()).count();
    cloudAnchor->Expiration(oneWeekFromNowUnixEpochTimeMs);

클라우드 공간 앵커 찾기

이전에 저장된 클라우드 공간 앵커를 찾는 것은 Azure 공간 앵커를 사용하는 주요 이유 중 하나입니다. 이를 위해 "감시자"를 사용합니다. 한 번에 하나의 Watcher만 사용할 수 있습니다. 여러 감시자는 지원되지 않습니다. 감시자가 클라우드 공간 앵커를 찾을 수 있는 여러 가지 방법( 앵커 찾기 전략이라고도 함)이 있습니다. 감시자에서 한 번에 한 가지 전략을 사용할 수 있습니다.

  • 식별자로 앵커를 찾습니다.
  • 이전에 찾은 앵커에 연결된 앵커를 찾습니다. 여기에서 앵커 관계에 대해 알아볼 수 있습니다.
  • 광역 위치 재결정을 사용하여 앵커를 찾습니다.

참고

앵커를 찾을 때마다 Azure Spatial Anchors는 수집된 환경 데이터를 사용하여 앵커에 대한 시각적 정보를 보강하려고 합니다. 앵커를 찾는 데 문제가 있는 경우 앵커를 만든 다음, 서로 다른 각도와 조명 조건에서 여러 번 배치하는 것이 유용할 수 있습니다.

식별자를 통해 클라우드 공간 앵커를 찾는 경우 애플리케이션의 백 엔드 서비스에 클라우드 공간 앵커 식별자를 저장하고 올바르게 인증할 수 있는 모든 디바이스에서 액세스할 수 있도록 할 수 있습니다. 이에 대한 예제는 자습서: 여러 디바이스 간에 Spatial Anchors 공유를 참조하세요.

AnchorLocateCriteria 개체를 인스턴스화하고, 찾고자 하는 식별자를 설정하고, AnchorLocateCriteria를 제공하여 세션에서 CreateWatcher 메서드를 호출합니다.

CreateWatcher 메서드에 대해 자세히 알아봅니다.

    auto criteria = std::make_shared<AnchorLocateCriteria>();
    criteria->Identifiers({ R"(id1)", R"(id2)", R"(id3)" });
    auto cloudSpatialAnchorWatcher = cloudSession_->CreateWatcher(criteria);

감시자를 만든 후에는 요청된 모든 앵커에 대해 AnchorLocated 이벤트가 발생합니다. 이 이벤트는 앵커가 있는 경우 또는 앵커를 찾을 수 없는 경우에 발생합니다. 이러한 상황이 발생하는 경우 이유가 상태에 명시됩니다. 감시자의 모든 앵커를 처리하거나, 찾거나, 찾지 못하게 되면 LocateAnchorsCompleted 이벤트가 발생합니다. 감시자당 식별자는 35개로 제한됩니다.

AnchorLocated 위임에 대해 자세히 알아봅니다.

    auto anchorLocatedToken = cloudSession_->AnchorLocated([this](auto&&, auto&& args) {
        switch (args->Status()) {
            case LocateAnchorStatus::Located: {
                std::shared_ptr<CloudSpatialAnchor> foundAnchor = args->Anchor();
                // Go add your anchor to the scene...
            }
                break;
            case LocateAnchorStatus::AlreadyTracked:
                // This anchor has already been reported and is being tracked
                break;
            case LocateAnchorStatus::NotLocatedAnchorDoesNotExist:
                // The anchor was deleted or never existed in the first place
                // Drop it, or show UI to ask user to anchor the content anew
                break;
            case LocateAnchorStatus::NotLocated:
                // The anchor hasn't been found given the location data
                // The user might in the wrong location, or maybe more data will help
                // Show UI to tell user to keep looking around
                break;
        }
    });

앵커 삭제

더 이상 사용하지 않을 때 앵커를 삭제하는 것은 Azure 리소스를 정리하기 위해 개발 프로세스와 관행 초기에 포함하는 것이 좋습니다.

DeleteAnchorAsync 메서드에 대해 자세히 알아봅니다.

    cloudSession_->DeleteAnchorAsync(cloudAnchor, [this](Status status) {
        // Perform any processing you may want when delete finishes
    });

세션 일시 중지, 재설정 또는 중지

세션을 일시적으로 중지하려면 Stop()을 호출하면 됩니다. 이렇게 하면 ProcessFrame()을 호출하는 경우에도 감시자 및 환경 처리가 중지됩니다. 그런 다음, Start()를 호출하여 처리를 다시 시작할 수 있습니다. 다시 시작하는 경우 세션에서 이미 캡처된 환경 데이터가 유지 관리됩니다.

Stop 메서드에 대해 자세히 알아봅니다.

    cloudSession_->Stop();

세션에서 캡처된 환경 데이터를 다시 설정하려면 를 호출 Reset()할 수 있습니다.

Reset 메서드에 대해 자세히 알아봅니다.

    cloudSession_->Reset();

세션이 모든 참조를 해제한 후 제대로 정리하려면

    cloudSession_ = nullptr;

다음 단계

이 가이드에서는 Azure Spatial Anchors SDK를 사용하여 앵커를 만들고 찾는 방법을 알아보았습니다. 앵커 관계에 대해 자세히 알아보려면 다음 가이드를 계속 진행하세요.