Android OpenGL ES 2.0(一)---入门

本文从下面链接翻译过来:

Android Lesson One: Getting Started

这是在Android上使用OpenGL ES2的第一个教程。 在本课中,我们将逐步介绍代码,并了解如何创建OpenGL ES2上下文并绘制到屏幕上。 我们还将了解着色器是什么以及它们如何工作,以及如何使用矩阵将场景转换为您在屏幕上看到的图像。 最后,您需要在清单文件(AndroidManifest.xml)中添加使用OpenGL ES2的说明,以告知Android应用市场你的应用仅对支持的设备可见。

开发环境搭建

在开始之前,您需要确保在计算机上安装了以下工具:
1.Java环境
2.Android studio开发工具
3.Android真机设备一部

开始

我们将查看下面的所有代码并解释每个部分的作用。 您可以通过创建自己的项目逐段复制代码,也可以在课程的最后下载完整的项目代码。 安装完工具后,在Android Studio中创建一个新的Android项目。 名称无关紧要,但对于本课,我将应用的入口称为LessonOneActivity。

我们来看看代码:

    /** GLSurfaceView的一个引用 */
    private GLSurfaceView mGLSurfaceView;

GLSurfaceView是一个特殊的视图,它为我们管理OpenGL表面并将其绘制到Android视图系统中。 它还增加了许多功能,使其更易于使用OpenGL,包括但不限于:

  • 它为OpenGL提供专用的渲染线程,以便不影响到Android主线程。
  • 它支持连续或按需渲染。
  • 它使用EGL(OpenGL和底层窗口系统之间的接口)来处理屏幕设置。

GLSurfaceView使得从Android设置和使用OpenGL相对轻松。

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mGLSurfaceView = new GLSurfaceView(this);
        // 检查系统是否支持 OpenGL ES 2.0.
        final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
        final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;
        if (supportsEs2) {
            // 请求使用OpenGL ES 2.0兼容的上下文.
            mGLSurfaceView.setEGLContextClientVersion(2);
            // 将渲染器设置为我们的演示渲染器,定义如下
            mGLSurfaceView.setRenderer(new LessonOneRenderer());
        } else {
            //如果您想同时支持ES 1和ES 2,则可以在此处创建与OpenGL ES 1.x兼容的渲染器。
            return;
        }
        setContentView(mGLSurfaceView);
    }

LessonOneActivity的onCreate()方法是创建OpenGL上下文以及一切开始重要部分。在onCreate()中,在调用父类之后做的第一件事是创建我们的GLSurfaceView。然后我们需要弄清楚系统是否支持OpenGL ES2。为此,我们得到一个ActivityManager实例,它允许我们与全局系统状态进行交互。然后我们可以使用它来获取设备配置信息,它将告诉我们设备是否支持OpenGL ES2。

一旦我们知道设备支持OpenGL ES2,我们告诉GLSurfaceView我们想要一个OpenGL ES2兼容表面,然后我们传入一个自定义渲染器。无论何时调整表面或绘制新帧,系统都会调用此渲染器。我们也可以通过传入不同的渲染器来支持OpenGL ES1.x,但是由于API不同,我们需要编写不同的代码。在本课中,我们只关注支持OpenGL ES2。

最后,我们将内容视图设置为GLSurfaceView,它告诉Activity的内容应由我们的OpenGL表面填充。要进入OpenGL,就这么简单!

    @Override
    protected void onResume() {
        super.onResume();
        // 当Activity的onResume()方法被调用时,必须调用GLSurfaceView的onResume()方法
        mGLSurfaceView.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 当Activity的onPause()方法被调用时,必须调用GLSurfaceView的onPause()方法
        mGLSurfaceView.onPause();
    }

GLSurfaceView需要我们在Activity的onResume()和onPaused()时调用它的onResume()和onPause()方法。 我们在此处添加调用以完善我们的Activity。

可视化3D世界

