OpenGL編程/現代OpenGL介紹
簡介
[編輯]大多數OpenGL方面的文檔都在使用一些已被廢棄或不贊成使用的特性,尤其是「固定流水線」。 OpenGL 2.0及其後包含了一個可編程流水線(programmable pipeline)——可編程部分在着色器(shader)中完成,由GLSL這麼一種像C的語言寫就。
本文檔寫作的目標羣體是那些正在學習OpenGL並且希望一開始就使用現代OpenGL的人。 可編程流水線更加靈活,但沒有固定流水線那麼直覺化。不過我們會保證以簡單代碼作爲開始。我們會使用類似於NeHe對OpenGL 1.x的教程那樣的方式,通過例子和教程來更好地理解可編程流水線背後的理念。
剛開始時頂點數組和着色器的使用看似十分痛苦,尤其在和舊式立即模式(immediate mode)和固定着色流水線進行對比時。[1]然而到了最後時——尤其當你在使用緩衝器對象時——你的代碼會乾淨更甚而且圖像會更快。
本頁的代碼示例均在共有領域下。可以盡情對它們做你想做的事。
分享該文檔給你的朋友!維基教科書應當得到更多認同和貢獻 :)
注意:
- 在某種程度上,的確可以混合固定流水線和可編程流水線,但是固定流水線已被不贊成使用,並且在OpenGL ES 2.0(或其WebGL衍生品)中完全不可用,所以我們不打算使用它。
- 現在已經有了OpenGL 3和4——值得提到的是它們引入了幾何着色器——但和之前相比,這只是一個輕微的進化。而且它在移動平臺上不可用(2012年),所以我們暫時繼續專注於OpenGL 2.0。
基礎庫
[編輯]示例代碼
[編輯]顯示一個2D的三角形
[編輯]Makefile
[編輯]GNU/Linux或MinGW
[編輯]MacOS
[編輯]其他系統
[編輯]初始化
[編輯]我們來創建文件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 <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之間,但我們的窗口不是方形的!在下一課中,我們會看到如何修復該外觀比例(aspect ratio)
頂點着色器
[編輯]區片着色器
[編輯]GLSL程序
[編輯]將三角形頂點傳給頂點着色器
[編輯]我們提到過:我們應該把三角形的每個頂點都傳給頂點着色器——使用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
中的數據緩衝區獲取每個頂點,並將它傳給頂點着色器。這些頂點定義了每個點在屏幕上的位置,並組成一個三角形——其像素使用區片着色器上色。
注意:在下一個教程中,我們會介紹頂點緩衝對象(Vertex Buffer Objects)——一種略複雜且更新的在顯示卡中存儲頂點的方式。
唯一剩下的部分是free_resources
——用來在我們退出程序時做些清理工作。
在本例中它並不致命,但用這種方式來組織應用程序是很好的做法:
void free_resources() {
glDeleteProgram(program);
}
我們的第一個OpenGL 2.0程序完成了!
如果它失敗了
[編輯]測試吧!
[編輯]備註
[編輯]- ↑ 因爲非常多3D特性在OpenGL 2中被移除,一些人甚是有趣地將它定義爲一個2D光柵引擎!