OGL(教程16)——基础贴图映射

原文地址:http://ogldev.atspace.co.uk/www/tutorial16/tutorial16.html

背景知识:
贴图的映射的意思是应用任何类型的图到3D模型的多个面上。这个图叫做纹理,它可以是任何东西。如砖头、树叶、贫瘠的土地,使用这些贴图增加场景的逼真程度。比如,看下面的两个图:
在这里插入图片描述

为了使纹理映射正确,你需要做三件事情。加载一个图片到OpenGL,提供顶点对应的纹理坐标,执行纹理采样操作,采样的使用的纹理坐标,以得到一个像素的颜色。由于三角形是被缩放、旋转、平移变换过的,最终才会被投影到屏幕上,而且在不同的摄像机视角来观察会有不同的效果。GPU所需要做的是,使贴图随着顶点的移动而一起移动。这样才能看起来逼真。为了实现这个,开发者需要提供一系列的坐标,被称之为纹理坐标,每个顶点都有对应的纹理坐标。由于GPU光栅化了三角形,它同样在三角形的表面上对纹理坐标也进行插值计算,然后在片段着色器中使用纹理坐标也采样贴图。这个动作称之为采样,采样的结果是一个单位纹理。这个单位纹理通常包含的是颜色,用来对一个像素点进行着色。在接下来的章节中,我们会看到单温纹理可以包含不同的数据,用来产生不同的效果。

OpenGL支持几种不同类型的贴图,如1D、2D、3D、立方体贴图等,用以实现不同的技术。我们现在从2D开始。一个2D的纹理有宽度和高度,可以是任意指定的大小。把宽度和高度相乘将会得到这个纹理总的单位纹理总数。你怎样指定一个顶点的纹理坐标?这个纹理坐标不是单位纹理在整个纹理贴图上的坐标。这样做的局限性很大,因为如果换一个不同大小的贴图之后,还需要更新所有顶点的纹理坐标,以作匹配这种新的贴图。理想的情况是,换贴图但是不要换纹理坐标。因此,纹理坐标被定义在纹理空间中,它的值都是在规格化的[0,1]范围内。这就意味着,纹理坐标通常是一个系数,然后把乘以纹理贴图的宽度或者高度以得到单位纹理在整个纹理中的坐标。比如,如果纹理坐标是[0.5,0.1],然后贴图的大小是宽度=320,高度=200,那么单位纹理的的位置在 (160,20) ,计算方式为:(0.5 * 320 = 160 以及0.1 * 200 = 20)。

最常用的变换是使用u和v作为纹理空间的轴,这里u代表了2D坐标系中的x轴,而v代表了2D坐标系中的y轴。OpenGL对u轴是从做到右,而v则是从下到上。如下图所示:
在这里插入图片描述

上图展示了纹理空间,左下角是原点。u是向右增长,而v向上增长。我们现在来考虑三角形,它的纹理坐标如下所示:

在这里插入图片描述

加入我们使用这个纹理,那么将会得到上面的图,而如果经过多种变换之后,我们会得到如下的图:
在这里插入图片描述

正如你看到的,纹理坐标和顶点黏在一起的,这个顶点的一个属性,在变换的时候不会改变。在插值的时候,多个像素会得到相同的纹理坐标。我们对物体进行旋转、拉伸、挤压,纹理也会随之变换,但是也有技术让纹理运动起来,通过改变纹理坐标实现,但是目前我们保持纹理坐标不变。

另外一个很重要的概念是,就是过滤。我们已经讨论过怎样使用纹理坐标采样一个纹理单元。单位纹理的位置通常是整数,而当纹理坐标是浮点数,比如0到1之间的某个浮点数,被映射成了 (152.34,745.14),这种情况下,向下取整变为(152,745)。所以,这个工作会得到差不多的效果,但是在某些情况下会变得很糟糕。另外一个很好的解决方式是,把此点的纹理用2x2个整数单位的单位纹理进行差值,比如上面的用(152,745),(153,745), (152,744) 和 (153,744)来做差值得到最终的颜色,这个线性差值,使用的是 (152.34,745.14)到四个点的距离进行差值。距离越近的,对颜色贡献越大,这种效果比之前的那个方法要好。