在本节中,我们将开始研究OpenGL ES2的工作原理以及如何开始在屏幕上绘制内容。 在LessonOneActivity中,我们将自定义的GLSurfaceView.Renderer传递给GLSurfaceView。 渲染器有三个重要的方法,系统会自动调用这些方法:

    @Override
    public void onSurfaceCreated(GL10 glUnused, EGLConfig eglConfig) {

    }

首次创建Surface时会调用此方法。 如果我们丢失Surface上下文并且稍后由系统重新创建它,也将调用此方法。

    @Override
    public void onSurfaceChanged(GL10 glUnused, int width, int height) {

    }

只要Surface发生变化,就会调用它; 例如,从纵向切换到横向时。 在创建Surface后也会调用它。

    @Override
    public void onDrawFrame(GL10 glUnused) {

    }

只要是绘制新帧的时候就会调用它。

您可能已经注意到传入的GL10实例称为glUnused。 使用OpenGL ES2绘图时我们不使用它; 相反,我们使用GLES20类的静态方法。 GL10参数仅在那里,因为相同的接口用于OpenGL ES1.x.

在我们的渲染器可以显示任何内容之前,我们需要显示一些内容。 在OpenGL ES2中,我们通过指定数字数组传递内容。 这些数字可以表示位置,颜色或我们需要的任何其他内容。 在这个演示中,我们将显示三个三角形。

    // New class members
    /** Store our model data in a float buffer. */
    private final FloatBuffer mTriangle1Vertices;
    private final FloatBuffer mTriangle2Vertices;
    private final FloatBuffer mTriangle3Vertices;
    /** How many bytes per float. */
    private final int mBytesPerFloat = 4;

    /**
     * Initialize the model data.
     */
    public LessonOneRenderer() {
        // This triangle is red, green, and blue.
        final float[] triangle1VerticesData = {
                // X, Y, Z,
                // R, G, B, A
                -0.5f, -0.25f, 0.0f,
                1.0f, 0.0f, 0.0f, 1.0f,

                0.5f, -0.25f, 0.0f,
                0.0f, 0.0f, 1.0f, 1.0f,

                0.0f, 0.559016994f, 0.0f,
                0.0f, 1.0f, 0.0f, 1.0f};

    ...

        // Initialize the buffers.
        mTriangle1Vertices = ByteBuffer.allocateDirect(triangle1VerticesData.length * mBytesPerFloat)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();

    ...

        mTriangle1Vertices.put(triangle1VerticesData).position(0);

    ...
    }

那么,这些是什么意思? 如果你曾经使用过OpenGL 1,你可能会习惯这样做:

    glBegin(GL_TRIANGLES);
    glVertex3f(-0.5f, -0.25f, 0.0f);
    glColor3f(1.0f, 0.0f, 0.0f);
    ...
    glEnd();

