OpenGL ES学习笔记(一)——基本用法、绘制流程与着色器编译

首先声明下,本文为笔者学习《OpenGL ES应用开发实践指南(Android卷)》的笔记,涉及的代码均出自原书,如有需要,请到原书指定源码地址下载。

      在Android、iOS等移动平台上,开发者可以使用跨平台应用编程接口创建二维或者三维图形,或进行图像处理和计算机视觉应用,结合两者将能构建丰富有趣的交互体验。前者称为OpenGL,后者称为OpenCV,不过本文主要介绍前者,OpenCV在后续文章中涉及。OpenGL应用于桌面系统的历史已经很长了,但考虑到移动平台的特点(计算能力、性能等),将OpenGL应用与移动端使用的是一个特殊的嵌入式版本:OpenGL ES(OpenGL for Embedded System)。OpenGL ES有三个版本:版本1.0提供了一个不太灵活的、固定功能的管道;2.0版本推出了可编程的管道,提供我们所需的所有功能;3.0版本在2.0的基础上增加了一些新的特性,目前还没有广泛使用。《OpenGL ES应用开发实践指南(Android卷)》基于2.0版本进行说明,本文主要内容如下:

  1. OpenGL ES的基本用法,即HelloWorld实现
  2. 二维或者三维图形绘制流程
  3. 着色器(顶点和片段)编译

一、OpenGL ES的基本用法

      OpenGL ES的HelloWorld实现非常简单,只需两步:(1)在xml布局文件或者代码中引入GLSurfaceView;(2)为GLSurfaceView设置Renderer。代码如下:

        glSurfaceView = new GLSurfaceView(this);

        // Check if the system supports OpenGL ES 2.0.
        ActivityManager activityManager = 
            (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        ConfigurationInfo configurationInfo = activityManager
            .getDeviceConfigurationInfo();
        // Even though the latest emulator supports OpenGL ES 2.0,
        // it has a bug where it doesn't set the reqGlEsVersion so
        // the above check doesn't work. The below will detect if the
        // app is running on an emulator, and assume that it supports
        // OpenGL ES 2.0.
        final boolean supportsEs2 =
            configurationInfo.reqGlEsVersion >= 0x20000
                || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 
                 && (Build.FINGERPRINT.startsWith("generic")
                  || Build.FINGERPRINT.startsWith("unknown")
                  || Build.MODEL.contains("google_sdk") 
                  || Build.MODEL.contains("Emulator")
                  || Build.MODEL.contains("Android SDK built for x86")));

        if (supportsEs2) {
            // Request an OpenGL ES 2.0 compatible context.
            glSurfaceView.setEGLContextClientVersion(2);            
            
            // Assign our renderer.
            glSurfaceView.setRenderer(new AirHockeyRenderer(this));
            rendererSet = true;
        } else {
            /*
             * This is where you could create an OpenGL ES 1.x compatible
             * renderer if you wanted to support both ES 1 and ES 2. Since 
             * we're not doing anything, the app will crash if the device 
             * doesn't support OpenGL ES 2.0. If we publish on the market, we 
             * should also add the following to AndroidManifest.xml:
             * 
             * <uses-feature android:glEsVersion="0x00020000"
             * android:required="true" />
             * 
             * This hides our app from those devices which don't support OpenGL
             * ES 2.0.
             */
            Toast.makeText(this, "This device does not support OpenGL ES 2.0.",
                Toast.LENGTH_LONG).show();
            return;
        }

        setContentView(glSurfaceView);

      首先通过GLSurfaceView的构造函数创建GLSurfaceView对象glSurfaceView,然后检查系统是否支持OpenGL ES 2.0版,支持就为glSurfaceView设置Renderer,否则弹出提示并返回,最后设置界面展示。

      需要说明的是,使用GLSurfaceView还需要处理Activity的生命周期,否则,用户切换到另一个应用,应用就会奔溃。

@Override
 protected void onPause() {
     super.onPause();
        
     if (rendererSet) {
         glSurfaceView.onPause();
    }
}

