Android NDK용 Google Cardboard 빠른 시작

이 가이드에서는 Android용 Cardboard SDK를 사용하여 자체 가상 현실 (VR) 환경을 만드는 방법을 설명합니다.

Cardboard SDK를 사용하면 스마트폰을 VR 플랫폼으로 전환할 수 있습니다. 스마트폰은 입체 렌더링으로 3D 장면을 표시하고, 머리 움직임을 추적하여 반응하며, 사용자가 뷰어 버튼을 누를 때 이를 감지하여 앱과 상호작용할 수 있습니다.

먼저 Cardboard SDK의 핵심 기능을 보여주는 데모 게임인 HelloCardboard를 사용합니다. 게임에서 사용자는 가상 세계를 둘러보고 사물을 찾고 수집합니다. 이 도구는 다음을 수행하는 방법을 보여줍니다.

  • 개발 환경 설정
  • 데모 앱 다운로드 및 빌드
  • 매개변수를 저장하려면 Cardboard 뷰어의 QR 코드를 스캔하세요.
  • 사용자의 머리 움직임 추적
  • 각 눈에 맞는 뷰 투영 매트릭스를 설정하여 입체 영상 렌더링

HelloCardboard는 Android NDK를 사용합니다. 모든 네이티브 메서드는 다음과 같은 특징이 있습니다.

  • HelloCardboardApp 클래스 메서드에 고유하게 제한됨 또는
  • 해당 클래스의 인스턴스를 만들거나 삭제합니다.

개발 환경 설정

하드웨어 요구사항:

  • Android 8.0 'Oreo' (API 수준 26) 이상을 실행하는 Android 기기
  • Cardboard 뷰어

소프트웨어 요구사항:

  • Android 스튜디오 버전 2022.1.1 'Electric Eel' 이상
  • Android SDK 13.0 'Tiramisu' (API 수준 33) 이상
  • 최신 버전의 Android NDK 프레임워크

    설치된 SDK를 검토하거나 업데이트하려면 Preferences > Appearance and Behavior로 이동하세요.

    Android 스튜디오의 System Settings(시스템 설정) > Android SDK

데모 앱 다운로드 및 빌드

Cardboard SDK는 각 셰이더에 사전 컴파일된 Vulkan 헤더 파일을 사용하여 빌드됩니다. 헤더 파일을 처음부터 빌드하는 단계는 여기에서 확인할 수 있습니다.

  1. 다음 명령어를 실행하여 GitHub에서 Cardboard SDK 및 HelloCardboard 데모 앱을 클론합니다.

    git clone https://github.com/googlevr/cardboard.git
  2. Android 스튜디오에서 Open an existing Android Studio Project를 선택한 다음 Cardboard SDK 및 HelloCardboard 데모 앱이 클론된 디렉터리를 선택합니다.

    코드가 Android 스튜디오의 Project 창에 표시됩니다.

  3. Cardboard SDK를 어셈블하려면 Gradle 탭의 cardboard/:sdk/Tasks/build 폴더 (View > Tool Windows > Gradle)에 있는 조합 옵션을 더블클릭합니다.

  4. 휴대전화에서 Run > Run...을 선택하여 HelloCardboard 데모 앱을 실행하고 hellocardboard-android 타겟을 선택합니다.

QR 코드 스캔

기기 매개변수를 저장하려면 Cardboard 뷰어에서 QR 코드를 스캔합니다.

사용자가 'SKIP'를 누르고 이전에 저장된 매개변수가 없는 경우 Cardboard는 Google I/O 2014에서 출시된 Google Cardboard v1 (Google I/O 2014에서 출시) 매개변수를 저장합니다.

데모 사용해 보기

HelloCardboard에서 3D 공간에서 최단 거리 구를 찾아 수집합니다.

구를 찾아서 수집하는 방법:

  1. 떠다니는 도형이 보일 때까지 머리를 아무 방향으로나 움직입니다.

  2. 구체를 똑바로 바라봅니다. 색상이 변경됩니다.

  3. Cardboard 뷰어 버튼을 눌러 구를 '수집'합니다.

기기 구성

사용자가 톱니바퀴 아이콘을 탭하여 Cardboard 뷰어를 전환하면 nativeSwitchViewer 메서드가 호출됩니다. nativeSwitchViewerCardboardQrCode_scanQrCodeAndSaveDeviceParams를 호출하여 뷰어의 QR 코드를 스캔하는 창을 엽니다. QR 코드를 스캔하면 뷰어의 렌즈 왜곡 및 기타 매개변수가 업데이트됩니다.

