openGL入门之2、着色器&纹理

学习链接来自:https://learnopengl-cn.github.io

1、着色器

1.1、概念

着色器(Shader)是运行在GPU上的小程序。这些小程序为图形渲染管线的某个特定部分而运行。从基本意义上来说,着色器只是一种把输入转化为输出的程序。着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。

1.2、GLSL语言

着色器是使用一种叫GLSL的类C语言写成的。GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。

典型的GLSL案例

#version version_number  //着色器的开头总是要声明版本
in type in_variable_name;//输入变量 in关键字  type一般是vec2 、vec3、vec4这些
in type in_variable_name;

out type out_variable_name;//输出变量 out关键字

uniform type uniform_name;//共享变量 各个着色器之间共享使用的 关键字uniform 

//每个着色器的入口点都是main函数
int main()
{
/*
	在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。
*/
  // 处理输入并进行一些图形操作
  ...
  // 输出处理过的结果到输出变量
  out_variable_name = weird_stuff_we_processed;
}

1.3、顶点着色器之顶点属性

顶点着色器的时候,每个输入变量也叫顶点属性(Vertex Attribute)。
我们能声明的顶点属性是有上限的,它一般由硬件来决定。OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件或许允许更多的顶点属性,你可以查询GL_MAX_VERTEX_ATTRIBS来获取具体的上限:

int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

1.4、GLSL数据类型

其GLSL中包含C等其它语言大部分的默认基础数据类型:int、float、double、uint和bool。GLSL也有两种容器类型,它们会在这个教程中使用很多,分别是向量(Vector)和矩阵(Matrix)。

GLSL中的向量是一个可以包含有1、2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n代表分量的数量):

vecn 包含n个float分量的默认向量
bvecn 包含n个bool分量的向量
ivecn 包含n个int分量的向量
uvecn 包含n个unsigned int分量的向量
dvecn 包含n个double分量的向量

大多数时候我们使用vecn,因为float足够满足大多数要求了。

一个向量的分量可以通过vec.x这种方式获取,这里x是指这个向量的第一个分量。你可以分别使用.x、.y、.z和.w来获取它们的第1、2、3、4个分量。GLSL也允许你对颜色使用rgba,或是对纹理坐标使用stpq访问相同的分量。

向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:

vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;

你可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;然而,你不允许在一个vec2向量中去获取.z元素。我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:

vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);

向量是一种灵活的数据类型,我们可以把用在各种输入和输出上。

1.5、输入in与输出out
GLSL定义了in和out关键字专门来实现这个目的。每个着色器使用这两个关键字设定输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。
注意顶点着色器的特殊输入
顶点着色器的输入特殊在,它从顶点数据中直接接收输入。为了定义顶点数据该如何管理,我们使用location这一元数据指定输入变量,这样我们才可以在CPU上配置顶点属性。顶点着色器需要为它的输入提供一个额外的layout标识(如layout (location = 0)),这样我们才能把它链接到顶点数据。
注意 片段着色器
片段着色器的特殊,它需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。
总结:所以,如果我们打算从一个着色器向另一个着色器发送数据,我们必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似的输入。当类型和名字都一样的时候,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了(这是在链接程序对象时完成的)
在这里插入图片描述

//顶点着色器定义输出变量并 赋值
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"out vec4 vertexColor;\n"
"void main()\n"
"{\n"
"   vertexColor = vec4(0.5, 0.5, 0.0, 1.0);\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";

//片段着色器输入  使用
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec4 vertexColor;\n"
"void main()\n"
"{\n"
"   FragColor = vertexColor;\n"
"}\n\0";

1.6、Uniform 共享变量;

着色器的共享变量,可以从cpu直接设置

Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。uniform是**全局的(**Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。

首先需要定义
我们可以在一个着色器中添加uniform关键字至类型和变量名前来声明一个GLSL的uniform。如
uniform vec4 ourColor; // 在OpenGL程序代码中设定这个变量

再就是如何在cpu获取地址进行修改设置
我们首先需要找到着色器中uniform属性的索引/位置值。当我们得到uniform的索引/位置值后,我们就可以更新它的值了。如
int vertexColorLocation = glGetUniformLocation(shaderProgram, “ourColor”);
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

**实践:**通过uniform设置让三角形随着时间改变颜色
在这里插入图片描述

//片段着色器定义
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"uniform vec4 ourColor;\n"
"void main()\n"
"{\n"
"   FragColor = ourColor;\n"
"}\n\0";

再渲染的时候使用
glUseProgram(shaderProgram);//使用着色器程序
        // 更新uniform颜色
        float timeValue = glfwGetTime();//获取运行的秒数
        float greenValue = sin(timeValue) / 2.0f + 0.5f;//sin函数让颜色在0.0到1.0之间改变,
        int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");//查找位置
        glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);//进行设置值 因为是vec4f类型 则使用glUniform4f的4f