这些方法在OpenGL ES2中不起作用。我们不是通过一堆方法调用来定义点,而是定义一个数组。 让我们再看看我们的数组:

    final float[] triangle1VerticesData = {
            // X, Y, Z,
            // R, G, B, A
            -0.5f, -0.25f, 0.0f,
            1.0f, 0.0f, 0.0f, 1.0f,
            ...

这代表三角形的一个点。 我们设置了前三个数字代表位置(X,Y和Z),后四个数字代表颜色(红色,绿色,蓝色和alpha(透明度))。 您不必太担心如何定义此数组; 请记住,当我们想要在OpenGL ES2中绘制内容时,我们需要以块的形式传递数据,而不是一次传递一个。

理解缓冲区

    // Initialize the buffers.
    mTriangle1Vertices = ByteBuffer.allocateDirect(triangle1VerticesData.length * mBytesPerFloat)
    .order(ByteOrder.nativeOrder()).asFloatBuffer();
    ...
    mTriangle1Vertices.put(triangle1VerticesData).position(0);

我们在Android上使用Java进行编码,但OpenGL ES2的底层实现实际上是用C语言编写的。在我们将数据传递给OpenGL之前,我们需要将其转换为一种它能够理解的形式。 Java和本机系统可能不会以相同的顺序存储它们的字节,因此我们使用一组特殊的缓冲区类并创建一个足够大的ByteBuffer来保存我们的数据,并告诉它使用本机字节顺序存储其数据。 然后我们将它转换为FloatBuffer,以便我们可以使用它来保存浮点数据。 最后,我们将数组复制到缓冲区中。

这个缓冲区的东西可能看起来很混乱(当我第一次遇到它时候也是这样认为!),但请记住,在将数据传递给OpenGL之前,我们需要做一个额外的步骤。 我们的缓冲区现在可以用于将数据传递到OpenGL。

另外,float buffers are slow on Froyo and moderately faster on Gingerbread,所以你可能不希望经常更换它们。

理解矩阵

    // New class definitions
    /**
     * Store the view matrix. This can be thought of as our camera. This matrix transforms world space to eye space;
     * it positions things relative to our eye.
     */
    private float[] mViewMatrix = new float[16];

    @Override
    public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
        // Set the background clear color to gray.
        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);

        // Position the eye behind the origin.
        final float eyeX = 0.0f;
        final float eyeY = 0.0f;
        final float eyeZ = 1.5f;

        // We are looking toward the distance
        final float lookX = 0.0f;
        final float lookY = 0.0f;
        final float lookZ = -5.0f;

        // Set our up vector. This is where our head would be pointing were we holding the camera.
        final float upX = 0.0f;
        final float upY = 1.0f;
        final float upZ = 0.0f;

        // Set the view matrix. This matrix can be said to represent the camera position.
        // NOTE: In OpenGL 1, a ModelView matrix is used, which is a combination of a model and
        // view matrix. In OpenGL 2, we can keep track of these matrices separately if we choose.
        Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);

    ...
    }

另一个“有趣”的主题是矩阵! 无论何时进行3D编程,这些都将成为您最好的朋友,因此您需要很好地了解它们。

当我们的Surface被创建时,我们要做的第一件事就是将清除屏幕的颜色设置为灰色。 alpha部分也已设置为灰色,但我们在本课程中没有进行Alpha混合,因此该值未使用。 我们只需要设置一次清除屏幕的颜色颜色,因为我们以后不会更改它。

我们要做的第二件事是设置我们的视图矩阵。 我们使用了几种不同类型的矩阵,它们都做了一些重要的事情:

  1. 模型矩阵。 该矩阵用于在“世界”中的某处放置模型。例如,如果您有一辆汽车的模型,并且您希望它位于东边1000米处,您将使用模型矩阵来执行此操作。
  2. 视图矩阵。 该矩阵代表相机。如果我们想要查看我们位于东边1000米处的汽车,我们也必须向东移动1000米(另一种思考方式是我们保持静止,世界其他地方向西移动1000米)。我们使用视图矩阵来做到这一点。
  3. 投影矩阵。 由于我们的屏幕是二维平面的,我们需要进行最后的转换,将我们的视图“投影”到我们的屏幕上并获得漂亮的3D视角。 这就是投影矩阵的用途。

你可以找到对矩阵的一个很好的解释SongHo’s OpenGL Tutorials。 我建议你多阅读几次,直到你理解为止; 别担心,我也阅读了好几次才理解它!

在OpenGL 1中,模型矩阵与视图矩阵是结合在一起的。Camera被假设放在了(0,0,0)位置并且面向-Z方向。

我们不需要手工构建这些矩阵。 Android有一个Matrix帮助程序类,可以为我们做繁重的工作。 在这里,我为摄像机创建了一个视图矩阵,它位于原点后面,朝向远处。

定义顶点和片元着色器

    final String vertexShader =
            "uniform mat4 u_MVPMatrix;      \n"     // A constant representing the combined model/view/projection matrix.

                    + "attribute vec4 a_Position;     \n"     // Per-vertex position information we will pass in.
                    + "attribute vec4 a_Color;        \n"     // Per-vertex color information we will pass in.

                    + "varying vec4 v_Color;          \n"     // This will be passed into the fragment shader.

                    + "void main()                    \n"     // The entry point for our vertex shader.
                    + "{                              \n"
                    + "   v_Color = a_Color;          \n"     // Pass the color through to the fragment shader.
                    // It will be interpolated across the triangle.
                    + "   gl_Position = u_MVPMatrix   \n"     // gl_Position is a special variable used to store the final position.
                    + "               * a_Position;   \n"     // Multiply the vertex by the matrix to get the final point in
                    + "}                              \n";    // normalized screen coordinates.