// Called by JNI method
void HelloCardboardApp::SwitchViewer() {
  CardboardQrCode_scanQrCodeAndSaveDeviceParams();
}

Android 스튜디오 x86 에뮬레이터 사용 설정

Android 스튜디오 x86 에뮬레이터용으로 빌드하려면 SDK샘플build.gradle 파일에서 다음 줄을 삭제합니다.

abiFilters 'armeabi-v7a', 'arm64-v8a'

이렇게 하면 모든 ABI가 사용 설정되고 생성된 .aar 파일의 크기가 크게 증가합니다. 자세한 내용은 Android ABI를 참고하세요.

머리 추적

헤드 추적기 만들기

헤드 추적기는 HelloCardboardApp의 생성자에서 한 번 생성됩니다.

HelloCardboardApp::HelloCardboardApp(JavaVM* vm, jobject obj, jobject asset_mgr_obj) {
  Cardboard_initializeAndroid(vm, obj); // Must be called in constructor
  head_tracker_ = CardboardHeadTracker_create();
}

VrActivity가 생성되면 nativeOnCreate 메서드를 호출하여 HelloCardboardApp 클래스의 인스턴스가 생성됩니다.

public void onCreate(Bundle savedInstance) {
  super.onCreate(savedInstance);
  nativeApp = nativeOnCreate(getAssets());
  //...
}

헤드 추적기 일시중지 및 다시 시작

헤드 추적기를 일시중지, 재개, 삭제하려면 CardboardHeadTracker_pause(head_tracker_), CardboardHeadTracker_resume(head_tracker_), CardboardHeadTracker_destroy(head_tracker_)를 각각 호출해야 합니다. 'HelloCardboard' 앱에서는 nativeOnPause, nativeOnResume, nativeOnDestroy에서 호출합니다.

// Code to pause head tracker in hello_cardboard_app.cc

void HelloCardboardApp::OnPause() { CardboardHeadTracker_pause(head_tracker_); }

// Call nativeOnPause in VrActivity
@Override
protected void onPause() {
  super.onPause();
  nativeOnPause(nativeApp);
  //...
}

// Code to resume head tracker in hello_cardboard_app.cc
void HelloCardboardApp::onResume() {
  CardboardHeadTracker_resume(head_tracker_);
  //...
}

// Call nativeOnResume in VrActivity
@Override
protected void onResume() {
  super.onResume();
  //...
  nativeOnResume(nativeApp);
}

// Code to destroy head tracker in hello_cardboard_app.cc
HelloCardboardApp::~HelloCardboardApp() {
  CardboardHeadTracker_destroy(head_tracker_);
  //...
}

// Call nativeOnDestroy in VrActivity
@Override
protected void onDestroy() {
  super.onDestroy();
  nativeOnDestroy(nativeApp);
  nativeApp = 0;
}

렌즈 왜곡

Cardboard가 새 QR 코드를 스캔할 때마다 다음 코드는 저장된 매개변수를 읽고 이를 사용하여 렌즈 왜곡 객체를 만듭니다. 그러면 렌더링된 콘텐츠에 적절한 렌즈 왜곡이 적용됩니다.

CardboardQrCode_getSavedDeviceParams(&buffer, &size);

CardboardLensDistortion_destroy(lens_distortion_);
lens_distortion_ = CardboardLensDistortion_create(
    buffer, size, screen_width_, screen_height_);

CardboardQrCode_destroy(buffer);

렌더링

Cardboard에서 콘텐츠를 렌더링하는 작업은 다음과 같습니다.

  • 텍스처 만들기
  • 왼쪽 및 오른쪽 눈의 뷰 및 투영 행렬 가져오기
  • 렌더기 만들기 및 왜곡 메시 설정
  • 각 프레임 렌더링

텍스처 만들기

모든 콘텐츠는 왼쪽 및 오른쪽 눈의 섹션으로 분할된 텍스처에 그려집니다. 이러한 섹션은 _leftEyeTexture_rightEyeTexture에서 각각 초기화됩니다.