@Override
protected void onResume() {
    super.onResume();
        
    if (rendererSet) {
        glSurfaceView.onResume();
    }
}

      除了GLSurfaceView,另一个重点就是Renderer。Renderer是一个接口,定义了上如下方法:

    public interface Renderer {
       void onSurfaceCreated(GL10 gl, EGLConfig config);
       void onSurfaceChanged(GL10 gl, int width, int height);
       void onDrawFrame(GL10 gl);
    }

     onSurfaceCreated(GL10 gl, EGLConfig config)方法在Surface被创建的时候调用。但在实践中,设备被唤醒或者用户从其他activity切换回来时,这个方法也可能被调用。

     onSurfaceChanged(GL10 gl, int width, int height)方法在Surface尺寸变化时调用,在横竖屏切换时,Surface尺寸都会变化。

     onDrawFrame(GL10 gl)方法在绘制一帧时调用。在这个方法中,一定要绘制一些东西,即使只是清空屏幕,因为在这个方法返回后,渲染缓冲区会被交换并显示到屏幕上,如果什么都没画,可能看到糟糕的闪烁效果。

     HelloWorld应用中使用了AirHockeyRenderer,该类实现了Renderer接口。

@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {
    // Set the OpenGL viewport to fill the entire surface.
    glViewport(0, 0, width, height);        
}
@Override
public void onDrawFrame(GL10 glUnused) {
    // Clear the rendering surface.
    glClear(GL_COLOR_BUFFER_BIT);
}

 

二、绘制流程

     首先,在OpenGL ES中,只支持三种类型的绘制:点、直线以及三角形。所以需要在绘制图像之前,需要把一个图像分解为这三种图像的组合。

     其次,OpenGL作为本地库直接运行在硬件上,没有虚拟机,也没有垃圾回收或者内存压缩。在Java层定义图像的数据需要能被OpenGL存取,因此,需要把内存从Java堆复制到本地堆。使用的方法是通过ByteBuffer:

private final FloatBuffer vertexData;

vertexData = ByteBuffer
    .allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT)
    .order(ByteOrder.nativeOrder())
    .asFloatBuffer();

vertexData.put(tableVerticesWithTriangles);

     在上述代码中,首先使用ByteBuffer.allocateDirect()分配一块本地内存,这块本地内存不会被垃圾回收器管理。这个方法需要确定分配的内存的大小(单位为字节);因为顶点存储在浮点数组中,并且每个浮点数有4个字节,所以这块内存的大小为tableVerticesWithTriangles.length * BYTES_PER_FLOAT。order(ByteOrder.nativeOrder())的意思是使缓冲区按照本地字节序组织内容,字节序请移步这里。asFloatBuffer()方法得到一个可以反映底层字节的FloatBuffer类实例,因为我们直接操作的是浮点数,而不是单独的字节。

     最后,通过在OpenGL管道传递数据,着色器告诉GPU如何绘制数据。着色器分为顶点着色器和片段着色器。

     综上,整体流程为:读取顶点数据——执行顶点着色器——组装图元——光栅化图元——执行片段着色器——写入帧缓冲区——显示到屏幕上。

    再介绍下着色器,顶点着色器生成每个顶点的最终位置,针对每个顶点,执行一次;片段着色器为组成点、直线和三角形的每个片段生成最终的颜色,针对每个片段,执行一次。

顶点着色器:

attribute vec4 a_Position;             

void main()                    
{                              
    gl_Position = a_Position;
    gl_PointSize = 10.0;
}

片段着色器:

precision mediump float; 
                                           
uniform vec4 u_Color;                                                 
  
void main()                            
{                                  
    gl_FragColor = u_Color;                                          
}

     着色器使用GLSL定义,GLSL是OpenGL的着色语言,语法结构与C语言相似。顶点着色器中,gl_Position接收当前顶点的位置,gl_PointSize设置顶点的大小;片段着色器中,gl_FragColor接收片段的颜色。precision mediump float为精度限定符,可以选择lowp、mediump和highp,分别对应低精度、中等精度和高精度。

 

