资讯详情

Android OpenGL基础(一、绘制三角形四边形)

一、OpenGL简介

1.1 OpenGL规范

??OpenGL 是跨平台图形 API,用于为 3D 指定图形处理硬件标准的软件接口。OpenGL ES 是 OpenGL 一种适用于嵌入式设备的标准形式。Android 支持多版 OpenGL ES API(最新推荐 Android 设备上使用版本):

  • OpenGL ES 1.0 和 1.1 - 此 API 规范受 Android 1.0 支持更高版本。
  • OpenGL ES 2.0 - 此 API 规范受 Android 2.2(API 级别 8)支持更高版本。
  • OpenGL ES 3.0 - 此 API 规范受 Android 4.3(API 级别 支持18)和更高版本。
  • OpenGL ES 3.1 - 此 API 规范受 Android 5.0(API 级别 21)及更高版本的支持。

1.2 OpenGL框架基本类

??Android 框架中,GLSurfaceView 是使用 OpenGL 图形视图容器, GLSurfaceView.Renderer 可控制视图中绘制的图形。

1.2.1 GLSurfaceView

??此类是一个 View,选择全屏或接近全屏的图形视图GLSurfaceView更合理。另外,如果你想, OpenGL ES 也可以考虑将图形整合到其布局的一小部分 TextureView。SurfaceView也可以用于OpenGL视图容器,但需要编写更多的代码,暂时不推荐。

1.2.2 GLSurfaceView.Renderer

??这个界面定义了在GLSurfaceView绘制图形所需的方法。通过这个接口的实现类 GLSurfaceView.setRenderer()GLSurfaceView 例子是相关的。GLSurfaceView.Renderer 接口要求实现以下方法:

  • onSurfaceCreated():创建系统 GLSurfaceView 调用此方法一次。通常用于设置只需要一次的操作,如设置 OpenGL 环境参数或初始化 OpenGL 图形对象。
  • onDrawFrame():系统将每次重新绘制 GLSurfaceView 调用这种方法。绘制图像。GLSurfaceView主要方法。
  • onSurfaceChanged():系统会在 GLSurfaceView 几何变化(包括几何变化) GLSurfaceView 当设备屏幕的尺寸发生变化或方向发生变化时,调用此方法。例如,当设备屏幕的方向为横向时,系统会调用此方法。

1.2.3 RenderMode

??GLSurfaceView渲染模式有两种:

  1. RENDERMODE_CONTINUOUSLY:自动连续调用GLSurfaceView.Renderer绘制时会执行GLSurfaceView.Renderer的onDrawFrame()方法;
  2. RENDERMODE_WHEN_DIRTY:surface创建后调用一次;或者主动调用开发者MyGLSurfaceView的requestRender时才会调用GLSurfaceView.Renderer绘制(执行)onDrawFrame()方法);

1.3 OpenGL标准化设备坐标

??OpenGL是右手坐标系。简单来说,正x轴在你的右手边,正y轴朝上,正z轴朝后。想象一下你的屏幕在三个轴的中心,正z轴穿过你的屏幕朝向你。

coordinate_systems_right_handed.png

