Android OpenGL ES 2.0绘图:绘制纹理

来源:互联网 发布:霍启山 章子怡 知乎 编辑:程序博客网 时间:2024/04/16 13:30

android使用openGL提供了特殊的view作为基础叫做GLSurfaceView。我们的view需要继承GLSurfaceView。纹理,在OpenGL中,可以理解为加载到显卡显存中的图片。

纹理,在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。

java代码

  1. public class MyGLSurfaceView extends GLSurfaceView { 
  2.  
  3. public MyGLSurfaceView(Context context) { 
  4. super(context); 
  5. setFocusableInTouchMode(true); 
  6. // Tell the surface view we want to create an OpenGL ES 2.0-compatible 
  7. // context, and set an OpenGL ES 2.0-compatible renderer. 
  8. this.setEGLContextClientVersion(2); 
  9. this.setRenderer(new MyRenderer()); 

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

java代码

  1. public class MyRenderer implements Renderer { 
  2.  
  3. public void onDrawFrame(GL10 gl) {} 
  4. public void onSurfaceChanged(GL10 gl, int width, int height) {} 
  5. public void onSurfaceCreated(GL10 gl, EGLConfig config) {} 

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

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

java代码

  1. public void onSurfaceCreated(GL10 gl, EGLConfig config) { 
  2.  
  3. GLES20.glEnable(GLES20.GL_TEXTURE_2D); 
  4. // Active the texture unit 0 
  5. GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 
  6. loadVertex(); 
  7. initShader(); 
  8. loadTexture(); 

1、启用2D纹理

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

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

2、加载顶点

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

java代码

  1. private void loadVertex() { 
  2.  
  3. // float size = 4 
  4. this.vertex = ByteBuffer.allocateDirect(quadVertex.length * 4
  5. .order(ByteOrder.nativeOrder()) 
  6. .asFloatBuffer(); 
  7. this.vertex.put(quadVertex).position(0); 
  8. // short size = 2 
  9. this.index = ByteBuffer.allocateDirect(quadIndex.length * 2
  10. .order(ByteOrder.nativeOrder()) 
  11. .asShortBuffer(); 
  12. this.index.put(quadIndex).position(0); 
  13. private FloatBuffer vertex; 
  14. private ShortBuffer index; 
  15. private float[] quadVertex = new float[] { 
  16. -0.5f, 0.5f, 0.0f, // Position 0 
  17. 01.0f, // TexCoord 0 
  18. -0.5f, -0.5f, 0.0f, // Position 1 
  19. 00// TexCoord 1 
  20. 0.5f , -0.5f, 0.0f, // Position 2 
  21. 1.0f, 0// TexCoord 2 
  22. 0.5f, 0.5f, 0.0f, // Position 3 
  23. 1.0f, 1.0f, // TexCoord 3 
  24. }; 
  25. private short[] quadIndex = new short[] { 
  26. (short)(0), // Position 0 
  27. (short)(1), // Position 1 
  28. (short)(2), // Position 2 
  29. (short)(2), // Position 2 
  30. (short)(3), // Position 3 
  31. (short)(0), // Position 0 
  32. }; 

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个三角形合成了一个矩形,几句话很难解释清楚。

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

3、初始化着色器

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

顶点着色器:

java代码

  1. uniform mat4 u_MVPMatrix; 
  2.  
  3. attribute vec4 a_position; 
  4. attribute vec2 a_texCoord; 
  5. varying vec2 v_texCoord; 
  6. void main() 
  7. gl_Position = a_position; 
  8. v_texCoord = a_texCoord; 

片段着色器:

java代码

  1. precision lowp float
  2.  
  3. varying vec2 v_texCoord; 
  4. uniform sampler2D u_samplerTexture; 
  5. void main() 
  6. gl_FragColor = texture2D(u_samplerTexture, v_texCoord); 

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

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

java代码

  1. private void initShader() { 
  2.  
  3. String vertexSource = Tools.readFromAssets("VertexShader.glsl"); 
  4. String fragmentSource = Tools.readFromAssets("FragmentShader.glsl"); 
  5. // Load the shaders and get a linked program 
  6. program = GLHelper.loadProgram(vertexSource, fragmentSource); 
  7. // Get the attribute locations 
  8. attribPosition = GLES20.glGetAttribLocation(program, "a_position"); 
  9. attribTexCoord = GLES20.glGetAttribLocation(program, "a_texCoord"); 
  10. uniformTexture = GLES20.glGetUniformLocation(program, 
  11. "u_samplerTexture"); 
  12. GLES20.glUseProgram(program); 
  13. GLES20.glEnableVertexAttribArray(attribPosition); 
  14. GLES20.glEnableVertexAttribArray(attribTexCoord); 
  15. // Set the sampler to texture unit 0 
  16. GLES20.glUniform1i(uniformTexture, 0); 

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

封装的加载着色器的辅助方法:

java代码

  1. public static int loadProgram(String vertexSource, String 
  2. fragmentSource) { 
  3.  
  4. // Load the vertex shaders 
  5. int vertexShader = GLHelper.loadShader(GLES20.GL_VERTEX_SHADER, 
  6. vertexSource); 
  7. // Load the fragment shaders 
  8. int fragmentShader = GLHelper.loadShader(GLES20.GL_FRAGMENT_SHADER, 
  9. fragmentSource); 
  10. // Create the program object 
  11. int program = GLES20.glCreateProgram(); 
  12. if (program == 0) { 
  13. throw new RuntimeException("Error create program."); 
  14. GLES20.glAttachShader(program, vertexShader); 
  15. GLES20.glAttachShader(program, fragmentShader); 
  16. // Link the program 
  17. GLES20.glLinkProgram(program); 
  18. int[] linked = new int[1]; 
  19. // Check the link status 
  20. GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linked, 0); 
  21. if (linked[0] == 0) { 
  22. GLES20.glDeleteProgram(program); 
  23. throw new RuntimeException("Error linking program: " + 
  24. GLES20.glGetProgramInfoLog(program)); 
  25. // Free up no longer needed shader resources 
  26. GLES20.glDeleteShader(vertexShader); 
  27. GLES20.glDeleteShader(fragmentShader); 
  28. return program; 

java代码

  1. public static int loadShader(int shaderType, String source) { 
  2.  
  3. // Create the shader object 
  4. int shader = GLES20.glCreateShader(shaderType); 
  5. if (shader == 0) { 
  6. throw new RuntimeException("Error create shader."); 
  7. int[] compiled = new int[1]; 
  8. // Load the shader source 
  9. GLES20.glShaderSource(shader, source); 
  10. // Compile the shader 
  11. GLES20.glCompileShader(shader); 
  12. // Check the compile status 
  13. GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 
  14. if (compiled[0] == 0) { 
  15. GLES20.glDeleteShader(shader); 
  16. throw new RuntimeException("Error compile shader: " + 
  17. GLES20.glGetShaderInfoLog(shader)); 
  18. return shader; 

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

4、加载纹理

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

java代码

  1. static int[] loadTexture(String path) { 
  2.  
  3. int[] textureId = new int[1]; 
  4. // Generate a texture object 
  5. GLES20.glGenTextures(1, textureId, 0); 
  6. int[] result = null
  7. if (textureId[0] != 0) { 
  8. InputStream is = Tools.readFromAsserts(path); 
  9. Bitmap bitmap; 
  10. try { 
  11. bitmap = BitmapFactory.decodeStream(is); 
  12. finally { 
  13. try { 
  14. is.close(); 
  15. catch (IOException e) { 
  16. throw new RuntimeException("Error loading Bitmap."); 
  17. result = new int[3]; 
  18. result[TEXTURE_ID] = textureId[0]; // TEXTURE_ID 
  19. result[TEXTURE_WIDTH] = bitmap.getWidth(); // TEXTURE_WIDTH 
  20. result[TEXTURE_HEIGHT] = bitmap.getHeight(); // TEXTURE_HEIGHT 
  21. // Bind to the texture in OpenGL 
  22. GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]); 
  23. // Set filtering 
  24. GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, 
  25. GLES20.GL_LINEAR); 
  26. GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, 
  27. GLES20.GL_NEAREST); 
  28. GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, 
  29. GLES20.GL_CLAMP_TO_EDGE); 
  30. GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, 
  31. GLES20.GL_CLAMP_TO_EDGE); 
  32. // Load the bitmap into the bound texture. 
  33. GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); 
  34. // Recycle the bitmap, since its data has been loaded into OpenGL. 
  35. bitmap.recycle(); 
  36. else { 
  37. throw new RuntimeException("Error loading texture."); 
  38. return result; 

这里使用了android的工具类吧bitmap直接转换成openGL纹理需要的格式了。过程是,先生成一个纹理的id在显卡上的,以后根据id上传纹理数据,以后保存这个id就

可以操作这个纹理了。至于纹理的一些过滤特性设置,将来再说。

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

java代码

  1. public void onDrawFrame(GL10 gl) { 
  2.  
  3. // clear screen to black 
  4. GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 
  5. GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 
  6. GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); 
  7. vertex.position(0); 
  8. // load the position 
  9. // 3(x , y , z) 
  10. // (2 + 3 )* 4 (float size) = 20 
  11. GLES20.glVertexAttribPointer(attribPosition, 
  12. 3, GLES20.GL_FLOAT, 
  13. false20, vertex); 
  14. vertex.position(3); 
  15. // load the texture coordinate 
  16. GLES20.glVertexAttribPointer(attribTexCoord, 
  17. 2, GLES20.GL_FLOAT, 
  18. false20, vertex); 
  19. GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, 
  20. index); 

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

每次都需要把数据上传到OpenGL,毕竟显存和内存不是同一个地方,OpenGL采用了CS模式。当然使用VBO等技术可以把数据缓存在显存,以提高运行性能。

原文链接:http://www.eyeandroid.com/thread-16422-1-4.html

0 0
原创粉丝点击