OpenGL 程式設計/現代 OpenGL 入門

大多數關於 OpenGL 的文件都使用了即將被棄用的功能,特別是“固定流水線”。OpenGL 2.0 及更高版本配備了可程式設計流水線,其中可程式設計部分由著色器完成,著色器使用類似 C 語言的GLSL編寫。
本文件適用於學習 OpenGL 並希望從一開始就使用現代 OpenGL 的人員。可程式設計流水線更靈活,但不如固定流水線直觀。但是,我們將確保我們首先使用簡單的程式碼。我們將使用類似於 NeHe 的 OpenGL 1.x 教程的方法,透過示例和教程來更好地理解可程式設計流水線背後的理論。
最初,與舊的立即模式和固定渲染流水線相比,頂點陣列和著色器看起來像是令人頭疼的東西。[1]但是,最終,特別是如果您使用緩衝區物件,您的程式碼將更加簡潔,圖形將更快。
此頁面中的程式碼示例屬於公有領域。請隨意使用它們。
分享此文件給您的朋友!Wikibooks 值得獲得更多認可和貢獻 :)
備註
- 在某種程度上可以混合使用固定流水線和可程式設計流水線,但固定流水線正在被棄用,並且在 OpenGL ES 2.0(或其 WebGL 派生版本)中完全不可用,因此我們不會使用它。
- 現在有 OpenGL 3 和 4,它們顯著引入了幾何著色器,但這一次與之前的版本相比,它只是一個輕微的演變。由於截至 2012 年它在移動平臺上不可用,因此我們目前將專注於 OpenGL 2.0。
您需要確保 OpenGL、SDL2 和 GLEW 已準備好使用。
在安裝頁面中描述瞭如何設定系統。
要將這些庫置於 OpenGL 堆疊中,請檢視API、庫和縮寫。
注意:我們選擇 SDL2 是因為它非常便攜,並且同時面向桌面和移動平臺。我們不會使用高階功能,並且我們幾乎所有的程式碼都將是純 OpenGL,因此您在切換到其他庫(如 FreeGLUT、GLFW 或 SFML)時不會有任何麻煩。我們可能會編寫專門的教程來介紹如何切換到這些庫。
您可以使用 Git 從gitlab 倉庫下載原始碼示例。
您可以直接執行它們,或者按照說明逐步建立您的第一個 OpenGL 應用程式。
讓我們從簡單開始 :) 而不是與一個複雜的程式作鬥爭,這個程式需要很長時間才能第一次執行,我們的目標是獲得一個基本但功能齊全的程式,然後我們可以逐步對其進行改進。
三角形是 3D 程式設計中最基本的單元。實際上,您在影片遊戲中看到的所有內容都是由三角形組成的!小的、帶紋理的三角形,但仍然是三角形 :)
為了在可程式設計流水線中顯示三角形,我們至少需要
- 一個 Makefile 來構建我們的應用程式
- 初始化 OpenGL 和輔助庫
- 一個包含三角形 3 個頂點(頂點的複數形式,即 3D 點)座標的陣列
- 一個 GLSL 程式,其中包含
- 一個頂點著色器:我們將每個頂點單獨傳遞給它,它將計算它們在螢幕上的(2D)座標
- 一個片段(畫素)著色器:OpenGL 將傳遞給它包含在我們三角形中的每個畫素,它將計算其顏色
- 將頂點傳遞給頂點著色器
程式碼示例儲存庫(請參閱頁面末尾的連結)使用 Makefile,因為它們是最簡單的構建工具。
為我們的示例配置“make”非常容易。在“Makefile”檔案中寫入以下內容
LDLIBS=-lglut -lGLEW -lGL -lSDL2
all: triangle
clean:
rm -f *.o triangle
.PHONY: all clean
要編譯您的應用程式,請在終端中輸入
make
一個更具可移植性的 Makefile 如下所示
CPPFLAGS=$(shell sdl2-config --cflags) $(EXTRA_CPPFLAGS)
LDLIBS=$(shell sdl2-config --libs) -lGLEW $(EXTRA_LDLIBS)
EXTRA_LDLIBS?=-lGL
all: triangle
clean:
rm -f *.o triangle
.PHONY: all clean
這允許您鍵入make clean,它將刪除三角形二進位制檔案和可能已生成的任何中間物件檔案。.PHONY 規則用於告訴 make“all”和“clean”不是檔案,因此如果在同一目錄中實際存在名為這樣的檔案,它不會感到困惑。
有關其他 MinGW/MXE 設定,請參閱安裝/Windows。
Mac OS X 的 Makefile 如下所示
CFLAGS=-I/opt/local/include/
LDFLAGS=-L/opt/local/lib/ -I/opt/local/include/
LDLIBS=-lGLEW -framework GLUT -framework OpenGL -framework Cocoa
all: triangle
clean:
rm -f *.o triangle
此 makefile 假設您使用MacPorts安裝了 glew。
如果您想使用不同的程式設計環境,請參閱設定 OpenGL部分。
請參閱安裝頁面以獲取有關如何配置構建環境的提示。
讓我們建立一個名為triangle.cpp的檔案
/* Using standard C++ output libraries */
#include <cstdlib>
#include <iostream>
using namespace std;
/* Use glew.h instead of gl.h to get all the GL prototypes declared */
#include <GL/glew.h>
/* Using SDL2 for the base window and OpenGL context init */
#include <SDL2/SDL.h>
/* ADD GLOBAL VARIABLES HERE LATER */
bool init_resources(void) {
/* FILLED IN LATER */
return true;
}
void render(SDL_Window* window) {
/* FILLED IN LATER */
}
void free_resources() {
/* FILLED IN LATER */
}
void mainLoop(SDL_Window* window) {
while (true) {
SDL_Event ev;
while (SDL_PollEvent(&ev)) {
if (ev.type == SDL_QUIT)
return;
}
render(window);
}
}
int main(int argc, char* argv[]) {
/* SDL-related initialising functions */
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = SDL_CreateWindow("My First Triangle",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
640, 480,
SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
SDL_GL_CreateContext(window);
/* Extension wrangler initialising */
GLenum glew_status = glewInit();
if (glew_status != GLEW_OK) {
cerr << "Error: glewInit: " << glewGetErrorString(glew_status) << endl;
return EXIT_FAILURE;
}
/* When all init functions run without errors,
the program can initialise the resources */
if (!init_resources())
return EXIT_FAILURE;
/* We can display something if everything goes OK */
mainLoop(window);
/* If the program exits in the usual way,
free resources and exit with a success */
free_resources();
return EXIT_SUCCESS;
}
在init_resources中,我們將建立我們的 GLSL 程式。在render中,我們將繪製三角形。在free_resources中,我們將銷燬 GLSL 程式。
我們的第一個三角形將在 2D 中顯示 - 我們很快就會轉向更復雜的內容。我們使用其 3 個點的 2D(x,y)座標來描述三角形。預設情況下,OpenGL 的座標在 [-1,1] 範圍內。
GLfloat triangle_vertices[] = {
0.0, 0.8,
-0.8, -0.8,
0.8, -0.8,
};
現在讓我們先記住這個資料結構,我們稍後將在程式碼中編寫它。
注意:座標介於 -1 和 +1 之間,但我們的視窗不是正方形!在下一課中,我們將瞭解如何修復縱橫比。
這是 GLSL 程式,它將逐個獲取我們陣列中的每個點,並告訴在哪裡將它們放在螢幕上。在本例中,我們的點已在 2D 螢幕座標中,因此我們不會更改它們。我們的 GLSL 程式如下所示
#version 120
attribute vec2 coord2d;
void main(void) {
gl_Position = vec4(coord2d, 0.0, 1.0);
}
#version 120表示 v1.20,即 OpenGL 2.1 中 GLSL 的版本。- OpenGL ES 2 的 GLSL 也基於 GLSL v1.20,但其版本為 1.00(
#version 100)。[2] coord2d是當前頂點;它是我們需要在 C 程式碼中宣告的輸入變數gl_Position是最終的螢幕位置;它是一個內建的輸出變數。vec4獲取我們的 *x* 和 *y* 座標,然後是 *z* 座標的0。最後一個,*w*=1.0用於齊次座標(用於 變換矩陣)。
現在我們需要讓 OpenGL 編譯這個著色器。在 main 函式上方開始 init_resources 函式。
bool init_resources() {
GLint compile_ok = GL_FALSE, link_ok = GL_FALSE;
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
const char *vs_source =
//"#version 100\n" // OpenGL ES 2.0
"#version 120\n" // OpenGL 2.1
"attribute vec2 coord2d; "
"void main(void) { "
" gl_Position = vec4(coord2d, 0.0, 1.0); "
"}";
glShaderSource(vs, 1, &vs_source, NULL);
glCompileShader(vs);
glGetShaderiv(vs, GL_COMPILE_STATUS, &compile_ok);
if (!compile_ok) {
cerr << "Error in vertex shader" << endl;
return false;
}
}
我們將原始碼作為字串傳遞給 glShaderSource(稍後我們將以不同且更方便的方式讀取著色器程式碼)。我們指定型別 GL_VERTEX_SHADER。
一旦 OpenGL 獲取了我們的 3 個點螢幕位置,它將填充它們之間的空間以形成一個三角形。對於 3 個點之間的每個畫素,它將呼叫片段著色器。在我們的片段著色器中,我們將說明我們希望將每個畫素顏色設定為藍色(這設定了 RGB - 紅、綠、藍顏色分量)。
#version 120
void main(void) {
gl_FragColor[0] = 0.0;
gl_FragColor[1] = 0.0;
gl_FragColor[2] = 1.0;
}
我們以類似的方式編譯它,型別為 GL_FRAGMENT_SHADER。讓我們繼續我們的 init_resources 過程。
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
const char *fs_source =
//"#version 100\n" // OpenGL ES 2.0
"#version 120\n" // OpenGL 2.1
"void main(void) { "
" gl_FragColor[0] = 0.0; "
" gl_FragColor[1] = 0.0; "
" gl_FragColor[2] = 1.0; "
"}";
glShaderSource(fs, 1, &fs_source, NULL);
glCompileShader(fs);
glGetShaderiv(fs, GL_COMPILE_STATUS, &compile_ok);
if (!compile_ok) {
cerr << "Error in fragment shader" << endl;
return false;
}
GLSL 程式是頂點著色器和片段著色器的組合。通常它們一起工作,頂點著色器甚至可以將其他資訊傳遞給片段著色器。
在 #include 下方建立一個全域性變數來儲存程式控制代碼。
GLuint program;
以下是將頂點和片段著色器連結到程式中的方法。繼續我們的 init_resources 過程,使用以下程式碼:
program = glCreateProgram();
glAttachShader(program, vs);
glAttachShader(program, fs);
glLinkProgram(program);
glGetProgramiv(program, GL_LINK_STATUS, &link_ok);
if (!link_ok) {
cerr << "Error in glLinkProgram" << endl;
return false;
}
我們提到過,我們使用 coord2d 屬性將每個三角形頂點傳遞給頂點著色器。以下是如何在 C 程式碼中宣告它。
首先,讓我們建立一個第二個全域性變數。
GLint attribute_coord2d;
使用以下程式碼結束我們的 init_resources 過程:
const char* attribute_name = "coord2d";
attribute_coord2d = glGetAttribLocation(program, attribute_name);
if (attribute_coord2d == -1) {
cerr << "Could not bind attribute " << attribute_name << endl;
return false;
}
return true;
}
現在我們可以將三角形頂點傳遞給頂點著色器。讓我們編寫我們的 render 過程。每個部分在註釋中都有解釋。
void render(SDL_Window* window) {
/* Clear the background as white */
glClearColor(1.0, 1.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(program);
glEnableVertexAttribArray(attribute_coord2d);
GLfloat triangle_vertices[] = {
0.0, 0.8,
-0.8, -0.8,
0.8, -0.8,
};
/* Describe our vertices array to OpenGL (it can't guess its format automatically) */
glVertexAttribPointer(
attribute_coord2d, // attribute
2, // number of elements per vertex, here (x,y)
GL_FLOAT, // the type of each element
GL_FALSE, // take our values as-is
0, // no extra data between each position
triangle_vertices // pointer to the C array
);
/* Push each element in buffer_vertices to the vertex shader */
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableVertexAttribArray(attribute_coord2d);
/* Display the result */
SDL_GL_SwapWindow(window);
}
glVertexAttribPointer 告訴 OpenGL 從 init_resources 中建立的資料緩衝區中檢索每個頂點,並將其傳遞給頂點著色器。這些頂點定義了每個點的螢幕位置,形成一個三角形,其畫素由片段著色器著色。
注意:在下一個教程中,我們將介紹頂點緩衝物件,這是一種稍微複雜一些且更新的方法,用於在圖形卡中儲存頂點。
唯一剩下的部分是 free_resources,用於在退出程式時進行清理。在本例中它不是必需的,但以這種方式構建應用程式是一個好習慣。
void free_resources() {
glDeleteProgram(program);
}
我們的第一個 OpenGL 2.0 程式完成了!
下一個教程將為我們第一個極簡程式碼新增更多健壯性。確保嘗試 tut02 程式碼(請參閱下面的程式碼連結)。

隨意嘗試此程式碼。
- 嘗試透過顯示 2 個三角形來建立一個正方形。
- 嘗試更改顏色。
- 閱讀我們使用的每個函式的 OpenGL 手冊頁。
- 嘗試在片段著色器中使用此程式碼 - 它有什麼作用?
gl_FragColor[0] = gl_FragCoord.x/640.0;
gl_FragColor[1] = gl_FragCoord.y/480.0;
gl_FragColor[2] = 0.5;
在下一個教程中,我們將新增一些實用函式,以便更容易地編寫和除錯著色器。
- ↑ 由於 OpenGL 2 中刪除了許多 3D 功能,因此有些人很有趣地 將其定義為一個 2D 光柵化引擎!
- ↑ "OpenGL ES Shading Language 1.0.17 Specification" (PDF). Khronos.org. 2009-05-12. 檢索於 2011-09-10.
OpenGL ES Shading Language(也稱為 GLSL ES 或 ESSL)基於 OpenGL Shading Language(GLSL)版本 1.20