??与通常的屏幕坐标不同,OpenGL假设屏幕采用均匀的方形坐标系,OpenGL标准化设备坐标采用(Normalized Device Coordinates, NDC),标准化设备坐标y轴向上,(0, 坐标是图像的中心,而不是左上角。标准化设备坐标x、y和z值都在-1.0到1.0的坐标系,任何落在范围外的坐标都会被丢弃/切割,不会画在手机屏幕上。 ??例如,三角形的三个点在OpenGL标准坐标系中表示如下(z轴为0,暂时忽略z轴):

二、基本用法

??下面是一个绘制三角形的简单例子OpenGL。

2.1 构建 OpenGL ES 环境

2.1.1 声明清单

??manifext.xml中声明了OpenGL的版本glEsVersion是OpenGL ES 2.0。

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.bc.example">     <uses-feature android:glEsVersion="0x00020000" android:required="true" /> </manifest>

2.1.2 创建GLSurfaceView及Render

  GLSurfaceView是使用 OpenGL 绘制的图形的视图容器,而GLSurfaceView.Renderer可控制该视图中绘制的图形。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android">
    <com.bc.example.opengl.MyGLSurfaceView android:id="@+id/my_gl_surface_view" android:layout_width="match_parent" android:layout_height="match_parent"/>
</FrameLayout>

  在自定义的MyGLSurfaceView中把GLSurfaceView与Render关联起来:

class MyGLSurfaceView(context: Context?, attrs: AttributeSet?) : GLSurfaceView(context, attrs) { 
        
    private val renderer: MyGLRenderer

    init { 
        
        // 创建一个OpenGL ES 2.0上下文
        setEGLContextClientVersion(2)
        renderer = MyGLRenderer()
        // GLSurfaceView关联Render
        setRenderer(renderer)
        renderMode = RENDERMODE_WHEN_DIRTY
    }
}

  GLSurfaceView.Renderer负责实际的绘制工作,这里先只把背景设置为黑色:

class MyGLRenderer : GLSurfaceView.Renderer { 
        
    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) { 
        
        // 设置背景色为黑色
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) { 
        
        // 上面提到OpenGL使用的是标准化设备坐标;
        GLES20.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(gl: GL10?) { 
        
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
    }
}

  至此,打开APP后可以看到一个黑色背景的GLSurfaceView。

2.2 扩展绘制方法

  如果需要在OpenGL中绘制其他内容,则在onDrawFrame()方法内扩充即可,在大型的OpenGL项目中,一般采用类似Android系统View体系的模板设计模式(即ViewGroup调用子View的draw()方法,层层调用)。   下面继续介绍绘制三角形的步骤,完成绘制三角形的主要工作在自定义的Triangle类中,只需要在onDrawFrame()中调用Triangle完成三角形的绘制:

class MyGLRenderer : GLSurfaceView.Renderer { 
        

    private lateinit var triangle: Triangle

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) { 
        
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
        // 我们的例子中在Triangle构造函数中就操作了GLES20,所以一定要在onSurfaceCreated中再去创建Triangle对象
        triangle = Triangle()
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) { 
        
        GLES20.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(gl: GL10?) { 
        
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
        // 绘制三角形
        triangle.draw()
    }
}

2.3 定义形状

  下面继续介绍Triangle绘制三角形的主要步骤,OpenGL使用FloatBuffer来管理顶点数据提高效率。一个三角形需要由三个顶点表示,这三个顶点在交给OpenGL时需要使用FloatBuffer格式,下面是三个顶点的定义方式:

class Triangle { 
        
    // 三角形三个点的坐标值(逆时针方向,在3D坐标系中,方向决定了哪面是正面)
    private var triangleCoords = floatArrayOf(
        0.0f, 0.622008459f, 0.0f,      // top
        -0.5f, -0.311004243f, 0.0f,    // bottom left
        0.5f, -0.311004243f, 0.0f      // bottom right
    )

    // 设置颜色(分别代表red, green, blue and alpha)
    private val color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)

    private var vertexBuffer: FloatBuffer =
        // 坐标点的数目 * float所占字节
        ByteBuffer.allocateDirect(triangleCoords.size * 4)
            .order(ByteOrder.nativeOrder()).asFloatBuffer().apply { 
        
                // 把坐标添加到FloatBuffer
                put(triangleCoords)
                // 设置buffer的位置为起始点0
                position(0)
            }
}