1.7、更多属性;解决多顶点颜色赋值

uniform对于设置一个在渲染迭代中会改变的属性是一个非常有用的工具,它也是一个在程序和着色器间数据交互的很好工具,但假如我们打算为每个顶点设置一个颜色的时候该怎么办?这种情况下,我们就不得不声明和顶点数目一样多的uniform了。在这一问题上更好的解决方案是在顶点属性中包含更多的数据,这是我们接下来要做的事情。
在这里插入图片描述

#include <iostream>
#include <glad/glad.h> 
#include <GLFW/glfw3.h>

using namespace std;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

/*
    顶点着色器GLSL源码
    GLSL版本号和OpenGL的版本是匹配的,使用openGL3.3版本则使用GLSL的330
    使用in关键字,在顶点着色器中声明所有的输入顶点属性
    layout (location = 0)设定了输入变量(顶点属性)的位置值;并且后面链接顶点属性设置的时候会通过顶点属性位置值进行绑定
    vec3 表示三个分量的值(注意每个值都是浮点数类型)
    第二行就是表示在位置0的地方有一个三个分量的输入变量aPos
    将输入的三维aPos转换为四维并赋值给全局变量gl_Position
*/
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"   位置变量的属性位置值为 0 
"layout (location = 1) in vec3 aColor;\n" 颜色变量的属性位置值为 1
"out vec4 vertexColor;\n"
"void main()\n"
"{\n"
"   vertexColor = vec4(aColor, 1.0);\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
/*
    片段着色器GLSL源码
    GLSL版本号和OpenGL的版本是匹配的,使用openGL3.3版本则使用GLSL的330
    out 表示输出变量 四维向量的变量FragColor
    暂时自定义为 一个Alpha值为1.0(1.0代表完全不透明)的橘黄色的vec4赋值给颜色输出。
    RGBA的四分量向量
    片段着色器所做的是计算像素最后的颜色输出。为了让事情更简单暂时自定义为  我们的片段着色器将会一直输出橘黄色。
*/
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"in vec4 vertexColor;\n"
"void main()\n"
"{\n"
"   FragColor = vertexColor;\n"// 将ourColor设置为我们从顶点数据那里得到的输入颜色
"}\n\0";

