Настройка графики с помощью OpenGL ES

Чтобы рисовать объекты и спрайты в вашей игре, вам необходимо настроить переменные отображения, поверхности и контекста, настроить рендеринг в игровом цикле и нарисовать каждую сцену и объект.

Существует два способа рисования изображений на экране для игры C или C++, а именно с помощью OpenGL ES или Vulkan .

  • OpenGL ES является частью спецификации Open Graphics Library (OpenGL®), предназначенной для мобильных устройств, таких как Android. Узнайте, как настроить OpenGL ES для вашей игры, в этом разделе.

  • Если вы используете Vulkan для своей игры, прочтите руководство «Начало работы с Vulkan» .

Прежде чем начать

Если вы еще этого не сделали, настройте объект GameActivity в своем проекте Android.

Настройка переменных OpenGL ES

  1. Для рендеринга игры вам понадобятся display , Surface , context и config . Добавьте следующие переменные 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.

  • Вулкан — Начало работы в NDK.

  • Vulkan — обзор в исходном коде Android.

  • Поймите игровые циклы Android — научитесь управлять кадрами, буферами очередей, обрабатывать обратные вызовы VSYNC и управлять потоками.