2.3.1 顶点缓冲对象

  这里引入三个名词:

  • 顶点数组对象:Vertex Array Object,VAO,表示存放顶点的数组,即例子中的triangleCoords;
  • 顶点缓冲对象:Vertex Buffer Object,VBO,表示存放顶点缓冲的数据,即例子中的FloatBuffer对象vertexBuffer;
  • 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO,表示存放顶点索引的数组,3.2小节会涉及到,用于描述顶点之间的顺序来重复使用顶点。   OpenGL会在GPU内存中存储大量顶点,使用顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存。使用缓冲对象VBO的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至GPU内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。   顶点数组对象与顶点缓冲对象关系如下,暂时简单了解即可:

2.4 绘制三角形

2.4.1 GLSL 着色器

  如果要渲染图形,OpenGL需要我们至少设置一个顶点着色器和一个片段着色器。我们需要做的第一件事是用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,然后编译这个着色器,这样我们就可以在着色器程序中使用它了。   着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。

2.4.2 顶点着色器

  顶点着色器是用于渲染形状的顶点的 OpenGL ES 图形代码。一个GLSL顶点着色器的源代码如下所示:

/**
* 顶点着色器代码
* 我们暂时将顶点着色器的源代码硬编码在代码文件顶部的C风格字符串中
**/
private val vertexShaderCode =
    "attribute vec4 vPosition;" +
            "void main() {" +
            "  gl_Position = vPosition;" +
            "}"
  • attribute是GLSL的关键字表示声明一个属性;
  • vec4是GLSL的数据类型关键字,包含4个float分量的默认向量
  • vPosition是开发者自定义的变量名;
  • gl_Position是GLSL的内建变量:顶点着色器输出向量,这里把我们自定义的vPosition赋值过去,后面我们会在着色器程序中取出来操作顶点着色器中的数据;

2.4.3 片段着色器

  片段着色器是用于使用颜色或纹理渲染形状面的 OpenGL ES 代码,主要工作是计算像素最后的颜色输出。一个片段着色器的源码如下:

/** * 片段着色器代码 */
private val fragmentShaderCode =
    "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            " gl_FragColor = vColor;" +
            "}"
  • Uniform是GLSL的关键字,是一种从CPU中的应用向GPU中的着色器发送数据的方式;与普通attribute不同的是,uniform是全局的(Global),即uniform变量在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
  • vColor是开发者自定义的变量名;
  • gl_FragColor是GLSL的内建变量:片段着色器对象,这里把我们自定义的vColor赋值过去,后面我们会在着色器程序中取出来并进行操作;

2.4.4 编译着色器代码

  为了能够让OpenGL使用上述着色器代码,首先需要在运行时动态编译它的源代码。编译操作只需执行一次,一般放在绘制对象的构造函数中完成。

/** * 编译着色器 * @param type 表示着色器的类型:GLES20.GL_VERTEX_SHADER GLES20.GL_FRAGMENT_SHADER * @param shaderCode 着色器源码;即上述硬编码的GLSL代码 **/
private fun loadShader(type: Int, shaderCode: String): Int { 
        
    // glCreateShader函数创建一个顶点着色器或者片段着色器,并返回新创建着色器的ID引用
    val shader = GLES20.glCreateShader(type)
    // 把着色器和代码关联,然后编译着色器
    GLES20.glShaderSource(shader, shaderCode)
    GLES20.glCompileShader(shader)
    return shader
}

2.4.5 着色器程序

  如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。

class Triangle { 
        
    init { 
        
        // 编译顶点着色器和片段着色器
        val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
        // glCreateProgram函数创建一个着色器程序,并返回新创建程序对象的ID引用
        mProgram = GLES20.glCreateProgram().also { 
        
            // 把顶点着色器添加到程序对象
            GLES20.glAttachShader(it, vertexShader)
            // 把片段着色器添加到程序对象
            GLES20.glAttachShader(it, fragmentShader)
            // 连接并创建一个可执行的OpenGL ES程序对象
            GLES20.glLinkProgram(it)
        }
    }
}