int main()
{
    /******************************1、 初始化opengl窗口*********************************************/
    //glfw 初始化和配置 
    glfwInit();//初始化GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//子版本号
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//指定哪个配置文件配置上下文:GLFW我们使用的是核心模式
#ifdef __APPLE__//如果时mac os xp等系统则要进行配置一下才能生效
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    //glfw创建窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);//GLFW将我们窗口的上下文设置为当前线程的主上下文
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    
    //GLAD加载所有的opengl函数指针
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    /******************************2、 着色器编译编译链接 *********************************************/
    //编写 编译顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);//传入GL_VERTEX_SHADER表示创建顶点着色器
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);//绑定源码 1表示只有一个
    glCompileShader(vertexShader);//编译
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);//检查是否编译报错  也就是检查GLSL的语法
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        cout << "ERROR:Shader::Vertex::Compilation Fail\n" << infoLog << endl;
    }

    //编写 编译片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);//传入GL_FRAGMENT_SHADER 表示创建片段着色器
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);//绑定源码 1表示只有一个
    glCompileShader(fragmentShader);//编译
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);//检查是否编译报错  也就是检查GLSL的语法
    if (!success)
    {
        glad_glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        cout << "ERROR:Shader::fragment::Compilation Fail:" << infoLog << endl;
    }

    //链接着色器程序
    unsigned int shaderProgram = glCreateProgram(); //创建着色器程序
    glAttachShader(shaderProgram, vertexShader);//之前编译的着色器附加到程序对象上
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);//链接它们
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);//检查程序是否链接报错
    if (!success)
    {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        cout << "ERROR:Program::shaderProgram::Link Fail:" << infoLog << endl;
    }
    glDeleteShader(vertexShader);//着色器程序已链接完毕则不再需要可以进行delete了
    glDeleteShader(fragmentShader);


    /******************************3、 顶点属性缓存这些   *********************************************/
    //绘制两个相连的三角形
    float vertices[] = {
        //位置                //顶点
        -0.9f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f, // left 
       -0.0f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,// right
       -0.45f, 0.5f, 0.0f,  0.0f, 0.0f, 1.0f,// top 
       0.9f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,// right
       0.45f, 0.5f, 0.0f  ,0.0f, 0.0f, 1.0f // top 
    };
    //索引数组
    unsigned int indices[] = {  // note that we start from 0!
        0, 1, 2,  // first Triangle
        1, 3, 4   // second Triangle
    };
    unsigned int VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO);//创建一个顶点数组 与顶点属性相绑定 
    glGenBuffers(1, &VBO);//创建顶点缓冲对象
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);//绑定顶点数组 配置顶点属性

    glBindBuffer(GL_ARRAY_BUFFER, VBO);//将GL_ARRAY_BUFFER类型的缓存与VBO绑定
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//之前定义的顶点数据复制到缓冲的内存
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//将GL_ELEMENT_ARRAY_BUFFER类型的缓冲与EBO绑定
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//把索引复制到缓冲里

    /*
        0;指定我们要配置的顶点位置属性 就是顶点着色器里面location那个   
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void *)0);//步长为6了
    glEnableVertexAttribArray(0);//以顶点属性位置值0作为参数,启用顶点属性
     /*
        1;指定我们要配置的顶点颜色属性 就是顶点着色器里面location那个  glVertexAttribPointer函数更新顶点格式
    */
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);//VBO 已经与顶点属性数组VAO进行绑定了 那么GL_ARRAY_BUFFER就可以解除绑定
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //记住:不要在VAO激活时解除绑定EBO,因为绑定的元素缓冲区对象存储在VAO中;保持EBO绑定。

    glBindVertexArray(0);//您可以在之后解除绑定VAO,这样其他VAO调用就不会意外地修改这个VAO

    //渲染循环
    //程序在我们主动关闭它之前不断绘制图像并能够接受用户输入 GLFW退出前一直保持运行
    while (!glfwWindowShouldClose(window))//检查一次GLFW是否被要求退出
    {
        //可接收键盘输入esc从而退出
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置状态函数
        glClear(GL_COLOR_BUFFER_BIT);//使用状态函数

        
        glUseProgram(shaderProgram);//使用着色器程序

        glBindVertexArray(VAO);//绑定顶点数组  就是使用顶点属性
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        //配置OpenGL如何绘制图元。第一个参数表示我们打算将其应用到所有的三角形的正面和背面,第二个参数告诉我们用线来绘制
        //注意如果进行设置glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) 那么就会一直是线框模式
        //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//使用当前绑定的索引缓冲对象中的索引进行绘制:

        glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
        glfwPollEvents();//检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数
    }
    
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    //释放/删除之前的分配的所有资源
    glfwTerminate();

	return 0;
}
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//会返回这个按键是否正在被按下
        glfwSetWindowShouldClose(window, true);//把WindowShouldClose属性设置为 true的方法关闭GLFW
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    //左上角坐标xy和宽高
    glViewport(0, 0, width, height);//OpenGL的显示试图
}

1.8、着色器封装成类

在这里插入图片描述
ShaderClass.h文件
输入顶点着色器和片段着色器的文件路径
完成读取编译链接,并提供use,set,delte等方法

#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

