Android OpenGL基础(六、帧缓冲)

本文详细介绍了OpenGL的图形渲染管线流程,包括顶点着色器、图元装配、几何着色器、光栅化、片段着色器等阶段。同时,深入讨论了帧缓冲对象(FBO)的概念和使用,包括创建帧缓冲、渲染到纹理以及帧缓冲在多级滤镜、相机帧缓存等高级效果中的应用。通过帧缓冲,开发者可以实现自定义的渲染流程和复杂效果。
摘要由CSDN通过智能技术生成

一、图形渲染管线流程

  经过前面几张的学习后,我们对OpenGL基础用法已经有了初步理解,现在来介绍下图形渲染管线流程,为OpenGL进阶知识做好准备。

  OpenGL的图形渲染管线(Graphics Pipeline)是指:将一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。主要流程如下:
pipeline.png

  1. 图形渲染管线的第一个部分是顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标,同时顶点着色器允许我们对顶点属性进行一些基本处理。
  2. 图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状。图元(Primitive),任何一个绘制指令的调用都将把图元传递给OpenGL。这是其中的几个:GL_POINTSGL_TRIANGLESGL_LINE_STRIP
  3. 图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。在《Android OpenGL基础(一、绘制三角形四边形)》的例子中生成了另一个三角形。
  4. 几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
  5. 片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
  6. 在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值,用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。

二、帧缓冲

  下面介绍OpenGL进阶知识:帧缓冲。了解帧缓冲后,可以实现多级滤镜、镜面、离屏渲染、相机帧缓存等高级效果。

  在《Android OpenGL基础(四、图片后处理)》一文中,我们介绍了图片滤镜的实现方法,如果要实现多级滤镜(比如先灰度滤镜再边缘滤镜)该如何处理。一种简单的方法是把多个滤镜的片段着色器代码都放在一起,但是这种实现方法不够灵活,代码耦合严重且不能实现自由组合滤镜。下面介绍帧缓冲的实现方式。

2.1 帧缓冲对象

  在前面章节的OpenGL例子中,在设置了顶点缓冲后,我们又设置了颜色缓冲或者纹理等,而颜色缓冲或者纹理等之所以可以展示出来,是因为它们都是在默认帧缓冲的渲染缓冲上进行的。可以理解为帧缓冲(Framebuffer Object, FBO)是一种可以缓冲颜色、纹理等渲染指令结果的高级缓冲。帧缓冲可以将纹理、渲染缓冲对象(Renderbuffer Object)作为其附件,在帧缓冲激活后,渲染指令将会写入到帧缓冲的附件中。

  一个完整的帧缓冲需要满足以下的条件:

  1. 附加至少一个缓冲(颜色、深度或模板缓冲)。
  2. 至少有一个颜色附件(Attachment)。
  3. 所有的附件都必须是完整的(保留了内存)。
  4. 每个缓冲都应该有相同的样本数。
      OpenGL提供了自定义帧缓冲的方法,因此实现多级滤镜可以通过自定义多级帧缓冲,每个帧缓冲附加一个纹理附件,这样每个帧缓冲对应一个滤镜实现,并将结果传递给下一个帧缓冲作为下一个缓冲的纹理,实现多级滤镜自由组合。

2.2 基本用法

  创建帧缓冲比较简单,此外在创建完后需要给帧缓冲附加一个附件。附件是一个内存位置,它能够作为帧缓冲的一个缓冲,可以将它想象为一个图像。

2.2.1 帧缓冲附件

  帧缓冲(Framebuffer Object, FBO)的附件有两种类型:

  1. 纹理
  2. 渲染缓冲对象(Renderbuffer Object):渲染缓冲对象(Renderbuffer Object)是在纹理之后引入到OpenGL中。渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。由于渲染缓冲对象通常都是只写的,常用于深度和模板附件(适合于大部分时间只写,而不需要从缓冲中读取值)。

  选择哪种附件通常的规则是,如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。如果你需要从缓冲中采样颜色或深度值等数据,那么应该选择纹理附件。

