纹理的作用
在渲染的时候,为了让图像看起来真实,我们就必须由足够多的定点,指定足够多的定点颜色,但是这样会导致每个模型都会有巨大的开销。为了解决这个问题,我们通常使用2D的图片来代替更多的端点,由于图片上有很多细节,所以在渲染时就可以直接使用图片覆盖在3D物体的表面,让渲染出来的物体看起来更加精致,这就是纹理的作用。
纹理的创建与加载
1. 加载纹理图片
我们知道纹理来源于2D的图片,所以第一步我们需要从原始纹理的图片文件中读出图像的原始数据。有如果使用了opencv的话,可以很方便读取常见格式的图片到mat对象中,但是在一般OpenGL工程中也不会为读取图片特别去配置opencv的环境,一般可以使用一些比较小巧的库。当前比较常用的是std_image.h,它是个单头文件图像加载库,它能够加载大部分流程的图像格式。也正是由于它只有一个头文件,所以将它加载到工程是非常方便的。
std_image.h文件下载地址为:https://github.com/nothings/stb/blob/master/stb_image.h
加载图像文件代码如下:
1 |
|
由于在load图像的时候申请了内存,所以在使用完成后,需要记得释放内存空间:
1 | stbi_image_free(data); |
2. 创建纹理
在载获取到了图像的数据后,需要使用OpenGL创建绑定纹理、载入图像数据,除此之外一般会要设置纹理的一些配置属性如:“纹理环绕方式”、“纹理过滤”、“多级渐远纹理”。
- 纹理环绕方式,可以设置当纹理坐标超过(0, 0)到(1, 1)这个限定范围后的操作,重复这个纹理图像,s、t时其两个方向
- 纹理过滤,可以指定在超过原始图像分辨率时怎么插值,有放大(Magnify)和缩小(Minify)两种情况
以下为这些操作的基本流程代码:
1 | unsigned int texture; |
纹理映射
为了将2D的图片贴附在3D的物体上,有一个非常关键的过程就是要确定纹理图的哪部分对应3D物体的那部分,这就是纹理映射要解决的问题。
1. 纹理坐标
首先要规定的是纹理坐标(Texture Coordinate),由于纹理实际上是一张2D的图像,所以纹理坐标是一个二维坐标,左下角为坐标原点,右上角坐标规定为(1,1),这就是整个纹理坐标系的范围;而另外由于原始纹理图片是有分辨率大小的,所以在映射时需要插值来获取超过了分辨的像素点。
2. 纹理属性
在确定了纹理坐标后,我们只需要将端点坐标与纹理坐标一一对应起来就可以了,使用OpenGL的顶点数组可以规定格式如下:
1 | float vertices[] = { |
如上代码,在vertices数组中每8个数据代表一个端点,前三个为三维的位置坐标,中间3个为端点的RGB颜色属性,最后两个为其对应纹理贴图中的位置的纹理坐标,在内存中分布如下图:
有了以上端点数组后,就可以指定顶点属性,告诉OpenGL如何解析。前面两个属性不再写了,下面是添加纹理坐标属性的代码:
1 | glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); |
3. 在Shader中的处理
一般在顶点Shader中,会将传入的顶点数组,依照设置的顶点属性进行解析,然后传入片元Shader中处理;在片元Shader中需要使用到GLSL供纹理对象使用的内建数据类型,叫做采样器(Sampler),它以纹理类型作为后缀,比如sampler1D、sampler3D,在这里我们使用sampler2D;另外我们使用GLSL内建的texture函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。
片元Shader的代码如下:
1 |
|
绘制纹理
以上纹理操作全部完成后,就可以绘制了:
1 | glBindTexture(GL_TEXTURE_2D, texture); |