class Shader
{
public:
    unsigned int ID;
    // constructor generates the shader on the fly
    // ------------------------------------------------------------------------
    Shader(const char* vertexPath, const char* fragmentPath)
    {
        // 1. retrieve the vertex/fragment source code from filePath
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;
        // ensure ifstream objects can throw exceptions:
        vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try
        {
            // open files
            vShaderFile.open(vertexPath);
            fShaderFile.open(fragmentPath);
            std::stringstream vShaderStream, fShaderStream;
            // read file's buffer contents into streams
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();
            // close file handlers
            vShaderFile.close();
            fShaderFile.close();
            // convert stream into string
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        }
        catch (std::ifstream::failure& e)
        {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
        }
        const char* vShaderCode = vertexCode.c_str();
        const char* fShaderCode = fragmentCode.c_str();
        // 2. compile shaders
        unsigned int vertex, fragment;
        // vertex shader
        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, NULL);
        glCompileShader(vertex);
        checkCompileErrors(vertex, "VERTEX");
        // fragment Shader
        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &fShaderCode, NULL);
        glCompileShader(fragment);
        checkCompileErrors(fragment, "FRAGMENT");
        // shader Program
        ID = glCreateProgram();
        glAttachShader(ID, vertex);
        glAttachShader(ID, fragment);
        glLinkProgram(ID);
        checkCompileErrors(ID, "PROGRAM");
        // delete the shaders as they're linked into our program now and no longer necessary
        glDeleteShader(vertex);
        glDeleteShader(fragment);
    }
    // activate the shader
    // ------------------------------------------------------------------------
    void use()
    {
        glUseProgram(ID);
    }
    void dele()
    {
        glDeleteProgram(ID);
    }
    // utility uniform functions
    // ------------------------------------------------------------------------
    void setBool(const std::string& name, bool value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
    }
    // ------------------------------------------------------------------------
    void setInt(const std::string& name, int value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    }
    // ------------------------------------------------------------------------
    void setFloat(const std::string& name, float value) const
    {
        glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
    }

private:
    // utility function for checking shader compilation/linking errors.
    // ------------------------------------------------------------------------
    void checkCompileErrors(unsigned int shader, std::string type)
    {
        int success;
        char infoLog[1024];
        if (type != "PROGRAM")
        {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
        else
        {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success)
            {
                glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
    }
};
#endif

顶点着色器文件内容

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec4 vertexColor;
void main()
{
   vertexColor = vec4(aColor, 1.0);
   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

片段着色器文件内容

#version 330 core
out vec4 FragColor;
in vec4 vertexColor;
void main()
{
	FragColor = vertexColor;
}

使用着色器类

定义初始化
 Shader ourShader("shader.vs", "shader.fs");
使用
 ourShader.use();
删除
    ourShader.dele();

完整代码

#include <iostream>
#include <glad/glad.h> 
#include <GLFW/glfw3.h>
#include "ShaderClass.h"

using namespace std;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    /******************************1、 初始化opengl窗口*********************************************/
    //glfw 初始化和配置 
    glfwInit();//初始化GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//子版本号
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//指定哪个配置文件配置上下文:GLFW我们使用的是核心模式
#ifdef __APPLE__//如果时mac os xp等系统则要进行配置一下才能生效
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    //glfw创建窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);//GLFW将我们窗口的上下文设置为当前线程的主上下文
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    
    //GLAD加载所有的opengl函数指针
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    /******************************2、 着色器编译编译链接 *********************************************/
    Shader ourShader("shader.vs", "shader.fs");

    /******************************3、 顶点属性缓存这些   *********************************************/

    //绘制两个相连的三角形
    float vertices[] = {
        //位置                //顶点
        -0.9f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f, // left 
       -0.0f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,// right
       -0.45f, 0.5f, 0.0f,  0.0f, 0.0f, 1.0f,// top 
       0.9f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,// right
       0.45f, 0.5f, 0.0f  ,0.0f, 0.0f, 1.0f // top 
    };
    //索引数组
    unsigned int indices[] = {  // note that we start from 0!
        0, 1, 2,  // first Triangle
        1, 3, 4   // second Triangle
    };
    unsigned int VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO);//创建一个顶点数组 与顶点属性相绑定 
    glGenBuffers(1, &VBO);//创建顶点缓冲对象
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);//绑定顶点数组 配置顶点属性

    glBindBuffer(GL_ARRAY_BUFFER, VBO);//将GL_ARRAY_BUFFER类型的缓存与VBO绑定
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//之前定义的顶点数据复制到缓冲的内存
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//将GL_ELEMENT_ARRAY_BUFFER类型的缓冲与EBO绑定
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//把索引复制到缓冲里

    /*
        0;指定我们要配置的顶点位置属性 就是顶点着色器里面location那个   
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void *)0);//步长为6了
    glEnableVertexAttribArray(0);//以顶点属性位置值0作为参数,启用顶点属性
     /*
        1;指定我们要配置的顶点颜色属性 就是顶点着色器里面location那个  glVertexAttribPointer函数更新顶点格式
    */
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);//VBO 已经与顶点属性数组VAO进行绑定了 那么GL_ARRAY_BUFFER就可以解除绑定
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //记住:不要在VAO激活时解除绑定EBO,因为绑定的元素缓冲区对象存储在VAO中;保持EBO绑定。

    glBindVertexArray(0);//您可以在之后解除绑定VAO,这样其他VAO调用就不会意外地修改这个VAO

    //渲染循环
    //程序在我们主动关闭它之前不断绘制图像并能够接受用户输入 GLFW退出前一直保持运行
    while (!glfwWindowShouldClose(window))//检查一次GLFW是否被要求退出
    {
        //可接收键盘输入esc从而退出
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置状态函数
        glClear(GL_COLOR_BUFFER_BIT);//使用状态函数

        
        ourShader.use();
       // glUseProgram(shaderProgram);//使用着色器程序

        glBindVertexArray(VAO);//绑定顶点数组  就是使用顶点属性
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        //配置OpenGL如何绘制图元。第一个参数表示我们打算将其应用到所有的三角形的正面和背面,第二个参数告诉我们用线来绘制
        //注意如果进行设置glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) 那么就会一直是线框模式
        //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//使用当前绑定的索引缓冲对象中的索引进行绘制:

        glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
        glfwPollEvents();//检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数
    }
    
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    ourShader.dele();

    //释放/删除之前的分配的所有资源
    glfwTerminate();

	return 0;
}
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//会返回这个按键是否正在被按下
        glfwSetWindowShouldClose(window, true);//把WindowShouldClose属性设置为 true的方法关闭GLFW
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    //左上角坐标xy和宽高
    glViewport(0, 0, width, height);//OpenGL的显示试图
}

