OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机、PDA 和游戏主机等嵌入式设备而设计。该 API 由 Khronos 集团定义推广,Khronos 是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准。
OpenGL ES 中最常用的纹理是 2D 纹理,也就是一个图像的二维数组,当我们使用纹理时,需要使用纹理坐标作为纹理图像中的索引。纹理坐标用 (s, t) 指定,或者(U, V)。纹理坐标如下图所示,纹理原点在左下角,往右为 s 轴,往上为 t 轴。而屏幕的方向是屏幕左上角为原点,往右为 x 轴,往下为 y 轴。所以纹理坐标方向和屏幕坐标方向是上下颠倒的,这点需要注意。
- // context用户解析纹理资源时使用,resourceId为纹理资源的ID
- public static int loadTexture(Context context, int resourceId) {
- //textureObjectIds用于存储OpenGL生成纹理对象的ID,我们只需要一个纹理
- final int[] textureObjectIds = new int[1];
- //1代表生成一个纹理
- glGenTextures(1, textureObjectIds, 0);
- //判断是否生成成功
- if(textureObjectIds[0] == 0) {
- Log.w(TAG, "generate a texture object failed!");
- return 0;
- }
- //加载纹理资源,解码成bitmap形式
- final BitmapFactory.Options options = new BitmapFactory.Options();
- options.inScaled = false;
- final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
- if (bitmap == null) {
- Log.w(TAG, "Resource ID: " + resourceId + " decoded failed");
- //删除指定的纹理对象
- glDeleteTextures(1,textureObjectIds, 0);
- return 0;
- }
- //第一个参数代表这是一个2D纹理,第二个参数就是OpenGL要绑定的纹理对象ID,也就是让OpenGL后面的纹理调用都使用此纹理对象
- glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);
- //设置纹理过滤参数,GL_TEXTURE_MIN_FILTER代表纹理缩写的情况,GL_LINEAR_MIPMAP_LINEAR代表缩小时使用三线性过滤的方式,至于过滤方式以后再详解
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
- //GL_TEXTURE_MAG_FILTER代表纹理放大,GL_LINEAR代表双线性过滤
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- //加载实际纹理图像数据到OpenGL ES的纹理对象中,这个函数是Android封装好的,可以直接加载bitmap格式,
- GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
- //bitmap已经被加载到OpenGL了,所以bitmap可释放掉了,防止内存泄露
- bitmap.recycle();
- //我们为纹理生成MIP贴图,提高渲染性能,但是可占用较多的内存
- glGenerateMipmap(GL_TEXTURE_2D);
- //现在OpenGL已经完成了纹理的加载,不需要再绑定此纹理了,后面使用此纹理时通过纹理对象的ID即可
- glBindTexture(GL_TEXTURE_2D, 0);
- //返回OpenGL生成的纹理对象ID
- return textureObjectIds[0];
- }
a_TextureCoordinates 代表我们设置的纹理坐标,然后通过 v_TextureCoordinates 传给片段着色器
- attribute vec4 a_Position;
- attribute vec2 a_TextureCoordinates;
- varying vec2 v_TextureCoordinates;
- void main()
- {
- v_TextureCoordinates = a_TextureCoordinates;
- gl_Position = a_Position;
- }
u_TextureUnit 代表实际纹理数据,v_TextureCoordinates 是我们传过来的纹理坐标,通过 texture2D 函数获取纹理对象上指定坐标位置的颜色,这个颜色建设此片段最终的颜色
- precision mediump float;
- uniform sampler2D u_TextureUnit;
- varying vec2 v_TextureCoordinates;
- void main()
- {
- gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
- }
不知道现在大家有没有理解纹理是怎么使用的,我总结一下,首先把一个纹理图像加载进 OpenGL 中生成一个纹理对象,这个纹理对象是在绘制图元(比如绘制矩形)时,将纹理指定位置的颜色作为此图元对应的颜色,然后这个矩形最终绘制出来就跟我们加载的纹理一样了,也就是相当于把纹理图像贴在这个矩形上了。
- //每行的前两个是矩形的(x, y)坐标,后来两个为纹理(s, t)坐标。因为屏幕方向和纹理方向是上下颠倒的,所以矩形左下角(-0.5f,-0.8f)取的是纹理左上角(0f,1f)的颜色,矩形右下角取纹理右上角的颜色
- private static final float[] vertexData = {
- 0f,
- 0f,
- 0.5f,
- 0.5f,
- -0.5f,
- -0.8f,
- 0f,
- 1f,
- 0.5f,
- -0.8f,
- 1f,
- 1f,
- 0.5f,
- 0.8f,
- 1f,
- 0f,
- -0.5f,
- 0.8f,
- 0f,
- 0f,
- -0.5f,
- -0.8f,
- 0f,
- 1f
- };
之前我们画单个三角形是使用 GL_TRIANGLES 这个参数,但是这里我们需要画四个三角形来组成矩形,所以这里我们定义了六组顶点数据,然后绘制三角形的时候需要使用 GL_TRIANGLE_FAN 这个参数,它是用来画三角形扇的。我们定义了 6 个顶点 1,2,3,4,5,6,通过这个参数最后会绘制四个三角形,分别是 (1,2,3),(1,3,4),(1,4,5),(1,5,6) 这四个三角形,如下图所示
生成顶点数据缓冲区
- mFloatBuffer = ByteBuffer
- .allocateDirect(vertexData.length * 4)
- .order(ByteOrder.nativeOrder())
- .asFloatBuffer()
- .put(vertexData);
在 Renderer 的 onSurfaceCreated 方法中需要获取 OpenGL 里面得到纹理对象的 ID
- texture = TextureHelper.loadTexture(context, R.drawable.TextureImage);
然后在 onDrawFrame 方法中设置纹理单元
- //激活纹理单元,GL_TEXTURE0代表纹理单元0,GL_TEXTURE1代表纹理单元1,以此类推。OpenGL使用纹理单元来表示被绘制的纹理
- glActiveTexture(GL_TEXTURE0);
- //绑定纹理到这个纹理单元
- glBindTexture(GL_TEXTURE_2D, textureId);
- //把选定的纹理单元传给片段着色器中的u_TextureUnit,
- glUniform1i(uTextureUnitLocation, 0);
- //传递矩形顶点坐标
- mFloatBuffer.position(0);
- glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 4, mFloatBuffer);
- glEnableVertexAttribArray(aPositionLocation);
- //传递纹理顶点坐标
- mFloatBuffer.position(2);
- glVertexAttribPointer(aTextureCoordinatesLocation, 2, GL_FLOAT, false, 4, mFloatBuffer);
- glEnableVertexAttribArray(aTextureCoordinatesLocation);
在 onDrawFrame 方法中绘制矩形,前面已经讲过了,这个矩形使用四个三角形画出来的,GL_TRIANGLE_FAN 用来画三角扇
- glDrawArrays(GL_TRIANGLE_FAN, 0, 6);
- //多了a_TextureCoordinates2和v_TextureCoordinates2,代表第二个纹理的坐标
- uniform mat4 u_Matrix;
- attribute vec4 a_Position;
- attribute vec2 a_TextureCoordinates;
- attribute vec2 a_TextureCoordinates2;
- varying vec2 v_TextureCoordinates;
- varying vec2 v_TextureCoordinates2;
- void main()
- {
- v_TextureCoordinates = a_TextureCoordinates;
- v_TextureCoordinates2 = a_TextureCoordinates2;
- gl_Position = u_Matrix * a_Position;
- gl_PointSize = 10.0;
- }
- //多了一个纹理单元和纹理坐标,我这里的混合方式就是把两张纹理的颜色相加,这只是做演示,实际混合根据你们自己的需求来定
- precision mediump float;
- uniform sampler2D u_TextureUnit;
- varying vec2 v_TextureCoordinates;
- uniform sampler2D u_TextureUnit2;
- varying vec2 v_TextureCoordinates2;
- void main()
- {
- gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates) + texture2D(u_TextureUnit2, v_TextureCoordinates2);
- }
在 onSurfaceCreated 获取第二张纹理
- textureBlend = TextureHelper.loadTexture(context, R.drawable.TextureImage2);
在 onDrawFrame 方法中激活纹理 2 单元
- glActiveTexture(GL_TEXTURE1);
- glBindTexture(GL_TEXTURE_2D, textureBlend);
- glUniform1i(uTextureUnit2Location, 1);
传递纹理 2 的顶点坐标给顶点着色器,纹理 1 和纹理 2 的坐标相同,所以顶点坐标就不用更新了
- mFloatBuffer.position(2);
- glVertexAttribPointer(aTextureCoordinates2Location, 2, GL_FLOAT, false, 4, mFloatBuffer);
- glEnableVertexAttribArray(aTextureCoordinates2Location);
到这里纹理 2 的顶点和纹理单元分别传给了顶点着色器和片段着色器,下一步直接绘制就行了,不需要改别的代码了
来源: http://www.phperz.com/article/17/0322/326579.html