阅读《计算机图形学编程(使用OpenGL和C++)》8 - 纹理贴图

纹理贴图就是将图片贴到模型上,让模型看起来更真实。

纹理贴图非常重要,因此硬件也为它提供了支持,使得它具备了实现实时的照片级真实感的超高性能。纹理单元是专为纹理设计的硬件组件,现代显卡通常带有数个纹理单元。

纹理图像就是我们准备贴图的图片,需要一个纹理对象来保存。

创建纹理对象我们在以下函数中实现:

// 加载纹理图像
GLuint Utils::loadTexture(const char * texImagePath)
{
    GLuint textureID;
    textureID = SOIL_load_OGL_texture(texImagePath,
        SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_INVERT_Y);
    if (textureID == 0)
    {
        std::cout << "could not find texture file" << texImagePath << std::endl;
    }
    return textureID;
}

首先需要声明一个GLuint类型的变量,用于保存OpenGL对象的整型ID。接下来,我们调用SOIL_load_OGL_texture()来实际生成纹理对象,接受图像文件名作为其参数之一。我们会经常使用这个函数,所以我们将它添加到Utils.cpp实用工具类中。

通常我们将纹理加载到OpenGL应用程序的步骤是:

(a)使用SOIL2实例化OpenGL纹理对象并从图像文件中读入数据;

(b)调用glBindTexture()以使新创建的纹理对象处于激活状态;

(c)使用glTexParameter()函数调整纹理设置。最终获得的结果就是现在可用的OpenGL纹理对象的整型ID。

知道如何加载纹理图像后,我们还要为模型中的每个顶点指定纹理坐标来将纹理应用到对象的渲染表面。

纹理坐标是对纹理图像(通常是2D)中的像素的引用。纹理图像中的像素被称为纹素(Texel),以便将它们与在屏幕上呈现的像素区分开。纹理坐标用于将3D模型上的点映射到纹理中的位置。除了将它定位在3D空间中的(x,y,z)坐标之外,模型表面上的每个点还具有纹理坐标(s,t),用来指定纹理图像中的哪个纹素为它提供颜色。这样,物体的表面被按照纹理图像“涂画”。纹理在对象表面上的朝向由分配给对象顶点的纹理坐标来确定。

为了确保渲染模型中的每个像素都使用纹理图像中的适当纹素进行绘制,纹理坐标也需要被放入顶点属性中,以便它们也由光栅着色器进行插值。以这种方式,纹理图像与模型顶点一起被插值或者填充。

让我们回去渲染我们的金字塔,只是这次用砖的图像添加纹理。我们需要指定:

(a)引用纹理图像的整型ID;

(b)模型顶点的纹理坐标;

(c)用于保存纹理坐标的缓冲区;

(d)顶点属性,以便顶点着色器可以接收并通过管线转发纹理坐标;

(e)显卡上用于保存纹理对象的纹理单元;

(f)我们将很快看到的用于访问GLSL中纹理单元的统一采样器变量

使用如下纹理图像

 为了最大限度地提高性能,我们希望在硬件中执行纹理处理。这意味着我们的片段着色器需要一种访问我们在C++/OpenGL应用程序中创建的纹理对象的方法。它的实现机制是通过一个叫作统一采样器变量的特殊GLSL工具。这是一个变量,用于指示显卡上的纹理单元,从加载的纹理对象中提取或“采样”哪个纹素。

layout (binding = 0) uniform sampler2D samp;

我们声明的变量名字叫作“samp”。声明的“layout (binding=0)”部分指定此采样器与纹理单元0相关联。

程序是之前的太阳系代码稍作加纹理修改