2、纹理

2.1、纹理坐标

在这里插入图片描述

2.2、纹理环绕方式

在这里插入图片描述

2.3、纹理过滤

在这里插入图片描述

2.4、多级渐远纹理

在这里插入图片描述

2.5、加载与创建纹理

在这里插入图片描述

2.5.1、首先要使用stb_image.h 加载图片;

在这里插入图片描述

2.5.2、生成纹理;纹理定义设置

定义纹理对象
unsigned int texture;
    glGenTextures(1, &texture);//生成纹理对象,同样也可以创建一个数组的纹理
    glBindTexture(GL_TEXTURE_2D, texture); //绑定它,让之后任何的纹理指令都可以配置当前绑定的纹理:
加载图片数据生成纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
	glGenerateMipmap(GL_TEXTURE_2D);
 为当前绑定的纹理对象设置环绕、过滤方式
	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);//缩小放大使用什么过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

2.5.3、应用纹理;着色器设置

顶点数组的时候纹理坐标的传入
在这里插入图片描述

片段着色器的使用
在这里插入图片描述
主代码

#include <iostream>
#include <glad/glad.h> 
#include <GLFW/glfw3.h>
#include "ShaderClass.h"
#include "stb_image.h"
using namespace std;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    /******************************1、 初始化opengl窗口*********************************************/
    //glfw 初始化和配置 
    glfwInit();//初始化GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//子版本号
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//指定哪个配置文件配置上下文:GLFW我们使用的是核心模式
#ifdef __APPLE__//如果时mac os xp等系统则要进行配置一下才能生效
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    //glfw创建窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);//GLFW将我们窗口的上下文设置为当前线程的主上下文
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    
    //GLAD加载所有的opengl函数指针
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    /******************************2、 着色器编译编译链接 *********************************************/
    Shader ourShader("shader.vs", "shader.fs");

    /******************************3、 顶点属性缓存这些   *********************************************/

    //绘制两个相连的三角形
    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[] = {
        0, 1, 3, // first triangle
        1, 2, 3  // second triangle
    };
    unsigned int VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO);//创建一个顶点数组 与顶点属性相绑定 
    glGenBuffers(1, &VBO);//创建顶点缓冲对象
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);//绑定顶点数组 配置顶点属性

    glBindBuffer(GL_ARRAY_BUFFER, VBO);//将GL_ARRAY_BUFFER类型的缓存与VBO绑定
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//之前定义的顶点数据复制到缓冲的内存
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//将GL_ELEMENT_ARRAY_BUFFER类型的缓冲与EBO绑定
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//把索引复制到缓冲里

    /*
        0;指定我们要配置的顶点位置属性 就是顶点着色器里面location那个   
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 *sizeof(float), (void *)0);//步长为8了
    glEnableVertexAttribArray(0);//以顶点属性位置值0作为参数,启用顶点属性
     /*
        1;指定我们要配置的顶点颜色属性 就是顶点着色器里面location那个  glVertexAttribPointer函数更新顶点格式
    */
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    /*
        2;指定我们要配置的顶点纹理属性 就是顶点着色器里面location那个  glVertexAttribPointer函数更新顶点格式
    */
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);
    glBindBuffer(GL_ARRAY_BUFFER, 0);//VBO 已经与顶点属性数组VAO进行绑定了 那么GL_ARRAY_BUFFER就可以解除绑定
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //记住:不要在VAO激活时解除绑定EBO,因为绑定的元素缓冲区对象存储在VAO中;保持EBO绑定。
    glBindVertexArray(0);//您可以在之后解除绑定VAO,这样其他VAO调用就不会意外地修改这个VAO

    // 加载创建纹理
    unsigned int texture;
    glGenTextures(1, &texture);//生成纹理对象,同样也可以创建一个数组的纹理
    glBindTexture(GL_TEXTURE_2D, texture); //绑定它,让之后任何的纹理指令都可以配置当前绑定的纹理:
    //为当前绑定的纹理对象设置环绕、过滤方式
    /*
    第一个参数指定了纹理目标;我们使用的是2D纹理,因此纹理目标是GL_TEXTURE_2D。
    第二个参数需要我们指定设置的选项与应用的纹理轴。我们打算配置的是WRAP选项,并且指定S和T轴。
    最后一个参数需要我们传递一个环绕方式(Wrapping),
    在这个例子中OpenGL会给当前激活的纹理设定纹理环绕方式为GL_REPEAT。
    */
    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);//线性
    // 加载图像
    int width, height, nrChannels;
    unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
    if (data)
    {
        /*
        当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像
        第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
        第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
        第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。
        第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
        下个参数应该总是被设为0(历史遗留的问题)。
        第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。
        最后一个参数是真正的图像数据
        */
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);//为当前绑定的纹理自动生成所有需要的多级渐远纹理
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);//释放图像的内存

    //渲染循环
    //程序在我们主动关闭它之前不断绘制图像并能够接受用户输入 GLFW退出前一直保持运行
    while (!glfwWindowShouldClose(window))//检查一次GLFW是否被要求退出
    {
        //可接收键盘输入esc从而退出
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置状态函数
        glClear(GL_COLOR_BUFFER_BIT);//使用状态函数

        
        ourShader.use();
       // glUseProgram(shaderProgram);//使用着色器程序

        glBindVertexArray(VAO);//绑定顶点数组  就是使用顶点属性
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        //配置OpenGL如何绘制图元。第一个参数表示我们打算将其应用到所有的三角形的正面和背面,第二个参数告诉我们用线来绘制
        //注意如果进行设置glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) 那么就会一直是线框模式
        //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        //在调用glDrawElements之前绑定纹理了,它会自动把纹理赋值给片段着色器的采样器:
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//使用当前绑定的索引缓冲对象中的索引进行绘制:

        glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
        glfwPollEvents();//检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数
    }
    
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    ourShader.dele();

    //释放/删除之前的分配的所有资源
    glfwTerminate();

	return 0;
}
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//会返回这个按键是否正在被按下
        glfwSetWindowShouldClose(window, true);//把WindowShouldClose属性设置为 true的方法关闭GLFW
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    //左上角坐标xy和宽高
    glViewport(0, 0, width, height);//OpenGL的显示试图
}