2.2.2 创建帧缓冲

  本文主要介绍以纹理作为附件的帧缓冲对象。创建方式如下:

// 创建纹理,后面会把这个纹理附加到帧缓冲上
int[] texFBO = {0};
GLES20.glGenTextures(1, texFBO, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texFBO[0]);
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

// 创建帧缓冲
int[] FBO = {0};
GLES20.glGenFramebuffers(1, FBO, 0);
// 绑定帧缓冲
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, FBO[0]);
// 把纹理作为帧缓冲的附件
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texFBO[0], 0);
Log.d(LOGTAG, "initFBO error status: " + GLES20.glGetError());
// 检查帧缓冲是否完整
int FBOstatus = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
if (FBOstatus != GLES20.GL_FRAMEBUFFER_COMPLETE)
    Log.e(LOGTAG, "initFBO failed, status: " + FBOstatus);

2.2.3 渲染到纹理

  在创建了帧缓冲及附加到其中的纹理后,接下来看怎么渲染到帧缓冲内的纹理,以及如何把帧缓冲的纹理渲染到屏幕上。要渲染到自定义FBO,只需要绑定当前FBO即可,在绑定到GL_FRAMEBUFFE目标之后,所有的读取和写入帧缓冲的操作将会影响当前绑定的帧缓冲。如果要渲染到屏幕上,则绑定到默认缓冲帧即可。

// 绑定自定义缓冲帧
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers.get(i))
// 绑定默认缓冲帧
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)

2.3.4 绘制流程

  在加入自定义帧缓冲后的绘制流程与只有默认帧缓冲的流程对比如下:
image.png
  代码流程如下所示,在onDrawFilterList中遍历自定义FBO列表,在每个列表中分别完成各自的滤镜操作,然后在渲染完成后,将纹理传给下一个帧缓冲。

class OpenGLImageFilter {
    /**
     * 顶点着色器代码;
     */
    private val vertexShaderCode =
        "attribute vec4 inputTextureCoordinate;" +
                " varying vec2 textureCoordinate;" +
                "attribute vec4 vPosition;" +
                "void main() {" +
                // 把vPosition顶点经过矩阵变换后传给gl_Position
                "  gl_Position = vPosition;" +
                "textureCoordinate = inputTextureCoordinate.xy;" +
                "}"

    /**
     * 片段着色器代码
     */
    private val fragmentShaderCode ="""
        varying highp vec2 textureCoordinate;
        uniform sampler2D inputImageTexture;
        void main() {
            gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
        }
    """
    
    /**
     * 遍历绘制滤镜list
     * 开始的纹理都绘制到FBO上,直到最后一个绘制到默认缓冲帧上进行上屏
     */
    fun onDrawFilterList(textureId: Int, cubeVertexBuffer: FloatBuffer?,
        textureVertexBuffer: FloatBuffer?) {
        if (filterLists != null) {
            val size: Int = filterLists.size
            var previousTexture = textureId
            // 遍历滤镜list
            for (i in 0 until size) {
                val filter: OpenGLImageFilter = filterLists.get(i)
                val isNotLast = i < size - 1
                if (isNotLast) {
                    // 不是最后,则绑定到FBO
                    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers.get(i))
                } else {
                    // 最后一个时,绑定到默认的帧缓冲
                    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0)
                }
                GLES20.glClearColor(0f, 0f, 0f, 0f)
                // 绘制到帧缓冲上的纹理
                filter.onDrawItem(previousTexture, cubeVertexBuffer, textureVertexBuffer)
                if (isNotLast) {
                    // 更新FBO纹理索引到下一个
                    previousTexture = frameBufferTextures.get(i)
                }
            }
        }
    }
    
    /**
     * 纹理绘制方法
     * @param textureId 纹理ID
     * @param vertexBuffer 顶点数组缓冲
     * @param textureVertexBuffer 纹理顶点数组缓冲
     */
    fun onDrawItem(textureId: Int, vertexBuffer: FloatBuffer,
        textureVertexBuffer: FloatBuffer) {
        GLES20.glUseProgram(glProgId)
        // 顶点缓冲数组重置
        vertexBuffer.position(0)
        GLES20.glVertexAttribPointer(glAttribPosition, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer)
        GLES20.glEnableVertexAttribArray(glAttribPosition)
        // 纹理顶点数组缓冲重置
        textureVertexBuffer.position(0)
        GLES20.glVertexAttribPointer(
            glAttribTextureCoordinate, 2, GLES20.GL_FLOAT, false, 0,
            textureVertexBuffer
        )
        GLES20.glEnableVertexAttribArray(glAttribTextureCoordinate)
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
        // 绑定纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
        GLES20.glUniform1i(glUniformTexture, 0)
        // 绘制纹理
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
        GLES20.glDisableVertexAttribArray(glAttribPosition)
        GLES20.glDisableVertexAttribArray(glAttribTextureCoordinate)
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
    }
}

