使用 OpenGL ES 配置图形

如需在游戏中绘制对象和精灵,您需要配置显示变量、Surface 变量和上下文变量,在游戏循环中设置渲染,然后绘制每个场景和对象。

对于 C 或 C++ 游戏,您可以通过两种方式将图像绘制到屏幕上,即,使用 OpenGL ESVulkan

准备工作

如果您尚未在 Android 项目中设置 GameActivity 对象,请执行此操作。

设置 OpenGL ES 变量

  1. 您需要使用 DisplaySurfaceContextConfig 来渲染游戏。将以下 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. Surface 可以是由 EGL 分配的离屏缓冲区 (pbuffer),也可以是由 Android 操作系统分配的窗口。初始化此 Surface:

    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 方法会查询当前的 Surface 尺寸,请求 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. 游戏循环会处理要渲染的可见场景和对象层次结构。在 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: