OpenGL绘制一张图片的流程--以android-openGL-canvas为例

样例代码来源: android-openGL-canvas

OpenGL文档参考:
OpenGL文档
本文只对流程做排序以及一些简单的说明,想了解原理的请查看OpenGL相关文档,有对相应函数有疑问的可查阅上述文档。

一、创建 eglContext。EGLContext 是一个比较重的对象,所以一般只创建一次。

例子代码代码主要在 EglHelper

  • egl = EGLContext.getEGL()
  • 获取 eglDisplay = eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
  • 获取 EGLConfig eglChooseConfig(..)
  • eglInitialize(mEgl, mEglDisplay, mEglConfig, eglContext)
  • 创建 eglContext = eglCreateContext(display, config, eglContext, attrib_list))
  • 大致的相关代码
    public EGLContext start(EGLContext eglContext) {
        if (GLThread.LOG_EGL) {
            Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
        }
        /*
         * Get an EGL instance
         */
        mEgl = (EGL10) EGLContext.getEGL();

        /*
         * Get to the default display.
         */
        mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);

        if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
            throw new RuntimeException("eglGetDisplay failed");
        }

        /*
         * We can now initialize EGL for that display
         */
        int[] version = new int[2];
        if (!mEgl.eglInitialize(mEglDisplay, version)) {
            throw new RuntimeException("eglInitialize failed");
        }
        mEglConfig = eglConfigChooser.chooseConfig(mEgl, mEglDisplay);

            /*
            * Create an EGL context. We want to do this as rarely as we can, because an
            * EGL context is a somewhat heavy object.
            */
        mEglContext = eglContextFactory.createContext(mEgl, mEglDisplay, mEglConfig, eglContext);
        if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
            mEglContext = null;
            throwEglException("createContext");
        }
        if (GLThread.LOG_EGL) {
            Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
        }

        mEglSurface = null;

        return mEglContext;
    }

二、创建 surface

  • eglCreateWindowSurface(display, config, nativeWindow, surfaceAttribs)

  • 将 context 和 surface 联系起来。
    eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)

  • 如果需要更换surface,就先destroy现在的
    eglDestroySurface

  • 大致的相关代码

    public boolean createSurface(Object surface) {
        if (GLThread.LOG_EGL) {
            Log.w("EglHelper", "createSurface()  tid=" + Thread.currentThread().getId());
        }
        /*
         * Check preconditions.
         */
        if (mEgl == null) {
            throw new RuntimeException("egl not initialized");
        }
        if (mEglDisplay == null) {
            throw new RuntimeException("eglDisplay not initialized");
        }
        if (mEglConfig == null) {
            throw new RuntimeException("mEglConfig not initialized");
        }

        /*
         *  The window size has changed, so we need to create a new
         *  surface.
         */
        destroySurfaceImp();

        /*
         * Create an EGL surface we can render into.
         */
        mEglSurface = eglWindowSurfaceFactory.createWindowSurface(mEgl,
                mEglDisplay, mEglConfig, surface);

        if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
            int error = mEgl.eglGetError();
            if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
                Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
            }
            return false;
        }

        /*
         * Before we can issue GL commands, we need to make sure
         * the context is current and bound to a surface.
         */
        if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
            /*
             * Could not make the context current, probably because the underlying
             * SurfaceView surface has been destroyed.
             */
            logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
            return false;
        }

        return true;
    }

三、接下来就是使用vertex数据,texture数据,vertex shader, texture shader进行绘制了,这一部分是可以随时替换数据绘制新的东西的,其中比较耗性能的部分是编译链接shader的部分,所以要对编译链接部分进行缓存以保证性能。

代码主要在GLES20Canvas

vertex 数据

glGenBuffers(...)
glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId)
glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer, GLES20.GL_STATIC_DRAW)

  • 大致的相关代码
    private int uploadBuffer(Buffer buffer, int elementSize) {
        mGLId.glGenBuffers(1, mTempIntArray, 0);
        checkError();
        int bufferId = mTempIntArray[0];
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId);
        checkError();
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer,
                GLES20.GL_STATIC_DRAW);
        checkError();
        return bufferId;
    }