三、着色器编译

     首先,从资源中加载着色器文本。即将本地资源文件读流,然后将流转换为String:

    public static String readTextFileFromResource(Context context,
        int resourceId) {
        StringBuilder body = new StringBuilder();

        try {
            InputStream inputStream = 
                context.getResources().openRawResource(resourceId);
            InputStreamReader inputStreamReader = 
                new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            String nextLine;

            while ((nextLine = bufferedReader.readLine()) != null) {
                body.append(nextLine);
                body.append('\n');
            }
        } catch (IOException e) {
            throw new RuntimeException(
                "Could not open resource: " + resourceId, e);
        } catch (Resources.NotFoundException nfe) {
            throw new RuntimeException("Resource not found: " + resourceId, nfe);
        }

        return body.toString();
    }

    这段代码很简单,不做具体介绍了。

    其次,编译着色器。

    /**
     * Loads and compiles a vertex shader, returning the OpenGL object ID.
     */
    public static int compileVertexShader(String shaderCode) {
        return compileShader(GL_VERTEX_SHADER, shaderCode);
    }
    
    /**
     * Loads and compiles a fragment shader, returning the OpenGL object ID.
     */
    public static int compileFragmentShader(String shaderCode) {
        return compileShader(GL_FRAGMENT_SHADER, shaderCode);
    }
    
    /**
     * Compiles a shader, returning the OpenGL object ID.
     */
    private static int compileShader(int type, String shaderCode) {
        
        // Create a new shader object.
        final int shaderObjectId = glCreateShader(type);

        if (shaderObjectId == 0) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "Could not create new shader.");
            }

            return 0;
        }
       
        // Pass in the shader source.
        glShaderSource(shaderObjectId, shaderCode);

        // Compile the shader.
        glCompileShader(shaderObjectId);

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

        if (LoggerConfig.ON) {
            // Print the shader info log to the Android log output.
            Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode + "\n:" 
                + glGetShaderInfoLog(shaderObjectId));
        }

        // Verify the compile status.
        if (compileStatus[0] == 0) {
            // If it failed, delete the shader object.
            glDeleteShader(shaderObjectId);

            if (LoggerConfig.ON) {
                Log.w(TAG, "Compilation of shader failed.");
            }

            return 0;
        }

        // Return the shader object ID.
        return shaderObjectId;
    }

      compileVertexShader和compileFragmentShader都是通过compileShader来实现的,编译顶点着色器和片段着色器通过type进行区分:GL_VERTEX_SHADER和GL_FRAGMENT_SHADER。

     compileShader方法包含的步骤是:新建着色器对象(glCreateShader)——上传着色器源代码(glShaderSource)——取出编译状态(glCompileShader)——取出着色器信息日志(glGetShaderiv)——验证编译状态并返回着色器对象ID(compileStatus[0] == 0。

    再次,把着色器一起链接进OpenGL的程序。

    /**
     * Links a vertex shader and a fragment shader together into an OpenGL
     * program. Returns the OpenGL program object ID, or 0 if linking failed.
     */
    public static int linkProgram(int vertexShaderId, int fragmentShaderId) {

        // Create a new program object.
        final int programObjectId = glCreateProgram();
        
        if (programObjectId == 0) {
            if (LoggerConfig.ON) {
                Log.w(TAG, "Could not create new program");
            }
            
            return 0;
        }

        // Attach the vertex shader to the program.
        glAttachShader(programObjectId, vertexShaderId);
        // Attach the fragment shader to the program.
        glAttachShader(programObjectId, fragmentShaderId);

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

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

        if (LoggerConfig.ON) {
            // Print the program info log to the Android log output.
            Log.v(TAG, "Results of linking program:\n" 
                + glGetProgramInfoLog(programObjectId));            
        }

        // Verify the link status.
        if (linkStatus[0] == 0) {
            // If it failed, delete the program object.
            glDeleteProgram(programObjectId);
            if (LoggerConfig.ON) {
                Log.w(TAG, "Linking of program failed.");
            }
            return 0;
        }

        // Return the program object ID.
        return programObjectId;
    }

     一个OpenGL的程序就是把一个顶点着色器和一个片段着色器链接在一起变成单个对象。链接与编译在流程上大致相同,主要流程为:新建程序并附上着色器对象——链接程序——验证链接状态并返回程序对象ID——给渲染类加入代码。

     再次是验证OpenGL程序的对象:

    /**
     * Validates an OpenGL program. Should only be called when developing the
     * application.
     */
    public static boolean validateProgram(int programObjectId) {
        glValidateProgram(programObjectId);
        
        final int[] validateStatus = new int[1];
        glGetProgramiv(programObjectId, GL_VALIDATE_STATUS, validateStatus, 0);
        Log.v(TAG, "Results of validating program: " + validateStatus[0]
            + "\nLog:" + glGetProgramInfoLog(programObjectId));

        return validateStatus[0] != 0;
    }

     调用glValidateProgram来验证这个程序,然后用GL_VALIDATE_STATUS参数调用glGetProgramiv()方法检测结果。需要说明的是,只有在开发和调试应用的时候才去验证程序。

     在onSurfaceCreate()结尾处使用程序。glUseProgram告诉OpenGL在绘制任何东西到屏幕上的时候使用这里定义的程序。验证程序对象之后需要获取uniform的位置和属性的位置,最后关联属性与顶点数据的数组,使能顶点数组。上述流程走完之后,就可以正常绘制任何图像了,绘制图像的代码在onDrawFrame()处:

        // Clear the rendering surface.
        glClear(GL_COLOR_BUFFER_BIT);
        
        // Draw the table.
        glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);        
        glDrawArrays(GL_TRIANGLES, 0, 6);
        
        // Draw the center dividing line.
        glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);        
        glDrawArrays(GL_LINES, 6, 2); 
        
        // Draw the first mallet blue.        
        glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);        
        glDrawArrays(GL_POINTS, 8, 1);

        // Draw the second mallet red.
        glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);        
        glDrawArrays(GL_POINTS, 9, 1);

      glUniform4f更新着色器代码中的u_Color值,glDrawArrays代表绘制,GL_TRIANGLES表示要画三角形,0,6表示读入从开头的6个顶点。