2.3.5 删除帧缓冲

  在不需要帧缓冲时,调用如下方法删除帧缓冲列表:

GLES20.glDeleteFramebuffers(frameBuffers.length, frameBuffers, 0);

三、相机帧缓冲

  同样地,对于相机也可以采用帧缓冲方案,来实现更复杂的效果。可以将OpenGL相机基础改造为自定义FBO的方式来实现相机预览。在一些复杂的项目中,需要用FBO实现相机预览,并在此基础上加入多级FBO来实现自由扩展相机滤镜或其他绘制需求。实现原理比较简单:在获得相机数据后,首先将相机纹理绘制到FBO,然后依次绘制多级FBO,最终绘制到默认帧缓冲进行上屏展示。

3.1 相机与FBO

  需要注意的是,在获取到相机帧后,相机帧的顶点坐标与2D图片不同(具体参考"相机预览及滤镜")。首先,分别用不同程序表示相机纹理绘制和2D纹理绘制程序,相机的OES着色器和用于FBO的2D纹理片段着色器GLSL代码如下:

class CameraGLRenderer(private val frameAvailableListener: SurfaceTexture.OnFrameAvailableListener) :
    GLSurfaceView.Renderer {

    // 顶点着色器
    private val vertexShaderCode = """attribute vec2 vPosition;
        attribute vec2 vTexCoord;
        varying vec2 texCoord;
        void main() {
        texCoord = vTexCoord;
        gl_Position = vec4 ( vPosition.x, vPosition.y, 0.0, 1.0 );
        }"""

    /**
     * 片段着色器;用于相机纹理;
     */
    private val fssOES = """#extension GL_OES_EGL_image_external : require
        precision mediump float;
        uniform samplerExternalOES sTexture;
        varying vec2 texCoord;
        void main() {
        gl_FragColor = texture2D(sTexture,texCoord);
        }"""

    /**
     * 片段着色器;用于2D的FBO
     */
    private val fss2D = """precision mediump float;
        uniform sampler2D sTexture;
        varying vec2 texCoord;
        void main() {
        gl_FragColor = texture2D(sTexture,texCoord);
        }"""

    // 顶点着色器;四边形顶点坐标
    private val squareCoords = floatArrayOf(-1f, -1f, -1f, 1f, 1f, -1f, 1f, 1f)
    // 相机纹理的四个点坐标
    private val texCoordOES = floatArrayOf(0f, 1f, 0f, 0f, 1f, 1f, 1f, 0f)
    // 2D纹理的四个点坐标
    private val texCoord2D = floatArrayOf(0f, 0f, 0f, 1f, 1f, 0f, 1f, 1f)
    // 四个顶点的缓冲数组
    private val vertexBuffer: FloatBuffer =
        ByteBuffer.allocateDirect(squareCoords.size * 4).order(ByteOrder.nativeOrder())
            .asFloatBuffer().apply {
                put(squareCoords)
                position(0)
            }
    // 相机纹理顶点的缓冲数组
    private val oESvertexBuffer: FloatBuffer =
        ByteBuffer.allocateDirect(texCoordOES.size * 4).order(ByteOrder.nativeOrder())
            .asFloatBuffer().apply {
                put(texCoordOES)
                position(0)
            }
    // 2D纹理顶点的缓冲数组
    private val texture2DvertexBuffer: FloatBuffer =
        ByteBuffer.allocateDirect(texCoord2D.size * 4).order(ByteOrder.nativeOrder())
            .asFloatBuffer().apply {
                put(texCoord2D)
                position(0)
            }

    /**
     * OES着色器程序ID引用
     */
    private var mOESProgram = 0

    /**
     * FBO用到的2D着色器程序
     */
    private var m2DProgram = 0
    
    init {
        // 编译顶点着色器和片段着色器
        val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val oESfragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fssOES)
        val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fss2D)
        // glCreateProgram函数创建一个着色器程序,并返回新创建程序对象的ID引用
        mOESProgram = GLES20.glCreateProgram().also {
            // 把顶点着色器添加到程序对象
            GLES20.glAttachShader(it, vertexShader)
            // 把片段着色器添加到程序对象
            GLES20.glAttachShader(it, oESfragmentShader)
            // 连接并创建一个可执行的OpenGL ES程序对象
            GLES20.glLinkProgram(it)
        }
        m2DProgram = GLES20.glCreateProgram().also {
            GLES20.glAttachShader(it, vertexShader)
            GLES20.glAttachShader(it, fragmentShader)
            GLES20.glLinkProgram(it)
        }
    }
}

