在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线管理的。
图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。
一、图形渲染管线(PipeLine)
渲染管线又称渲染流水线,它是图形图像从数据一步一步形成最终输出的画面所要经历的各种操作过程。
指的是对一些原始数据经过一系列的处理变换并最终把这些数据输出到屏幕上的整个过程。
OpenGL(开放图形库)是一种应用程序编程接口(Application Programming Interface,API)它是一种可以对图形硬件设备特征进行访问的软件库。
在OpenGL 3.0以前的版本或者使用兼容模式的OpenGL环境,OpenGL包含一个固定管线(fixed-function pipeline),它可以在不使用着色器的环境下处理几何与像素数据。我们看到的glBegin()、glRectf()以及glEnd()这些函数都是以前固定管线模式中所使用的API函数。
从3.1版本开始,固定管线从核心模式中去除,因此我们必须使用着色器来完成工作。现代OpenGL渲染管线严重依赖着色器来处理传入的数据,我们一般会使用GLSL(OpenGL Shading Language)编写着色器程序,GLSL语法类似于C语言,GLSL编译以后运行在GPU端。
现代OpenGL是用的"可编程式图形管线",之前则是用的"固定管线"。
二、流程
图形渲染管线的整个处理流程可以被划分为几个阶段,上一个阶段的输出数据作为下一个阶段的输入数据,是一个串行的,面向过程的执行过程。每一个阶段分别在GPU上运行各自的数据处理程序,这个程序就是着色器。
下边是图形渲染管线每一个阶段的抽象表示,蓝色部分代表允许自定义着色器。
1、顶点数据是一些顶点的集合,顶点一般是3维的点坐标组成。
2、基本图元(Primitives)包括点,线段,三角形等,是构成实体模型的基本单位,需要在传入顶点数据的同时通知openGL这些顶点数据要组成的基本图元类型,并将并所有的点装配成指定图元的形状。
3、顶点着色器(Vertex Shader)包含对一些顶点属性(数据)的基本处理。
4、图元装配(Primitive Assembly)把所有输入的顶点数据作为输入,并将所有的点装配成指定图元的形状。
5、几何着色器(Geometry Shader):把基本图元形式的顶点的几何作为输入,可以通过产生新顶点构造出新的基本图元来生成其它形状。
6、细分着色器(Tessellation Shader):可以把基本图元细分为更多的基本图形,创建出更加平滑的视觉效果。
7、光栅化(Rasterization):即像素化。把细分着色器输出的基本图形映射为屏幕上网格的像素点,生成供片段着色器处理的片段(Fragment),光栅化包含一个剪裁操作,会舍弃超出定义的视窗之外的像素。
8、片段着色器(Fragment Shader):主要作用是计算出每一个像素点最终的颜色,通常片段着色器会包含3D场景的一些额外的数据,如光线、阴影等。
9、测试与混合:是对每个像素点进行深度测试,Alpha测试等测试并进行颜色混合的操作,这些测试与混合操作决定了屏幕视窗上每个像素点最终的颜色以及透明度。
在整个渲染管线中需要自定义处理的主要是顶点着色器和片段着色器。
三、顶点输入:
OpenGL是一个3D图形库,所以我们在OpenGL中指定的所有坐标都是3D坐标(x、y和z)。OpenGL不是简单地把所有的3D坐标变换为屏幕上的2D像素;OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。所有在所谓的标准化设备坐标(Normalized Device Coordinates)范围内的坐标才会最终呈现在屏幕上(在这个范围以外的坐标都不会显示)。
渲染一个三角形,我们一共要指定三个顶点,每个顶点都有一个3D位置。我们会将它们以标准化设备坐标的形式(OpenGL的可见区域)定义为一个float
数组。
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};
由于OpenGL是在3D空间中工作的,而我们渲染的是一个2D三角形,我们将它顶点的z坐标设置为0.0。这样子的话三角形每一点的深度都是一样的,从而使它看上去像是2D的。
标准化设备坐标接着会变换为屏幕坐标,这是使用glViewport函数提供的数据,进行视口变换(Viewport Transform)完成的。
示例程序:
#include <GL/glew.h>
#include <SFML/Window.hpp> #define GLSL(src) "#version 150 core\n" #src//一个宏定义,用于将GLSL的源文件和前面的版本声明信息链接起来。 // Vertex渲染器代码片段,用于接收输入的顶点位置和颜色,并输出颜色传递给片元着色器
const GLchar* vertexSource = GLSL(
in vec2 position;
in vec3 color;
out vec3 mColor;
void main() {
mColor = color;
gl_Position = vec4(position, 0.0f, 1.0f);
}
); // Fragment渲染器片段,接受了顶点着色器的顶点颜色信息
const GLchar* fragmentSource = GLSL(
in vec3 mColor;
out vec4 oColor;
void main() {
oColor = vec4(mColor, 1.0f);
}
); // 创建指定类型的Shader
GLuint createShader(GLenum type, const GLchar* src)
{
GLuint shader = glCreateShader(type);
glShaderSource(shader, , &src, nullptr);
glCompileShader(shader);
return shader;
} int main(int argc, char* argv[])
{
// 初始化Window窗口
sf::ContextSettings settings;
settings.depthBits = ;
settings.stencilBits = ; const unsigned int WIDTH = ;
const unsigned int HEIGHT = ;
const sf::String TITLE = "Modern OpenGL";
sf::Window window(sf::VideoMode(WIDTH, HEIGHT, ), TITLE,
sf::Style::Titlebar | sf::Style::Close, settings); // 初始化GLEW
glewExperimental = GL_TRUE;
glewInit(); // 顶点数据
GLfloat vertices[] = {
0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f
}; // 创建Vertex Array Object
GLuint VAO;
glGenVertexArrays(, &VAO);
glBindVertexArray(VAO); // 创建Vertex Buffer Object并装载数据
GLuint VBO;
glGenBuffers(, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 创建Vertex渲染器和Fragment渲染器
GLuint vertexShader = createShader(GL_VERTEX_SHADER, vertexSource);
GLuint fragmentShader = createShader(GL_FRAGMENT_SHADER, fragmentSource); // 将Vertex渲染器和Fragment渲染器关联到Shader Program
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glUseProgram(shaderProgram); // 设置Vertex数据的布局属性(这里包括postion和color两个属性)
GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
glEnableVertexAttribArray(posAttrib);
glVertexAttribPointer(posAttrib, , GL_FLOAT, GL_FALSE, * sizeof(GLfloat), ); GLint colorAttrib = glGetAttribLocation(shaderProgram, "color");
glEnableVertexAttribArray(colorAttrib);
glVertexAttribPointer(colorAttrib, , GL_FLOAT, GL_FALSE, * sizeof(GLfloat), (void*)( * sizeof(GLfloat))); // 这个While循环是SFML的固定模式用于做事件处理
while (window.isOpen())
{
// 内层While循环用于处理事件响应
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
window.close();
}
window.setActive();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 绘制图形
glDrawArrays(GL_TRIANGLES, , );
window.display();
}
// 释放资源
glDeleteProgram(shaderProgram);
glDeleteShader(fragmentShader);
glDeleteShader(vertexShader); glDeleteBuffers(, &VBO);
glDeleteVertexArrays(, &VAO); return EXIT_SUCCESS;
}
最后的效果: