forked from cybercser/OpenGL_3_3_Tutorial_Translation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTutorial 5 A Textured Cube opengl-tutorial_org.txt
152 lines (104 loc) · 7.85 KB
/
Tutorial 5 A Textured Cube opengl-tutorial_org.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
Tutorial 5 : A Textured Cube
第五课:纹理方块
关于UV坐标
自行加载.BMP图片
在OpenGL中使用纹理
什么是滤波和mipmap?怎样使用?
线性滤波(Linear filtering)
各向异性滤波(Anisotropic filtering)
Mipmap
怎样利用GLFW加载纹理?
压缩纹理
创建压缩纹理
使用压缩纹理
反转UV坐标
总结
练习
参考文献
本课学习如下几点:
什么是UV坐标
怎样自行加载纹理
怎样在OpenGL中使用纹理
什么是滤波?什么是mipmap?怎样使用?
怎样利用GLFW更加鲁棒地加载纹理?
什么是alpha通道?
关于UV坐标
给一个模型贴纹理时,需要通过某种方式告诉OpenGL用哪一块图像来填充三角形。这是借助UV坐标来实现的。
每个顶点除了位置坐标外还有两个浮点数坐标:U和V。这两个坐标用于获取纹理,如下图所示:
注意纹理是怎样在三角形上扭曲的。
自行加载.BMP图片
了解BMP文件格式并不重要:很多库可以帮你做这个。但BMP格式极为简单,可以帮助你理解那些库的工作原理。所以,我们从头开始写一个BMP文件加载器,以便你理解其工作原理,不过(在实际工程中)千万别再用这个实验品。
如下是加载函数的声明:
使用方式如下:
接下来看看如何读取BMP文件。
首先需要一些数据。读取文件时将设置这些变量。
现在正式开始打开文件。
文件一开始是54字节长的文件头,用于标识“这是不是一个BMP文件”、图像大小、像素位等等。来读取文件头吧:
文件头总是以“BM”开头。实际上,如果用十六进制编辑器打开BMP文件,你会看到如下情形:
因此,得检查一下头两个字节是否确为‘B’和‘M’:
现在可以读取文件中图像大小、数据位置等信息了:
如果这些信息缺失得手动补齐:
现在我们知道了图像的大小,可以为之分配一些内存,把图像读进去:
到了真正的OpenGL部分了。创建纹理和创建顶点缓冲器差不多:
创建一个纹理、绑定、填充、配置。
在glTexImage2D函数中,GL_RGB表示颜色由三个分量构成,GL_BGR则说明在内存中颜色值是如何存储的。实际上,BMP存储的并不是RGB红绿蓝,而是BGR蓝绿红,因此得把这个告诉OpenGL。
稍后再解释最后两行代码。同时,得在C++代码中使用刚写好的函数加载一个纹理:
另外十分重要的一点:使用2次幂(power-of-two)的纹理!
优质纹理:128*128、256*256、1024*1024、2*2……
劣质纹理:127*128、3*5
勉强可以但很怪异的纹理:128*256
在OpenGL中使用纹理
先来看看片断着色器。大部分代码一目了然:
注意三个点:
片断着色器需要UV坐标。看似合情合理。
同时也需要一个“Sampler2D”来获知要加载哪一个纹理(同一个着色器中可以访问多个纹理)
最后一点,用texture()访问纹理,该方法返回一个(R,G,B,A)的vec4变量。马上就会了解到分量A。
顶点着色器也很简单,只需把UV坐标传给片断着色器:
还记得第四课中的“layout(location = 1) in vec2 vertexUV” 吗?我们得在这儿把相同的事情再做一遍,但这次的缓冲器中放的不是(R,G,B)三元组,而是(U,V)数对。
上述UV坐标对应于下面的模型:
其余的就很清楚了。创建一个缓冲器、绑定、填充、配置,与往常一样绘制顶点缓冲器对象。要注意把glVertexAttribPointer的第二个参数(大小)3改成2。
结果如下:
放大后:
什么是滤波和mipmap?怎样使用?
正如在上面截图中看到的,纹理质量不是很好。这是因为在loadBMP_custom函数中,有两行这样写道:
这意味着在片断着色器中,texture()将直接提取位于(U,V)坐标的纹素(texel)。
有几种方法可以改善这一状况。
线性滤波
若采用线性滤波。texture()会查看周围的纹素,然后根据UV坐标距离各纹素中心的距离来混合颜色。这就避免了前面看到的锯齿状边缘。
线性滤波可以显著改善纹理质量,应用的也很多。但若想获得更高质量的纹理,可以采用各向异性滤波,不过速度上有些慢。
各向异性滤波
这种方法逼近了真正片断中的纹素区块。例如下图中稍稍旋转了的纹理,各向异性滤波将沿蓝色矩形框的主方向,作一定数量的采样(即所谓的“各向异性层级”),计算出其内的颜色。
Mipmap
线性滤波和各向异性滤波都存在一个共同的问题。那就是如果从远处观察纹理,只对4个纹素作混合显得不够。实际上,如果3D模型位于很远的地方,屏幕上只看得见一个片断(像素),那计算平均值得出最终颜色值时,图像所有的纹素都应该考虑在内。很显然,这样做没有考虑性能问题。相反,人们引入了mipmap这一概念:
一开始,把图像缩小到原来的1/2,接着一次做下去,直到图像只有1x1大小(应该是图像所有纹素的平均值)绘制模型时,根据纹素大小选择合适的mipmap。
可以选用nearest、linear、anisotropic等任意一种滤波方式来对mipmap采样。要想效果更好,可以对两个mipmap采样然后混合,得出结果。
好在这个比较简单,OpenGL都帮我们做好了,只需一个简单的调用:
怎样利用GLFW加载纹理?
我们的loadBMP_custom函数很棒,因为这是我们自己写的!不过用专门的库更好。GLFW就可以加载纹理(仅限TGA文件):
压缩纹理
学到这儿,你可能会想怎样加载JPEG文件而不是TGA文件呢?
简单的说:别这么干。还有更好的选择。
创建压缩纹理
下载The Compressonator,一款ATI工具
用它加载一个二次幂纹理
将其压缩成DXT1、DXT3或DXT5格式(这些格式之间的差别请参考Wikipedia):
生成mipmap,这样就不用在运行时生成mipmap了。
导出为.DDS文件。
至此,图像已压缩为可被GPU直接使用的格式。在着色中随时调用texture()均可以实时解压。这一过程看似很慢,但由于它节省了很多内存空间,传输的数据量就少了。传输内存数据开销很大;纹理解压缩却几乎不耗时(有专门的硬件负责此事)。一般情况下,才用压缩纹理可使性能提升20%。
使用压缩纹理
来看看怎样加载压缩纹理。这和加载BMP的代码很相似,只不过文件头的结构不一样:
文件头之后是真正的数据:紧接着是mipmap层级。可以一次性批量地读取:
这里要处理三种格式:DXT1、DXT3和DXT5。我们得把“fourCC”标识转换成OpenGL能识别的值。
像往常一样创建纹理:
现在只需逐个填充mipmap:
反转UV坐标
DXT压缩源自DirectX。和OpenGL相比,DirectX中的V纹理坐标是反过来的。所以使用压缩纹理时,得用(coord.v, 1.0-coord.v)来获取正确的纹素。这步操作何时做都可以:可以在导出脚本中做,可以在加载器中做,也可以在着色器中做……
总结
刚刚学习的是创建、加载以及在OpenGL中使用纹理。
总的来说,压缩纹理体积小、加载迅速、使用便捷,应该只用压缩纹理;主要的缺点是得用The Compressonator来转换图像格式。
练习
源代码中实现了DDS加载器,但没有做纹理坐标的改动(译者注:指文中讲述的反转 UV坐标)。在适当的位置添加该功能,以使正方体正确显示。
试试各种DDS格式。所得结果有何不同?压缩率呢?
试试在The Compressonator不生成mipmap。结果如何?请给出3种方案解决这一问题。
参考文献
Using texture compression in OpenGL , Sébastien Domine, NVIDIA