OpenGl ES 2.0 Learn For Android(二)给三角形贴上图片
不好意思,最近加班,一个月无休。人都不知道出于什么状态了。自然也不愿意更新博客。现在有一天的休息时间,想想还是来更新一下,确认我还活着。。。
上一篇的话,介绍了OpenGL ES的顶点着色器,片段着色器,世界的坐标系。当前的世界都在[-1,1]里面。当讲到三维世界的时候,我们便可以拓宽这个世界。但是三维的变换需要一些数学知识,我也不能描述得很清晰,所以放到之后再说。今天这个短篇,先说一下纹理的事情,因为这个其实是互通的。
前两篇的内容在这里。
OpenGl ES 2.0 Learn For Android
OpenGl ES 2.0 Learn For Android(一)世界是三角形的
1. 什么是纹理
openg里,你会见到一个常用类型Texture
。这个就是纹理了。那什么是纹理呢?
在opengl里,纹理可以用来表示图像,照片,甚至由一个数学算法生成的分形数据。每个二维的纹理都由许多小的纹理元素(texel)组成,它们是小块的数据,类似片段和像素。要使用纹理,最常用的方式是直接从一个图像问题加载数据。
每个二维的纹理都有其自己的坐标空间,其范围是从一个拐角的(0,0)到另一个拐角的(1,1)。按照惯例,一个维度叫做S,而另一个称为T。当我们想要把一个纹理应用于一个三角形或一组三角形的时候我们要为每个顶点指定一组ST纹理坐标,以便opengl知道需要用哪个纹理的哪个部分。这些纹理坐标有时也会被称为UV纹理坐标。
大体的样子就如下图所示。
2. 讲纹理应用到程序###
这里的话,我这边就演示纹理最直接的用法,也就是贴图。首先的话,修改顶点着色器和片段着色器,让它们能够接收使用纹理。
private String mVertexShaderCode =
"attribute vec4 a_Position; \n" +
"attribute vec2 a_TextureCoordinates;\n" +
"varying vec2 v_TextureCoordinates;"+
"void main() \n" +
"{ \n" +
" v_TextureCoordinates = a_TextureCoordinates;\n"+
" gl_Position = a_Position; \n" +
"} \n";
相较上一个顶点着色器,他增加的有
...
attribute vec2 a_TextureCoordinates;
varying vec2 v_TextureCoordinates;
void main()
{
v_TextureCoordinates = a_TextureCoordinates;
...
}
这边a_TextureCoordinates
用来接收CPU->GPU的贴图顶点信息,v_TextureCoordinates
是Vertex->fragment进行传值。
同样的,对之前的片段着色器进行更改:
private String mFragmentShaderCode =
"precision mediump float; \n" +
"uniform sampler2D u_TextureUnit; \n" +
"varying vec2 v_TextureCoordinates; \n " +
"void main() \n" +
"{ \n" +
" gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates); \n" +
"}";
sampler2D u_TextureUnit
说明这个是一个2D的纹理对象,gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
则是对应的片段着色器取纹理对应坐标点的颜色信息了。
关于attribute
和uniform
,上一篇已经说过了,大家不记得可以回看一下:OpenGL ES 三种类型修饰 uniform attribute varying
3. 纹理的加载赋值###
有了上一篇的编译连接过程,这里对纹理的加载连接过程就会理解得轻松一些。
废话不多说,直接贴代码。
public static int loadTexture(Context context, int resourceId) {
//在GUP创建一个材质空间
final int[] textureObjectIds = new int[1];
glGenTextures(1, textureObjectIds, 0);
if (textureObjectIds[0] == 0) {
Log.w(TAG, "Could not generate a new OpenGL texture object.");
return 0;
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
//加载一张图片进内存
// Read in the resource
final Bitmap bitmap = BitmapFactory.decodeResource(
context.getResources(), resourceId, options);
if (bitmap == null) {
Log.w(TAG, "Resource ID " + resourceId + " could not be decoded.");
glDeleteTextures(1, textureObjectIds, 0);
return 0;
}
//申明是一个2D的材质
// Bind to the texture in OpenGL
glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);
//纹理过滤
// Set filtering: a default must be set, or the texture will be
// black.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Load the bitmap into the bound texture.
texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
// Note: Following code may cause an error to be reported in the
// ADB log as follows: E/IMGSRV(20095): :0: HardwareMipGen:
// Failed to generate texture mipmap levels (error=3)
// No OpenGL error will be encountered (glGetError() will return
// 0). If this happens, just squash the source image to be
// square. It will look the same because of texture coordinates,
// and mipmap generation will work.
//生成MIP贴图
glGenerateMipmap(GL_TEXTURE_2D);
// Recycle the bitmap, since its data has been loaded into
// OpenGL.
bitmap.recycle();
// Unbind from the texture.
glBindTexture(GL_TEXTURE_2D, 0);
return textureObjectIds[0];
}
与着色器的生成绑定类似,纹理的生成使用过程也是glGenTextures->glBindTexture->赋予一些属性(如2D/3D,纹理过滤等等)。
纹理过滤的参数我这边不再赘述。可以细看《OpenGL ES应用开发实践指南:Android卷》7-1。这边贴一张参数表。
4. 纹理顶点赋值,进行绘制###
有了GPU的绘制部分,有了纹理的加载部分。现在只需要对顶点赋值,进行绘制就好了。
现在从网上找了一张图片。图片自然都是四方的,如下。
那我们改一下上一篇的绘制顶点,让它绘制一个方形。
private float[] mTrianglePoints = {0.5f, -0.5f, -0.5f, -0.5f, 0.5f, 0.5f,-0.5f,0.5f};
其他的不变,讲绘制三角形的参数变一下,变成绘制三角带。
// Draw the triangle.
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDrawArrays
的三角形,三角扇,三角带,在上一篇结束的地址里有提到,大家看一下具体的介绍。
重要的是,对应的贴图顶点坐标。
private float[] mTexturePoints = {1.0f, 1.0f, 0f, 1.0f, 1.0f, 0f, 0f, 0f};
大家可以尝试一下打乱顺序会发生什么。
//对纹理顶点'a_TextureCoordinates'进行赋值
mTextureData.position(0);
glVertexAttribPointer(aTextureCoordinatesLocation, 2, GL_FLOAT,
false, 0, mTextureData);
glEnableVertexAttribArray(aTextureCoordinatesLocation);
//加载纹理
mTextureId = loadTexture(mContext, R.drawable.golden_triangle);
//使用纹理0。这里有个知识点,多纹理贴图,有兴趣可以搜一下
// Set the active texture unit to texture unit 0.
glActiveTexture(GL_TEXTURE0);
//使用我们加载的纹理
// Bind the texture to this unit.
glBindTexture(GL_TEXTURE_2D, mTextureId);
//传递给管线
// Tell the texture uniform sampler to use this texture in the shader by
// telling it to read from texture unit 0.
glUniform1i(uTextureUnitLocation, 0);
我们纹理也传递给了管线,顶点也传递给了管线。那么跑一下看一下效果。
下面是效果图。
demo地址:
https://github.com/YueZhiFengMing/LearnOpenGl/tree/master/ThirdTexture
参考资料###
- https://www.khronos.org/registry/OpenGL-Refpages/es2.0/
- 《OpenGL ES应用开发实践指南:Android卷》