纹理的加载

当我们设置好纹理对象后,就可以加载我们的纹理数据了。下面这个例子演示了如何加载一幅2*2像素的纹理:




加载一幅2*2的纹理 


这个函数非常重要,下面我们来看一下每个参数所表示的意义:

第一个参数很简单,表示绑定纹理对象的种类,这里我们依然以GL_TEXTURE_2D的纹理为例。

第二个参数表示该纹理对应的mipmaps的等级,0表示没有进行缩小的原始图片等级。

第三个参数表示了纹理所采用的内部格式,内部格式是我们的像素数据在显卡中存储的格式,这里的GL_RGB显然就表示纹理中像素的颜色值是以RGB的格式存储的。

第四个和第五个参数表示了纹理的宽和高,这里我们采用的是2*2像素宽度的纹理,因此这两个参数都是2。

第六个参数通常为0.

第七和第八个参数描述了像素在内存中的存储格式和数据类型。

第九个参数是存放纹理数据的指针。

不过问题又来了,我们如何生成一个含有纹理数据的数组呢?在实际工作中,我们通常需要使用一张JPG和PNG等格式的图片文件作为模型的纹理,而OpenGL中并没有提供相关API用于将这些图片文件转换成我们所需要的数组。因此我们需要使用第三方库来解决这一难题,目前使用得最广泛的应该是SOIL库,下面我们将以它为例来介绍如何将图片文件转换成纹理数据。

SOIL

SOIL(Simple OpenGL Image Library)是一个简单易用的图片加载库,它提供的API可以将指定的任意格式图片文件加载并生成纹理数据:




SOIL库加载图片生成纹理


这里,我们将一个名为“img.png”的图片文件加载进内存,最终将存储纹理数据的指针image返回给我们,并且这个API还同时可以获取图片的宽度width和高度height,也可以指定内部格式为RGB。

纹理数据使用完毕后释放的工作也可以交给它完成:




SOIL库释放纹理数据 


正如上一篇中所提到的,OpenGL中纹理坐标系是以纹理左下角为坐标原点的,而图片中像素的存储顺序是从左上到右下的,因此我们需要对我们的坐标系进行一次Y轴的“翻转”。我们以后的教程中的0,0坐标将会假设为是纹理的左上角而不再是左下角,这样讲解起来会更加直观一些。

使用纹理

我们知道纹理是采用纹理坐标进行采样的,我们在使用纹理时,需要将顶点对应的纹理坐标输入OpenGL。这里我们创建一个矩形区域的顶点数组,数组里每个顶点所包含的数据除位置、颜色外,还有纹理坐标的s和t值。




包含纹理坐标的顶点数组


顶点着色器中将纹理坐标Texcoord接收并传入片段着色器:




顶点着色器


和其他顶点属性一样,将纹理坐标以AttribPointer的形式输入着色器:




添加纹理坐标属性


现在只剩下最后一件事情要处理,那就是在片段着色器中进行真正的纹理采样。采样方式很简单,首先在着色器中声明一个sapler2D类型的采样器tex,然后调用texture函数进行采样。texture函数第一个参数是采样器,第二个参数就是我们由顶点着色器传入的纹理坐标。在这里我们对采集后的纹理与我们顶点的颜色进行一个混合,混合的方式就是对两者做一个笛卡尔积。




片段着色器


最终我们得到的效果如下:




纹理与顶点颜色混合效果


纹理单元

纹理单元是能够被着色器采样的纹理对象的引用, 纹理通过调用glBindTexture函数绑定到指定的纹理单元。由于你没有明确指定使用哪个纹理单元,所以纹理被默认绑定到了GL_TEXTURE0,这就是为什么我们在片段着色器中给我们的采样器对象传入一个0它依然能够正常工作的原因。

那么我们怎么指定当前我们使用的纹理单元呢?OpenGL提供了一个叫做glActiveTexture的API来让我们选择用来绑定纹理的纹理单元ID:




指定纹理单元


OpenGL中最大支持的纹理单元数量根据显卡不同而有所区别,最少支持48个。不过基本上你同时用完所有纹理单元的几率为0。下面让我们通过一个例子来看看怎样同时使用多个纹理单元来完成多张纹理的混合,首先我们需要修改一下我们的片段着色器,让它同时对两张纹理进行采样:




使用着色器同时对两张纹理进行采样


colKitten和colPuppy分别对应两幅纹理中的采样结果,最终的片段颜色值由mix函数将两者进行混合后得到。

mix这个函数是GLSL中一个特殊的线性插值函数,他将前两个参数的值基于第三个参数按照以下公式进行插值:

genType mix (genType x, genType y, float a)

返回线性混合的x和y,如:x⋅(1−a)+y⋅a

现在我们的两个采样器对象texKitten和texPuppy已经准备好了,你需要在应用程序中将两幅纹理绑定给两个纹理单元,再将这两个纹理单元的ID通过glUniform函数分别赋值给我们的采样器,应用程序端代码如下: