发布于 2017-03-22 09:36:04 | 231 次阅读 | 评论: 0 | 来源: PHPERZ
OpenGL ES 移动设备上的OpenGL
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的顶点和纹理单元分别传给了顶点着色器和片段着色器,下一步直接绘制就行了,不需要改别的代码了