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程序完成了!

如果它失败了[编辑]

测试吧![编辑]

备注[编辑]

  1. 因为非常多3D特性在OpenGL 2中被移除,一些人甚是有趣地将它定义为一个2D光栅引擎