2.4.6 总结

  下面将上面几个流程串联起来,在实际绘制时执行的方法draw()中激活着色器程序,然后操作顶点着色器和片段着色器。下面是绘制三角形的完整流程代码:

class Triangle { 
        
    // 三角形三个点的坐标值(逆时针方向,在3D坐标系中,方向决定了哪面是正面)
    private var triangleCoords = floatArrayOf(
        0.0f, 0.5f, 0.0f,      // top
        -0.5f, -0.5f, 0.0f,    // bottom left
        0.5f, -0.5f, 0.0f      // bottom right
    )
    // 每个顶点的坐标数
    const val COORDS_PER_VERTEX = 3

    // 设置颜色(分别代表red, green, blue and alpha)
    private val color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)

    private var vertexBuffer: FloatBuffer =
        // 坐标点的数目 * float所占字节
        ByteBuffer.allocateDirect(triangleCoords.size * 4)
            .order(ByteOrder.nativeOrder()).asFloatBuffer().apply { 
        
                // 把坐标添加到FloatBuffer
                put(triangleCoords)
                // 设置buffer的位置为起始点0
                position(0)
            }

    /** * 顶点着色器代码; */
    private val vertexShaderCode =
        "attribute vec4 vPosition;" +
                "void main() {" +
                " gl_Position = vPosition;" +
                "}"

    /** * 片段着色器代码 */
    private val fragmentShaderCode =
        "precision mediump float;" +
                "uniform vec4 vColor;" +
                "void main() {" +
                " gl_FragColor = vColor;" +
                "}"

    /** * 着色器程序ID引用 */
    private var mProgram: Int

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

    private fun loadShader(type: Int, shaderCode: String): Int { 
        
        // glCreateShader函数创建一个顶点着色器或者片段着色器,并返回新创建着色器的ID引用
        val shader = GLES20.glCreateShader(type)
        // 把着色器和代码关联,然后编译着色器
        GLES20.glShaderSource(shader, shaderCode)
        GLES20.glCompileShader(shader)
        return shader
    }

    private val vertexCount: Int = triangleCoords.size / COORDS_PER_VERTEX
    private val vertexStride: Int = COORDS_PER_VERTEX * 4 // 4 bytes per vertex

    /** * 实际绘制时执行的方法 **/
    fun draw() { 
        
        // 激活着色器程序,把程序添加到OpenGL ES环境
        GLES20.glUseProgram(mProgram)
        // 获取顶点着色器中的vPosition变量(因为之前已经编译过着色器代码,所以可以从着色器程序中获取);用唯一ID表示
        val position = GLES20.glGetAttribLocation(mProgram, "vPosition")
        // 允许操作顶点对象position
        GLES20.glEnableVertexAttribArray(position)
        // 将顶点数据传递给position指向的vPosition变量;将顶点属性与顶点缓冲对象关联
        GLES20.glVertexAttribPointer(
            position, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
            false, vertexStride, vertexBuffer)
        // 获取片段着色器中的vColor变量
        val colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor")
        // 通过colorHandle设置绘制的颜色值
        GLES20.glUniform4fv(colorHandle, 1, color, 0)
        // 绘制顶点数组;
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)
        // 操作完后,取消允许操作顶点对象position
        GLES20.glDisableVertexAttribArray(position)
    }
}

  绘制结果如下:

三、绘制四边形

  OpenGL只支持绘制点、线、三角形(GL_POINTS、GL_LINES、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN等等)等图元。对于绘制四边形,OpenGL ES中的典型方式是使用两个绘制在一起的三角形:

3.1 绘制几何图形的方法

  OpenGL ES 提供了两类方法来绘制一个空间几何图形:

  • public abstract void glDrawArrays(int mode, int first, int count) 使用VetexBuffer 来绘制,顶点的顺序由vertexBuffer中的顺序指定。
  • public abstract void glDrawElements(int mode, int count, int type, Buffer indices) ,可以重新定义顶点的顺序,顶点的顺序由indices Buffer 指定。