复制代码

 1 ...
 2 #define numVAOs 1
 3 #define numVBOs 3
 4 ...
 5 // 屏幕改变大小的回调函数,重绘透视矩阵
 6 void window_reshape_callback(GLFWwindow* window, int newWidth, int newHeight) {
 7     aspect = (float)newWidth / (float)newHeight; // 回调提供的新的宽度、高度
 8     glViewport(0, 0, newWidth, newHeight); // 设置和帧缓冲区相关的屏幕区域
 9     pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 1.0472 radians = 60 degrees
10 
11 }
12 
13 void setupVertices(void)
14 {    
15     ...
16     // 金字塔的纹理坐标
17     float pyrTexCoords[36] = {
18         0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,     0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f, // 前侧面、右侧面
19         0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f,     0.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f, // 后侧面、左侧面
20         0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,     1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f  // 底面的两个三角形
21     };
22 
23     ...
24 
25     glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
26     glBufferData(GL_ARRAY_BUFFER, sizeof(pyrTexCoords), pyrTexCoords, GL_STATIC_DRAW); // 将纹理坐标加载到VBO中
27 }
28 
29 void init(GLFWwindow* window) {
30     ...
31     // 构建透视矩阵,从display函数移到init函数后只创建一次,
32     glfwGetFramebufferSize(window, &width, &height);
33     aspect = (float)width / (float)height;
34     pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f); // 1.0472 radians = 60 degrees
35 
36     brickTexture = Utils::loadTexture("F:/OPENGL/dev/OpenGL/OpenGL/res/brick2.jpg");
37 }
38 
39 void display(GLFWwindow* window, double currentTime)
40 {
41     glClear(GL_DEPTH_BUFFER_BIT);
42     glClear(GL_COLOR_BUFFER_BIT);
43     glUseProgram(renderingProgram);
44 
45     //glEnable(GL_CULL_FACE); // 要求识别并“剔除”背向的三角形
46 
47 ...
48     // 太阳旋转
49     glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvStack.top())); 
50     glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
51     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
52     glEnableVertexAttribArray(0);
53 
54     glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
55     glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
56     glEnableVertexAttribArray(1);
57 
58     glActiveTexture(GL_TEXTURE0);
59     glBindTexture(GL_TEXTURE_2D, brickTexture);
60 
61     glEnable(GL_DEPTH_TEST);
62     glDepthFunc(GL_LEQUAL);
63 
64     glDrawArrays(GL_TRIANGLES, 0, 18);    // 绘制太阳
65     ...
66 }
67 
68 
69 顶点着色器
70 #version 460
71 layout (location = 0) in vec3 position;
72 layout (location = 1) in vec2 texCoord;
73 out vec2 tc;  // 纹理坐标输出到光栅着色器用于插值
74 uniform mat4 proj_matrix;
75 uniform mat4 mv_matrix;
76 layout (binding = 0) uniform sampler2D samp; // 顶点,着色器中未使用
77 
78 void main(void)
79 { 
80    gl_Position = proj_matrix * mv_matrix * vec4(position, 1.0);
81    tc = texCoord;
82 }
83 
84 片段着色器
85 #version 460
86 in vec2 tc; // 输入插值过的材质坐标
87 out vec4 color;
88 uniform mat4 mv_matrix;
89 uniform mat4 proj_matrix;
90 layout (binding = 0) uniform sampler2D samp;
91 
92 void main(void)
93 { 
94 color = texture(samp, tc);
95 }
 
 

这里还把透视矩阵给移动位置了,不要在display中次次渲染,节省性能。但是如果窗口变化了大小需要重新计算透视矩阵,为此在init函数前调用一个回调函数即可。

main.cpp

int main(void)
{
    ...
    glfwSetWindowSizeCallback(window, window_reshape_callback); // GLFW窗口,GLFW在调整窗口大小时调用的函数的名称
    init(window);
    ...
}

效果如下:

 由于是给金字塔换皮肤,纹理坐标也是对应设置,图中的金字塔换了完全的新皮肤,而旁边的两个立方体就缺东少西了。

猜你喜欢

转载自blog.csdn.net/ttod/article/details/135346299