LearnOpenGL(二)——绘制三角形

6 篇文章 2 订阅
4 篇文章 2 订阅

在绘制三角形前,我们需要了解两个对象:顶点数组对象VAO和顶点缓冲对象VBO

VAO:可以理解为用来管理顶点属性的,它存储的是定点的属性配置,在绘制不同的顶点数据时,只需要绑定不同的VAO就可以了

VBO:用来管理顶点数据的内存,它在GPU中存储大量的顶点数据,供着色器访问

了解了这两个对象后,就开始进行今天的三角形的绘制流程:

一、输入顶点数据

在绘制三角形前,需要输入三角形的三个顶点数据。OpenGL是一个3D图形库,所以它的坐标系是个三维的坐标系(x、y、z、),OpenGL的坐标系是一个标准的设备坐标系,它的范围是[-1,1],因为我们今天画的三角形是一个二维的平面三角形,所以设置它的z坐标为0,顶点数据如下:

float vertices[] = 
    {
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f, 
        0.0f, 0.5f, 0.0f
    };

接下来需要创建一个VBO来管理顶点数据的内存,并把它绑定到目标上

unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glGenBuffers90; 这个函数是用来生成顶点缓冲对象的,它的第二个参数是所要生成的顶点缓冲对象,第一个参数是生成的缓冲对象的唯一一个ID

glBindBuffer(); 将缓冲对象绑定到目标缓冲上。第一个参数是目标缓冲对象,OpenGL有很多个缓冲对象,因为我们绑定的是顶点数据,所以在这里使用的目标缓冲是GL_ARRAY_BUFFER

glBufferData(); 将顶点数据复制到缓冲内存中。第一个参数是目标缓冲对象,第二个参数是存储的顶点数据的大小,第三个参数是缓存的顶点数据,第四个参数指定了显卡如何管理给定的数据,GL_STATIC_DRAW表示数据几乎不会被改变,GL_DYNAMIC_DRAW表示数据会被改变很多,GL_STREAM_DRAW 表示每次绘制时都会改变数据,因为三角形的顶点是固定的,所以使用GL_STATIC_DRAW

接下来,我们需要链接顶点的属性,告诉OpenGL如何解析顶点数据

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

glVertexAttribPointer(); 用来设置顶点数据的属性。

第一个参数指定配置的顶点属性的位置,它与顶点着色器中设置的顶点的位置一致,这里先设置为0,表示位于缓冲中的起始位置

第二个参数表示顶点数据的大小,因为我们定义的三角形的数据是一个vec3类型的(即x、y、z)三个坐标,所以它的大小是3

第三个参数表示每个顶点坐标使用的数据类型,我们使用的是float类型,所以是GL_FLOAT

第四个参数表示我们是否希望顶点数据被标准画,如果希望,那么顶点数据将会被映射到[0,1]的范围,这里我们不希望被标准画,使用GL_FALSE

第五个参数表示每个顶点之间的步长,我们使用的是float类型的数据,且每个顶点之间间隔3个数据,所以使用3 * sizeof(float)计算步长

第六个参数表示位置数据距离起始位置的偏移量,我们定义的数据位于起始位置,偏移量是0

glEnableVertexAttribArray(); 表示启用顶点属性,我们在链接顶点属性后,需要启用它,默认是禁用,参数表示顶点属性的位置

还记得我们提到的顶点数组对象吗?在这里我们生成顶点数组对象VAO用于管理顶点属性,使用方法和生成VBO一样

unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);

顶点数据输入到GPU后,我们接下来就需要创建着色器来处理这些顶点数据了。

二、创建着色器

1、编写顶点着色器

OpenGL定义每个着色器必须有一个main函数入口

const char* vertexshadersource = "#version 330 core\n"
"layout (location = 0) in vec3 apos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(apos.x, apos.y, apos.z, 1.0);\n"
"}\0";

#version 330 core; 指定当前使用的版本是3.3版本,并且使用的是核心模式core

layout (location = 0); 定义输入变量的位置为起始位置,我们在链接顶点属性时定义的位置为起始位置,in 表示输入,apos表示我们输入的三角形的顶点数据

gl_Position; 表示将处理后的顶点数据输出

着色器源码编写完成后,我们需要创建一个着色器,并将着色源码附加到着色器上

unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexshadersource, NULL);

glCreateShader(); 创建着色器,参数表示创建的着色器类型,GL_VERTEX_SHADER表示顶点着色器,GL_FRAGMENT_SHADER表示片段着色器

glShaderSource(); 将着色器源码附着到着色器上。

第一个参数表示要被替换的着色器源码的句柄

第二个参数表示着色器使用的字符串数

第三个参数指定要被替换的着色器源码

第四个参数,NULL表示字符串一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;
}