3.2 索引缓冲对象

  以上两种方式都可以用来绘制四边形,区别在于glDrawElements方式通过另外一个索引数组表示顶点间的绘制顺序,更加灵活。通过索引数组告诉 OpenGL ES 图形管道按什么顺序绘制这些顶点。   同样地,索引数组也需要通过FloatBuffer的形式传递给OpenGL:

// 四个顶点的绘制顺序数组
    private val drawOrder = shortArrayOf(0, 1, 2, 0, 2, 3)

    // 四个顶点绘制顺序数组的缓冲数组
    private val drawListBuffer: ShortBuffer =
        ByteBuffer.allocateDirect(drawOrder.size * 2).order(ByteOrder.nativeOrder())
            .asShortBuffer().apply {
                put(drawOrder)
                position(0)
            }

3.3 绘制四边形

  绘制四边形的代码如下,整体逻辑和绘制三角形类似,不同的是采用了glDrawElements方法进行绘制:

class Square { 
        
    // 每个顶点的坐标数
    private val COORDS_PER_VERTEX = 3
    private var squareCoords = floatArrayOf(
        -0.5f, 0.5f, 0.0f,      // top left
        -0.5f, -0.5f, 0.0f,      // bottom left
        0.5f, -0.5f, 0.0f,      // bottom right
        0.5f, 0.5f, 0.0f       // top right
    )

    // 四个顶点的缓冲数组
    private val vertexBuffer: FloatBuffer =
        ByteBuffer.allocateDirect(squareCoords.size * 4).order(ByteOrder.nativeOrder())
            .asFloatBuffer().apply { 
        
                put(squareCoords)
                position(0)
            }

    // 四个顶点的绘制顺序数组
    private val drawOrder = shortArrayOf(0, 1, 2, 0, 2, 3)

    // 四个顶点绘制顺序数组的缓冲数组
    private val drawListBuffer: ShortBuffer =
        ByteBuffer.allocateDirect(drawOrder.size * 2).order(ByteOrder.nativeOrder())
            .asShortBuffer().apply { 
        
                put(drawOrder)
                position(0)
            }

    /** * 顶点着色器代码; * 暂时将顶点着色器的源代码硬编码在C风格字符串中 */
    private val vertexShaderCode =
        "attribute vec4 vPosition;" +
                "void main() {" +
                " gl_Position = vPosition;" +
                "}"

    /** * 片段着色器代码 */
    private val fragmentShaderCode =
        "precision mediump float;" +
                "uniform vec4 vColor;" +
                "void main() {" +
                " gl_FragColor = vColor;" +
                "}"

    // 设置颜色(分别代表red, green, blue and alpha)
    private val color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)

    /** * 着色器程序ID引用 */
    private var mProgram: Int

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

    private fun loadShader(type: Int, shaderCode: String): Int { 
        
        // glCreateShader函数创建一个顶点着色器或者片段着色器,并返回新创建着色器的ID引用
        val shader = GLES20.glCreateShader(type)
        // 把着色器和代码关联,然后编译着色器
        GLES20.glShaderSource(shader, shaderCode)
        GLES20.glCompileShader(shader)
        return shader
    }

    private val vertexStride: Int = COORDS_PER_VERTEX * 4

    fun draw() { 
        
        // 激活着色器程序 Add program to OpenGL ES environment
        GLES20.glUseProgram(mProgram)
        // 获取顶点着色器中的vPosition变量(因为之前已经编译过着色器代码,所以可以从着色器程序中获取);用唯一ID表示
        val position = GLES20.glGetAttribLocation(mProgram, "vPosition")
        // 允许操作顶点对象position
        GLES20.glEnableVertexAttribArray(position)
        // 将顶点数据传递给position指向的vPosition变量
        GLES20.glVertexAttribPointer(
            position 

标签: c型角形连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台