💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 第五课:带纹理的立方体 # 第五课:纹理立方体 本课学习如下几点: - 什么是UV坐标 - 怎样自行加载纹理 - 怎样在OpenGL中使用纹理 - 什么是滤波?什么是mipmap?怎样使用? - 怎样利用GLFW更加有效地加载纹理? - 什么是alpha通道? ## 关于UV坐标 给一个模型贴纹理时,需要通过某种方式告诉OpenGL用哪一块图像来填充三角形。这是借助UV坐标来实现的。 每个顶点除了位置坐标外还有两个浮点数坐标:U和V。这两个坐标用于获取纹理,如下图所示: ![](https://box.kancloud.cn/2015-11-02_5636f30402a11.png) 注意纹理是怎样在三角形上扭曲的。 ## 自行加载.BMP图片 了解BMP文件格式并不重要:很多库可以帮你做这个。但BMP格式极为简单,可以帮助你理解那些库的工作原理。所以,我们从头开始写一个BMP文件加载器,以便你理解其工作原理,不过(在实际工程中)**千万别再用这个实验品**。 如下是加载函数的声明: ``` <pre class="calibre16">``` GLuint <span class="token3">loadBMP_custom</span><span class="token1">(</span>const char <span class="token">*</span> imagepath<span class="token1">)</span><span class="token1">;</span> ``` ``` 使用方式如下: ``` <pre class="calibre16">``` GLuint image <span class="token">=</span> <span class="token3">loadBMP_custom</span><span class="token1">(</span><span class="token5">"./my_texture.bmp"</span><span class="token1">)</span><span class="token1">;</span> ``` ``` 接下来看看如何读取BMP文件。 首先需要一些数据。读取文件时将设置这些变量。 ``` <pre class="calibre16">``` <span class="token2">// Data read from the header of the BMP file</span> unsigned char header<span class="token1">[</span><span class="token6">54</span><span class="token1">]</span><span class="token1">;</span> <span class="token2">// Each BMP file begins by a 54-bytes header</span> unsigned int dataPos<span class="token1">;</span> <span class="token2">// Position in the file where the actual data begins</span> unsigned int width<span class="token1">,</span> height<span class="token1">;</span> unsigned int imageSize<span class="token1">;</span> <span class="token2">// = width*height*3</span> <span class="token2">// Actual RGB data</span> unsigned char <span class="token">*</span> data<span class="token1">;</span> ``` ``` 现在正式开始打开文件。 ``` <pre class="calibre16">``` <span class="token2">// Open the file</span> FILE <span class="token">*</span> file <span class="token">=</span> <span class="token3">fopen</span><span class="token1">(</span>imagepath<span class="token1">,</span><span class="token5">"rb"</span><span class="token1">)</span><span class="token1">;</span> <span class="token4">if</span> <span class="token1">(</span><span class="token">!</span>file<span class="token1">)</span> <span class="token1">{</span><span class="token3">printf</span><span class="token1">(</span><span class="token5">"Image could not be openedn"</span><span class="token1">)</span><span class="token1">;</span> <span class="token4">return</span> <span class="token6">0</span><span class="token1">;</span><span class="token1">}</span> ``` ``` 文件一开始是54字节长的文件头,用于标识“这是不是一个BMP文件”、图像大小、像素位等等。来读取文件头吧: ``` <pre class="calibre16">``` <span class="token4">if</span> <span class="token1">(</span> <span class="token3">fread</span><span class="token1">(</span>header<span class="token1">,</span> <span class="token6">1</span><span class="token1">,</span> <span class="token6">54</span><span class="token1">,</span> file<span class="token1">)</span><span class="token">!=</span><span class="token6">54</span> <span class="token1">)</span><span class="token1">{</span> <span class="token2">// If not 54 bytes read : problem</span> <span class="token3">printf</span><span class="token1">(</span><span class="token5">"Not a correct BMP filen"</span><span class="token1">)</span><span class="token1">;</span> <span class="token4">return</span> <span class="token6">false</span><span class="token1">;</span> <span class="token1">}</span> ``` ``` 文件头总是以“BM”开头。实际上,如果用十六进制编辑器打开BMP文件,你会看到如下情形: ![](https://box.kancloud.cn/2015-11-02_5636f30427858.png) 因此,得检查一下头两个字节是否确为‘B’和‘M’: ``` <pre class="calibre16">``` <span class="token4">if</span> <span class="token1">(</span> header<span class="token1">[</span><span class="token6">0</span><span class="token1">]</span><span class="token">!=</span><span class="token5">'B'</span> <span class="token">||</span> header<span class="token1">[</span><span class="token6">1</span><span class="token1">]</span><span class="token">!=</span><span class="token5">'M'</span> <span class="token1">)</span><span class="token1">{</span> <span class="token3">printf</span><span class="token1">(</span><span class="token5">"Not a correct BMP filen"</span><span class="token1">)</span><span class="token1">;</span> <span class="token4">return</span> <span class="token6">0</span><span class="token1">;</span> <span class="token1">}</span> ``` ``` 现在可以读取文件中图像大小、数据位置等信息了: ``` <pre class="calibre16">``` <span class="token2">// Read ints from the byte array</span> dataPos <span class="token">=</span> <span class="token">*</span><span class="token1">(</span>int<span class="token">*</span><span class="token1">)</span><span class="token">&</span><span class="token1">(</span>header<span class="token1">[</span><span class="token6">0x0A</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> imageSize <span class="token">=</span> <span class="token">*</span><span class="token1">(</span>int<span class="token">*</span><span class="token1">)</span><span class="token">&</span><span class="token1">(</span>header<span class="token1">[</span><span class="token6">0x22</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> width <span class="token">=</span> <span class="token">*</span><span class="token1">(</span>int<span class="token">*</span><span class="token1">)</span><span class="token">&</span><span class="token1">(</span>header<span class="token1">[</span><span class="token6">0x12</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> height <span class="token">=</span> <span class="token">*</span><span class="token1">(</span>int<span class="token">*</span><span class="token1">)</span><span class="token">&</span><span class="token1">(</span>header<span class="token1">[</span><span class="token6">0x16</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> ``` ``` 如果这些信息缺失得手动补齐: ``` <pre class="calibre16">``` <span class="token2">// Some BMP files are misformatted, guess missing information</span> <span class="token4">if</span> <span class="token1">(</span>imageSize<span class="token">==</span><span class="token6">0</span><span class="token1">)</span> imageSize<span class="token">=</span>width<span class="token">*</span>height<span class="token">*</span><span class="token6">3</span><span class="token1">;</span> <span class="token2">// 3 : one byte for each Red, Green and Blue component</span> <span class="token4">if</span> <span class="token1">(</span>dataPos<span class="token">==</span><span class="token6">0</span><span class="token1">)</span> dataPos<span class="token">=</span><span class="token6">54</span><span class="token1">;</span> <span class="token2">// The BMP header is done that way</span> ``` ``` 现在我们知道了图像的大小,可以为之分配一些内存,把图像读进去: ``` <pre class="calibre16">``` <span class="token2">// Create a buffer</span> data <span class="token">=</span> <span class="token4">new</span> <span class="token3">unsigned</span> char <span class="token1">[</span>imageSize<span class="token1">]</span><span class="token1">;</span> <span class="token2">// Read the actual data from the file into the buffer</span> <span class="token3">fread</span><span class="token1">(</span>data<span class="token1">,</span><span class="token6">1</span><span class="token1">,</span>imageSize<span class="token1">,</span>file<span class="token1">)</span><span class="token1">;</span> <span class="token2">//Everything is in memory now, the file can be closed</span> <span class="token3">fclose</span><span class="token1">(</span>file<span class="token1">)</span><span class="token1">;</span> ``` ``` 到了真正的OpenGL部分了。创建纹理和创建顶点缓冲器差不多:创建一个纹理、绑定、填充、配置。 在glTexImage2D函数中,GL\_RGB表示颜色由三个分量构成,GL\_BGR则说明在内存中颜色值是如何存储的。实际上,BMP存储的并不是RGB,而是BGR,因此得把这个告诉OpenGL。 ``` <pre class="calibre16">``` <span class="token2">// Create one OpenGL texture</span> GLuint textureID<span class="token1">;</span> <span class="token3">glGenTextures</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>textureID<span class="token1">)</span><span class="token1">;</span> <span class="token2">// "Bind" the newly created texture : all future texture functions will modify this texture</span> <span class="token3">glBindTexture</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> textureID<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Give the image to OpenGL</span> <span class="token3">glTexImage2D</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span>GL_RGB<span class="token1">,</span> width<span class="token1">,</span> height<span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span> GL_BGR<span class="token1">,</span> GL_UNSIGNED_BYTE<span class="token1">,</span> data<span class="token1">)</span><span class="token1">;</span> <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MAG_FILTER<span class="token1">,</span> GL_NEAREST<span class="token1">)</span><span class="token1">;</span> <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MIN_FILTER<span class="token1">,</span> GL_NEAREST<span class="token1">)</span><span class="token1">;</span> ``` ``` 稍后再解释最后两行代码。同时,得在C++代码中使用刚写好的函数加载一个纹理: ``` <pre class="calibre16">``` GLuint Texture <span class="token">=</span> <span class="token3">loadBMP_custom</span><span class="token1">(</span><span class="token5">"uvtemplate.bmp"</span><span class="token1">)</span><span class="token1">;</span> ``` ``` 另外十分重要的一点: 使用2次幂(power-of-two)的纹理! - 优质纹理: 128*128*, 256*256, 1024*1024, 2\*2… - 劣质纹理: 127*128, 3*5, … - 勉强可以但很怪异的纹理: 128\*256 ## 在OpenGL中使用纹理 先来看看片断着色器。大部分代码一目了然: ``` <pre class="calibre16">``` #version <span class="token6">330</span> core <span class="token2">// Interpolated values from the vertex shaders</span> <span class="token4">in</span> vec2 UV<span class="token1">;</span> <span class="token2">// Ouput data</span> out vec3 color<span class="token1">;</span> <span class="token2">// Values that stay constant for the whole mesh.</span> uniform sampler2D myTextureSampler<span class="token1">;</span> void <span class="token3">main</span><span class="token1">(</span><span class="token1">)</span><span class="token1">{</span> <span class="token2">// Output color = color of the texture at the specified UV</span> color <span class="token">=</span> <span class="token3">texture</span><span class="token1">(</span> myTextureSampler<span class="token1">,</span> UV <span class="token1">)</span><span class="token1">.</span>rgb<span class="token1">;</span> <span class="token1">}</span> ``` ``` 注意三个点: - 片断着色器需要UV坐标。看似合情合理。 - 同时也需要一个“Sampler2D”来获知要加载哪一个纹理(同一个着色器中可以访问多个纹理) - 最后一点,用texture()访问纹理,该方法返回一个(R,G,B,A)的vec4变量。马上就会了解到分量A。 顶点着色器也很简单,只需把UV坐标传给片断着色器: ``` <pre class="calibre16">``` #version <span class="token6">330</span> core <span class="token2">// Input vertex data, different for all executions of this shader.</span> <span class="token3">layout</span><span class="token1">(</span>location <span class="token">=</span> <span class="token6">0</span><span class="token1">)</span> <span class="token4">in</span> vec3 vertexPosition_modelspace<span class="token1">;</span> <span class="token3">layout</span><span class="token1">(</span>location <span class="token">=</span> <span class="token6">1</span><span class="token1">)</span> <span class="token4">in</span> vec2 vertexUV<span class="token1">;</span> <span class="token2">// Output data ; will be interpolated for each fragment.</span> out vec2 UV<span class="token1">;</span> <span class="token2">// Values that stay constant for the whole mesh.</span> uniform mat4 MVP<span class="token1">;</span> void <span class="token3">main</span><span class="token1">(</span><span class="token1">)</span><span class="token1">{</span> <span class="token2">// Output position of the vertex, in clip space : MVP * position</span> gl_Position <span class="token">=</span> MVP <span class="token">*</span> <span class="token3">vec4</span><span class="token1">(</span>vertexPosition_modelspace<span class="token1">,</span><span class="token6">1</span><span class="token1">)</span><span class="token1">;</span> <span class="token2">// UV of the vertex. No special space for this one.</span> UV <span class="token">=</span> vertexUV<span class="token1">;</span> <span class="token1">}</span> ``` ``` 还记得第四课中的“layout(location = 1) in vec2 vertexUV” 吗?我们得在这儿把相同的事情再做一遍,但这次的缓冲器中放的不是(R,G,B)三元组,而是(U,V)数对。 ``` <pre class="calibre16">``` <span class="token2">// Two UV coordinatesfor each vertex. They were created with Blender. You'll learn shortly how to do this yourself.</span> static const GLfloat g_uv_buffer_data<span class="token1">[</span><span class="token1">]</span> <span class="token">=</span> <span class="token1">{</span> <span class="token6">0.000059</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.000004</span>f<span class="token1">,</span> <span class="token6">0.000103</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.336048</span>f<span class="token1">,</span> <span class="token6">0.335973</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335903</span>f<span class="token1">,</span> <span class="token6">1.000023</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.000013</span>f<span class="token1">,</span> <span class="token6">0.667979</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335851</span>f<span class="token1">,</span> <span class="token6">0.999958</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.336064</span>f<span class="token1">,</span> <span class="token6">0.667979</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335851</span>f<span class="token1">,</span> <span class="token6">0.336024</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.671877</span>f<span class="token1">,</span> <span class="token6">0.667969</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.671889</span>f<span class="token1">,</span> <span class="token6">1.000023</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.000013</span>f<span class="token1">,</span> <span class="token6">0.668104</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.000013</span>f<span class="token1">,</span> <span class="token6">0.667979</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335851</span>f<span class="token1">,</span> <span class="token6">0.000059</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.000004</span>f<span class="token1">,</span> <span class="token6">0.335973</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335903</span>f<span class="token1">,</span> <span class="token6">0.336098</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.000071</span>f<span class="token1">,</span> <span class="token6">0.667979</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335851</span>f<span class="token1">,</span> <span class="token6">0.335973</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335903</span>f<span class="token1">,</span> <span class="token6">0.336024</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.671877</span>f<span class="token1">,</span> <span class="token6">1.000004</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.671847</span>f<span class="token1">,</span> <span class="token6">0.999958</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.336064</span>f<span class="token1">,</span> <span class="token6">0.667979</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335851</span>f<span class="token1">,</span> <span class="token6">0.668104</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.000013</span>f<span class="token1">,</span> <span class="token6">0.335973</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335903</span>f<span class="token1">,</span> <span class="token6">0.667979</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335851</span>f<span class="token1">,</span> <span class="token6">0.335973</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335903</span>f<span class="token1">,</span> <span class="token6">0.668104</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.000013</span>f<span class="token1">,</span> <span class="token6">0.336098</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.000071</span>f<span class="token1">,</span> <span class="token6">0.000103</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.336048</span>f<span class="token1">,</span> <span class="token6">0.000004</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.671870</span>f<span class="token1">,</span> <span class="token6">0.336024</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.671877</span>f<span class="token1">,</span> <span class="token6">0.000103</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.336048</span>f<span class="token1">,</span> <span class="token6">0.336024</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.671877</span>f<span class="token1">,</span> <span class="token6">0.335973</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335903</span>f<span class="token1">,</span> <span class="token6">0.667969</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.671889</span>f<span class="token1">,</span> <span class="token6">1.000004</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.671847</span>f<span class="token1">,</span> <span class="token6">0.667979</span>f<span class="token1">,</span> <span class="token6">1.0</span>f<span class="token">-</span><span class="token6">0.335851</span>f <span class="token1">}</span><span class="token1">;</span> ``` ``` 上述UV坐标对应于下面的模型: ![](https://box.kancloud.cn/2015-11-02_5636f30435bf7.png) 其余的就很清楚了。创建一个缓冲器、绑定、填充、配置,与往常一样绘制顶点缓冲器对象。要注意把glVertexAttribPointer的第二个参数(大小)3改成2。 结果如下: ![](https://box.kancloud.cn/2015-11-02_5636f30448791.png) 放大后: ![](https://box.kancloud.cn/2015-11-02_5636f30457f09.png) ## 什么是滤波和mipmap?怎样使用? 正如在上面截图中看到的,纹理质量不是很好。这是因为在loadBMP\_custom函数中,有两行这样写道: ``` <pre class="calibre16">``` <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MAG_FILTER<span class="token1">,</span> GL_NEAREST<span class="token1">)</span><span class="token1">;</span> <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MIN_FILTER<span class="token1">,</span> GL_NEAREST<span class="token1">)</span><span class="token1">;</span> ``` ``` 这意味着在片断着色器中,texture()将直接提取位于(U,V)坐标的纹素(texel)。 ![](https://box.kancloud.cn/2015-11-02_5636f30466268.png) 有几种方法可以改善这一状况。 ### 线性滤波(Linear filtering) 若采用线性滤波。texture()会查看周围的纹素,然后根据UV坐标距离各纹素中心的距离来混合颜色。这就避免了前面看到的锯齿状边缘。 ![](https://box.kancloud.cn/2015-11-02_5636f30474ea3.png) 线性滤波可以显著改善纹理质量,应用的也很多。但若想获得更高质量的纹理,可以采用各向异性滤波,不过速度上有些慢。 ### 各向异性滤波(Anisotropic filtering) 这种方法逼近了真正片断中的纹素区块。例如下图中稍稍旋转了的纹理,各向异性滤波将沿蓝色矩形框的主方向,作一定数量的采样(即所谓的“各向异性层级”),计算出其内的颜色。 ![](https://box.kancloud.cn/2015-11-02_5636f3048247b.png) ### Mipmaps 线性滤波和各向异性滤波都存在一个共同的问题。那就是如果从远处观察纹理,只对4个纹素作混合显得不够。实际上,如果3D模型位于很远的地方,屏幕上只看得见一个片断(像素),那计算平均值得出最终颜色值时,图像所有的纹素都应该考虑在内。很显然,这样做没有考虑性能问题。相反,人们引入了mipmap这一概念: ![](https://box.kancloud.cn/2015-11-02_5636f3048d3f8.jpg) - 一开始,把图像缩小到原来的1/2,接着一次做下去,直到图像只有1×1大小(应该是图像所有纹素的平均值) - 绘制模型时,根据纹素大小选择合适的mipmap。 - 可以选用nearest、linear、anisotropic等任意一种滤波方式来对mipmap采样。 - 要想效果更好,可以对两个mipmap采样然后混合,得出结果。 好在这个比较简单,OpenGL都帮我们做好了,只需一个简单的调用: ``` <pre class="calibre16">``` <span class="token2">// When MAGnifying the image (no bigger mipmap available), use LINEAR filtering</span> <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MAG_FILTER<span class="token1">,</span> GL_LINEAR<span class="token1">)</span><span class="token1">;</span> <span class="token2">// When MINifying the image, use a LINEAR blend of two mipmaps, each filtered LINEARLY too</span> <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MIN_FILTER<span class="token1">,</span> GL_LINEAR_MIPMAP_LINEAR<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Generate mipmaps, by the way.</span> <span class="token3">glGenerateMipmap</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">)</span><span class="token1">;</span> ``` ``` ## 怎样利用GLFW加载纹理? 我们的loadBMP\_custom函数很棒,因为这是我们自己写的!不过用专门的库更好。GLFW就可以加载纹理(仅限TGA文件): ``` <pre class="calibre16">``` GLuint <span class="token3">loadTGA_glfw</span><span class="token1">(</span>const char <span class="token">*</span> imagepath<span class="token1">)</span><span class="token1">{</span> <span class="token2">// Create one OpenGL texture</span> GLuint textureID<span class="token1">;</span> <span class="token3">glGenTextures</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>textureID<span class="token1">)</span><span class="token1">;</span> <span class="token2">// "Bind" the newly created texture : all future texture functions will modify this texture</span> <span class="token3">glBindTexture</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> textureID<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Read the file, call glTexImage2D with the right parameters</span> <span class="token3">glfwLoadTexture2D</span><span class="token1">(</span>imagepath<span class="token1">,</span> <span class="token6">0</span><span class="token1">)</span><span class="token1">;</span> <span class="token2">// Nice trilinear filtering.</span> <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_WRAP_S<span class="token1">,</span> GL_REPEAT<span class="token1">)</span><span class="token1">;</span> <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_WRAP_T<span class="token1">,</span> GL_REPEAT<span class="token1">)</span><span class="token1">;</span> <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MAG_FILTER<span class="token1">,</span> GL_LINEAR<span class="token1">)</span><span class="token1">;</span> <span class="token3">glTexParameteri</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> GL_TEXTURE_MIN_FILTER<span class="token1">,</span> GL_LINEAR_MIPMAP_LINEAR<span class="token1">)</span><span class="token1">;</span> <span class="token3">glGenerateMipmap</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">)</span><span class="token1">;</span> <span class="token2">// Return the ID of the texture we just created</span> <span class="token4">return</span> textureID<span class="token1">;</span> <span class="token1">}</span> ``` ``` ## 压缩纹理 学到这儿,你可能会想怎样加载JPEG文件而不是TGA文件呢? 简单的说:别这么干。还有更好的选择。 ### 创建压缩纹理 - 下载[The Compressonator](http://developer.amd.com/tools-and-sdks/archive/legacy-cpu-gpu-tools/the-compressonator/),一款ATI工具 - 用它加载一个二次幂纹理 - 将其压缩成DXT1、DXT3或DXT5格式(这些格式之间的差别请参考[Wikipedia](http://en.wikipedia.org/wiki/S3_Texture_Compression)): ![](https://box.kancloud.cn/2015-11-02_5636f30499c2e.png) - 生成mipmap,这样就不用在运行时生成mipmap了。 - 导出为.DDS文件。 至此,图像已压缩为可被GPU直接使用的格式。在着色中随时调用texture()均可以实时解压。这一过程看似很慢,但由于它节省了很多内存空间,传输的数据量就少了。传输内存数据开销很大;纹理解压缩却几乎不耗时(有专门的硬件负责此事)。一般情况下,才用压缩纹理可使性能提升20%。 ### 使用压缩纹理 来看看怎样加载压缩纹理。这和加载BMP的代码很相似,只不过文件头的结构不一样: ``` <pre class="calibre16">``` GLuint <span class="token3">loadDDS</span><span class="token1">(</span>const char <span class="token">*</span> imagepath<span class="token1">)</span><span class="token1">{</span> unsigned char header<span class="token1">[</span><span class="token6">124</span><span class="token1">]</span><span class="token1">;</span> FILE <span class="token">*</span>fp<span class="token1">;</span> <span class="token2">/* try to open the file */</span> fp <span class="token">=</span> <span class="token3">fopen</span><span class="token1">(</span>imagepath<span class="token1">,</span> <span class="token5">"rb"</span><span class="token1">)</span><span class="token1">;</span> <span class="token4">if</span> <span class="token1">(</span>fp <span class="token">==</span> NULL<span class="token1">)</span> <span class="token4">return</span> <span class="token6">0</span><span class="token1">;</span> <span class="token2">/* verify the type of file */</span> char filecode<span class="token1">[</span><span class="token6">4</span><span class="token1">]</span><span class="token1">;</span> <span class="token3">fread</span><span class="token1">(</span>filecode<span class="token1">,</span> <span class="token6">1</span><span class="token1">,</span> <span class="token6">4</span><span class="token1">,</span> fp<span class="token1">)</span><span class="token1">;</span> <span class="token4">if</span> <span class="token1">(</span><span class="token3">strncmp</span><span class="token1">(</span>filecode<span class="token1">,</span> <span class="token5">"DDS "</span><span class="token1">,</span> <span class="token6">4</span><span class="token1">)</span> <span class="token">!=</span> <span class="token6">0</span><span class="token1">)</span> <span class="token1">{</span> <span class="token3">fclose</span><span class="token1">(</span>fp<span class="token1">)</span><span class="token1">;</span> <span class="token4">return</span> <span class="token6">0</span><span class="token1">;</span> <span class="token1">}</span> <span class="token2">/* get the surface desc */</span> <span class="token3">fread</span><span class="token1">(</span><span class="token">&</span>header<span class="token1">,</span> <span class="token6">124</span><span class="token1">,</span> <span class="token6">1</span><span class="token1">,</span> fp<span class="token1">)</span><span class="token1">;</span> unsigned int height <span class="token">=</span> <span class="token">*</span><span class="token1">(</span>unsigned int<span class="token">*</span><span class="token1">)</span><span class="token">&</span><span class="token1">(</span>header<span class="token1">[</span><span class="token6">8</span> <span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> unsigned int width <span class="token">=</span> <span class="token">*</span><span class="token1">(</span>unsigned int<span class="token">*</span><span class="token1">)</span><span class="token">&</span><span class="token1">(</span>header<span class="token1">[</span><span class="token6">12</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> unsigned int linearSize <span class="token">=</span> <span class="token">*</span><span class="token1">(</span>unsigned int<span class="token">*</span><span class="token1">)</span><span class="token">&</span><span class="token1">(</span>header<span class="token1">[</span><span class="token6">16</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> unsigned int mipMapCount <span class="token">=</span> <span class="token">*</span><span class="token1">(</span>unsigned int<span class="token">*</span><span class="token1">)</span><span class="token">&</span><span class="token1">(</span>header<span class="token1">[</span><span class="token6">24</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> unsigned int fourCC <span class="token">=</span> <span class="token">*</span><span class="token1">(</span>unsigned int<span class="token">*</span><span class="token1">)</span><span class="token">&</span><span class="token1">(</span>header<span class="token1">[</span><span class="token6">80</span><span class="token1">]</span><span class="token1">)</span><span class="token1">;</span> ``` ``` 文件头之后是真正的数据:紧接着是mipmap层级。可以一次性批量地读取: ``` <pre class="calibre16">``` unsigned char <span class="token">*</span> buffer<span class="token1">;</span> unsigned int bufsize<span class="token1">;</span> <span class="token2">/* how big is it going to be including all mipmaps? */</span> bufsize <span class="token">=</span> mipMapCount <span class="token">></span> <span class="token6">1</span> <span class="token">?</span> linearSize <span class="token">*</span> <span class="token6">2</span> <span class="token1">:</span> linearSize<span class="token1">;</span> buffer <span class="token">=</span> <span class="token1">(</span>unsigned char<span class="token">*</span><span class="token1">)</span><span class="token3">malloc</span><span class="token1">(</span>bufsize <span class="token">*</span> <span class="token3">sizeof</span><span class="token1">(</span>unsigned char<span class="token1">)</span><span class="token1">)</span><span class="token1">;</span> <span class="token3">fread</span><span class="token1">(</span>buffer<span class="token1">,</span> <span class="token6">1</span><span class="token1">,</span> bufsize<span class="token1">,</span> fp<span class="token1">)</span><span class="token1">;</span> <span class="token2">/* close the file pointer */</span> <span class="token3">fclose</span><span class="token1">(</span>fp<span class="token1">)</span><span class="token1">;</span> ``` ``` 这里要处理三种格式:DXT1、DXT3和DXT5。我们得把“fourCC”标识转换成OpenGL能识别的值。 ``` <pre class="calibre16">``` unsigned int components <span class="token">=</span> <span class="token1">(</span>fourCC <span class="token">==</span> FOURCC_DXT1<span class="token1">)</span> <span class="token">?</span> <span class="token6">3</span> <span class="token1">:</span> <span class="token6">4</span><span class="token1">;</span> unsigned int format<span class="token1">;</span> <span class="token3">switch</span><span class="token1">(</span>fourCC<span class="token1">)</span> <span class="token1">{</span> case FOURCC_DXT1<span class="token1">:</span> format <span class="token">=</span> GL_COMPRESSED_RGBA_S3TC_DXT1_EXT<span class="token1">;</span> <span class="token4">break</span><span class="token1">;</span> case FOURCC_DXT3<span class="token1">:</span> format <span class="token">=</span> GL_COMPRESSED_RGBA_S3TC_DXT3_EXT<span class="token1">;</span> <span class="token4">break</span><span class="token1">;</span> case FOURCC_DXT5<span class="token1">:</span> format <span class="token">=</span> GL_COMPRESSED_RGBA_S3TC_DXT5_EXT<span class="token1">;</span> <span class="token4">break</span><span class="token1">;</span> default<span class="token1">:</span> <span class="token3">free</span><span class="token1">(</span>buffer<span class="token1">)</span><span class="token1">;</span> <span class="token4">return</span> <span class="token6">0</span><span class="token1">;</span> <span class="token1">}</span> ``` ``` 像往常一样创建纹理: ``` <pre class="calibre16">``` <span class="token2">// Create one OpenGL texture</span> GLuint textureID<span class="token1">;</span> <span class="token3">glGenTextures</span><span class="token1">(</span><span class="token6">1</span><span class="token1">,</span> <span class="token">&</span>textureID<span class="token1">)</span><span class="token1">;</span> <span class="token2">// "Bind" the newly created texture : all future texture functions will modify this texture</span> <span class="token3">glBindTexture</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> textureID<span class="token1">)</span><span class="token1">;</span> ``` ``` 现在只需逐个填充mipmap: ``` <pre class="calibre16">``` unsigned int blockSize <span class="token">=</span> <span class="token1">(</span>format <span class="token">==</span> GL_COMPRESSED_RGBA_S3TC_DXT1_EXT<span class="token1">)</span> <span class="token">?</span> <span class="token6">8</span> <span class="token1">:</span> <span class="token6">16</span><span class="token1">;</span> unsigned int offset <span class="token">=</span> <span class="token6">0</span><span class="token1">;</span> <span class="token2">/* load the mipmaps */</span> <span class="token4">for</span> <span class="token1">(</span>unsigned int level <span class="token">=</span> <span class="token6">0</span><span class="token1">;</span> level <span class="token"><</span> mipMapCount <span class="token">&&</span> <span class="token1">(</span>width <span class="token">||</span> height<span class="token1">)</span><span class="token1">;</span> <span class="token">++</span>level<span class="token1">)</span> <span class="token1">{</span> unsigned int size <span class="token">=</span> <span class="token1">(</span><span class="token1">(</span>width<span class="token">+</span><span class="token6">3</span><span class="token1">)</span><span class="token">/</span><span class="token6">4</span><span class="token1">)</span><span class="token">*</span><span class="token1">(</span><span class="token1">(</span>height<span class="token">+</span><span class="token6">3</span><span class="token1">)</span><span class="token">/</span><span class="token6">4</span><span class="token1">)</span><span class="token">*</span>blockSize<span class="token1">;</span> <span class="token3">glCompressedTexImage2D</span><span class="token1">(</span>GL_TEXTURE_2D<span class="token1">,</span> level<span class="token1">,</span> format<span class="token1">,</span> width<span class="token1">,</span> height<span class="token1">,</span> <span class="token6">0</span><span class="token1">,</span> size<span class="token1">,</span> buffer <span class="token">+</span> offset<span class="token1">)</span><span class="token1">;</span> offset <span class="token">+</span><span class="token">=</span> size<span class="token1">;</span> width <span class="token">/</span><span class="token">=</span> <span class="token6">2</span><span class="token1">;</span> height <span class="token">/</span><span class="token">=</span> <span class="token6">2</span><span class="token1">;</span> <span class="token1">}</span> <span class="token3">free</span><span class="token1">(</span>buffer<span class="token1">)</span><span class="token1">;</span> <span class="token4">return</span> textureID<span class="token1">;</span> ``` ``` ### 反转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](http://www.oldunreal.com/editing/s3tc/ARB_texture_compression.pdf) , Sébastien Domine, NVIDIA