glCompileShader(); 编译着色器

glGetShaderiv(); 检测着色器是否成功,将检测结果存储到第三个参数中,第二个参数表示检测的是着色器的编译

glGetShaderInfoLog(); 编译失败后,获取编译失败的log输入到logInfo中

2、编写片段着色器

const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\0";

out vec4 FragColor; out表示最终的输出颜色

片段着色器的创建和编译和顶点着色器一样

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

着色器编译完成后,就需要把这两个着色器渲染到一个着色程序上

3、创建着色程序

unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmenShader);
glLinkProgram(shaderProgram);

glCreateProgram(); 创建着色器程序

glAttachShader();将着色器附着到着色程序上。第一个参数表示附着到的着色程序,第二个参数表示被附着的着色器

glLinkProgram();链接着色器和着色器程序

接下来就是检测是否链接成功

glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) 
{
    glGetProgramInfoLog(shaderProgram, 512, NULL, logInfo);
    std::cout << "Link Shader Error" << logInfo << std::endl;
}

glGetProgramiv(); 检测链接是否成功,第二个参数表示检测的是程序链接

检测成功后,我们需要激活着色程序

glUseProgram(shaderProgram);

激活程序后,着色器就没有用了,我们希望删除它们

glDeleteShader(vertexShader);
glDeleteShader(fragmenShader);

最后,见证奇迹的时刻到了:描画三角形

glDrawArrays(GL_TRIANGLES, 0, 3);

glDrawArrays(); 绘制三角形。

第一个参数指定绘制图元的类型,这里我们绘制的是三角形GL_TRIANGLES

第二个参数顶点数组的起始索引,我们之前设置过起始位置为0

第三个参数表示要绘制几个顶点,我们画三角形需要三个顶点,所以是3

绘制结果:

最后的最后,删除顶点数组对象、顶点缓冲对象和着色程序

glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);

完整代码如下

#include "Glad\glad.h"
#include "GLFW\glfw3.h"
#include <iostream>


//编写顶点着色器
const char* vertexshadersource = "#version 330 core\n"
"layout (location = 0) in vec3 apos;\n"
"void main()\n"
"{\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"
"void main()\n"
"{\n"
"   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\0";

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;
    }

    //创建顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    //将着色器源码附加到着色器上并编译它
    glShaderSource(vertexShader, 1, &vertexshadersource, NULL);
    glCompileShader(vertexShader);

    //检测是否编译成功,如果失败,打印log
    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, &fragmentShaderSource, NULL);
    glCompileShader(fragmenShader);
    glGetShaderiv(fragmenShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmenShader, 512, NULL, logInfo);
        std::cout << "Compile FragShader Error" << logInfo << std::endl;
    }

    //将两个着色器对象链接到一个用来渲染的着色程序上
    //创建一个程序对象
    unsigned int shaderProgram = glCreateProgram();
    //将顶点和片段着色器附加到着色程序上并链接他们
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmenShader);
    glLinkProgram(shaderProgram);
    //检测是否链接成功
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success)
    {
        glGetProgramInfoLog(shaderProgram, 512, NULL, logInfo);
        std::cout << "Link Shader Error" << logInfo << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmenShader);

    //顶点数据处理
    //定义顶点数组
    float vertices[] = 
    {
        -0.5f, -0.5f, 0.0f,
        0.5f, -0.5f, 0.0f, 
        0.0f, 0.5f, 0.0f
    };
    
    unsigned int VBO, VAO;
    //创建并绑定顶点数组对象,用于存储顶点数据的配置
    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, 3 * sizeof(float), (void*)0);
    //启用顶点属性
    glEnableVertexAttribArray(0);
    //解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
   
    glViewport(0, 0, 800, 600);
    while (!glfwWindowShouldClose(window))
    {
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        //激活程序对象
        glUseProgram(shaderProgram);
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);
    glfwTerminate();

    return 0;
}

现将代码封装在类里,并从文件中读取着色器源码,并将代码稍作修改,画一个彩色的三角形

在头文件中定义类

#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();

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

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

在cpp中实现

#include "glsl.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::useProgram()
{
    glUseProgram(mProgramID);
}

void CShader::Draw()
{
    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.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 0.5f, 0.0f,  0.0f, 0.0f, 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, 6 * sizeof(float), (void*)0);
    //启用顶点属性
    glEnableVertexAttribArray(0);
    //链接颜色属性
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    //启用颜色属性
    glEnableVertexAttribArray(1);
    //解除绑定
    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();
       
    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;
}

顶点着色器

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

片段着色器

#version 330 core
out vec4 FragColor;
//uniform vec4 ourcolor;
in vec3 ourcolor;
void main()
{
    FragColor = vec4(ourcolor, 1.0);
};

绘制结果

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值