OpenGL:纹理(中文注释版)

Textures是LearnOpenGL CN教程中的经典项目,里面包含了完整的OpenGL渲染流程,着色器的编写,着色器类的应用,纹理的加载等一系列经典的应用场景,是OpenGL入门过程中必须掌握的。最近在复习之前学习的OpenGL时,发现有些基本的概念比较模糊,于是对照着重新写了一遍,把每个比较重要的地方都加上了中文注释,方便以后自己查阅。此代码基于Linux下的Clion编写,项目目录如下:

  • Shaders文件夹:包含顶点着色器和片段着色器。着色器的后缀可以任意定义,有辨识度即可,例如可以将着色器命名为shader.vert,shader.frag;
  • Textures文件夹:包含需要加载的两张纹理图片。注意,png后缀的图片在加载时需要将参数由GL_RGB改为GL_RGBA,因为png图片包含alpha通道,是可以显示透明效果的,普通的jpg图片为rgb色彩空间,无透明显示功能;
  • CmakeLists.txt:clion自动生成的cmake文件,需要手动更改一下参数;
  • glad.c:glad库文件,编写OpenGL必须要加上;
  • main.cpp:主要的代码文件;
  • shader_s.cpp:着色器类,可直接用着色器类去编译着色器文件,更加快捷方便;
  • stb_image.h / stb_image.cpp:一个非常流行的图像加载库,这里用来加载纹理图片(stb_image.h为下载的库文件,stb_image.cpp需要自己编写)。

 

源代码网址:https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/4.2.textures_combined/textures_combined.cpp

 

以下部分为添加了注释的代码段,仅供参考。

CmakeLists.txt 

cmake_minimum_required(VERSION 3.15)
project(Textures)
set(CMAKE_CXX_STANDARD 14)
set(SOURCE_FILES main.cpp glad.c  stb_image.cpp)
add_executable(Textures ${SOURCE_FILES})
target_link_libraries(Textures glfw3 GL m Xrandr Xi X11 Xxf86vm pthread dl Xinerama Xcursor)

 

stb_image.cpp 

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

 

Shader.fs 

#version 330 core
out vec4 FragColor;    // 表示最终的输出颜色

in vec3 ourColor;   // 从顶点着色器传来的输入变量(名称相同、类型相同)
in vec2 TexCoord;

// Sampler(采样器), GLSL中供纹理对象使用的内建数据类型, 以纹理类型作为后缀, 比如sampler1D、sampler3D,
uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
	// 在两个纹理之间线性插值(80%的箱子,20%的笑脸)
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

 

Shader.vs 

#version 330 core
layout (location = 0) in vec3 aPos;        // 位置变量的属性位置值为0
layout (location = 1) in vec3 aColor;      // 颜色变量的属性位置值为1
layout (location = 2) in vec2 aTexCoord;   // 纹理变量的属性位置值为2

out vec3 ourColor;  // 向片段着色器输出一个颜色
out vec2 TexCoord;  // 向片段着色器输出一个纹理

void main()
{
	gl_Position = vec4(aPos, 1.0);  // 归一化的裁剪空间坐标(xyz各个维度的范围为-1到1), 第四个分量w是1.由于透视投影,该分量假定另一个值大于1。
	ourColor = aColor;    // 将ourColor设置为我们从顶点数据那里得到的输入颜色
	TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}

 

main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "stb_image.h"
#include "shader_s.h"

#include <iostream>

void processInput(GLFWwindow *window);  // 处理键盘/鼠标的输入

// 设置窗口大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main() {
    // glfw的初始化和配置
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);  // 设置主版本
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);  // 设置次版本
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 使用OpenGL核心模式

