跳轉至內容

OpenGL 程式設計/現代 OpenGL 入門

來自 Wikibooks,開放世界中的開放書籍
我們的第一個程式

大多數關於 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 應用程式。

在 2D 中顯示三角形

[編輯 | 編輯原始碼]

讓我們從簡單開始 :) 而不是與一個複雜的程式作鬥爭,這個程式需要很長時間才能第一次執行,我們的目標是獲得一個基本但功能齊全的程式,然後我們可以逐步對其進行改進。

三角形是 3D 程式設計中最基本的單元。實際上,您在影片遊戲中看到的所有內容都是由三角形組成的!小的、帶紋理的三角形,但仍然是三角形 :)

為了在可程式設計流水線中顯示三角形,我們至少需要

  • 一個 Makefile 來構建我們的應用程式
  • 初始化 OpenGL 和輔助庫
  • 一個包含三角形 3 個頂點(頂點的複數形式,即 3D 點)座標的陣列
  • 一個 GLSL 程式,其中包含
    • 一個頂點著色器:我們將每個頂點單獨傳遞給它,它將計算它們在螢幕上的(2D)座標
    • 一個片段(畫素)著色器:OpenGL 將傳遞給它包含在我們三角形中的每個畫素,它將計算其顏色
  • 將頂點傳遞給頂點著色器

程式碼示例儲存庫(請參閱頁面末尾的連結)使用 Makefile,因為它們是最簡單的構建工具。

GNU/Linux 或 MinGW

[編輯 | 編輯原始碼]

為我們的示例配置“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 程式

[編輯 | 編輯原始碼]

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;

在下一個教程中,我們將新增一些實用函式,以便更容易地編寫和除錯著色器。

  1. 由於 OpenGL 2 中刪除了許多 3D 功能,因此有些人很有趣地 將其定義為一個 2D 光柵化引擎
  2. "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

< OpenGL 程式設計

瀏覽和下載 完整程式碼
華夏公益教科書