• OpenGL入门(四)之纹理Texture


    本系列文章为Learn OpenGL个人学习总结!
    OpenGL入门(一)之认识OpenGL和创建Window
    OpenGL入门(二)之渲染管线pipeline,VAO、VBO和EBO
    OpenGL入门(三)之着色器Shader
    OpenGL入门(四)之纹理Texture
    OpenGL入门(五)之Matrix矩阵操作和坐标系统
    OpenGL进阶(一)之帧缓冲FrameBuffer
    OpenGL进阶(二)之像素缓冲PixelBuffer

    纹理

    纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节!我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点,来减小开销!

    为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(采集片段颜色)。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。

    纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角。

    一个纹理坐标:

    float texCoords[] = {
        0.0f, 0.0f, // 左下角
        1.0f, 0.0f, // 右下角
        0.5f, 1.0f // 上中
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    纹理环绕方式

    当纹理坐标超出默认范围时,可以设置环绕方式来展示不同的视觉效果输出!默认为GL_REPEAT,还有GL_MIRRORED_REPEATGL_CLAMP_TO_EDGEGL_CLAMP_TO_BORDER

    前边提到过纹理坐标的分量可以通过STPQ访问:

    // set the texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    
    • 1
    • 2
    • 3

    纹理过滤

    纹理像素(Texture Pixel):组成一张图片的无数个像素点
    纹理坐标:设置的顶点数组

    OpenGL以这个顶点的纹理坐标数据去查找纹理图像上的像素,然后进行采样提取纹理像素的颜色。那么就会有一个问题,在对应查找像素的时候,用什么方式去拿像素颜色,OpenGL提供了纹理过滤来帮助我们选择,这里有两个重要的选项!

    • GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。
    • GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。

    当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项:

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); //缩小
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //放大
    
    • 1
    • 2

    多级渐变纹理

    多级渐变纹理主要是用来解决物体远近,纹理大小,分辨率不同的问题!距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个,且节省内存!

    注意: 多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理。

    另外:OpenGL提供glGenerateMipmap()函数,在创建完一个纹理后调用它OpenGL就会为纹理图像创建一系列多级渐远纹理!

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    
    ...//创建纹理
    glGenerateMipmap(GL_TEXTURE_2D);
    
    • 1
    • 2
    • 3
    • 4

    加载和创建纹理

    这里使用stb_image.h库来加载图片!
    https://github.com/nothings/stb

    使用非常简单,在我们的cpp文件中添加:

    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"
    
    • 1
    • 2

    创建纹理:

    int width, height, nrChannels;
    //这里会拿到图像的宽高和颜色通道个数
    unsigned char *data = stbi_load("../dependency/stb/container.jpg", &width, &height, &nrChannels, 0);
    
    //生成纹理
    unsigned int texture1;
    glGenTextures(1, &texture1);
    glBindTexture(GL_TEXTURE_2D, texture1);//绑定
    
    ... //设置环绕和过滤
    
    /**
     * @brief 生成纹理
     * 第1个参数:指定纹理target
     * 第2个参数:指定多级渐远纹理的级别  0:基本级别
     * 第3个参数:纹理存储格式。 这里图像只有RGB
     * 第4,5个参数:纹理的宽高
     * 第6个参数:总设置为0(历史问题)
     * 第7,8个参数:源图像的格式和数据类型
     * 第9个参数:图像数据
    */
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
    
    ...
    stbi_image_free(data);//释放图像内存
    
    • 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

    应用纹理

    这里我们绘制一个矩形,并把图片纹理贴在矩形上

        //绘制矩形+纹理
        float vertices[] = {
        //     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
         0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
         0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
        };
    
    
        const char *vertexShaderSource = "#version 330 core\n"
        "layout (location = 0) in vec3 aPos;\n"
        "layout (location = 1) in vec3 aColor;\n"
        "layout (location = 2) in vec2 aTexCoord;\n"
        
        "out vec3 vertexColor;\n"
        "out vec2 texCoord;\n"
        "void main()\n"
        "{\n"
        "   gl_Position = vec4(aPos, 1.0);\n"
        "   vertexColor = aColor;\n"
        "   texCoord = aTexCoord;\n"
        "}\0";
    
        const char *fragmentShaderSource = "#version 330 core\n"
        "in vec3 vertexColor;\n" //接收顶点着色器中的输入变量(名称、类型相同) 
        "in vec2 texCoord;\n" //接收纹理(名称、类型相同)
        "out vec4 color;\n"
        "uniform sampler2D texture1;\n" //声明一个纹理采样器
        "void main()\n"
        "{\n"
        "   color = texture(texture1, texCoord) * vec4(vertexColor, 1.0);\n" //将输出颜色设置为纹理  第一个参数纹理采样器,第二个参数纹理坐标
        "}\0";
    
    ...
    
    • 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

    GLSL内建的texture()函数,可以得到一个纹理颜色,第一个参数纹理采样器,第二个参数纹理坐标。

    这样我们就能绘制出一个正确的纹理图片了!

    需要注意的点:
    在前边我们了解了uniform这个变量,这里在片段着色器中声明了一个采样器,但是在代码中却没有给它赋值,目前看工作正常,能够绘制出图片!这是因为OpenGL中有默认的纹理单元0,且这个纹理单元默认激活!

    纹理单元

    当我们需要绘制多个纹理时,就要使用纹理单元,把纹理单元赋值给采样器,就能够和纹理一一对应起来!

    glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
    glBindTexture(GL_TEXTURE_2D, texture);
    
    • 1
    • 2

    激活一个纹理单元之后,调用glBindTexture,就会把纹理绑定到当前已激活的纹理单元!

    OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

    修改一下我们的片段着色器:

    const char *fragmentShaderSource = "#version 330 core\n"
        "in vec3 vertexColor;\n" //接收顶点着色器中的输入变量(名称、类型相同) 
        "in vec2 texCoord;\n" //接收纹理(名称、类型相同)
        "out vec4 color;\n"
        "uniform sampler2D texture1;\n" //声明一个纹理采样器
        "uniform sampler2D texture2;\n" 
        "void main()\n"
        "{\n"
        "   color = mix(texture(texture1, texCoord), texture(texture2, texCoord), 0.2) * vec4(vertexColor, 1.0);\n" //将输出颜色设置为纹理  第一个参数纹理采样器,第二个参数纹理坐标
        "}\0";
    
    ...
    //有两个纹理时,就需要设置纹理单元
    glUseProgram(shaderProgram);
    glUniform1i(glGetUniformLocation(shaderProgram, "texture1"), 0);//指定采样器属于哪个纹理单元
    glUniform1i(glGetUniformLocation(shaderProgram, "texture2"), 1);
    
    ...
    //渲染
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture1);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texture2);
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。

    另外一个需要注意的问题:纹理上下颠倒了!这是因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。
    stb_image.h能够在图像加载时帮助我们翻转y轴,在加载前调用:

    stbi_set_flip_vertically_on_load(true);
    
    • 1

    这时我们将得到两个纹理叠加的效果!

  • 相关阅读:
    java中compareTo比较两个日期大小
    【C++】不能两次杀死同一条鱼 - 浅述shared_ptr智能指针的使用方法
    Flutter折腾学习成长记(25)
    golang promethus consul 服务发现
    【Datawhale学习笔记】从大模型到AgentScope
    解决selenium使用chrome下载文件(如pdf)时,反而打开浏览器的预览界面
    【蓝牙协议】简介:蓝牙芯片、蓝牙协议架构
    VUE-----生命周期
    【使用 BERT 的问答系统】第 7 章 :BERT 模型的未来
    git 命令总结
  • 原文地址:https://blog.csdn.net/aiynmimi/article/details/126668472