開始學習OpenGL,參考的是著名的LearnOpenGL這個網站,在這里做一些總結性的記錄,只是方便自己日后查找或者記錄自己的一些拓展思考,關於OpenGL的具體內容請移步:
https://learnopengl-cn.github.io/
或英文原版:https://learnopengl.com/
配置環境
LearnOpenGL中使用了GLFW和GLAD兩個庫來配置環境,原文已經很詳細地列出了所有步驟,就不再多說了,獲取兩個庫之后在Visual Studio的項目屬性中的VC++目錄
中,將兩個庫的include文件夾加入包含目錄,將GLFW的lib文件夾加入庫目錄,然后在鏈接器
的輸入中把glfw3.lib這個文件加入附加依賴項,最后別忘記把glad.c
這個文件加入到工程中。
初始化OpenGL
首先包含之前引入的兩個庫的頭文件,注意一定要先引入glad.h
#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將我們窗口的上下文設置為當前線程的主上下文
初始化GLAD
調用OpenGL的函數之前需初始化GLAD用於管理函數指針。
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
視口設置
最后還需要設置OpenGL的視口大小,並編寫用戶改變窗口大小時的回調函數。
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";
創建並編譯Shader
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();//檢查有沒有觸發什么事件
}

繪制三角形完成!