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 스튜디오 버전 2024.1.2 'Koala 기능 출시' 이상
  • Android SDK 15.0 'Vanilla Ice Cream' (API 수준 35) 이상
  • 최신 버전의 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 스튜디오의 프로젝트 창에 코드가 표시됩니다.

  3. Cardboard SDK를 어셈블하려면 Gradle 탭 (보기 > 도구 창 > Gradle)의 cardboard/:sdk/Tasks/build 폴더 내에서 assemble 옵션을 더블클릭합니다.

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

QR 코드 스캔

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

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

데모 사용해 보기

HelloCardboard에서는 3D 공간에서 측지선 구를 찾아 수집합니다.

구체를 찾아 수집하려면 다음 단계를 따르세요.

  1. 떠다니는 모양이 보일 때까지 머리를 여러 방향으로 움직입니다.

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

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

기기 구성

사용자가 톱니바퀴 아이콘을 탭하여 Cardboard 뷰어를 전환하면 nativeSwitchViewer 메서드가 호출됩니다. nativeSwitchViewer를 호출합니다. 그러면 시청자의 QR 코드를 스캔하는 창이 열립니다.CardboardQrCode_scanQrCodeAndSaveDeviceParams 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_);