在OpenGL ES2中,我们想要在屏幕上显示的任何内容首先必须通过顶点和片元着色器。好消息是这些着色器并不像它们看起来那么复杂。顶点着色器对每个顶点执行操作,这些操作的结果用于片元着色器,对每个像素执行额外的计算。

每个着色器基本上由输入,输出和程序组成。首先,我们定义一个uniform类型变量,它是一个包含所有变换的组合矩阵。用于将所有顶点投影到屏幕上。然后我们为位置和颜色定义两个attribute类型变量。这些属性将从我们之前定义的缓冲区中读取,并指定每个顶点的位置和颜色。然后我们定义一个varying类型变量,它在三角形上进行插值计算,并将其传递给片元着色器。当它到达片元着色器时,它将为每个像素保存一个插值。

假设我们定义了一个三角形的三个点分别是红色、绿色和蓝色,我们调整它的大小,使其占据屏幕上的10个像素。 当片元着色器运行时,它将为每个像素包含不同的varying类型颜色。 在某一点上, varying 类型颜色可能是红色,也可能是在红色和蓝色之间,还有可能是更紫色的颜色。

除了设置颜色外,我们还告诉OpenGL顶点的最终位置应该在屏幕上的具体位置。 然后我们定义片元着色器:

    final String fragmentShader =
            "precision mediump float;       \n"     // Set the default precision to medium. We don't need as high of a
                    // precision in the fragment shader.
                    + "varying vec4 v_Color;          \n"     // This is the color from the vertex shader interpolated across the
                    // triangle per fragment.
                    + "void main()                    \n"     // The entry point for our fragment shader.
                    + "{                              \n"
                    + "   gl_FragColor = v_Color;     \n"     // Pass the color directly through the pipeline.
                    + "}                              \n";

这是片元着色器,它实际上会将东西显示到屏幕上。 在这个着色器中,我们从顶点着色器中获取varying类型的颜色值,然后直接将其传递给OpenGL。 该点已经按像素插值,因为片元着色器针对将要绘制的每个像素运行。

更多信息请参考OpenGL ES 2 quick reference card。

将着色器加载到OpenGL中

        // Load in the vertex shader.
        int vertexShaderHandle = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);

        if (vertexShaderHandle != 0)
        {
            // Pass in the shader source.
            GLES20.glShaderSource(vertexShaderHandle, vertexShader);

            // Compile the shader.
            GLES20.glCompileShader(vertexShaderHandle);

            // Get the compilation status.
            final int[] compileStatus = new int[1];
            GLES20.glGetShaderiv(vertexShaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);

            // If the compilation failed, delete the shader.
            if (compileStatus[0] == 0)
            {
                GLES20.glDeleteShader(vertexShaderHandle);
                vertexShaderHandle = 0;
            }
        }

        if (vertexShaderHandle == 0)
        {
            throw new RuntimeException("Error creating vertex shader.");
        }

首先,我们创建着色器对象。如果成功,我们将获得对象的引用。然后我们使用这个引用来传递着色器源代码,然后我们编译它。我们可以从OpenGL获取状态,看看它是否成功编译。如果有错误,我们可以使用GLES20.glGetShaderInfoLog(着色器)找出原因。 我们按照相同的步骤加载片元着色器。

将顶点和片段着色器链接到一个程序中

        // Create a program object and store the handle to it.
        int programHandle = GLES20.glCreateProgram();

        if (programHandle != 0)
        {
            // Bind the vertex shader to the program.
            GLES20.glAttachShader(programHandle, vertexShaderHandle);

            // Bind the fragment shader to the program.
            GLES20.glAttachShader(programHandle, fragmentShaderHandle);

            // Bind attributes
            GLES20.glBindAttribLocation(programHandle, 0, "a_Position");
            GLES20.glBindAttribLocation(programHandle, 1, "a_Color");

            // Link the two shaders together into a program.
            GLES20.glLinkProgram(programHandle);

            // Get the link status.
            final int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);

            // If the link failed, delete the program.
            if (linkStatus[0] == 0)
            {
                GLES20.glDeleteProgram(programHandle);
                programHandle = 0;
            }
        }

        if (programHandle == 0)
        {
            throw new RuntimeException("Error creating program.");
        }

