android openGL ES2 一切从绘制纹理开始

纹理,在openGL中,可以理解为加载到显卡显存中的图片。Android设备在2.2开始支持openGL ES2.0,从前都是ES1.0 和 ES1.1的版本。简单来说,openGL ES是为了嵌入设备进行功能剪裁后的openGL版本。ES2.0是和1.x版本不兼容的,区别和兼容性参见android 官方文档。

首先,android使用openGL提供了特殊的view作为基础叫做GLSurfaceView。我们的view需要继承GLSurfaceView。如下简单示例:

public class MyGLSurfaceView extends GLSurfaceView {

    public MyGLSurfaceView(Context context) {
        super(context);
        setFocusableInTouchMode(true);

        // Tell the surface view we want to create an OpenGL ES 2.0-compatible
        // context, and set an OpenGL ES 2.0-compatible renderer.
        this.setEGLContextClientVersion(2);

        this.setRenderer(new MyRenderer());
    }

}

并没有什么特别之处,android view的渲染操作需要实现一个render接口,GLSurfaceView的渲染接口为android.opengl.GLSurfaceView.Renderer。我们需要实现接口的方法。

public class MyRenderer implements Renderer {

public void onDrawFrame(GL10 gl) {}

public void onSurfaceChanged(GL10 gl, int width, int height) {}

public void onSurfaceCreated(GL10 gl, EGLConfig config) {}

}

接口实现3个方法,对应绘制,绘制区域变化,区域创建。需要说明的是参数GL10 gl是openGL es1.x版本的对象。这里我们不会使用到。还有一点就是,onDrawFrame方法的调用是有系统调用的,不需要手动调用。系统会以一定的频率不断的回调。

接下来我们进入ES2.0的使用,先上代码:

public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    GLES20.glEnable(GLES20.GL_TEXTURE_2D);
    // Active the texture unit 0
    GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

    loadVertex();
    initShader();
    loadTexture();
}

绘制区域创建的时候,我们设置了启用2D的纹理,并且激活了纹理单元unit0。什么意思呢,说起来话长,以后慢慢说。简单说一下,记住openGL是基于状态的,就是很多状态的设置和切换,这里启用GL_TEXTURE_2D就是一个状态的开启,表明openGL可以使用2D纹理。

那神马是激活纹理单元,这个和硬件有点关系,openGL要显卡会划分存储纹理的存储区域不止一个区域。这里是使用区域 unit 0,多重纹理绘制可以开启多个,这个以后说。接下来,调用了三个函数,载入顶点,初始化着色器,载入纹理。

第一,载入顶点,openGL绘制图形是根据顶点以后链接起来的。为什么要这样,其实这样很强大是一种设计吧。顶点可以暂时简单理解为含有位置信息的坐标点。展开代码如下:

private void loadVertex() {
    // float size  = 4
    this.vertex = ByteBuffer.allocateDirect(quadVertex.length * 4)
                            .order(ByteOrder.nativeOrder())
                            .asFloatBuffer();

    this.vertex.put(quadVertex).position(0);

    // short size = 2
    this.index = ByteBuffer.allocateDirect(quadIndex.length * 2)
                           .order(ByteOrder.nativeOrder())
                           .asShortBuffer();

    this.index.put(quadIndex).position(0);
}

private FloatBuffer vertex;
private ShortBuffer index;

private float[] quadVertex = new float[] {
        -0.5f, 0.5f, 0.0f, // Position 0
        0, 1.0f, // TexCoord 0

        -0.5f, -0.5f, 0.0f, // Position 1
        0, 0, // TexCoord 1

        0.5f , -0.5f, 0.0f, // Position 2
        1.0f, 0, // TexCoord 2

        0.5f, 0.5f, 0.0f, // Position 3
        1.0f, 1.0f, // TexCoord 3
};

private short[] quadIndex = new short[] {
        (short)(0), // Position 0
        (short)(1), // Position 1
        (short)(2), // Position 2

        (short)(2), // Position 2
        (short)(3), // Position 3
        (short)(0), // Position 0
};

FloatBuffer,ShortBuffer是封装了本地数据结构的封装对象。是的,这个2个对象里面的数据不被java虚拟机管理,相当于C语言的存储方式。具体的介绍可以参看这里(想了解的猛击)
quadVertex的数据就是一个矩形的坐标,和纹理坐标。一两句话很难解释清楚,这里涉及到openGL的几个经典的坐标系,下次说。概括的说,openGL的坐标是单位化的,都是0.0-1.0的浮点型,屏幕的中心点是(0,0)。而纹理的坐标左下角是(0,0)。 这里的quadVertex是在屏幕中大概花了一个矩形贴了一个图片, position0 是左上点,以后左下,右下,右上的顺序,纹理坐标同理。

