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

一、OpenGL简介

1.1 OpenGL规范

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

  • 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, 0)坐标是这个图像的中心,而不是左上角。标准化设备坐标是一个x、y和z值都在-1.0到1.0的一个坐标系,任何落在范围外的坐标都会被丢弃/裁剪,不会绘制到手机屏幕上。

  例如:一个三角形的三个点在OpenGL标准坐标系中表示如下(z轴都是0,暂时先忽略z轴):

ndc.png

二、基础用法

  下面通过一个简单的绘制三角形的例子入门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内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。

      顶点数组对象与顶点缓冲对象关系如下,暂时简单了解即可:
    vertex_array_objects.png

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中的典型方式是使用两个绘制在一起的三角形:

ccw-square.png

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, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
            false, vertexStride, vertexBuffer
        )
        // 获取片段着色器中的vColor变量
        val colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor")
        // 通过colorHandle设置绘制的颜色值
        GLES20.glUniform4fv(colorHandle, 1, color, 0)
        // 按drawListBuffer中指定的顺序绘制四边形
        GLES20.glDrawElements(
            GLES20.GL_TRIANGLES, drawOrder.size,
            GLES20.GL_UNSIGNED_SHORT, drawListBuffer
        )
        // 操作完后,取消允许操作顶点对象position
        GLES20.glDisableVertexAttribArray(position)
    }
}

  绘制结果如下:

3.4 其他方式

  再介绍下不使用索引数组,而是通过glDrawArrays()方式绘制四边形的方式,这种方式需要指定四个顶点相互连接的方式是GLES20.GL_TRIANGLE_STRIP:

class Square {
    // 顶点数
    private val vertexCount: Int = squareCoords.size / COORDS_PER_VERTEX

    fun draw() {
        // 省略...
        // 按GL_TRIANGLE_FAN方式连接绘制四个顶点
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, vertexCount)
        // 省略...
    }
}

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

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是绘制六棱锥的 OpenGL 代码: ``` #include <GL/glut.h> void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); gluLookAt(0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glColor3f(1.0, 0.0, 0.0); glBegin(GL_TRIANGLES); glVertex3f(0.0, 1.0, 0.0); glVertex3f(-1.0, 0.0, 1.0); glVertex3f(1.0, 0.0, 1.0); glEnd(); glColor3f(0.0, 1.0, 0.0); glBegin(GL_TRIANGLES); glVertex3f(0.0, 1.0, 0.0); glVertex3f(1.0, 0.0, 1.0); glVertex3f(1.0, 0.0, -1.0); glEnd(); glColor3f(0.0, 0.0, 1.0); glBegin(GL_TRIANGLES); glVertex3f(0.0, 1.0, 0.0); glVertex3f(1.0, 0.0, -1.0); glVertex3f(-1.0, 0.0, -1.0); glEnd(); glColor3f(1.0, 1.0, 0.0); glBegin(GL_TRIANGLES); glVertex3f(0.0, 1.0, 0.0); glVertex3f(-1.0, 0.0, -1.0); glVertex3f(-1.0, 0.0, 1.0); glEnd(); glColor3f(1.0, 0.0, 1.0); glBegin(GL_QUADS); glVertex3f(1.0, 0.0, 1.0); glVertex3f(-1.0, 0.0, 1.0); glVertex3f(-1.0, 0.0, -1.0); glVertex3f(1.0, 0.0, -1.0); glEnd(); glColor3f(0.0, 1.0, 1.0); glBegin(GL_QUADS); glVertex3f(0.0, -1.0, 0.0); glVertex3f(1.0, 0.0, 1.0); glVertex3f(1.0, 0.0, -1.0); glVertex3f(-1.0, 0.0, -1.0); glEnd(); glutSwapBuffers(); } void init() { glClearColor(0.0, 0.0, 0.0, 1.0); glEnable(GL_DEPTH_TEST); } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(400, 400); glutCreateWindow("Hexagonal Pyramid"); init(); glutDisplayFunc(display); glutMainLoop(); return 0; } ``` 这段代码使用了 glClear、glLoadIdentity、gluLookAt、glBegin 和 glEnd 等 OpenGL 函数来绘制六棱锥。其中,glClear 函数用于清除颜色缓冲区和深度缓冲区,glLoadIdentity 函数用于重置当前矩阵为单位矩阵,gluLookAt 函数用于设置视点位置以及观察点和上方向,glBegin 和 glEnd 函数用于开始和结束绘制一个图元。在 glBegin 和 glEnd 之间,我们使用 glVertex3f 函数来指定顶点的坐标,并使用 glColor3f 函数来指定顶点的颜色。具体来说,我们先用三角形绘制六棱锥的四个侧面,再用两个四边形绘制六棱锥的底面和顶点。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值