顶点着色器

#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, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}

片段着色器

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

//在调用glDrawElements之前绑定纹理了,它会自动把纹理赋值给片段着色器的采样器:
uniform sampler2D ourTexture;

void main()
{
//ourTexture  采样器
//TexCoord  由顶点坐标那边传来的纹理坐标
//最后输出还可以把颜色也叠加上去
    FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);;
}

2.6、纹理单元 实现多幅纹理图片叠加

纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。
OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。

2.6.1、首先我们要编写片段着色器

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

//sampler2D就是表示纹理单元类型的 是uniform卡类型
uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
/*
最终输出颜色现在是两个纹理的结合。
GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。
如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。
0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。
*/
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

2.6.2、同样将纹理2获取设置

完全与纹理1设置一样

//纹理2
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);
    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);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
    if (data)
    {
        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);

2.6.3、纹理绑定着色器

 /*
使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。
我们只需要设置一次即可,所以这个会放在渲染循环的前面
设置uniforms变量的时候需激活着色器
*/
    ourShader.use();
    glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);//绑定这个着色器的第0个纹理
    ourShader.setInt("texture2", 1);//绑定这个着色器的第0个纹理

2.6.4、在渲染中激活绑定

//激活纹理进行绑定
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, texture2);

2.6.5、实践在这里插入图片描述

