Mengonfigurasi grafik dengan OpenGL ES

Untuk menggambar objek dan sprite dalam game, Anda harus mengonfigurasi variabel layar, permukaan, dan konteks, menyiapkan rendering dalam game loop, serta menggambar setiap scene dan objek.

Untuk game C atau C++, ada dua cara untuk menggambar gambar ke layar, yaitu dengan OpenGL ES atau Vulkan.

Sebelum memulai

Jika Anda belum melakukannya, siapkan objek GameActivity di project Android Anda.

Menyiapkan variabel OpenGL ES

  1. Anda memerlukan layar, platform, konteks, dan konfigurasi untuk merender game Anda. Tambahkan variabel OpenGL ES berikut ke file header mesin game Anda:

    class NativeEngine {
     //...
     private:
      EGLDisplay mEglDisplay;
      EGLSurface mEglSurface;
      EGLContext mEglContext;
      EGLConfig mEglConfig;
    
      bool mHasFocus, mIsVisible, mHasWindow;
      bool mHasGLObjects;
      bool mIsFirstFrame;
    
      int mSurfWidth, mSurfHeight;
    }
    
  2. Pada konstruktor untuk mesin game Anda, inisialisasi nilai default variabel.

    NativeEngine::NativeEngine(struct android_app *app) {
      //...
      mEglDisplay = EGL_NO_DISPLAY;
      mEglSurface = EGL_NO_SURFACE;
      mEglContext = EGL_NO_CONTEXT;
      mEglConfig = 0;
    
      mHasFocus = mIsVisible = mHasWindow = false;
      mHasGLObjects = false;
      mIsFirstFrame = true;
    
      mSurfWidth = mSurfHeight = 0;
    }
    
  3. Inisialisasi layar untuk dirender.

    bool NativeEngine::InitDisplay() {
      if (mEglDisplay != EGL_NO_DISPLAY) {
        return true;
      }
    
      mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
      if (EGL_FALSE == eglInitialize(mEglDisplay, 0, 0)) {
        LOGE("NativeEngine: failed to init display, error %d", eglGetError());
        return false;
      }
      return true;
    }
    
  4. Platform dapat berupa buffer di luar layar (pbuffer) yang dialokasikan oleh EGL atau jendela yang dialokasikan oleh Android OS. Inisialisasi platform ini:

    bool NativeEngine::InitSurface() {
      ASSERT(mEglDisplay != EGL_NO_DISPLAY);
      if (mEglSurface != EGL_NO_SURFACE) {
        return true;
      }
    
      EGLint numConfigs;
      const EGLint attribs[] = {
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // request OpenGL ES 2.0
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_BLUE_SIZE, 8,
        EGL_GREEN_SIZE, 8,
        EGL_RED_SIZE, 8,
        EGL_DEPTH_SIZE, 16,
        EGL_NONE
      };
    
      // Pick the first EGLConfig that matches.
      eglChooseConfig(mEglDisplay, attribs, &mEglConfig, 1, &numConfigs);
      mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, mApp->window,
                                           NULL);
      if (mEglSurface == EGL_NO_SURFACE) {
        LOGE("Failed to create EGL surface, EGL error %d", eglGetError());
        return false;
      }
      return true;
    }
    
  5. Inisialisasi konteks rendering. Contoh ini membuat konteks OpenGL ES 2.0:

    bool NativeEngine::InitContext() {
      ASSERT(mEglDisplay != EGL_NO_DISPLAY);
      if (mEglContext != EGL_NO_CONTEXT) {
        return true;
      }
    
      // OpenGL ES 2.0
      EGLint attribList[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
      mEglContext = eglCreateContext(mEglDisplay, mEglConfig, NULL, attribList);
      if (mEglContext == EGL_NO_CONTEXT) {
        LOGE("Failed to create EGL context, EGL error %d", eglGetError());
        return false;
      }
      return true;
    }
    
  6. Konfigurasi setelan OpenGL ES sebelum menggambar. Contoh ini dijalankan di awal setiap frame. Hal ini akan memungkinkan pengujian kedalaman, menetapkan warna yang jelas ke hitam, dan menghapus buffer warna dan kedalaman.

    void NativeEngine::ConfigureOpenGL() {
      glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
      glEnable(GL_DEPTH_TEST);
      glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    }
    

Merender dengan game loop

  1. Game loop merender frame dan mengulanginya tanpa batas waktu hingga pengguna berhenti. Di sela-sela frame, game Anda dapat:

    Untuk merender frame ke layar, metode DoFrame dipanggil tanpa batas di game loop:

    void NativeEngine::GameLoop() {
      // Loop indefinitely.
      while (1) {
        int events;
        struct android_poll_source* source;
    
        // If not animating, block until we get an event.
        while ((ALooper_pollAll(IsAnimating() ? 0 : -1, NULL, &events,
                                (void **) &source)) >= 0) {
          // Process events.
          ...
        }
    
        // Render a frame.
        if (IsAnimating()) {
            DoFrame();
        }
      }
    }
    
  2. Pada metode DoFrame, buat kueri dimensi platform saat ini, minta SceneManager untuk merender frame, dan menukar buffer display.

    void NativeEngine::DoFrame() {
      ...
      // Query the current surface dimension.
      int width, height;
      eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &width);
      eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &height);
    
      // Handle dimension changes.
      SceneManager *mgr = SceneManager::GetInstance();
      if (width != mSurfWidth || height != mSurfHeight) {
        mSurfWidth = width;
        mSurfHeight = height;
        mgr->SetScreenSize(mSurfWidth, mSurfHeight);
        glViewport(0, 0, mSurfWidth, mSurfHeight);
      }
      ...
      // Render scenes and objects.
      mgr->DoFrame();
    
      // Swap buffers.
      if (EGL_FALSE == eglSwapBuffers(mEglDisplay, mEglSurface)) {
        HandleEglError(eglGetError());
      }
    }
    

