LearnOpenGL(三)——纹理(Texture)

5 篇文章 0 订阅
4 篇文章 2 订阅
本文详细介绍了OpenGL中如何实现纹理映射,包括指定纹理坐标、链接纹理属性、加载和创建纹理、编写顶点和片段着色器以及绑定纹理采样器。通过实例展示了如何使用stb_image库加载图片并将其应用到纹理,最终实现图像的渲染。内容涵盖纹理坐标设置、纹理属性链接、纹理加载和创建等关键步骤。
摘要由CSDN通过智能技术生成

通过前一章的绘制三角形,我们可以看到,通过多顶点添加颜色,可以绘制出丰富的图像,但如果要绘制更真实复杂的图像,那么就需要足够多的顶点,并且指定足够多的颜色,这无疑会增加额外的开销。这时候,引入了纹理这一概念。纹理就是一张2D(3D)的图片,我们可以为图片增加更多的细节处理,将处理好的图片加载进来就可以的到想要的图像。

一、指定三角形的纹理坐标

为了纹理映射到三角形中,我们需要指定三角形的每个顶点对应纹理的什么位置取样。纹理也有一个纹理坐标,纹理坐标的x轴和y轴的取值范围是[0,1],即纹理的左下角是(0,0),右上角是(1,1),指定三角形的左下角对纹理坐标的左下角(0,0)取样,三角形的右下角对纹理坐标的右下角(1,0)取样,三角形的顶点对纹理坐标的中上(0.5,1)取样,三角形顶点数据如下:

float vertices[] =
{
    //顶点位置            颜色            纹理坐标
    -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.5f, 0.0f,  0.0f, 0.0f, 1.0f, 0.5f, 1.0f
};

二、链接纹理属性

链接纹理属性和链接顶点、颜色属性方法相同,但需要重新设置步长和起始位置偏移量,同时指定纹理位置为2

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

三、加载和创建纹理

我们设置完带纹理坐标的纹理属性后,需要把纹理加载进来,我们可以通过写一个图片加载器加载纹理,但太过于麻烦,stb_image是一个比较流行的图像加载库,它可以支持各种格式的图片,我们使用这个库来加载图片,std_image.h可以从这里下载,在引用这个库时,需要添加这个宏定义,让其只包含相关函数定义的源码

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

接下来就是加载图片

int width, height, nchannels;
unsigned char* data = stbi_load("./image/wall.jpg", &width, &height, &nchannels, 0);

stbi_load();是stb_imag库提供的图片加载函数,第一个参数是图片资源路径,第二、三个参数是图片的宽高,第三个参数是图片的通道数(如果是rgb的话,就是三个通道)

创建并绑定纹理对象

glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);

设置纹理环绕和过滤方式

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexParameteri();设置指定轴的纹理环绕和过滤方式,环绕和过滤后续文章中会描述。

第一个参数,指定纹理目标

第二个参数,指定环绕轴过滤对象,GL_TEXTURE_WRAP_S为s(x)轴,GL_TEXTURE_WRAP_T为t(y)轴,GL_TEXTURE_MIN_FILTER表示缩放是的过滤方式,GL_TEXTURE_MAG_FILTER表示放大时的过滤方式

第三个参数是环绕方式

生成纹理

if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    //生成多级渐远纹理
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    std::cout << "Load Texture Failed!" << std::endl;
    return;
}

glTexImage2D();将图片加载到纹理

第一个参数是目标纹理

第二个参数是设置多级渐远纹理为基本等级(后续描述)

第三个参数告诉OpenGL将图片加载为RGB的方式

第四和第五个参数是纹理的宽高

第七个参数是原图的格式为RGB格式

第八个参数是原图的数据类型

第九个参数是图片数据

glGenerateMipmap();生成多级渐远的纹理

纹理生成后,不要忘记释放掉图片资源

 stbi_image_free(data);

四、编写顶点和片段着色器

顶点着色器

#version 330 core
layout (location = 0) in vec3 apos;
layout (location = 1) in vec3 acolor;
layout (location = 2) in vec2 atexcoord;
out vec3 ourcolor;
out vec2 texcoord;
void main()
{
   gl_Position = vec4(apos.x, apos.y, apos.z, 1.0);
   ourcolor = acolor;
   texcoord = atexcoord;
};

atexcoord 是在设置顶点属性时,传进来的纹理坐标,location为2

texcoord = atexcoord;将纹理坐标输出到片段着色器中

片段着色器

#version 330 core
out vec4 FragColor;
//uniform vec4 ourcolor;
in vec3 ourcolor;
in vec2 texcoord;

uniform sampler2D ourTexture;
void main()
{
    FragColor = texture(ourTexture, texcoord);
};

uniform sampler2D ourTexture;定义纹理采样器,从外界输入进来的纹理图片,需要在外界绑定纹理图片

使用texture函数对纹理进行采样,第一个参数是纹理采样器,第二个是纹理坐标

五、绑定纹理采样器

最后一步就是绑定问你采样器,这样,图片就绘制出来了

glBindTexture(GL_TEXTURE_2D, mTexture);

绘制的结果如图

源码如下

glsl.h

#pragma once
#include "Glad\glad.h"
#include "GLFW\glfw3.h"
#include <iostream>
#include <fstream>
#include <sstream>