编译,链接shader等

  • 以两个 String 对象作为参数,分别是 vertex shader 和 fragment shader
  • 编译
    glCreateShader
    glShaderSource
    glCompileShader
    private static int loadShader(int type, String shaderCode) {
        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode);
        checkError();
        GLES20.glCompileShader(shader);
        checkError();

        return shader;
    }
  • 链接
    glCreateProgram
    glAttachShader vertex
    glAttachShader fragment
    glLinkProgram
    glGetProgramiv
    private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) {
        int program = GLES20.glCreateProgram();
        checkError();
        if (program == 0) {
            throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError());
        }
        GLES20.glAttachShader(program, vertexShader);
        checkError();
        GLES20.glAttachShader(program, fragmentShader);
        checkError();
        GLES20.glLinkProgram(program);
        checkError();
        int[] mLinkStatus = mTempIntArray;
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0);
        if (mLinkStatus[0] != GLES20.GL_TRUE) {
            Log.e(TAG, "Could not link program: ");
            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
            GLES20.glDeleteProgram(program);
            program = 0;
        }
        loadHandles(params, program);
        return program;
    }

设定绘制区域

  • glViewport(width, height)
    此时就可以利用 width 和 height 来对 model view projection 矩阵进行一些初始设置了
    @Override
    public void setSize(int width, int height) {
        mWidth = width;
        mHeight = height;
        GLES20.glViewport(0, 0, mWidth, mHeight);
        checkError();
        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
        Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1);
        if (getTargetTexture() == null) {
            mScreenWidth = width;
            mScreenHeight = height;
            Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);
            Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);
        }
    }

对model-view矩阵进行处理(也可以不处理,看需求)

可以是平移,旋转,放缩之类的。

传入 texture, 一个带有宽高和图像数据的对象。

  • glUseProgram(programId) 使用之前编译链接好的的id
  • 将texture数据传入到OpenGL里的gl操作
    glActiveTexture(GLES20.GL_TEXTURE0)
    private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
        GLES20.glUseProgram(program);
        checkError();
        enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        checkError();
        texture.onBind(this);
        GLES20.glBindTexture(texture.getTarget(), texture.getId());
        checkError();
        GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
        checkError();
        GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha());
        checkError();
    }

glGenTextures
glBindTexture
glTexParameteri 设定相关的texture params

    @Override
    public void setTextureParameters(BasicTexture texture) {
        int target = texture.getTarget();
        GLES20.glBindTexture(target, texture.getId());
        checkError();
        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    }
  • 如果bitmap的宽高是2的倍数
    glBindTexture
    glTexImage2D
    @Override
    public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
        int target = texture.getTarget();
        GLES20.glBindTexture(target, texture.getId());
        checkError();
        GLUtils.texImage2D(target, 0, bitmap, 0);
    }
  • 如果不是2的倍数
    glBindTexture
    GLUtils.texSubImage2D
    @Override
    public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
            int format, int type) {
        int target = texture.getTarget();
        GLES20.glBindTexture(target, texture.getId());
        checkError();
        GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
    }
glUniform1i(...) // 对shader里面的textureSampler传值,因为ActiveTexture的是Texture0,所以传0就可以了。
    GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);

将vertex的值传入OpenGL

glBindBuffer
glVertexAttribPointer
glBindBuffer 0 清理

    private void setPosition(ShaderParameter[] params, int offset) {
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates);
        checkError();
        GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE);
        checkError();
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
        checkError();
    }

texture Matrix

  • 利用texture的宽高设定 textureMatrix。 textureMatrix 是shader 里的一个uniform对象,用来映射从texture对应位置获取的颜色到绘制区域。例如在不进行平移旋转等情况下,第一个像素点就绘制在第一个位置。
        TextureMatrixTransformer.copyTextureCoordinates(...);
        TextureMatrixTransformer.convertCoordinate(...);
        TextureMatrixTransformer.setTextureMatrix(...);

model view projection matrix 将这个matrix传入到OpenGL里

translateM
scaleM
multiplyMM
glUniformMatrix4fv(...)

    private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) {
        Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
        Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
        Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0);
        GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE);
        checkError();
    }

最后就是绘制了

glEnableVertexAttribArray positionHandle
glDrawArrays
glDisableVertexAttribArray positionHandle

    private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width,
            float height) {
        setMatrix(params, x, y, width, height);
        int positionHandle = params[INDEX_POSITION].handle;
        GLES20.glEnableVertexAttribArray(positionHandle);
        checkError();
        GLES20.glDrawArrays(type, 0, count);
        checkError();
        GLES20.glDisableVertexAttribArray(positionHandle);
        checkError();
    }

四、总结

以上,说明了OpenGL绘制的三个流程。由于OpenGL是独立于语言的,这些流程不仅适用于Android,使用其它语言编写也可借鉴。从上面冗长的流程可以看到,每次要进行绘制都写那么多代码实在不经济,那么接下来的文章就会讨论如何封装以上的流程了。其实开头的github仓库里面的代码已经有封装好的类了,有需要的话可以先去看源代码。感谢阅读!
接下来可以看:
如何封装 opengl 流程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容