3.2 初始化FBO

  在onSurfaceChanged中创建FrameBuffer,设置FrameBuffer的宽高:

class CameraGLRenderer(private val frameAvailableListener: SurfaceTexture.OnFrameAvailableListener) :GLSurfaceView.Renderer {
    // FBO引用
    private val FBO = intArrayOf(0)
    // 相机纹理
    private val texCamera = intArrayOf(0)
    // FBO的2D纹理
    private  var texFBO:IntArray = intArrayOf(0)
    // 
    private  var texDraw:IntArray = intArrayOf(0)

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        initFBO(width, height)
    }
    
    /**
    * 初始化FBO
    **/
    private fun initFBO(width: Int, height: Int) {
        // 创建相机纹理
        GLES20.glGenTextures(1, texCamera, 0)
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texCamera[0])
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST)
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST)
        mSTexture = SurfaceTexture(texCamera[0])
        mSTexture?.setOnFrameAvailableListener(frameAvailableListener)
        // 创建
        GLES20.glGenTextures(1, texDraw, 0)
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texDraw[0])
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
            GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST)
        // 创建FBO的纹理
        GLES20.glGenTextures(1, texFBO, 0)
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texFBO[0])
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0,
            GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST)
        // 创建FBO
        GLES20.glGenFramebuffers(1, FBO, 0)
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, FBO[0])
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
            GLES20.GL_TEXTURE_2D, texFBO.get(0), 0)
        val FBOstatus = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER)
        mFBOWidth = width
        mFBOHeight = height
    }
}

3.3 绘制流程

  自定义CameraTextureListener接口来实现多级滤镜:

public interface CameraTextureListener {
    /**
    * 
    **/
    public boolean onCameraTexture(int texIn, int texOut, int width, int height);
}

  绘制流程如下:

