กำหนดค่ากราฟิกด้วย OpenGL ES

ในการวาดสิ่งต่างๆ และสไปรท์ในเกม คุณจะต้องกำหนดค่า ตัวแปรดิสเพลย์ แพลตฟอร์ม และบริบท ตั้งค่าการแสดงผลใน Game Loop ให้วาดฉากและวัตถุแต่ละชิ้น

การวาดรูปภาพบนหน้าจอสำหรับเกม C หรือ C++ ทำได้ 2 วิธีดังนี้ OpenGL ES หรือ Vulkan

ก่อนเริ่มต้นใช้งาน

หากคุณยังไม่ได้อ่าน ตั้งค่าออบเจ็กต์ GameActivity ใน โปรเจ็กต์ Android

ตั้งค่าตัวแปร OpenGL ES

  1. คุณจะต้องมีจอแสดงผล surface บริบท และ กำหนดค่าเพื่อแสดงผลเกม เพิ่ม ตัวแปร OpenGL ES ต่อไปนี้ไปยังไฟล์ส่วนหัวของเกมเอนจินของคุณ

    class NativeEngine {
     //...
     private:
      EGLDisplay mEglDisplay;
      EGLSurface mEglSurface;
      EGLContext mEglContext;
      EGLConfig mEglConfig;
    
      bool mHasFocus, mIsVisible, mHasWindow;
      bool mHasGLObjects;
      bool mIsFirstFrame;
    
      int mSurfWidth, mSurfHeight;
    }
    
  2. ในตัวสร้างสำหรับ Game Engine ให้กำหนดค่าเริ่มต้นสำหรับ ตัวแปร

    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. เริ่มต้นการแสดงผล

    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. พื้นผิวอาจเป็นบัฟเฟอร์นอกหน้าจอ (บัฟเฟอร์) ที่จัดสรรโดย EGL หรือ ที่ระบบปฏิบัติการ Android จัดสรรให้ เริ่มต้นแพลตฟอร์มนี้:

    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. เริ่มต้นบริบทการแสดงผล ตัวอย่างนี้สร้างองค์ประกอบ บริบท 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. กำหนดการตั้งค่า OpenGL ES ก่อนวาด ตัวอย่างนี้ดำเนินการที่ จุดเริ่มต้นของทุกเฟรม โดยเปิดใช้การทดสอบความลึก กำหนดสีที่ชัดเจนเป็น สีดำ ซึ่งจะล้างบัฟเฟอร์สีและความลึก

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

แสดงผลด้วย Game Loop

  1. Game Loop จะแสดงเฟรมหนึ่งและแสดงซ้ำไปเรื่อยๆ จนกว่าผู้ใช้จะเลิกเล่น ระหว่างเฟรม เกมของคุณอาจมีลักษณะดังนี้

    หากต้องการแสดงผลเฟรมไปยังจอแสดงผล จะเรียกเมธอด DoFrame ใน 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. ในเมธอด DoFrame ให้ค้นหามิติข้อมูลพื้นผิวปัจจุบัน คำขอ SceneManager เพื่อแสดงผลเฟรมและสลับบัฟเฟอร์การแสดงผล

    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());
      }
    }
    

แสดงภาพฉากและวัตถุ

  1. Game Loop จะประมวลผลลำดับชั้นของฉากและวัตถุที่มองเห็นได้เพื่อแสดงผล ในตัวอย่าง Endless Tunnel SceneManager ติดตามหลายฉาก โดยเปิดใช้เพียงฉากเดียวในแต่ละครั้ง ในตัวอย่างนี้ ฉากปัจจุบันคือ แสดงผล:

    void SceneManager::DoFrame() {
      if (mSceneToInstall) {
        InstallScene(mSceneToInstall);
        mSceneToInstall = NULL;
      }
    
      if (mHasGraphics && mCurScene) {
        mCurScene->DoFrame();
      }
    }
    
  2. ฉากอาจมีพื้นหลัง ข้อความ สไปรท์ และ ออบเจ็กต์ในเกม แสดงผลตามลำดับที่เหมาะกับเกมของคุณ ตัวอย่างนี้ แสดงผลพื้นหลัง ข้อความ และวิดเจ็ต:

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

แหล่งข้อมูล

อ่านข้อมูลต่อไปนี้เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับ OpenGL ES และ Vulkan

  • OpenGL ES - รูปภาพและกราฟิกใน Android

  • OpenGL ES - ภาพรวมในแหล่งที่มา Android

  • Vulkan - เริ่มต้นใช้งานใน NDK

  • Vulkan - ภาพรวม ในแหล่งที่มา Android

  • ทำความเข้าใจ Game Loop ของ Android - เรียนรู้วิธีก้าวเดิน เฟรม บัฟเฟอร์คิว จัดการ Callback ของ VSYNC และจัดการชุดข้อความ