开始学习OpenGL,参考的是著名的LearnOpenGL这个网站,在这里作一些总结性的记录,只是方便本身往后查找或者记录本身的一些拓展思考,关于OpenGL的具体内容请移步:
https://learnopengl-cn.github.io/
或英文原版:https://learnopengl.com/c++
LearnOpenGL中使用了GLFW和GLAD两个库来配置环境,原文已经很详细地列出了全部步骤,就再也不多说了,获取两个库以后在Visual Studio的项目属性中的VC++目录
中,将两个库的include文件夹加入包含目录,将GLFW的lib文件夹加入库目录,而后在连接器
的输入中把glfw3.lib这个文件加入附加依赖项,最后别忘记把glad.c
这个文件加入到工程中。git
首先包含以前引入的两个库的头文件,注意必定要先引入glad.hgithub
#include <glad/glad.h> #include <GLFW/glfw3.h>
开始使用OpenGL绘图以前,要先初始化OpenGL环境。编程
glfwInit();//初始化GLFW glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//告诉glfw使用OpenGL3.3版本 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);//告诉glfw使用OpenGL核心(core)模式
使用glfwCreateWindow建立一个窗口对象,传入窗口的宽度、高度和窗口名字。数组
建立窗口成功以后就将窗口设置为当前线程主上下文。并发
GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);//建立窗口对象 if (window == NULL)//若窗口生成失败则退出程序 { std::cout << "Create Window failed" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window);//通知GLFW将咱们窗口的上下文设置为当前线程的主上下文
调用OpenGL的函数以前需初始化GLAD用于管理函数指针。函数
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { std::cout << "Failed to initialize GLAD" << std::endl; return -1; }
最后还须要设置OpenGL的视口大小,并编写用户改变窗口大小时的回调函数。oop
glViewport(0, 0, 800, 600);//设置视口,前两个参数为视口左下角位置,后两个为视口宽高 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//设置回调函数,用户改变窗口大小时调用
回调函数:学习
void framebuffer_size_callback(GLFWwindow* window, int width, int height) { glViewport(0, 0, width, height); }
作好这些准备工做以后就能够开始经过循环来持续渲染咱们要显示的内容了,在循环体中咱们须要响应用户的按键,渲染并交换缓冲和检查事件,此处使用glClearColor来设置背景色并清空屏幕缓冲,这样屏幕就会一直渲染为咱们设置的背景色。网站
while (!glfwWindowShouldClose(window))//render loop { processInput(window);//按键响应 glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//设置颜色 glClear(GL_COLOR_BUFFER_BIT);//清空屏幕的颜色缓冲 glfwSwapBuffers(window);//交换前缓冲和后缓冲 glfwPollEvents();//检查有没有触发什么事件 } glfwTerminate();//清理全部的资源并正确地退出应用程序 return 0;
按键响应函数,当用户按下ESC键时关闭窗口:
void processInput(GLFWwindow* window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(window,true); } }
准备工做就绪,终于能够开始绘制了,绘制第一个三角形被LearnOpenGL的做者称之为入门现代OpenGL的最难部分。旧版本的OpenGL使用当即渲染模式,只需几行代码就能绘制出一个简单图形,但现代OpenGL使用核心渲染模式,绘图要基于着色器和缓冲区来进行,致使要画出一个三角形以前,须要掌握基本的着色器编写和控制OpenGL中各缓冲区的知识,对我这个没有图形编程基础的人来讲第一次看完这部份内容的时候说实话是很蒙圈的,但理解了以后也会发现这样的设计在渲染大量复杂物体的时候相比于旧的当即渲染模式是很是优越的。
绘制一个三角形(或者说任何图形)大概分为这几个步骤:
首先固然要知道这个图形的全部顶点,OpenGL的坐标系取值在-1.0到1.0之间,因此首先要准备好包含顶点数据的数组:
float vertices[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f };
OpenGL使用GLSL语言编写着色器,语法相似于C语言,关于这两个着色器的具体原理须要了解计算机图形渲染管线的知识,LearnOpenGL里说的也不是很详细,目前只须要知道顶点着色器用于处理每一个顶点,片元着色器用于处理光栅化以后的每一个像素便可,这些代码的具体含义LearnOpenGL里有详细解释,再也不详述。
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" "}\n\0";
unsigned int VertexShader; VertexShader = glCreateShader(GL_VERTEX_SHADER);//建立Shader glShaderSource(VertexShader, 1, &vertexShaderSource, NULL);//把着色器源码附加到着色器对象上 glCompileShader(VertexShader);//编译Shader
检查Shader是否编译成功,若失败则打印日志:
int success; char infoLog[512]; glGetShaderiv(VertexShader, GL_COMPILE_STATUS, &success);//检测编译时错误 if (!success) { glGetShaderInfoLog(VertexShader, 512, NULL, infoLog);//若是编译失败,用glGetShaderInfoLog获取错误消息 std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; }
对片元着色器也作相同操做:
unsigned int fragmentShader; fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); // check for shader compile errors glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl; }
建立一个总着色器程序,并将顶点和片元着色器连接上去,后续就可使用这个ShaderProgram来直接渲染物体。
//建立着色器程序 unsigned int ShaderProgram; ShaderProgram = glCreateProgram(); glAttachShader(ShaderProgram, VertexShader); glAttachShader(ShaderProgram, fragmentShader); glLinkProgram(ShaderProgram);//把以前编译的着色器附加到程序对象上,而后用glLinkProgram连接它们 //检测连接着色器程序是否失败,并获取相应的日志 glGetProgramiv(ShaderProgram, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(ShaderProgram, 512, NULL, infoLog); //cout... }
连接完毕后就能够删除掉以前建立的Shader对象了:
glDeleteShader(VertexShader); glDeleteShader(fragmentShader);
OpenGL中有多种缓冲对象用于存放各类数据,在绘制以前要将数据先放入缓冲区。
对每一个缓冲对象都有以下步骤:
咱们先来建立VAO和VBO对象:
unsigned int VAO; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); unsigned int VBO; glGenBuffers(1, &VBO);//使用glGenBuffers函数和一个缓冲ID生成一个VBO对象 glBindBuffer(GL_ARRAY_BUFFER, VBO);//使用glBindBuffer函数把新建立的缓冲绑定到GL_ARRAY_BUFFER目标上 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把以前定义的顶点数据复制到缓冲的内存
而后告诉OpenGL如何解析顶点数据并启用顶点属性:
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上) glEnableVertexAttribArray(0);//以顶点属性位置值做为参数,启用顶点属性
VAO用于存储随后的顶点属性调用。这样的好处就是,当配置顶点属性指针时,你只须要将那些调用执行一次,以后再绘制物体的时候只须要绑定相应的VAO就好了 。VBO用于存放顶点数据并发送到GPU上。
接下来只须要在渲染循环中调用绘制函数便可。
while (!glfwWindowShouldClose(window))//render loop { processInput(window); glClearColor(0.2f, 0.3f, 0.3f, 1.0f);//设置颜色 glClear(GL_COLOR_BUFFER_BIT);//清空屏幕的颜色缓冲 glUseProgram(ShaderProgram);//激活程序对象 glBindVertexArray(VAO);//这里因为只有一个对象因此其实不须要每次都绑定VAO,但如有多个须要绘制的对象则须要切换绑定不一样的VAO glDrawArrays(GL_TRIANGLES, 0, 3);//绘制 glfwSwapBuffers(window);//交换前缓冲和后缓冲 glfwPollEvents();//检查有没有触发什么事件 }
绘制三角形完成!