class CameraGLRenderer(private val frameAvailableListener: SurfaceTexture.OnFrameAvailableListener) :
    GLSurfaceView.Renderer {

    override fun onDrawFrame(gl: GL10?) {
        if (mUpdateST) {
            mSTexture?.updateTexImage()
            mUpdateST = false
        }
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
        // 滤镜集合list
        val texListener: CameraTextureListener = getCameraTextureListener()
        if (texListener != null) {
            // texCamera(OES) -> texFBO
            drawTex(texCamera[0], true, FBO[0])
            // call user code (texFBO -> texDraw)
            val modified =
                texListener.onCameraTexture(texFBO[0], texDraw[0], mCameraWidth, mCameraHeight)
            if (modified) {
                // texDraw -> screen
                drawTex(texDraw[0], false, 0)
            } else {
                // texFBO -> screen
                drawTex(texFBO[0], false, 0)
            }
        } else {
            // texCamera(OES) -> screen
            drawTex(texCamera[0], true, 0)
        }
    }

    private fun drawTex(tex: Int, isOES: Boolean, fbo: Int) {
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo)
        if (fbo == 0) {
            GLES20.glViewport(0, 0, mView.getWidth(), mView.getHeight())
        } else {
            GLES20.glViewport(0, 0, mFBOWidth, mFBOHeight)
        }
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
        if (isOES) {
            GLES20.glUseProgram(progOES)
            GLES20.glVertexAttribPointer(vPosOES, 2, GLES20.GL_FLOAT, false, 4 * 2, vert)
            GLES20.glVertexAttribPointer(vTCOES, 2, GLES20.GL_FLOAT, false, 4 * 2, texOES)
        } else {
            GLES20.glUseProgram(prog2D)
            GLES20.glVertexAttribPointer(vPos2D, 2, GLES20.GL_FLOAT, false, 4 * 2, vert)
            GLES20.glVertexAttribPointer(vTC2D, 2, GLES20.GL_FLOAT, false, 4 * 2, tex2D)
        }
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
        if (isOES) {
            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex)
            GLES20.glUniform1i(GLES20.glGetUniformLocation(progOES, "sTexture"), 0)
        } else {
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex)
            GLES20.glUniform1i(GLES20.glGetUniformLocation(prog2D, "sTexture"), 0)
        }
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
        GLES20.glFlush()
    }
}

The End

欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
请添加图片描述

Android OpenGL开发者文档:https://developer.android.com/guide/topics/graphics/opengl

opengl学习资料:https://learnopengl-cn.github.io/

Android OpenGL基础(一、绘制三角形四边形):https://juejin.cn/post/7076751737461145630

Android OpenGL基础(二、坐标系统):https://juejin.cn/post/7077132016759603208/

Android OpenGL基础(三、绘制Bitmap纹理):https://juejin.cn/post/7079678062849163277/

Android OpenGL基础专栏:https://juejin.cn/column/7076751675595653150

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
学习OpenGL ES(Embedded Systems)可以让你在Android平台上构建高性能的2D和3D图形应用程序。以下是一些学习OpenGL ES的步骤和建议: 1. 了解OpenGL ES的基础知识:OpenGL ES是一个跨平台的图形API,用于在移动设备上进行高性能的2D和3D绘图。在学习OpenGL ES之前,建议先了解OpenGL ES的基础知识,例如图形管线、着色器、顶点缓冲区对象(VBO)等。 2. 学习OpenGL ES的编程语言:OpenGL ES支持多种编程语言,包括C、C++、Java等。对于Android开发者来说,Java是最常用的编程语言。 3. 下载OpenGL ES开发工具:为了开始学习OpenGL ES,需要安装一个开发环境。Android Studio是一个常用的Android开发工具,可以通过安装Android Studio来获取OpenGL ES的开发环境。 4. 学习OpenGL ES的API:OpenGL ES有许多的API可以使用,例如OpenGL ES 1.0、OpenGL ES 2.0、OpenGL ES 3.0等。建议从OpenGL ES 2.0开始学习,因为它支持现代的图形管线和着色器编程。 5. 掌握OpenGL ES的基本概念和技术:学习OpenGL ES的一些基本概念和技术包括着色器编程、渲染缓冲区对象(RBO)、缓冲区对象(FBO)等。 6. 实践:最好的学习方法是通过实践来掌握OpenGL ES。可以通过编写简单的图形应用程序来加深对OpenGL ES的理解和掌握。 7. 学习OpenGL ES的高级技术:一旦掌握了基本概念和技术,可以开始学习OpenGL ES的高级技术,例如纹理映射、光照、阴影等。 总之,学习OpenGL ES需要掌握基本概念和技术,并通过实践来加深理解。此外,需要耐心和毅力,因为OpenGL ES是一个复杂的主题,需要花费时间和精力来学习。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值