void HelloCardboardApp::GlSetup() {
  LOGD("GL SETUP");

  if (framebuffer_ != 0) {
    GlTeardown();
  }

  // Create render texture.
  glGenTextures(1, &texture_);
  glBindTexture(GL_TEXTURE_2D, texture_);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, screen_width_, screen_height_, 0,
               GL_RGB, GL_UNSIGNED_BYTE, 0);

  left_eye_texture_description_.texture = texture_;
  left_eye_texture_description_.left_u = 0;
  left_eye_texture_description_.right_u = 0.5;
  left_eye_texture_description_.top_v = 1;
  left_eye_texture_description_.bottom_v = 0;

  right_eye_texture_description_.texture = texture_;
  right_eye_texture_description_.left_u = 0.5;
  right_eye_texture_description_.right_u = 1;
  right_eye_texture_description_.top_v = 1;
  right_eye_texture_description_.bottom_v = 0;

  //...
  CHECKGLERROR("GlSetup");
}

이러한 텍스처는 매개변수로 CardboardDistortionRenderer_renderEyeToDisplay에 전달됩니다.

왼쪽 및 오른쪽 눈에 대한 뷰 및 투영 행렬 가져오기

먼저 왼쪽 및 오른쪽 눈의 눈 행렬을 가져옵니다.

CardboardLensDistortion_getEyeFromHeadMatrix(
    lens_distortion_, kLeft, eye_matrices_[0]);
CardboardLensDistortion_getEyeFromHeadMatrix(
    lens_distortion_, kRight, eye_matrices_[1]);
CardboardLensDistortion_getProjectionMatrix(
    lens_distortion_, kLeft, kZNear, kZFar, projection_matrices_[0]);
CardboardLensDistortion_getProjectionMatrix(
    lens_distortion_, kRight, kZNear, kZFar, projection_matrices_[1]);

이제 각 눈의 왜곡 메시를 가져와서 왜곡 렌더기에 전달합니다.

CardboardLensDistortion_getDistortionMesh(lens_distortion_, kLeft, &left_mesh);
CardboardLensDistortion_getDistortionMesh(lens_distortion_, kRight, &right_mesh);

렌더기 만들기 및 올바른 왜곡 메시 설정

렌더기는 한 번만 초기화해야 합니다. 렌더기가 생성되면 CardboardLensDistortion_getDistortionMesh 함수에서 반환된 메시 값에 따라 왼쪽 눈과 오른쪽 눈의 새 왜곡 메시를 설정합니다.

distortion_renderer_ = CardboardOpenGlEs2DistortionRenderer_create();
CardboardDistortionRenderer_setMesh(distortion_renderer_, &left_mesh, kLeft);
CardboardDistortionRenderer_setMesh(distortion_renderer_, &right_mesh, kRight);

콘텐츠 렌더링

각 프레임의 현재 헤드 방향을 CardboardHeadTracker_getPose에서 가져옵니다.

CardboardHeadTracker_getPose(head_tracker_, monotonic_time_nano, &out_position[0], &out_orientation[0]);

뷰 및 투영 매트릭스와 함께 현재 헤드 방향을 사용하여 각 눈에 대한 뷰 투영 행렬을 작성하고 화면에 콘텐츠를 렌더링합니다.

// Draw eyes views
for (int eye = 0; eye < 2; ++eye) {
  glViewport(eye == kLeft ? 0 : screen_width_ / 2, 0, screen_width_ / 2,
             screen_height_);

  Matrix4x4 eye_matrix = GetMatrixFromGlArray(eye_matrices_[eye]);
  Matrix4x4 eye_view = eye_matrix * head_view_;

  Matrix4x4 projection_matrix =
      GetMatrixFromGlArray(projection_matrices_[eye]);
  Matrix4x4 modelview_target = eye_view * model_target_;
  modelview_projection_target_ = projection_matrix * modelview_target;
  modelview_projection_room_ = projection_matrix * eye_view;

  // Draw room and target. Replace this to render your own content.
  DrawWorld();
}

CardboardDistortionRenderer_renderEyeToDisplay를 사용하여 콘텐츠에 왜곡 수정을 적용하고 콘텐츠를 화면에 렌더링합니다.

// Render
CardboardDistortionRenderer_renderEyeToDisplay(
    distortion_renderer_, /* target_display = */ 0, /* x = */ 0, /* y = */ 0,
    screen_width_, screen_height_, &left_eye_texture_description_,
    &right_eye_texture_description_);