// 如果是苹果电脑,需要加上这个
#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif

    // 创建OpenGL窗口
    // -------------------------------------------------------------------------------------------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Textures", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);  // 将窗口的上下文设置为当前线程的主上下文

    // 初始化glad,加载OpenGL函数指针地址的函数
    // -----------------------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    // 指定当前视口尺寸(前两个参数为左下角位置,后两个参数是渲染窗口宽、高)
    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);

    // 创建和编译着色器
    Shader ourShader("../Shaders/shader.vs", "../Shaders/shader.fs");

    // 设置VAO,VBO,EBO,顶点属性等
    // -------------------------------------------------------------------------
    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  // 左上
    };
    unsigned int indices[] = {  // EBO的索引
            0, 1, 3, // 第一个三角形
            1, 2, 3  // 第二个三角形
    };

    // 生成并绑定VAO,VBO,EBO(绑定顺序为VAO,VBO,EBO)
    GLuint VAO;
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    GLuint VBO;
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    GLuint EBO;
    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

    // 将顶点数据和索引数据绑定至当前默认的缓冲中
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices ,GL_STATIC_DRAW);

    // 顶点属性, 大小3, 步长32(4 * 8), float长度为4, 向右移动8个float; 位置顶点属性在前, 故偏移量为0
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // 颜色属性, 大小3, 步长32; 因位置属性值为3, 故偏移量为3
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // 纹理属性, 大小2, 步长32; 因颜色属性值为3, 故偏移量为3 + 3 = 6
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    // 创建并加载纹理
    // ------------------------
    // 纹理1
    GLuint texture1;
    glGenTextures(1, &texture1);
    glBindTexture(GL_TEXTURE_2D, texture1);
    // 设置纹理环绕方式为GL_REPEAT
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // 设置纹理过滤方式为GL_LINEAR(线性过滤)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 加载并生成纹理
    int width, height, nrChannels;
    unsigned char *data = stbi_load("../Textures/container.jpg", &width, &height, &nrChannels, 0);
    stbi_set_flip_vertically_on_load(true);    // 告诉stb_image.h在y轴上翻转加载的纹理
    if (data)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);   // 生成纹理
        glGenerateMipmap(GL_TEXTURE_2D);    // 为纹理对象生成一组完整的mipmap
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);  // 释放图像的内存

    // ------------------------
    // 纹理2
    GLuint texture2;
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);
    // 设置纹理环绕方式为GL_REPEAT
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // 设置纹理过滤方式为GL_LINEAR(线性过滤)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    data = stbi_load("../Textures/awesomeface.png", &width, &height, &nrChannels, 0);
    if (data)
    {
        // 此处png格式的awesomeface是GL_RGBA, A表示的是透明度
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);  // 释放图像的内存

    // 告诉opengl每个采样器分别属于哪个纹理单元(只需执行一次)
    ourShader.use(); // 设置uniform之前需要激活/使用着色器
    // 通过纹理类进行设置(建议的方法)
    ourShader.setInt("texture1", 0);
    ourShader.setInt("texture2", 1);
    // 也可以这样手动设置
    // glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);
    // glUniform1i(glGetUniformLocation(ourShader.ID, "texture2"), 1);

    // 解绑VAO, VBO和EBO(解绑顺序不能乱)
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    // 正常模式和线框模式
    //glPolygonMode(GL_FRONT_AND_BACK, GL_FULL);
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // 渲染循环
    // -----------------------------------
    while (!glfwWindowShouldClose(window))
    {
        processInput(window);   // 处理输入

        // 渲染
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);   // 设置清空屏幕所用的颜色
        glClear(GL_COLOR_BUFFER_BIT);   // 清空屏幕的颜色缓冲, 它接受一个缓冲位(Buffer Bit)来指定要清空的缓冲

        // 在相应的纹理单元上绑定纹理
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1);    // 绑定纹理1箱子
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);    // 绑定纹理2笑脸

        // 绘制图形
        ourShader.use();
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        // glBindVertexArray(0);

        // 交换缓冲并且检查是否有触发事件(比如键盘输入、鼠标移动等)
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 删除VAO和VBO
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);

    // 释放/删除之前的分配的所有资源,退出程序
    glfwTerminate();
    return 0;
}

void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值