哪个纹理单位最终被选择,通常叫做过滤。最简单的方式是最近距离过滤,更复杂的方式是我们上面使用的线性过滤。近邻顾虑的领啊为一个名字是点过滤。OpenGL提供了几种不同类型的过滤方式,你可以选择其中任一个。通常,提供更好效果的过滤方法,其耗时也会大。所以在性能和效果之间要做一个权衡。

现在,我们理解了OpenGL是如何使用纹理坐标来采样一个纹理。纹理在OpenGL中,蕴含着四个概念:纹理对象,纹理单元,采样对象和shader中的采样统一。这个不懂。

纹理对象包含了纹理的图片的数据,单位是纹理单元。纹理可以是不同的类型,1D,2D不同维度。数据类型可以是不同的格式,如RGB、RGBA等。OpenGL指定原数据在内存中的起始位置,然后加载数据到GPU。还有很多其他的参数你可以控制,比如过滤的类型等。纹理对象也会关联一个顶点缓冲对象。当创建了这个句柄之后,加载纹理数据和参数之后,你可以再不同的句柄之间做绑定,以得到不同的OpenGL状态。你不再需要加载数据。从现在开始,OpenGL驱动的任务是确保在GPU渲染之前把数据加载好。

纹理对象不是直接绑定到shader,shader是真正做采样的地方。但是,纹理单元所对应的索引需要传递给shader。这样shader可以通过纹理单元来访问对纹理对象。有可能有多个纹理单元,最多有几个和显卡有关系。为了绑定一个纹理对象A到纹理单元0,你首先要确保单元0是可用的,然后才能绑定纹理对象A。你可以把纹理单元1变为可用,然后绑定另外一个或者相同的纹理对象给它。

每个纹理单元有几种不同的纹理对象,这会造成一定的复杂度。这个叫做纹理对象的目标。当你绑定纹理对象到纹理单元之后,你要指定纹理对象是1D,2D还是其他类型。所以你可以把纹理对象A绑定到1D对象,纹理对象B绑定到2D目标。

采样操作通常发生在片段着色器中,而且有一个单独的函数来处理。采样函数需要知道纹理单元来访问,你可以在片段着色器中采样多个纹理单元。这里有一组特殊的统一变量来做这个事情,根据纹理目标,有sampler2D、samper2D、sampler3D、samplerCube等等。你要创建你想要的采样器统一变量。

最后一个概念是采样对象,不要把采样统一变量和他弄混了。他们是单独的实体。一个事情是,贴图对象包含了贴图数据,而且还有一个采样的参数配置。这些参数是采样的状态。但是,你可以创建采样对象,配置它一个采样状态,然后绑定到纹理单元。当你对采样对象做这些事情,将会改变他的状态。不要担心——现在我们不需要使用采样对象,我们现在只需要知道它的存在即可。

下图总结了纹理相关的概念:
在这里插入图片描述

代码注释:
OpenGL知道从内存中使用不同的格式加载纹理数据,但是不提供任何方法加载PNG和JPG格式图片到内存。我们需要使用额外的库。在ImageMagick中有很多选项,它是一个免费的库,支持很多图片格式,而且是跨平台的。查看链接http://ogldev.atspace.co.uk/instructions.html 来安装。

大多数的纹理处理函数都封装在下面的类中:

(ogldev_texture.h:27)

class Texture
{
public:
   Texture(GLenum TextureTarget, const std::string& FileName);

   bool Load();

   void Bind(GLenum TextureUnit);
};

当创建一个Texture对象时,你需要指定目标,我们使用GL_TEXTURE_2D还有文件名。之后我们可以使用Load函数。这个可能会失败,如果文件不存在,或者ImageMagic遇到了其他的错误。当你使用指定的纹理实例时,你需要绑定它到纹理单元。

(ogldev_texture.cpp:31)

try {
   m_pImage = new Magick::Image(m_fileName);
   m_pImage->write(&m_blob, "RGBA");
}
catch (Magick::Error& Error) {
   std::cout << "Error loading texture '" << m_fileName << "': " << Error.what() << std::endl;
   return false;
}

猜你喜欢

转载自blog.csdn.net/wodownload2/article/details/83032566
今日推荐