在我们使用顶点和片元着色器之前,我们需要将它们绑定到一个程序中。 这是将顶点着色器的输出与片元着色器的输入相连接的内容。 这也是让我们从程序传递输入并使用着色器绘制形状的原因。

我们创建一个新的程序对象,如果成功,我们就会附加我们的着色器。 我们希望将位置和颜色作为属性传递,因此我们需要绑定这些属性。 然后我们将着色器链接在一起。

//New class members
/** This will be used to pass in the transformation matrix. */
private int mMVPMatrixHandle;
 
/** This will be used to pass in model position information. */
private int mPositionHandle;
 
/** This will be used to pass in model color information. */
private int mColorHandle;
 
@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config)
{
    ...
 
    // Set program handles. These will later be used to pass in values to the program.
    mMVPMatrixHandle = GLES20.glGetUniformLocation(programHandle, "u_MVPMatrix");
    mPositionHandle = GLES20.glGetAttribLocation(programHandle, "a_Position");
    mColorHandle = GLES20.glGetAttribLocation(programHandle, "a_Color");
 
    // Tell OpenGL to use this program when rendering.
    GLES20.glUseProgram(programHandle);
}

在我们成功链接着色器程序之后,我们完成了几项任务,以便我们可以实际使用它。 第一个任务是获取引用,以便我们可以将数据传递到着色器程序中。 然后我们告诉OpenGL在绘图时使用这个着色器程序。 由于我们在本课中只使用了一个着色器程序,因此我们可以将它放在onSurfaceCreated()而不是onDrawFrame()中。

设置透视投影

// New class members
/** Store the projection matrix. This is used to project the scene onto a 2D viewport. */
private float[] mProjectionMatrix = new float[16];
 
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height)
{
    // Set the OpenGL viewport to the same size as the surface.
    GLES20.glViewport(0, 0, width, height);
 
    // Create a new perspective projection matrix. The height will stay the same
    // while the width will vary as per aspect ratio.
    final float ratio = (float) width / height;
    final float left = -ratio;
    final float right = ratio;
    final float bottom = -1.0f;
    final float top = 1.0f;
    final float near = 1.0f;
    final float far = 10.0f;
 
    Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
}

onSurfaceChanged()被调用至少一次,并且每当我们的surface被改变时。 由于我们只需要在我们投影到的屏幕发生变化时重置投影矩阵,onSurfaceChanged()就是理想的选择。

把东西画到屏幕上!

    // New class members
    /**
     * Store the model matrix. This matrix is used to move models from object space (where each model can be thought
     * of being located at the center of the universe) to world space.
     */
    private float[] mModelMatrix = new float[16];

    @Override
    public void onDrawFrame(GL10 glUnused)
    {
        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);

        // Do a complete rotation every 10 seconds.
        long time = SystemClock.uptimeMillis() % 10000L;
        float angleInDegrees = (360.0f / 10000.0f) * ((int) time);

        // Draw the triangle facing straight on.
        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
        drawTriangle(mTriangle1Vertices);

        ...
    }

这是实际显示在屏幕上的内容。 首先清除了屏幕,因此我们没有得到任何奇怪的镜面效果。我们希望三角形在屏幕上能有平滑的动画,我们使用时间旋转三角形。 每当您在屏幕上制作动画时,通常最好使用时间而不是帧速率。