quadIndx神马意思呢,就是这刚才的这些顶点索引排列。这里一个矩形也就4个顶点,每个顶点3个位置坐标,2个纹理坐标。也就是说一个顶点有5个float数据。至于为什么顶点为什么这么排列下次说,是2个三角形合成了一个矩形,几句话很难解释清楚。

所以说,这段代码就是把矩形的位置和纹理坐标,存储到本地数据,准备后面使用而已。

第二,初始化着色器。这个着色器就是ES2.0的特色,又叫可编程着色器,也是区别于ES1.x的本质。这里只做简单的介绍。可编程着色器是一种脚本,语法类似C语言,脚本分为顶点着色器和片段着色器,分别对应了openGL不同的渲染流程。如下:

顶点着色器:

uniform mat4 u_MVPMatrix;

attribute vec4 a_position;
attribute vec2 a_texCoord;

varying vec2 v_texCoord;

 void main()
 {
    gl_Position = a_position;
    v_texCoord  = a_texCoord;
 }

片段着色器:

precision lowp float;       

varying vec2 v_texCoord;
uniform sampler2D u_samplerTexture;

void main()
{
  gl_FragColor = texture2D(u_samplerTexture, v_texCoord);
}

这里记住一句话,顶点着色器,会在顶点上执行;片段着色器会在像素点上执行。刚才的矩形就有4个顶点,每个顶点都会应用这个脚本。也就是说,顶点是位置相关信息,片段是色彩纹理相关信息。

这个2段脚本都是文本,需要编译,链接,等等一些操作才能被ES2.0所使用。过程就像C语言的编译运行过程。openGL 提供了相关函数去做这些事情。如下:

private void initShader() {
    String vertexSource   = Tools.readFromAssets("VertexShader.glsl");
    String fragmentSource = Tools.readFromAssets("FragmentShader.glsl");

    // Load the shaders and get a linked program
    program = GLHelper.loadProgram(vertexSource, fragmentSource);

    // Get the attribute locations
    attribPosition = GLES20.glGetAttribLocation(program, "a_position");
    attribTexCoord = GLES20.glGetAttribLocation(program, "a_texCoord");

    uniformTexture = GLES20.glGetUniformLocation(program, "u_samplerTexture");

    GLES20.glUseProgram(program);
    GLES20.glEnableVertexAttribArray(attribPosition);
    GLES20.glEnableVertexAttribArray(attribTexCoord);
    // Set the sampler to texture unit 0
    GLES20.glUniform1i(uniformTexture, 0);
}

可以看到,顶点和片段一起构成一个program,它可以被openGL所使用,是一个编译好的脚本程序,存储在显存。 GLES20.glGetAttribLocation 和 GLES20.glGetUniformLocation 这句话是神马作用呢。简单说就是,java程序和着色器脚本数据通信的。把就像参数的传递一样,这样脚本就能根据外界的参数变化,实时的改变openGL流水线渲染的处理流程。

以下是我封装的载入着色器的辅助方法:

public static int loadProgram(String vertexSource, String fragmentSource) {
    // Load the vertex shaders
    int vertexShader = GLHelper.loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);

    // Load the fragment shaders
    int fragmentShader = GLHelper.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);

    // Create the program object
    int program = GLES20.glCreateProgram();

    if (program == 0) {
        throw new RuntimeException("Error create program.");
    }

    GLES20.glAttachShader(program, vertexShader);
    GLES20.glAttachShader(program, fragmentShader);

    // Link the program
    GLES20.glLinkProgram(program);

    int[] linked = new int[1];

    // Check the link status
    GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linked, 0);

    if (linked[0] == 0) {
        GLES20.glDeleteProgram(program);
        throw new RuntimeException("Error linking program: " +  GLES20.glGetProgramInfoLog(program));
    }

    // Free up no longer needed shader resources
    GLES20.glDeleteShader(vertexShader);
    GLES20.glDeleteShader(fragmentShader);

    return program;
}
public static int loadShader(int shaderType, String source) { 

    // Create the shader object
    int shader = GLES20.glCreateShader(shaderType);

    if (shader == 0) {
        throw new RuntimeException("Error create shader.");
    }

    int[] compiled = new int[1];

    // Load the shader source
    GLES20.glShaderSource(shader, source);

    // Compile the shader
    GLES20.glCompileShader(shader);

    // Check the compile status
    GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);

    if (compiled[0] == 0) {
        GLES20.glDeleteShader(shader);
        throw new RuntimeException("Error compile shader: " + GLES20.glGetShaderInfoLog(shader));
    }

    return shader;
}

