在绘制三角形前,我们需要了解两个对象:顶点数组对象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);
};
绘制结果