پیکربندی گرافیک با OpenGL ES

برای رسم کردن اشیا و چهرک‌ها در بازی‌تان، باید متغیرهای نمایشگر، سطح، و بافت را پیکربندی کنید، پرداز را در حلقه بازی‌تان راه‌اندازی کنید، و هر صحنه و شیء را رسم کنید.

دو روش برای طراحی تصاویر روی صفحه برای بازی C یا C++ وجود دارد، یعنی با OpenGL ES، یا Vulkan.

  • OpenGL ES بخشی از مشخصات کتابخانه گرافیک باز (OpenGL®) است که برای دستگاه‌های همراه مانند Android درنظر گرفته شده است. در این موضوع با نحوه پیکربندی OpenGL ES برای بازی‌تان آشنا شوید.

  • اگر از Vulkan برای بازی‌تان استفاده می‌کنید، راهنمای شروع کار با Vulkan را بخوانید.

قبل از شروع به کار

اگر قبلاً این کار را نکرده‌اید، شیء GameActivity را در پروژه Android خود راه‌اندازی کنید.

متغیرهای OpenGL ES را تنظیم کنید

  1. برای پرداز کردن بازی‌تان به نمایشگر، سطح، زمینه، و پیکربندی نیاز دارید. متغیرهای 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. در سازنده موتور بازی، مقادیر پیش‌فرض متغیرها را مقداردهی اولیه کنید.

    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. سطح می‌تواند بافر خارج از صفحه (pbuffer) تخصیص‌یافته توسط 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);
    }
    

با حلقه بازی رندر کنید

  1. حلقه بازی یک قاب را رندر می‌کند و تا زمانی که کاربر از بازی خارج شود، به‌طور نامحدود تکرار می‌شود. بین قاب‌ها، بازی شما ممکن است:

    برای پرداز کردن یک قاب در نمایشگر، روش DoFrame به‌طور نامحدود در حلقه بازی فراخوانده می‌شود:

    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. حلقه بازی سلسله‌مراتبی از صحنه‌ها و اشیای نمایان را برای پردازش ارائه می‌کند. در مثال «تونل بی‌پایان»، یک 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 Source.

  • Vulkan - شروع به کار در NDK.

  • Vulkan - نمای کلی در منبع Android.

  • آشنایی با حلقه‌های بازی Android - با سرعت‌دهی به قاب‌ها، بافرهای صف، مدیریت پاسخ‌برگ‌های VSYNC، و مدیریت رشته‌ها آشنا شوید.