Part I 空气曲棍球 Chapter7(7.2 Loading Textures into OpenGL)

7.2 加载纹理(Loading Textures into OpenGL


首先我们需要把图片文件数据加载到OpenGL中,在com.airhockey.android.util包中创建类TextureHelper,并添加如下签名的函数:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
public static int loadTexture(Context context, int resourceId) {
}

    该函数接收Android的Context及纹理图片的资源id作为参数并返回已经加载的OpenGL纹理id,就像创建其它OpenGL对象一样,这里首先创建一个单元素的数组来存储纹理对象id,如下代码:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
final int[] textureObjectIds = new int[1];
glGenTextures(1, textureObjectIds, 0);
if (textureObjectIds[0] == 0) {
if (LoggerConfig.ON) {
    Log.w(TAG, "Could not generate a new OpenGL texture object.");
}
return 0;
}

    通过函数glGenTextures(1, textureObjectId, 0)调用产生一个纹理对象,OpenGL会把产生的对象id存储在textureObjectId中,同时我们检查只有在产生的id不为0的情况下才会继续,否则将会打印log并返回0值。由于TAG还没有定义,在TextureHelper类的顶部增加如下定义:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
private static final String TAG = "TextureHelper";

7.2.1 加载并绑定纹理(Loading in Bitmap Data and Binding to the Texture)

    下一步是使用Android提供的API从提供的图片文件中加载数据,OpenGL并不能直接从PNG或者JPEG图片文件中直接读取数据,因为这些数据是编码成不同压缩格式的;OpenGL需要的是没有被压缩的原始数据,因此我们需要使用Android提供的图片解码器把相关图片解码成OpenGL能够读取的数据。
    现在继续编写loadTexture(),下面代码将会解码相关图片:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
if (bitmap == null) {
    if (LoggerConfig.ON) {
        Log.w(TAG, "Resource ID " + resourceId + " could not be decoded.");
    }
    glDeleteTextures(1, textureObjectIds, 0);
    return 0;
}

    我们首先创建了BitmapFactory.Options类型的对象options,并且把inScaled设置为false,这相当于告诉Android这里需要的是图片原始数据而不是缩放后的数据。
    然后以context、纹理图片资源id、以及刚刚的options为参数调用BitmapFactory.decodeResource()函数执行真正的图片解码,这个调用将会把图片文件解码成bitmap或者返回null假如失败的话;我们对返回结果进行检查,如果解码图片失败(bitmap为null)的话将会删除刚刚已经分配的纹理对象,假如解码成功的话将会继续处理纹理。
    在使用我们刚刚创建的纹理对象之前,我们需要告诉OpenGL以后的调用都是在刚刚生成的对象上进行,可以通过调用函数glBindTexture()来做到这一点,如下代码所示:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
glBindTexture(GL_TEXTURE_2D, textureObjectIds[0]);

    第一个参数GL_TEXTURE_2D告诉OpenGL这是一个2D纹理,第二个参数告诉OpenGL需要绑定到哪个纹理ID。

7.2.2 理解纹理过滤模式(Understanding Texture Filtering)

    我们还需要使用纹理过滤模式指定当纹理放大或者缩小时该如何操作,当我们把纹理绘制到绘图表面的时候,纹理的像素并不会与OpenGL产生的片元刚好吻合(不需要放大或者缩小),这里有放大或者缩小两种情况,当我们需要把多个像素映射同一个片元的时候就是缩小,当我们需要把同一个像素映射到多个片元的时候就对应于放大;我们可以配置OpenGL使用过滤模式针对这两种情况分别处理。
    首先我们来看两种基本的过滤模式:最近邻过滤、双线性插值,还有其它的一些模式我们将会在稍后详细介绍,我们将会使用如下图片来展示不同的过滤模式。


7.2.2.1 最近邻过滤(Nearest-Neighbor Filtering)

    该模式会针对每一个片元选择最近的像素,当我们放大纹理的时候这种过滤所产生的块状非常严重,如下图所示:


    可以清楚的看到每一个像素都是一个小小的方块。
    当我们缩小纹理的时候,纹理的很多细节都消失了,因为我们没有足够多的片元容纳所有的像素,如下图所示:


7.2.2.2 双线性过滤(Bilinear Filtering)
    双线性过滤使用双线性插值来平滑每个像素之间的变化,对于这种方法OpenGL不是使用最近的像素而是使用相邻的四个像素然后使用之前我们介绍过的线性插值来产生新像素值,之所以作为双线性是因为它是在x、y两个方向上做插值的。下图是前面同一张纹理使用双线性过滤放大后的效果:


    现在的效果看起来更平滑了,由于我们把纹理放大了很多,所以看起来仍然有很多“块”状,但是并不像使用最近邻过滤那么明显。
7.2.2.3 贴图分级细化(Mipmapping)
    虽然双线性过滤对于纹理放大效果不错,但是对于当缩小到一定程度的大小时效果就没那么好了;纹理越缩得小,每个片元上面对应的像素越多,由于双线性过滤对每个片元仅仅使用四个像素,因此还是丢失了很多细节,这对于运动的物体将会引入很多噪声。
    我们可以使用贴图分级细化克服这种缺陷,这种技术会产生不同尺寸的优化过的纹理,当生存一系列纹理的时候,OpenGL可以使用所有的像素产生不同级别的纹理,保证所有的像素在纹理贴图的时候都会使用到;在执行渲染的时候OpenGL会参考每个片元上像素的数量来决定最合适使用哪个级别的纹理。下图是使用该技术产生的一系列纹理:

    使用这种技术将会消耗更多的内存,但是渲染速度也会更快,因为更小级别的纹理在GPU中消耗的内存也更少。
    为了更好的理解这种技术如何提高贴图的质量,我们把Android机器人图标使用双线性过滤缩小到原始大小的12.5%,如下图所示:


    从效果上看就像是使用最近邻过滤一样,现在看看当使用贴图分级细化时的效果,如下图所示:


    从启用这种技术时,OpenGL会选择最合适的纹理级别,然后再使用双线性过滤进行纹理贴图;由于每一个级别的纹理都是使用原图所有的像素构建而来的,因此产生的纹理图片能够保留更多的细节,纹理效果也看上去更好些。

7.2.2.4 三线性过滤(Trilinear Filtering)
    当我们使用双线性过滤配合mimap技术时,有时候在OpenGL使用不同级别的纹理时会产生可见的“隔带”效果或者看见一条“线“,此时我们可以使用三线性过滤告诉OpenGL对于每个片元在最近的两个纹理级别之间使用8个像素执行插值,这样可以消除两个纹理级别之间的差异并得到一张平滑的图片。

7.2.3 设置默认的过滤模式(Setting Default Texture Filtering Parameters)

    现在我们理解了纹理过滤模式,现在继续修改loadTexture(),并添加如下代码:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    我们调用函数glTexParameteri()指定相关值,GL_TEXTURE_MIN_FILTER代码缩小,GL_TEXTURE_MAG_FILTER代表放大,对于缩小我们选择参数GL_LINEAR_MIPMAP_LINEAR告诉OpenGL使用三线性过滤,而对于放大则使用参数GL_LINEAR告诉OpenGL使用双线性过滤。
    下表展示了不同的过滤模式:

GL_NEARESTNearest-neighbor filtering
GL_NEAREST_MIPMAP_NEARESTNearest-neighbor filtering with mipmaps
GL_NEAREST_MIPMAP_LINEARNearest-neighbor filtering with interpolation between mipmap levels
GL_LINEARBilinear filtering
GL_LINEAR_MIPMAP_NEARESTBilinear filtering with mipmaps
GL_LINEAR_MIPMAP_LINEARTrilinear filtering (bilinear filtering with interpolation between mipmap levels)

 


7.2.4 加载纹理并返回纹理ID(Loading the Texture into OpenGL and Returning the ID)

    现在我们可以使用GLUtils.texImage2D()函数加载纹理,如下代码:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);

    这个调用将会告诉OpenGL读取bitmap对象中的图像数据然后拷贝到当前绑定的纹理对象中。
    现在图像数据已经加载到了OpenGL中,我们不需要再操持Android的bitmap对象了,在一般情况下,可能会经过几个GC周期后该对象占用的内存才会最终被Dalvik虚拟机回收,因此我们需要调用recycle()立即释放相关内存,如下代码所示:    

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
bitmap.recycle();

    OpenGL产生多个分级纹理也是很容易的,我们可以调用glGenerateMipmap()告诉OpenGL生存所有需要的纹理,如下代码所示:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
glGenerateMipmap(GL_TEXTURE_2D);

    现在我们已经完成加载纹理,一个好的习惯是解绑当前纹理以免后面不小心修改了当前纹理,如下代码所示:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
glBindTexture(GL_TEXTURE_2D, 0);

    传递0为参数给函数glBindTexture()代表解绑当前纹理,最后一步是返回相应纹理的ID,如下代码:

//AirHockeyTextured/src/com/airhockey/android/util/TextureHelper.java
return textureObjectIds[0];

    现在该方法可以从资源目录中读取图片数据并把相应数据加载到OpenGL中,如果不出意外的话将会返回一个代表当前纹理引用的纹理ID数值给调用者,否则加载失败的话返回0。

    现在我们已经成功创建了纹理对象,下一节我们将修改着色器以便使用当前创建的纹理对象(点击进入下一节)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值