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光栅引擎!