使用 OpenGL ES 配置图形
使用集合让一切井井有条
根据您的偏好保存内容并对其进行分类。
如需在游戏中绘制对象和精灵,您需要配置显示变量、Surface 变量和上下文变量,在游戏循环中设置渲染,然后绘制每个场景和对象。
对于 C 或 C++ 游戏,您可以通过两种方式将图像绘制到屏幕上,即,使用 OpenGL ES 或 Vulkan。
准备工作
如果您尚未在 Android 项目中设置 GameActivity 对象,请执行此操作。
设置 OpenGL ES 变量
您需要使用 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;
}
在游戏引擎的构造函数中,初始化各变量的默认值。
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;
}
初始化要呈现的屏幕。
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;
}
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;
}
初始化渲染上下文。以下示例创建了一个 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;
}
在绘制之前配置您的 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);
}
使用游戏循环进行渲染
游戏循环会渲染一帧,并无限重复,直到用户退出为止。您的游戏可能会在帧之间执行以下操作:
为了将帧渲染到屏幕,游戏循环中会无限调用 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();
}
}
}
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());
}
}
渲染场景和对象
游戏循环会处理要渲染的可见场景和对象层次结构。在 Endless Tunnel 示例中,SceneManager
会跟踪多个场景,并且一次只有一个场景处于活动状态。在此示例中,渲染的是当前场景:
void SceneManager::DoFrame() {
if (mSceneToInstall) {
InstallScene(mSceneToInstall);
mSceneToInstall = NULL;
}
if (mHasGraphics && mCurScene) {
mCurScene->DoFrame();
}
}
场景可能会包含背景、文本、精灵和游戏对象,具体取决于您的游戏。按照适合您的游戏的顺序渲染它们。以下示例展示了如何渲染背景、文本和微件:
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:
本页面上的内容和代码示例受内容许可部分所述许可的限制。Java 和 OpenJDK 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2023-09-25。
[null,null,["最后更新时间 (UTC):2023-09-25。"],[],[],null,["# Configure graphics with OpenGL ES\n\nTo draw objects and sprites in your game, you will need to configure the\ndisplay, surface and context variables, set up rendering in your game loop, and\ndraw each scene and object.\n\nThere are two ways to draw images to the screen for a C or C++ game, namely with\n[OpenGL ES](/develop/ui/views/graphics/opengl/about-opengl), or\n[Vulkan](/ndk/guides/graphics/getting-started).\n\n- [OpenGL ES](/develop/ui/views/graphics/opengl/about-opengl) is part of the [Open Graphics\n Library (OpenGL®)](https://www.khronos.org/opengles/) specification\n intended for mobile devices such as Android. Learn how to configure OpenGL ES\n for your game in this topic.\n\n- If you use Vulkan for your game, read the\n [Getting started with Vulkan](/ndk/guides/graphics/getting-started)\n guide.\n\n| **Note:** The code in this topic is based on the [Endless Tunnel](https://github.com/android/ndk-samples/tree/master/endless-tunnel) sample, where details may differ for your game. Understand and adapt these concepts for your specific use case.\n\nBefore you get started\n----------------------\n\nIf you haven't already done so,\n[set up a GameActivity object](/games/agdk/game-activity) in your\nAndroid project.\n\nSet up OpenGL ES variables\n--------------------------\n\n1. You will need a [display](/reference/android/opengl/EGLDisplay),\n [surface](/reference/android/opengl/EGLSurface),\n [context](/reference/android/opengl/EGLContext), and\n [config](/reference/android/opengl/EGLConfig) to render your game. Add the\n following OpenGL ES variables to your game engine's header file:\n\n class NativeEngine {\n //...\n private:\n EGLDisplay mEglDisplay;\n EGLSurface mEglSurface;\n EGLContext mEglContext;\n EGLConfig mEglConfig;\n\n bool mHasFocus, mIsVisible, mHasWindow;\n bool mHasGLObjects;\n bool mIsFirstFrame;\n\n int mSurfWidth, mSurfHeight;\n }\n\n2. In the constructor for your game engine, initialize the default values for\n the variables.\n\n NativeEngine::NativeEngine(struct android_app *app) {\n //...\n mEglDisplay = EGL_NO_DISPLAY;\n mEglSurface = EGL_NO_SURFACE;\n mEglContext = EGL_NO_CONTEXT;\n mEglConfig = 0;\n\n mHasFocus = mIsVisible = mHasWindow = false;\n mHasGLObjects = false;\n mIsFirstFrame = true;\n\n mSurfWidth = mSurfHeight = 0;\n }\n\n3. Initialize the display to render.\n\n bool NativeEngine::InitDisplay() {\n if (mEglDisplay != EGL_NO_DISPLAY) {\n return true;\n }\n\n mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);\n if (EGL_FALSE == eglInitialize(mEglDisplay, 0, 0)) {\n LOGE(\"NativeEngine: failed to init display, error %d\", eglGetError());\n return false;\n }\n return true;\n }\n\n4. The surface can be an off-screen buffer (pbuffer) allocated by EGL, or a\n window allocated by the Android OS. Initialize this surface:\n\n bool NativeEngine::InitSurface() {\n ASSERT(mEglDisplay != EGL_NO_DISPLAY);\n if (mEglSurface != EGL_NO_SURFACE) {\n return true;\n }\n\n EGLint numConfigs;\n const EGLint attribs[] = {\n EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // request OpenGL ES 2.0\n EGL_SURFACE_TYPE, EGL_WINDOW_BIT,\n EGL_BLUE_SIZE, 8,\n EGL_GREEN_SIZE, 8,\n EGL_RED_SIZE, 8,\n EGL_DEPTH_SIZE, 16,\n EGL_NONE\n };\n\n // Pick the first EGLConfig that matches.\n eglChooseConfig(mEglDisplay, attribs, &mEglConfig, 1, &numConfigs);\n mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, mApp-\u003ewindow,\n NULL);\n if (mEglSurface == EGL_NO_SURFACE) {\n LOGE(\"Failed to create EGL surface, EGL error %d\", eglGetError());\n return false;\n }\n return true;\n }\n\n5. Initialize the rendering context. This example creates an\n [OpenGL ES 2.0](/reference/android/opengl/GLES20) context:\n\n bool NativeEngine::InitContext() {\n ASSERT(mEglDisplay != EGL_NO_DISPLAY);\n if (mEglContext != EGL_NO_CONTEXT) {\n return true;\n }\n\n // OpenGL ES 2.0\n EGLint attribList[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };\n mEglContext = eglCreateContext(mEglDisplay, mEglConfig, NULL, attribList);\n if (mEglContext == EGL_NO_CONTEXT) {\n LOGE(\"Failed to create EGL context, EGL error %d\", eglGetError());\n return false;\n }\n return true;\n }\n\n6. Configure your OpenGL ES settings before drawing. This example is executed at\n the beginning of every frame. It enables depth testing, sets the clear color to\n black, and clears the color and depth buffers.\n\n void NativeEngine::ConfigureOpenGL() {\n glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n glEnable(GL_DEPTH_TEST);\n glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);\n }\n\nRender with the game loop\n-------------------------\n\n1. The game loop renders a frame and repeats indefinitely until the user quits.\n Between frames, your game may:\n\n - [Process events](/games/agdk/game-activity/get-started#handle-events) such as\n input, [audio output](/games/sdk/oboe), and networking events.\n\n - Update the game logic and user interface.\n\n - Render a frame to the display.\n\n To render a frame to the display, the `DoFrame` method is called\n indefinitely in the game loop: \n\n void NativeEngine::GameLoop() {\n // Loop indefinitely.\n while (1) {\n int events;\n struct android_poll_source* source;\n\n // If not animating, block until we get an event.\n while ((ALooper_pollAll(IsAnimating() ? 0 : -1, NULL, &events,\n (void **) &source)) \u003e= 0) {\n // Process events.\n ...\n }\n\n // Render a frame.\n if (IsAnimating()) {\n DoFrame();\n }\n }\n }\n\n2. In the `DoFrame` method, query the current surface dimensions, request\n `SceneManager` to render a frame, and swap the display buffers.\n\n void NativeEngine::DoFrame() {\n ...\n // Query the current surface dimension.\n int width, height;\n eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &width);\n eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &height);\n\n // Handle dimension changes.\n SceneManager *mgr = SceneManager::GetInstance();\n if (width != mSurfWidth || height != mSurfHeight) {\n mSurfWidth = width;\n mSurfHeight = height;\n mgr-\u003eSetScreenSize(mSurfWidth, mSurfHeight);\n glViewport(0, 0, mSurfWidth, mSurfHeight);\n }\n ...\n // Render scenes and objects.\n mgr-\u003eDoFrame();\n\n // Swap buffers.\n if (EGL_FALSE == eglSwapBuffers(mEglDisplay, mEglSurface)) {\n HandleEglError(eglGetError());\n }\n }\n\nRender scenes and objects\n-------------------------\n\n1. The game loop processes a hierarchy of visible scenes and objects to render.\n In the Endless Tunnel example, a `SceneManager` keeps track of multiple scenes,\n with only one scene active at a time. In this example, the current scene is\n rendered:\n\n void SceneManager::DoFrame() {\n if (mSceneToInstall) {\n InstallScene(mSceneToInstall);\n mSceneToInstall = NULL;\n }\n\n if (mHasGraphics && mCurScene) {\n mCurScene-\u003eDoFrame();\n }\n }\n\n2. Depending on your game, a scene may contain background, text, sprites and\n game objects. Render them in the order suitable for your game. This example\n renders the background, text, and widgets:\n\n void UiScene::DoFrame() {\n // clear screen\n glClearColor(0.0f, 0.0f, 0.0f, 1.0f);\n glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n glDisable(GL_DEPTH_TEST);\n\n RenderBackground();\n\n // Render the \"Please Wait\" sign and do nothing else\n if (mWaitScreen) {\n SceneManager *mgr = SceneManager::GetInstance();\n mTextRenderer-\u003eSetFontScale(WAIT_SIGN_SCALE);\n mTextRenderer-\u003eSetColor(1.0f, 1.0f, 1.0f);\n mTextRenderer-\u003eRenderText(S_PLEASE_WAIT, mgr-\u003eGetScreenAspect() * 0.5f,\n 0.5f);\n glEnable(GL_DEPTH_TEST);\n return;\n }\n\n // Render all the widgets.\n for (int i = 0; i \u003c mWidgetCount; ++i) {\n mWidgets[i]-\u003eRender(mTrivialShader, mTextRenderer, mShapeRenderer,\n (mFocusWidget \u003c 0) ? UiWidget::FOCUS_NOT_APPLICABLE :\n (mFocusWidget == i) ? UiWidget::FOCUS_YES : UiWidget::FOCUS_NO,tf);\n }\n glEnable(GL_DEPTH_TEST);\n }\n\nResources\n---------\n\nRead the following for more information about OpenGL ES and Vulkan:\n\n- [OpenGL ES](/develop/ui/views/graphics/opengl/about-opengl) - Images and graphics in Android.\n\n- [OpenGL ES](https://source.android.com/devices/graphics/arch-egl-opengl) -\n Overview in Android Source.\n\n- [Vulkan](/ndk/guides/graphics/getting-started) - Getting started in NDK.\n\n- [Vulkan](https://source.android.com/devices/graphics/arch-vulkan) - Overview\n in Android Source.\n\n- [Understand Android game loops](/games/develop/gameloops) - learn to pace\n frames, queue buffers, handle VSYNC callbacks, and manage threads."]]