为什么openGL的很多操作目标都是int类型的,因为openGL只会在显存生成或绑定地址,返回id,以后用id相当于句柄去改变它的内部状态。

第三,就是载入纹理了。载入纹理,就是把图片的数据上传到显存,以后在使用它。请注意纹理图片的长和宽最好是2的N次方,不然不一定能绘制出来。

static int[] loadTexture(String path) {
     int[] textureId = new int[1];

     // Generate a texture object
     GLES20.glGenTextures(1, textureId, 0);

     int[] result = null;

     if (textureId[0] != 0) {

         InputStream is = Tools.readFromAsserts(path);

         Bitmap bitmap;
         try {
             bitmap = BitmapFactory.decodeStream(is);
         } finally {
             try {
                 is.close();
             } catch (IOException e) {
                 throw new RuntimeException("Error loading Bitmap.");
             }
         }

         result = new int[3];
         result[TEXTURE_ID] = textureId[0]; // TEXTURE_ID
         result[TEXTURE_WIDTH] = bitmap.getWidth(); // TEXTURE_WIDTH
         result[TEXTURE_HEIGHT] = bitmap.getHeight(); // TEXTURE_HEIGHT

         // Bind to the texture in OpenGL
         GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]);

         // Set filtering
         GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
         GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

         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);

         // Load the bitmap into the bound texture.
         GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

         // Recycle the bitmap, since its data has been loaded into OpenGL.
         bitmap.recycle();

     } else {
         throw new RuntimeException("Error loading texture.");
     }

     return result;
 }

代码一目了然,这里使用了android的工具类吧bitmap直接转换成openGL纹理需要的格式了。过程是,先生成一个纹理的id在显卡上的,以后根据id上传纹理数据,以后保存这个id就可以操作这个纹理了。至于纹理的一些过滤特性设置,下次再说。

现在貌似就剩下绘制了,准备好了顶点信息,顶点对应的纹理坐标。初始化了着色器,上传了纹理图片。接下来就已把他们合起来绘制了。

public void onDrawFrame(GL10 gl) {
    // clear screen to black
    GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

    GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);

    vertex.position(0);
    // load the position
    // 3(x , y , z)
    // (2 + 3 )* 4 (float size) = 20
    GLES20.glVertexAttribPointer(attribPosition,
                                 3, GLES20.GL_FLOAT,
                                 false, 20, vertex);

    vertex.position(3);
    // load the texture coordinate
    GLES20.glVertexAttribPointer(attribTexCoord,
                                  2, GLES20.GL_FLOAT,
                                  false, 20, vertex);

    GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, index);
}

我尽力保持了代码的简单,openGL的基于状态体现,bind这个函数无处不在,这里bindTexture就是通知openGL使用那个id的纹理图片。接下来的操作就是针对bind的图片的。绘制就需要让openGL知道绘制神马。所以这里需要用到vertex这个本地数据容器,里面装在的是顶点和纹理坐标信息。 GLES20.glVertexAttribPointer就是把顶点数据,按照openGL喜欢的格式上传到显卡存储。draw方法的调用,是在前面应用了纹理id的情况下,所以绘制纹理坐标的时候,会使用上传的纹理图片。

是的,每次都需要把数据上传到openGL,毕竟显存和内存不是同一个地方,openGL采用了客户端-服务端的设计模式。当然使用VBO等技术可以把数据缓存在显存,提高运行性能。这个以后再说吧。

android openGL ES2 一切从绘制纹理开始

时间: 2024-10-10 21:48:53

android openGL ES2 一切从绘制纹理开始的相关文章

android openGL ES2 一切从绘制纹理開始

纹理.在openGL中,能够理解为载入到显卡显存中的图片.Android设备在2.2開始支持openGL ES2.0.从前都是ES1.0 和 ES1.1的版本号.简单来说,openGL ES是为了嵌入设备进行功能剪裁后的openGL版本号.ES2.0是和1.x版本号不兼容的,差别和兼容性參见android 官方文档. 首先,android使用openGL提供了特殊的view作为基础叫做GLSurfaceView.我们的view须要继承GLSurfaceView.例如以下简单演示样例: publi

Android +NDK+eclipse+opengl ES2.0 开启深度测试

