מדריך למתחילים בנושא Google Cardboard ל-Android NDK

במדריך הזה מוסבר איך להשתמש ב-Cardboard SDK ל-Android כדי ליצור חוויות מציאות מדומה (VR) משלכם.

אפשר להשתמש ב-Cardboard SDK כדי להפוך סמארטפון לפלטפורמת VR. טלפון חכם יכול להציג סצנות תלת-ממדיות עם עיבוד סטריאוסקופי, לעקוב אחרי תנועות הראש ולהגיב להן, ולקיים אינטראקציה עם אפליקציות על ידי זיהוי מתי המשתמש לוחץ על לחצן המשקפיים.

כדי להתחיל, תשתמשו ב-HelloCardboard, משחק הדגמה שמציג את התכונות העיקריות של Cardboard SDK. במשחק, המשתמשים מסתכלים סביב עולם וירטואלי כדי למצוא ולאסוף אובייקטים. המדריך הזה מסביר איך:

  • הגדרת סביבת הפיתוח
  • הורדה ובנייה של אפליקציית ההדגמה
  • סריקת קוד ה-QR של משקפי Cardboard כדי לשמור את הפרמטרים שלהם
  • מעקב אחרי תנועות הראש של המשתמש
  • עיבוד תמונות סטריאוסקופיות על ידי הגדרת מטריצת הקרנת התצוגה הנכונה לכל עין

האפליקציה HelloCardboard משתמשת ב-Android NDK. כל שיטה מקורית היא:

  • מוגבל באופן ייחודי לשיטת מחלקה HelloCardboardApp, או
  • יוצרת או מוחקת מופע של המחלקה הזו

הגדרת סביבת הפיתוח

דרישות חומרה:

דרישות התוכנה:

  • Android Studio בגרסה 2024.1.2 Koala Feature Drop ואילך
  • ‫Android SDK 15.0 ‏Vanilla Ice Cream (רמת API‏ 35) ואילך
  • הגרסה האחרונה של מסגרת Android NDK

    כדי לעיין בערכות ה-SDK המותקנות או לעדכן אותן, עוברים אל Preferences (העדפות) > Appearance and Behavior (מראה והתנהגות).

    System Settings > Android SDK ב-Android Studio.

הורדה ובנייה של אפליקציית ההדגמה

‫Cardboard SDK מבוסס על קובץ כותרת Vulkan שעבר קומפילציה מראש לכל shader. כאן מוסבר איך ליצור את קובצי הכותרת מאפס.

  1. מריצים את הפקודה הבאה כדי לשכפל את Cardboard SDK ואת אפליקציית ההדגמה HelloCardboard מ-GitHub:‏

    git clone https://github.com/googlevr/cardboard.git
  2. ב-Android Studio, בוחרים באפשרות Open an existing Android Studio Project (פתיחת פרויקט קיים ב-Android Studio), ואז בוחרים את הספרייה שאליה בוצע שיבוט של Cardboard SDK ואפליקציית ההדגמה HelloCardboard.

    הקוד יופיע בחלון Project ב-Android Studio.

  3. כדי להרכיב את Cardboard SDK, לוחצים לחיצה כפולה על האפשרות assemble בתיקייה cardboard/:sdk/Tasks/build בכרטיסייה Gradle (תצוגה > חלונות כלים > Gradle).

  4. מריצים את אפליקציית ההדגמה HelloCardboard בטלפון על ידי בחירה באפשרות Run (הפעלה) > Run... (הפעלה...) ובחירת יעד hellocardboard-android.

סריקת קוד ה-QR

כדי לשמור את פרמטרי המכשיר, סורקים את קוד ה-QR במשקפי Cardboard:

אם המשתמש לוחץ על 'דילוג' ואין פרמטרים שנשמרו קודם, Cardboard שומר את הפרמטרים של Google Cardboard v1 (הושק ב-Google I/O בשנת 2014).

נסה את ההדגמה

ב-HelloCardboard, מחפשים כדורים גאודזיים ואוספים אותם במרחב תלת-ממדי.

כדי למצוא ולאסוף כדור:

  1. מזיזים את הראש לכל כיוון עד שרואים צורה צפה.

  2. מסתכלים ישירות על הכדור. כתוצאה מכך, הצבעים משתנים.

  3. לוחצים על הכפתור במכשיר Cardboard כדי 'לאסוף' את הכדור.

הגדרת המכשיר

כשמשתמש מקיש על סמל גלגל השיניים כדי להחליף את משקפי Cardboard, המערכת קוראת לשיטה nativeSwitchViewer. ‫nativeSwitchViewer calls CardboardQrCode_scanQrCodeAndSaveDeviceParams, ייפתח חלון לסריקת קוד ה-QR של הצופה. עיוות העדשה של הצופה ופרמטרים אחרים מתעדכנים ברגע שקוד ה-QR נסרק.

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

הפעלת אמולטור x86 של Android Studio

כדי לבצע build לאמולטור x86 של Android Studio, מסירים את השורה הבאה מקובצי build.gradle ב-SDK וב-Sample:

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

הפעולה הזו תפעיל את כל ה-ABI ותגדיל באופן משמעותי את הגודל של קובץ .aar שנוצר. מידע נוסף זמין במאמר בנושא ממשקי יישומים בינאריים (ABI) ב-Android.

מעקב ראש

יצירת מכשיר למעקב אחר תנועות הראש

ה-head tracker נוצר פעם אחת בקונסטרוקטור של 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, נוצר מופע של המחלקה HelloCardboardApp על ידי קריאה ל-method‏ nativeOnCreate:

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_);