学习内容来源and参考java
opengl es 3.0编程指南git
3D图形渲染最基本的操做之一是对一个表面进行纹理,纹理能够表现只从网格的几何形状中没法获得的附加细节。在opengl es3.0中的纹理有多种形式:2D纹理,2D纹理数组,3D纹理以及立方图纹理。编程
2D纹理是一个图像数据的二维数组。一个纹理单独数据元素称做“纹素”(Texel)。图像中的每一个纹素根据基本格式和数据类型指定。若是用2D纹理渲染时,纹理坐标用做图像中的索引。2D纹理的纹理坐标用一对2D坐标(s,t)或者(u,v)来表示,这些坐标用于查找一个纹理贴图的规范化坐标。数组
纹理坐标以下所示:函数
纹理图像的左下坐标由(0.0,0.0)决定,右上角坐标由(1.0,1.0)指定。在[0.0,1.0]以外的坐标是容许的,在区间以外的纹理读取行为由纹理包装模式决定。性能
立方图是由6个单独2D纹理面组成的纹理。对于立方图纹理贴图,通常使用环境贴图特效,即在物体上的倒影经过使用一个表示环境的立方图渲染。。一般,生成环境贴图所用的立方图经过在场景中央放置一个摄像机,从6个轴方向(+x,-x,+y,-y,+z,-z)捕捉场景图像并将结果保存在立方体的每一个面上。学习
3D纹理能够看作2D纹理多个切片的一个数组,用一个三元坐标(s,t,r)访问,r坐标选择3D纹理须要采样的切片,(s,t)用来读取每一个切片中的2D贴图。动画
2D纹理数组与3D纹理数组类似,可是用途不一样,通常用来存储2D图像的动画。数组的每一个切片标识纹理动画的一帧。坐标使用与3D纹理相同,都是使用(s,t,r)来表示,r坐标选择2D数组中须要采样的切片,(s,t)用来选取切片。ui
纹理对象是一个容器对象,保存所须要渲染的纹理数据,如图像数据,过滤模式以及包装模式。纹理对象使用一个无符号整数表示,该整数位纹理对象的句柄,生成纹理对象的函数以下:
void glGenTextures(GLsizei n,GLuint *textures);//native 实现 // C function void glGenTextures ( GLsizei n, GLuint *textures ) java层代码 public static native void glGenTextures( int n, java.nio.IntBuffer textures );
Java代码中n表示生成纹理对象数量,textures为一个保存n个纹理对象id的无符号整数数组。建立时,glGenTextures方法生成的纹理对象是一个空的容器,用于加载纹理数据和参数。纹理对象在不使用时候能够经过调用glDeleteTextures(...)
方法来删除。
一旦使用glGenTextures(...)
来生成纹理对象id,应用程序就必须经过glBindTexture()
绑定纹理对象进行操做。一旦绑定到一个特定的目标,纹理对象会一直绑定在此目标中直到删掉为止。在绑定完成后,须要去加载图像了,用于加载立方图纹理和2D的基本函数是glTexImage2D()
。在opengl es3.0中可使用多种代替方法指定2D纹理,包括不可变纹理(glTexStorage2D)以及glTexSubImage2D的结合。为了获得最佳性能,推荐使用不可变纹理。在Android中,系统帮咱们封装了GLUtils来方便咱们使用:
public static void texImage2D(int target, int level, Bitmap bitmap, int border); //target为将纹理对象绑定到GL_TEXTURE_2D,GL_TEXTURE_3D,GL_TEXTURE_2D_ARRAY,GL_TEXTURE_CUBE_MAP的标识 //level为指定要加载mip的级别,第一个级别为0,后续的递增 //bitmap为须要加载的图像 //border在opengl es中忽略,传0便可
上述方法使用以下:
private void generateTexture(Bitmap bitmap) { int[] size = new int[1]; GLES30.glGenTextures(size.length, size, 0); if (size[0] == 0) { Log.w(TAG, "建立纹理失败!"); return; } int target = size[0]; GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, target); GLUtils.texImage2D(target, 0, bitmap, 0); GLES30.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); GLES30.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); }
上述的glTexParameteri(..)
方法将缩小和放大过滤模式设置为GL_NEAREST
。这个对于纹理贴图来讲是必须的,由于咱们尚未为纹理加载完整的mip贴图链,所以,必须选择非mip贴图缩小过滤器。其余的模式还有GL_LINEAR,提供双线性非mip贴图过滤。
纹理坐标用于生成一个2d索引,用来从纹理贴图中读取,当缩小和放大的过滤器设置为GL_NEAREST的时候,一个纹素将在提供的纹理坐标位置上读取,这个称为点采样或者最近采样。but,使用该方法采样可能会形成严重的视觉伪像,由于三角形在屏幕空间中变得较小,在不一样像素间的差值,纹理坐标可能有很大的跳跃,从而形成从一个大的纹理涂重取得少许样本,形成锯齿伪像,而且可能形成巨大的性能损失。
上述的解决办法能够经过mip贴图来解决伪像的问题。其思路是构建一个mip贴图链,开始于原来指定的图像,后续的每一个图像在每一个维度上是前一个图像的通常,一直持续到最后到达链底部的1x1纹理。mip贴图级别能够变成生成(上述的level参数),一个mip级别中的每一个像素一般根据上一级别中相同位置的4个像素的平均值计算。
纹理渲染时会发生两种过滤状况:缩小和放大。
对于放大而言,mip贴图是不起做用的,由于咱们老是从最大的可用级别进行采样。对于缩小来讲,可使用不一样的采样方式。 过滤模式使用glTexParameteri(...)
进行设置:
public static native void glTexParameteri( int target, int pname, int param ); //纹理对象,GL_TEXTURE_2D,GL_TEXTURE_3D,GL_TEXTURE_2D_ARRAY,GL_TEXTURE_CUBE_MAP //pname通常指定为GL_TEXTURE_MIN_FILTER(缩小),GL_TEXTURE_MAG_FILTER(放大) //param为采用的过滤模式
过滤模式的采样过程如图所示:
GL_LINEAR
GL_NEAREST
更加详细介绍关于过滤模式的区别能够查看这篇博客。
opengls es3.0中提供glGenerateMipmap()
方法来自动生成mip贴图。
public static native void glGenerateMipmap( int target ); //以前生成的纹理对象,GL_TEXTURE_2D,GL_TEXTURE_3D,GL_TEXTURE_2D_ARRAY,GL_TEXTURE_CUBE_MAP
纹理包装模式用于指定纹理坐标超出[0.0,1.0]方位内所发生的行为。使用glTexParameter[i|f]v来进行设置:
public static native void glTexParameterfv( int target, int pname, java.nio.FloatBuffer params ); // C function void glTexParameteri ( GLenum target, GLenum pname, GLint param ) public static native void glTexParameteri( int target, int pname, int param );
这些模式能够为s,t,r坐标进行单独设置,GL_TEXTURE_WRAP_S设置s坐标的模式,GL_TEXTURE_WRAP_T设置t坐标的模式,GL_TEXTURE_WRAP_R设置r坐标的模式。
在opengl es中有三种模式可供使用:
代码demo
GLES30.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); GLES30.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
这里的demo不考虑图像拉伸的问题,旨在理清展现的逻辑
简单使用2D纹理展现一下效果,首先封装一个展现纹理的关键方法:
代码位于 https://github.com/JerryChan123/LearnOEL/tree/gl30 的[2D纹理普通贴图&&2D纹理立方体贴图]提交当中
private Bitmap mBitmap; public int loadTexture(Context context, int resId) { int[] textureObjIds = new int[1]; GLES30.glGenTextures(1, textureObjIds, 0); if (textureObjIds[0] == 0) { return 0; } BitmapFactory.Options options = new BitmapFactory.Options(); options.inScaled = false; if (mBitmap == null) { mBitmap = BitmapFactory.decodeResource(context.getResources(), resId, options); if (mBitmap == null) { GLES30.glDeleteTextures(1, textureObjIds, 0); return 0; } } GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureObjIds[0]);//bind GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST); GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR); GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, mBitmap, 0); GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D); GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);//unbind return textureObjIds[0]; }
在SurfaceView的onDraw()方法中进行绘制:
... int texCoord = GLES30.glGetAttribLocation(mProgram, "texCoord"); GLES30.glEnableVertexAttribArray(texCoord); GLES30.glVertexAttribPointer(texCoord, 2, GLES30.GL_FLOAT, false, 0, textBuffer); int textureId = loadTexture(mContext, R.drawable.aa); GLES30.glActiveTexture(GLES30.GL_TEXTURE0); GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId); int uTextureUnitLocation = GLES30.glGetUniformLocation(mProgram, "s_texture"); GLES30.glUniform1i(uTextureUnitLocation, 0); ...
绘制出来图像以下所示:
附上次绘制立方体的纹理贴图效果:
代码地址:https://github.com/JerryChan123/LearnOEL/tree/gl30 提交信息为[add the texture for cube]