https://learnopengl-cn.github.io/01%20Getting%20started/03%20Hello%20Window/
-
代码
// ConsoleApplication.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include "pch.h" //GLEW #include <iostream> #define GLEW_STATIC #include <GL/glew.h> #include <GLFW/glfw3.h> #include "SOIL2/SOIL2.h" #include "glm/glm.hpp" #include "glm/gtc/type_ptr.hpp" #include "Shader.h" void processInput(GLFWwindow *window); int main() { //初始化GLFW glfwInit(); //使用flfwWindowHint函数来配置GLFW /* glfwWindowHint函数的第一个参数代表选项的名称, 我们可以从很多以GLFW_开头的枚举值中选择; 第二个参数接受一个整型, 用来设置这个选项的值 */ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //glfwCreateWindow函数需要窗口的宽和高作为它的前两个参数 //第三个参数表示这个窗口的名称 GLFWwindow* window = glfwCreateWindow(800, 600, "B7040312", NULL, NULL); if (window == NULL) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } //创建完窗口我们就可以通知GLFW将我们窗口的上下文设置为当前线程的主上下文了 glfwMakeContextCurrent(window); //GLEW是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLEW //glewExperimental所做的是即使驱动程序的扩展程序字符串中不存在扩展程序,也允许加载扩展程序入口点。 glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { std::cout << "Failed to initialize GLEW" << std::endl; return -1; } /* 开始渲染之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,这样OpenGL才只能知道怎样相对于窗口大小显示数据和坐标。 我们可以通过调用glViewport函数来设置窗口的维度(Dimension): */ /* 然而,当用户改变窗口的大小的时候,视口也应该被调整。 我们可以对窗口注册一个回调函数(Callback Function), 它会在每次窗口大小被调整的时候被调用 */ int width, height; glfwGetFramebufferSize(window, &width, &height); /* glViewport函数前两个参数控制窗口左下角的位置。 第三个和第四个参数控制渲染窗口的宽度和高度(像素),这里我们是直接从GLFW中获取的。 */ glViewport(0, 0, width, height); /* 我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。 因此,我们需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop), 它能在我们让GLFW退出前一直保持运行。 下面几行的代码就实现了一个简单的渲染循环: */ /* glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出, 如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了。 */ while (!glfwWindowShouldClose(window)) { // input // ----- processInput(window); // render // ------ //要把所有的渲染(Rendering)操作放到渲染循环中, //因为我们想让这些渲染指令在每次渲染循环迭代的时候都能被执行。 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); /* glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲), 它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。 */ /* glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态, 并调用对应的回调函数(可以通过回调方法手动设置) */ glfwSwapBuffers(window); glfwPollEvents(); } /* 当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。 我们可以在main函数的最后调用glfwTerminate函数来完成。 */ glfwTerminate(); return 0; } void processInput(GLFWwindow *window) { //检测是否按下esc,按下则退出循环 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); }
-
效果:
- EBO画长方形
//GLEW
#include <iostream>
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "SOIL2/SOIL2.h"
#include "glm/glm.hpp"
#include "glm/gtc/type_ptr.hpp"
#include "Shader.h"
void processInput(GLFWwindow *window);
//顶点着色器源码
//顶点着色器主要的目的是把3D坐标转为另一种3D坐标(后面会解释),
//同时顶点着色器允许我们对顶点属性进行一些基本处理。
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";
//片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。
//通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
//对象着色器源码
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";
int main() {
//初始化GLFW
glfwInit();
//使用flfwWindowHint函数来配置GLFW
/*
glfwWindowHint函数的第一个参数代表选项的名称,
我们可以从很多以GLFW_开头的枚举值中选择;
第二个参数接受一个整型,
用来设置这个选项的值
*/
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//glfwCreateWindow函数需要窗口的宽和高作为它的前两个参数
//第三个参数表示这个窗口的名称
GLFWwindow* window = glfwCreateWindow(800, 600, "B7040312", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
//创建完窗口我们就可以通知GLFW将我们窗口的上下文设置为当前线程的主上下文了
glfwMakeContextCurrent(window);
//GLEW是用来管理OpenGL的函数指针的,所以在调用任何OpenGL的函数之前我们需要初始化GLEW
//glewExperimental所做的是即使驱动程序的扩展程序字符串中不存在扩展程序,也允许加载扩展程序入口点。
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
std::cout << "Failed to initialize GLEW" << std::endl;
return -1;
}
/*
开始渲染之前还有一件重要的事情要做,我们必须告诉OpenGL渲染窗口的尺寸大小,这样OpenGL才只能知道怎样相对于窗口大小显示数据和坐标。
我们可以通过调用glViewport函数来设置窗口的维度(Dimension):
*/
/*
然而,当用户改变窗口的大小的时候,视口也应该被调整。
我们可以对窗口注册一个回调函数(Callback Function),
它会在每次窗口大小被调整的时候被调用
*/
int width, height;
glfwGetFramebufferSize(window, &width, &height);
/*
glViewport函数前两个参数控制窗口左下角的位置。
第三个和第四个参数控制渲染窗口的宽度和高度(像素),这里我们是直接从GLFW中获取的。
*/
glViewport(0, 0, width, height);
//创建着色器对象 创建类型 附加着色器源码 编译
// build and compile our shader program
// ------------------------------------
// vertex shader(顶点着色器)
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// check for shader compile errors
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// fragment shader(片段着色器)
int 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;
}
// link shaders
int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// check for linking errors
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
/*
由于我们希望渲染一个三角形,我们一共要指定三个顶点,每个顶点都有一个3D位置。
我们会将它们以标准化设备坐标的形式(OpenGL的可见区域)定义为一个float数组。
由于OpenGL是在3D空间中工作的,而我们渲染的是一个2D三角形,我们将它顶点的z坐标设置为0.0。
这样子的话三角形每一点的深度(Depth,译注2)都是一样的,从而使它看上去像是2D的。
*/
float vertices[] = {
0.5f, 0.5f, 0.0f, // top right
0.5f, -0.5f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f // top left
};
unsigned int indices[] = { // note that we start from 0!
0, 1, 3, // first Triangle
1, 2, 3 // second Triangle
};
/*
定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。
它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。
顶点着色器接着会处理我们在内存中指定数量的顶点。
我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,
它会在GPU内存(通常被称为显存)中储存大量顶点。
使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。
从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。
当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。
*/
//顶点缓冲对象
//顶点数组对象
unsigned int VBO, VAO, EBO;
//使用glGenVertexArrays函数和一个缓冲ID生成一个VAO对象:
glGenVertexArrays(1, &VAO);
//VBO:
//使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:
//OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。
//OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。
glGenBuffers(1, &VBO);
//EBO:
//使用glGenBuffers函数和一个缓冲ID生成一个EBO对象:
glGenBuffers(1, &EBO);
//VAO:首先绑定顶点数组对象
glBindVertexArray(VAO);
//VBO:
//我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:
//从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用
//都会用来配置当前绑定的缓冲(VBO)。
glBindBuffer(GL_ARRAY_BUFFER, VBO);
//VAO:然后把顶点数组复制到缓冲中供OpenGL使用
//VBO:然后我们可以调用glBufferData函数,把索引复制到缓冲里
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
//EBO:
//我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:
//从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用
//都会用来配置当前绑定的缓冲(VBO)。
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
//VAO:最后设置顶点属性指针
//对glVertexAttribPointer的调用将VBO注册为顶点属性的绑定顶点缓冲区对象,因此之后我们可以安全地解除绑定
//链接顶点属性
//每个顶点属性从一个VBO管理的内存中获得它的数据,而具体是从哪个VBO(程序中可以有多个VBO)
//获取则是通过在调用glVertexAttribPointer时绑定到GL_ARRAY_BUFFER的VBO决定的。
//由于在调用glVertexAttribPointer之前绑定的是先前定义的VBO对象,顶点属性0现在会链接到它的顶点数据。
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//我们现在应该使用glEnableVertexAttribArray,以顶点属性位置值作为参数,启用顶点属性;顶点属性默认是禁用的
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//之后您可以解除绑定VAO,这样其他VAO调用就不会意外修改此VAO,但这很少发生。 修改其他
// VAO无论如何都需要调用glBindVertexArray,因此通常在不需要时,我们通常不会取消绑定VAO(也没有VBO)。
glBindVertexArray(0);
/*
一般当你打算绘制多个物体时,你首先要生成/配置所有的VAO(和必须的VBO及属性指针),
然后储存它们供后面使用。
当我们打算绘制物体的时候就拿出相应的VAO,绑定它,绘制完物体后,再解绑VAO。
*/
/*
我们希望程序在我们主动关闭它之前不断绘制图像并能够接受用户输入。
因此,我们需要在程序中添加一个while循环,我们可以把它称之为渲染循环(Render Loop),
它能在我们让GLFW退出前一直保持运行。
下面几行的代码就实现了一个简单的渲染循环:
*/
/*
glfwWindowShouldClose函数在我们每次循环的开始前检查一次GLFW是否被要求退出,
如果是的话该函数返回true然后渲染循环便结束了,之后为我们就可以关闭应用程序了。
*/
while (!glfwWindowShouldClose(window))
{
// input
// -----
processInput(window);
// render
// ------
//要把所有的渲染(Rendering)操作放到渲染循环中,
//因为我们想让这些渲染指令在每次渲染循环迭代的时候都能被执行。
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// draw our first triangle
glUseProgram(shaderProgram);
glBindVertexArray(VAO); //我们只有一个VAO,因此不必每次都绑定它,但是我们这样做是为了使事情更有条理
//glDrawArrays函数,它使用当前激活的着色器,之前定义的顶点属性配置,和VBO的顶点数据(通过VAO间接绑定)来绘制图元。
//glDrawArrays(GL_TRIANGLES, 0, 3);
//用glDrawElements来替换glDrawArrays函数,来指明我们从索引缓冲渲染。
//使用glDrawElements时,我们会使用当前绑定的索引缓冲对象中的索引进行绘制:
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
/*
glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),
它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上。
*/
/*
glfwPollEvents函数检查有没有触发什么事件(比如键盘输入、鼠标移动等)、更新窗口状态,
并调用对应的回调函数(可以通过回调方法手动设置)
*/
glfwSwapBuffers(window);
glfwPollEvents();
}
//delete
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
/*
当渲染循环结束后我们需要正确释放/删除之前的分配的所有资源。
我们可以在main函数的最后调用glfwTerminate函数来完成。
*/
glfwTerminate();
return 0;
}
void processInput(GLFWwindow *window)
{
//检测是否按下esc,按下则退出循环
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
-
效果