Merender scene dan objek

  1. Game loop memproses hierarki scene dan objek yang terlihat untuk dirender. Pada contoh Endless Tunnel, SceneManager akan melacak beberapa scene, dengan hanya 1 scene yang aktif dalam satu waktu. Dalam contoh ini, scene saat ini dirender:

    void SceneManager::DoFrame() {
      if (mSceneToInstall) {
        InstallScene(mSceneToInstall);
        mSceneToInstall = NULL;
      }
    
      if (mHasGraphics && mCurScene) {
        mCurScene->DoFrame();
      }
    }
    
  2. Bergantung pada game Anda, scene dapat berisi objek latar belakang, teks, sprite, dan game. Lakukan render dalam urutan yang sesuai untuk game Anda. Contoh ini merender latar belakang, teks, dan widget:

    void UiScene::DoFrame() {
      // clear screen
      glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      glDisable(GL_DEPTH_TEST);
    
      RenderBackground();
    
      // Render the "Please Wait" sign and do nothing else
      if (mWaitScreen) {
        SceneManager *mgr = SceneManager::GetInstance();
        mTextRenderer->SetFontScale(WAIT_SIGN_SCALE);
        mTextRenderer->SetColor(1.0f, 1.0f, 1.0f);
        mTextRenderer->RenderText(S_PLEASE_WAIT, mgr->GetScreenAspect() * 0.5f,
                                  0.5f);
        glEnable(GL_DEPTH_TEST);
        return;
      }
    
      // Render all the widgets.
      for (int i = 0; i < mWidgetCount; ++i) {
        mWidgets[i]->Render(mTrivialShader, mTextRenderer, mShapeRenderer,
              (mFocusWidget < 0) ? UiWidget::FOCUS_NOT_APPLICABLE :
              (mFocusWidget == i) ? UiWidget::FOCUS_YES : UiWidget::FOCUS_NO,tf);
      }
      glEnable(GL_DEPTH_TEST);
    }
    

Referensi

Baca informasi berikut untuk mengetahui informasi OpenGL ES dan Vulkan selengkapnya:

  • OpenGL ES - Gambar dan grafis di Android.

  • OpenGL ES - Ringkasan di Sumber Android.

  • Vulkan - Memulai di NDK.

  • Vulkan - Ringkasan di Sumber Android.

  • Memahami game loop Android - pelajari cara mengatur kecepatan frame, mengantre buffer, menangani callback VSYNC, dan mengelola thread.