参考:https://www.opengl.org/discussion_boards/showthread.php/172736-OpenGL-ES-Depth-Buffer-Problem 环境:eclipse,ndkr8,opengl es2.0,android 最近使用eclipse和NDK进行android opengl es2.0的开发,发现了绘制的物体显示与深度无关,而与绘制的前后顺序有关.想了一下,应该是深度测试没有开启,开启了glEnable(GL_DEPTH_TEST),但是

Android OpenGL ES零基础系列(一):理解GLSurfaceView,GLSurfaceView.Render的基本用法

转载请注明出处 前言 OpenGL ES是OpenGL的一个子集,是针对手机.PDA和游戏主机等嵌入式设备而设计的.该API由Khronos集团定义推广,Khronos是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准. 因此OpenGL ES作为第三方库被应用在android中. 到目前为止,OpenGL ES已经发展有了3个版本,OpenGL ES 1.0 , OpenGL ES 2.0 , OpenGL ES 3.0.其中OpenGL ES 1.0 是以OpenGL 1.3

[android] OpenGL与OpenGL ES简介

简介 OpenGL OpenGL的全称是Open Graphics Library,即开放的图形库接口,它定义了一个跨编程语言,跨平台的编程接口的规范,它主要用于3D图形(2D也可以)编程.OpenGL的前身是SGI公司为其图形工作站开发的IRIS GL.IRIS RL是一个工业标准的3D图形软件接口,功能虽然强大,但是移植性不好,于是SGI公司在IRIS GL的基础上开发了OpenGL. OpenGL体系简单,而且具有跨平台特性,它不像Direct3D,是Microsoft开发的windows

Android OpenGL ES(八)----纹理编程框架

1.把纹理加载进OpenGL中 我们的第一个任务就是把一个图像文件的数据加载到一个OpenGL的纹理中. 作为开始,让我们重新舍弃第二篇的框架,重新创建一个程序,新建一个util工具包,在该包下创建一个新类TextureHelper,我们将以下面的方法签名开始: public static int loadTexture(Context context,int resourceId){} 这个方法会把Android上下文,和资源ID作为输入参数,并返回加载图像的OpenGL纹理的ID.开始时,我

Android用OpenGL ES2.0显示YUV数据,在手机上需要两种坐标系的解决方案

如题 ,不知道大家看懂了这个题目没有,给个链接:http://blog.csdn.net/wangchenggggdn/article/details/8896453(下称链接①), 里面评论有很多人提到了这个问题,我也是其中一员,但是问遍了所有人,自己也发帖(http://bbs.csdn.net/topics/390769358) 寻求解决方案,却终究没能得到一个可用的方案. 从2014年4月中旬遇到这个问题,纠结了两个多星期,终于在看了好多好多资料之后,于4月的最后一个周一,暂时解决了这么

初学Android OpenGL ES之使用纹理 八十三

在网上发现这些讲纹理的文章,非常不错 android 游戏导引(4. 简单纹理贴图) http://www.cnblogs.com/shengdoushi/archive/2011/01/13/1934181.html Android OpenGL es 纹理坐标设定与贴图规则 http://blog.csdn.net/cjkwin/article/details/6016224 Android OpenGL | ES给立方体进行纹理映射 http://www.ourunix.org/andro

Android OpenGL ES(七)----理解纹理与纹理过滤

1.理解纹理 OpenGL中的纹理可以用来表示图像,照片,甚至由一个数学算法生成的分形数据.每个二维的纹理都由许多小的纹理元素组成,它们是小块的数据,类似于我们前面讨论过的片段和像素.要使用纹理,最常用的方式是直接从一个图像文件加载数据. 每个二维纹理都有其自己的坐标空间,其范围是从一个拐角的(0,0)到另一个拐角的(1,1).按照惯例,一个维度叫做S,而另一个称为T.当我们想要把一个纹理应用于一个三角形或一组三角形的时候,我们要为每个顶点指定一组ST纹理坐标,以便OpenGL知道需要用那个纹理

Android OpenGL入门示例:绘制三角形和正方形 (附完整源码)

Android上对OpenGl的支持是无缝的,所以才有众多3D效果如此逼真的游戏,在Camera的一些流程中也有用到GLSurfaceView的情况.本文记录OpenGL在Android上的入门级示例,绘制一个三角形和正方形.尽管功能简单,可是我捣腾了好几个晚上,大量网上文章上的代码都有点问题,不是绘制不出来就是挂了. 第一个文件:MainActivity.java package com.example.learnopengl1; import android.opengl.GLSurface