实际绘图在drawTriangle中完成:

    // New class members
    /** Allocate storage for the final combined matrix. This will be passed into the shader program. */
    private float[] mMVPMatrix = new float[16];

    /** How many elements per vertex. */
    private final int mStrideBytes = 7 * mBytesPerFloat;

    /** Offset of the position data. */
    private final int mPositionOffset = 0;

    /** Size of the position data in elements. */
    private final int mPositionDataSize = 3;

    /** Offset of the color data. */
    private final int mColorOffset = 3;

    /** Size of the color data in elements. */
    private final int mColorDataSize = 4;

    /**
     * Draws a triangle from the given vertex data.
     *
     * @param aTriangleBuffer The buffer containing the vertex data.
     */
    private void drawTriangle(final FloatBuffer aTriangleBuffer)
    {
        // Pass in the position information
        aTriangleBuffer.position(mPositionOffset);
        GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
                mStrideBytes, aTriangleBuffer);

        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Pass in the color information
        aTriangleBuffer.position(mColorOffset);
        GLES20.glVertexAttribPointer(mColorHandle, mColorDataSize, GLES20.GL_FLOAT, false,
                mStrideBytes, aTriangleBuffer);

        GLES20.glEnableVertexAttribArray(mColorHandle);

        // This multiplies the view matrix by the model matrix, and stores the result in the MVP matrix
        // (which currently contains model * view).
        Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);

        // This multiplies the modelview matrix by the projection matrix, and stores the result in the MVP matrix
        // (which now contains model * view * projection).
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);

        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
    }

你还记得我们最初创建渲染器时定义的那些缓冲区吗? 我们终于可以使用它们了。 我们需要告诉OpenGL如何使用GLES20.glVertexAttribPointer()来使用这些数据。 让我们来看看第一个调用。

// Pass in the position information
aTriangleBuffer.position(mPositionOffset);
GLES20.glVertexAttribPointer(mPositionHandle, mPositionDataSize, GLES20.GL_FLOAT, false,
        mStrideBytes, aTriangleBuffer);
GLES20.glEnableVertexAttribArray(mPositionHandle);

我们将缓冲区位置设置为位置偏移量,它位于缓冲区的开头。然后我们告诉OpenGL使用这些数据并将其提供给顶点着色器并将其应用于我们的position属性。我们还需要告诉OpenGL每个顶点或步幅之间有多少个元素。

注意:步幅需要以字节为单位进行定义。虽然我们在顶点之间有7个元素(3个用于位置,4个用于颜色),但实际上我们有28个字节,因为每个浮点数占用4个字节。忘记此步骤可能不会导致任何错误,但您会想知道为什么您在屏幕上看不到任何内容。

最后,我们启用顶点属性并转到下一个属性进行设置。继续看代码,我们构建一个组合矩阵,将点投影到屏幕上。 我们也可以在顶点着色器中执行此操作,但由于它只需要完成一次,所以我们可以只缓存结果。 我们使用GLES20.glUniformMatrix4fv()将最终矩阵传递给顶点着色器,GLES20.glDrawArrays()将我们的点转换为三角形并将其绘制在屏幕上。

概括

呼! 这是一个很重要的课程,如果你完成了这一课,你会非常开心。 我们学习了如何创建OpenGL上下文,传递形状数据,加载顶点和像素着色器,设置转换矩阵,最后将它们组合在一起。 如果一切顺利,您应该会看到类似于下侧屏幕截图的内容。

这一课有很多要消化的内容,你可能需要多次阅读这些步骤才能理解它。 OpenGL ES2需要更多的设置工作才能开始,但是一旦你完成了这个过程几次,你就会记住前面的流程。

Android OpenGL ES 2.0(一)---入门_第1张图片

在Android Market上发布

在开发应用程序时,我们不希望无法运行这些应用程序的人在市场上看到它们,否则当应用程序在其设备上崩溃时,我们可能会收到大量糟糕的评论和评分。 要防止OpenGL ES2应用程序出现在不支持它的设备上,您可以将其添加到清单中:

这告诉市场您的应用程序需要OpenGL ES2,它会将您的应用程序隐藏掉如果设备不支持它。

进一步探索

尝试更改动画速度,顶点或颜色,看看会发生什么!
可以从GitHub上的项目站点下载本课程的完整源代码。

你可能感兴趣的:(Android,移动开发,Android,OpenGL,ES2.0)