開始學習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();//檢查有沒有觸發什麼事件 }
繪製三角形完成!