总结:

(1)定义顶点属性数组,并将数组复制到本地内存;

(2)创建顶点着色器和片段着色器,着色器只是可以运行在GPU上的一个特殊类型的程序。

(3)如何创建和编译着色器;

(4)链接顶点着色器和片段着色器形成OpenGL程序对象;

(5)关联顶点着色器内部的属性变量与顶点属性数组。

转载于:https://www.cnblogs.com/younghao/p/5087689.html

OpenGL ES 3.0 英文版 第1章——OpenGL ES 3.0简介   第1章简单介绍OpenGL ES,概述了OpenGL ES 3.0图形管线,讨论了OpenGL ES 3.0的设计理念和限制,最后介绍了OpenGL ES 3.0中使用的一些约定和类型。   第2章——你好,三角形:一个OpenGL ES 3.0示例   第2章介绍绘制三角形的一个简单OpenGL ES 3.0示例。我们的目的是说明OpenGL ES 3.0程序的样子,向读者介绍一些API概念,并说明如何构建和运行OpenGL ES 3.0示例程序。   第3章——EGL简介   第3章介绍EGL——为OpenGL ES 3.0创建表面和渲染上下文的API。我们说明与原生窗口系统通信、选择配置和创建EGL渲染上下文及表面的方法,传授足够多的EGL知识,你可以了解到启动OpenGL ES 3.0进行渲染所需的所有知识。   第4章——着色器和程序   着色器对象和程序对象是OpenGL ES 3.0中最基本的对象。第4章介绍创建着色器对象、编译着色器和检查编译错误的方法。这一章还说明如何创建程序对象、将着色器对象连接到程序对象以及链接最终程序对象的方法。我们讨论如何查询程序对象的信息以及加载统一变量(uniform)的方法。此外,你将学习有关源着色器和程序二进制代码之间的差别以及它们的使用方法。   第5章——OpenGL ES着色语言   第5章介绍编写着色器所需的着色语言的基础知识。这些着色语言基础知识包括变量和类型、构造器、结构、数组、统一变量、统一变量块(uniform block)和输入/输出变量。该章还描述着色语言的某些更细微的部分,例如精度限定符和不变性。   第6章——顶点属性、顶点数组和缓冲区对象   从第6章开始(到第11章为止),我们将详细介绍管线,教授设置和编程图形管线各个部分的方法。这一旅程从介绍几何形状输入图形管线的方法开始,包含了对顶点属性、顶点数组和缓冲区对象的讨论。   第7章——图元装配和光栅化   在前一章讨论几何形状输入图形管线的方法之后,第7章将讨论几何形状如何装配成图元,介绍OpenGL ES 3.0中所有可用的图元类型,包括点精灵、直线、三角形、三角形条带和三角扇形。此外,我们还说明了在顶点上进行坐标变换的方法,并简单介绍了OpenGL ES 3.0管线的光栅化阶段。   第8章——顶点着色器   我们所介绍的管线的下一部分是顶点着色器。第8章概述了顶点着色器如何融入管线以及OpenGL ES 着色语言中可用于顶点着色器的特殊变量,介绍了多个顶点着色器的示例,包括逐像素照明和蒙皮(skinning)。我们还给出了用顶点着色器实现OpenGL ES 1.0(和1.1)固定功能管线的示例。   第9章——纹理   第9章开始介绍片段着色器,描述OpenGL ES 3.0中所有可用的纹理功能。该章提供了创建纹理、加载纹理数据以及纹理渲染的细节,描述了纹理包装模式、纹理过滤、纹理格式、压缩纹理、采样器对象、不可变纹理、像素解包缓冲区对象和Mip贴图。该章介绍了OpenGL ES 3.0支持的所有纹理类型:2D纹理、立方图、2D纹理数组和3D纹理。   第10章——片段着色器   第9章的重点是如何在片段着色器中使用纹理,第10章介绍编写片段着色器所需知道的其他知识。该章概述了片段着色器和所有可用的特殊内建变量,还演示了用片段着色器实现OpenGL ES 1.1中所有固定功能技术的方法。多重纹理、雾化、Alpha测试和用户裁剪平面的例子都使用片段着色器实现。   第11章——片段操作   第11章讨论可以适用于整个帧缓冲区或者在OpenGL ES 3.0片段管线中执行片段着色器后适用于单个片段的操作。这些操作包括剪裁测试、模板测试、深度测试、多重采样、混合和抖动。本章介绍OpenGL ES 3.0图形管线的最后阶段。   第12章——帧缓冲区对象   第12章讨论使用帧缓冲区对象渲染屏幕外表面。帧缓冲区对象有多种用法,最常见的是渲染到一个纹理。本章提供API帧缓冲区对象部分的完整概述。理解帧缓冲区对象对于实现许多高级特效(如反射、阴影贴图和后处理)至关重要。   第13章——同步对象和栅栏   第13章概述同步对象和栅栏,它们是在OpenGL ES 3.0主机应用和GPU执行中同步的有效图元。我们讨论同步对象和栅栏的使用方法,并以一个示例作为结束。   第14章——OpenGL ES 3.0高级编程   第14章是核心章节,将本书介绍的许多主题串联在一起。我们已经选择了高级渲染技术的一个样本,并展示了实现这些功能的示例。该章包含使用法线贴图的逐像素照明、环境贴图、粒子系统、图像后处理、程序纹理、阴影贴图、地形渲染
好的,关于绘制多边形,我会为您提供一些OpenGL学习笔记。 首先,我们需要了解一下OpenGL绘制多边形的基本流程OpenGL绘制多边形的过程包括三个基本步骤:指定顶点数据、指定绘制方式和执行绘制。 指定顶点数据:在OpenGL中,我们可以使用glVertex等函数来指定多边形的顶点。例如,如果我们要绘制一个三角形,我们可以使用以下代码: ``` glBegin(GL_TRIANGLES); glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glEnd(); ``` 其中,glBegin和glEnd函数用来指定绘制的方式,GL_TRIANGLES表示绘制三角形。glVertex3f函数用来指定三角形的三个顶点,每个顶点由三个坐标值组成。 指定绘制方式:OpenGL支持多种绘制方式,例如GL_TRIANGLESGL_TRIANGLE_STRIP、GL_TRIANGLE_FAN等。在上面的代码中,我们使用了GL_TRIANGLES来指定绘制三角形的方式。 执行绘制:最后,我们需要调用glDrawArrays函数来执行绘制。例如,如果我们要绘制一个三角形,我们可以使用以下代码: ``` glDrawArrays(GL_TRIANGLES, 0, 3); ``` 其中,GL_TRIANGLES表示绘制三角形的方式,0表示顶点数组的起始位置,3表示顶点的数量。 以上就是OpenGL绘制多边形的基本流程,下面我们来看一下如何绘制着色多边形。 绘制着色多边形的过程与绘制普通多边形的过程基本相同,只需要在绘制前调用glColor函数来指定颜色即可。例如,如果我们要绘制一个红色的三角形,我们可以使用以下代码: ``` glBegin(GL_TRIANGLES); glColor3f(1.0f, 0.0f, 0.0f); // 指定颜色为红色 glVertex3f(0.0f, 1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 0.0f); glVertex3f(1.0f, -1.0f, 0.0f); glEnd(); ``` 其中,glColor3f函数用来指定颜色,三个参数分别表示红、绿、蓝三个颜色通道的值,取值范围为0到1。 希望以上内容能够帮助到您。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值