class CShader
{
public:
    //创建编译shader
    CShader(const char* vShaderPath, const char* fShaderPath);
    //激活着色程序
    void useProgram();
    //绘制图形
    void Draw();
    //处理顶点数据
    void dealVetPoint();
    //删除着色器和着色程序
    void deleteShaderAndProgram();
    //加载纹理
    void loadTexture();

    //uniform工具函数
    void setUniform4f();

    ~CShader();
public:
    unsigned int mProgramID;    //程序ID
    unsigned int VAO;           //顶点数组对象
    unsigned int VBO;           //顶点缓冲对象
    unsigned int EBO;           //元素缓冲对象
    unsigned int mTexture;      //纹理对象
};

 glsl.cpp

#include "glsl.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

CShader::CShader(const char* vShaderPath, const char* fShaderPath)
{
    //从文件中读取着色器
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;
    vShaderFile.open(vShaderPath);
    if (vShaderFile.fail())
    {
        std::cout << "Open vShader Failed!" << std::endl;
        return;
    }
    fShaderFile.open(fShaderPath);
    if (fShaderFile.fail())
    {
        std::cout << "Open fShader Failed!" << std::endl;
        return;
    }

    //将文件中的缓冲内容读取到数据流中
    std::stringstream vShaderStream, fShaderStream;
    vShaderStream << vShaderFile.rdbuf();
    fShaderStream << fShaderFile.rdbuf();

    vShaderFile.close();
    fShaderFile.close();

    //将数据流转换到string中
    std::string vertexCode, fragmentCode;
    vertexCode = vShaderStream.str();
    fragmentCode = fShaderStream.str();

    const char* vShaderCode = vertexCode.c_str();
    const char* fShaderCode = fragmentCode.c_str();

    //创建编译着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vShaderCode, NULL);
    glCompileShader(vertexShader);
    int success;
    char logInfo[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, logInfo);
        std::cout << "Compile vertShader Error" << logInfo << std::endl;
    }

    unsigned int fragmenShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmenShader, 1, &fShaderCode, NULL);
    glCompileShader(fragmenShader);
    glGetShaderiv(fragmenShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmenShader, 512, NULL, logInfo);
        std::cout << "Compile FragShader Error" << logInfo << std::endl;
    }

    //创建链接着色器程序
    mProgramID = glCreateProgram();
    glAttachShader(mProgramID, vertexShader);
    glAttachShader(mProgramID, fragmenShader);
    glLinkProgram(mProgramID);
    glGetProgramiv(mProgramID, GL_LINK_STATUS, &success);
    if (!success)
    {
        glGetProgramInfoLog(mProgramID, 512, NULL, logInfo);
        std::cout << "Link Shader Error" << logInfo << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmenShader);
}

void CShader::loadTexture()
{
    int width, height, nchannels;
    //加载图片
    unsigned char* data = stbi_load("./image/wall.jpg", &width, &height, &nchannels, 0);
    //创建并绑定纹理对象
    glGenTextures(1, &mTexture);
    glBindTexture(GL_TEXTURE_2D, mTexture);
    //设置纹理环绕和过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    //生成纹理
    if (data)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        //生成多级渐远纹理
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Load Texture Failed!" << std::endl;
        return;
    }
    
    stbi_image_free(data);
}

void CShader::useProgram()
{
    glUseProgram(mProgramID);
}

void CShader::Draw()
{
    glBindTexture(GL_TEXTURE_2D, mTexture);
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);
}

void CShader::dealVetPoint()
{
    //顶点数据处理
    //定义顶点数组
    float vertices[] =
    {
        //顶点位置            颜色            纹理坐标
        -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
        0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.5f, 0.0f,  0.0f, 0.0f, 1.0f, 0.5f, 1.0f
    };

    //创建并绑定顶点数组对象,用于存储顶点数据的配置
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);
    //生成一个顶点缓冲对象VBO
    glGenBuffers(1, &VBO);
    //将生成的VBO绑定到目标上
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //将顶点数据复制到缓冲内存中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    //链接顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    //启用顶点属性
    glEnableVertexAttribArray(0);
    //链接颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    //启用颜色属性
    glEnableVertexAttribArray(1);
    //链接纹理属性
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    //启用纹理属性
    glEnableVertexAttribArray(2);
    //解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}

void CShader::setUniform4f()
{
    int uniforLoc = glGetUniformLocation(mProgramID, "ourcolor");
    glUniform4f(uniforLoc, 1.0f, 0.0f, 0.0f, 1.0f);
}

void CShader::deleteShaderAndProgram()
{
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(mProgramID);
}

CShader::~CShader()
{

}

int main(int argc, char *argv[])
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,  3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_ANY_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow *window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
    if (NULL == window)
    {
        std::cout << "Create Windows Failed" << std::endl;
        glfwTerminate();
        return -1;
    }
    
    glfwMakeContextCurrent(window);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Load Failed" << std::endl;
        return -1;
    }
  
    CShader shader("./shader/shader.vs", "./shader/shader.fs");
    shader.dealVetPoint();
    shader.loadTexture();
       
    glViewport(0, 0, 800, 600);

    while (!glfwWindowShouldClose(window))
    {
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        shader.useProgram();
        //shader.setUniform4f();
        shader.Draw();
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    shader.deleteShaderAndProgram();



    //查看支持多少个attribute
    int nrAttributes;
    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
    std::cout << "Max Num is: " << nrAttributes << std::endl;

    glfwTerminate();
    system("pause");
    return 0;
}

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值