主代码

#include <iostream>
#include <glad/glad.h> 
#include <GLFW/glfw3.h>
#include "ShaderClass.h"
#include "stb_image.h"
using namespace std;
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    /******************************1、 初始化opengl窗口*********************************************/
    //glfw 初始化和配置 
    glfwInit();//初始化GLFW
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本号
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//子版本号
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//指定哪个配置文件配置上下文:GLFW我们使用的是核心模式
#ifdef __APPLE__//如果时mac os xp等系统则要进行配置一下才能生效
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    //glfw创建窗口
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);//GLFW将我们窗口的上下文设置为当前线程的主上下文
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    
    //GLAD加载所有的opengl函数指针
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    /******************************2、 着色器编译编译链接 *********************************************/
    Shader ourShader("shader.vs", "shader.fs");

    /******************************3、 顶点属性缓存这些   *********************************************/

    //绘制两个相连的三角形
    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[] = {
        0, 1, 3, // first triangle
        1, 2, 3  // second triangle
    };
    unsigned int VBO, VAO, EBO;
    glGenVertexArrays(1, &VAO);//创建一个顶点数组 与顶点属性相绑定 
    glGenBuffers(1, &VBO);//创建顶点缓冲对象
    glGenBuffers(1, &EBO);

    glBindVertexArray(VAO);//绑定顶点数组 配置顶点属性

    glBindBuffer(GL_ARRAY_BUFFER, VBO);//将GL_ARRAY_BUFFER类型的缓存与VBO绑定
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//之前定义的顶点数据复制到缓冲的内存
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);//将GL_ELEMENT_ARRAY_BUFFER类型的缓冲与EBO绑定
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);//把索引复制到缓冲里

    /*
        0;指定我们要配置的顶点位置属性 就是顶点着色器里面location那个   
    */
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 *sizeof(float), (void *)0);//步长为8了
    glEnableVertexAttribArray(0);//以顶点属性位置值0作为参数,启用顶点属性
     /*
        1;指定我们要配置的顶点颜色属性 就是顶点着色器里面location那个  glVertexAttribPointer函数更新顶点格式
    */
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    /*
        2;指定我们要配置的顶点纹理属性 就是顶点着色器里面location那个  glVertexAttribPointer函数更新顶点格式
    */
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);
    glBindBuffer(GL_ARRAY_BUFFER, 0);//VBO 已经与顶点属性数组VAO进行绑定了 那么GL_ARRAY_BUFFER就可以解除绑定
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); //记住:不要在VAO激活时解除绑定EBO,因为绑定的元素缓冲区对象存储在VAO中;保持EBO绑定。
    glBindVertexArray(0);//您可以在之后解除绑定VAO,这样其他VAO调用就不会意外地修改这个VAO

    // 加载创建纹理
    unsigned int texture, texture2;
    glGenTextures(1, &texture);//生成纹理对象,同样也可以创建一个数组的纹理
    glBindTexture(GL_TEXTURE_2D, texture); //绑定它,让之后任何的纹理指令都可以配置当前绑定的纹理:
    //为当前绑定的纹理对象设置环绕、过滤方式
    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);//线性
    // 加载图像
    int width, height, nrChannels;
    stbi_set_flip_vertically_on_load(true);//在加载图像前反转一下;是因为OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。
    unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
    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 << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);//释放图像的内存
   
    //纹理2
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);
    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);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    stbi_set_flip_vertically_on_load(true);
    data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
    if (data)
    {
        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);

    /*
    使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。
    我们只需要设置一次即可,所以这个会放在渲染循环的前面
    设置uniforms变量的时候需激活着色器
    */
    ourShader.use();
    glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);//绑定这个着色器的第0个纹理
    ourShader.setInt("texture2", 1);//绑定这个着色器的第0个纹理


    //渲染循环
    //程序在我们主动关闭它之前不断绘制图像并能够接受用户输入 GLFW退出前一直保持运行
    while (!glfwWindowShouldClose(window))//检查一次GLFW是否被要求退出
    {
        //可接收键盘输入esc从而退出
        processInput(window);

        glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //设置状态函数
        glClear(GL_COLOR_BUFFER_BIT);//使用状态函数


        //激活纹理进行绑定
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);
        
        ourShader.use();
       // glUseProgram(shaderProgram);//使用着色器程序

        glBindVertexArray(VAO);//绑定顶点数组  就是使用顶点属性
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        //配置OpenGL如何绘制图元。第一个参数表示我们打算将其应用到所有的三角形的正面和背面,第二个参数告诉我们用线来绘制
        //注意如果进行设置glPolygonMode(GL_FRONT_AND_BACK, GL_FILL) 那么就会一直是线框模式
        //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        //在调用glDrawElements之前绑定纹理了,它会自动把纹理赋值给片段着色器的采样器:
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);//使用当前绑定的索引缓冲对象中的索引进行绘制:

        glfwSwapBuffers(window);//交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
        glfwPollEvents();//检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,并调用对应的回调函数
    }
    
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    ourShader.dele();

    //释放/删除之前的分配的所有资源
    glfwTerminate();

	return 0;
}
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//会返回这个按键是否正在被按下
        glfwSetWindowShouldClose(window, true);//把WindowShouldClose属性设置为 true的方法关闭GLFW
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    //左上角坐标xy和宽高
    glViewport(0, 0, width, height);//OpenGL的显示试图
}

顶点着色器

#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, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}

片段着色器

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

//sampler2D就是表示纹理单元类型的 是uniform卡类型
uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2) * vec4(ourColor, 1.0);
}

3、实践

3.1、修改片段着色器,仅让笑脸图案朝另一个方向看

在这里插入图片描述

3.2、生成四个笑脸

尝试用不同的纹理环绕方式,设定一个从0.0f到2.0f范围内的(而不是原来的0.0f到1.0f)纹理坐标。试试看能不能在箱子的角落放置4个笑脸
在这里插入图片描述
边缘填充方式在这里插入图片描述
边缘填充方式
在这里插入图片描述

3.3、动态调节两幅纹理的混合度

在这里插入图片描述
在片段着色器使用

#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

//sampler2D就是表示纹理单元类型的 是uniform卡类型
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform float mixValue;

void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), mixValue);
}

渲染的时候设置

//激活纹理进行绑定
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);
        ourShader.setFloat("mixValue", mixValue);
        ourShader.use();
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
入门学习OpenGL,您可以按照以下步骤进行: 1. 了解图形学基础知识:在开始学习OpenGL之前,建议您先了解一些基本的图形学概念,例如坐标系、向量、矩阵、光照和着色器等。这将有助于您理解OpenGL的工作原理。 2. 学习OpenGL的基本概念:学习OpenGL的基本概念,例如顶点缓冲对象(VBO)、顶点数组对象(VAO)、着色器程序、纹理和帧缓冲等。这些概念是OpenGL编程的基础。 3. 配置开发环境:安装并配置您选择的开发环境,例如使用C/C++语言开发的OpenGL程序需要安装相应的编译器和库文件。 4. 学习OpenGL的API和函数:掌握OpenGL的API和函数,了解它们的作用和使用方法。您可以参考OpenGL的官方文档或者查找一些优秀的教程和示例代码。 5. 编写简单的OpenGL程序:从简单的程序开始编写,例如绘制一个简单的三角形或矩形,并逐步扩展到更复杂的场景。这样可以帮助您逐步理解和掌握OpenGL的各项功能和技术。 6. 学习OpenGL的高级特性:一旦您掌握了OpenGL的基础知识,可以开始学习一些高级特性,例如光照、阴影、透明度和几何变换等。这些技术可以帮助您创建更加逼真和复杂的图形效果。 7. 参考优秀的资源和教程:除了官方文档外,还可以参考一些优秀的OpenGL教程和资源,例如在线教程、书籍、博客和论坛等。这些资源可以帮助您更好地理解和应用OpenGL。 记住,学习OpenGL需要一定的时间和实践,所以要有耐心并持续进行练习。通过不断练习和实践,您会逐渐掌握OpenGL编程的技巧和技术。祝您学习顺利!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值