从OpenGL 1.x 到 2.x的迁移(即从固定管线到可编程管线的迁移)

OPENGL – THEN AND NOW

I had spent a fair amount of time on OpenGL about 10 years back, though I wouldn’t call myself an expert. Over these 10 years, I noticed OpenGL evolving and kept pace with it from the outside. Then came WebGL and wanted to get my hands dirty. That’s when I realized that I was way out of touch. As they say, the devil is in the details. All the terminology and jargon just wasn’t adding up. So I went back to basics.

Here is an attempt to summarize the evolution and status of OpenGL. It’s not meant to be an introduction to OpenGL but more for those who want to go from “then” to “now” in one page. For more details, see OpenGL – VBO, Shader, VAO.

Background

Traditionally, all graphics processing was done in the CPU which generated a bitmap (pixel image) in the frame buffer (a portion in RAM) and pushed it to the video display. Graphics Processing Unit (GPU) changed that paradigm. It was specialized hardware to do the heavy graphics computations. The GPU provided a set of “fixed” functions to do some standard operations on the graphics data, which is referred to as the Fixed Function Pipeline (FFP). Though the Fixed Function Pipelline was fast and efficient, it lacked flexibility. So GPUs introduced the Programmable Pipeline, the programmable alternative to the “hard coded” approach.

OpenGL

OpenGL 1.0 (Classic OpenGL) provided libraries to compute on the CPU and interfaced with the Fixed Function Pipeline. OpenGL 2.0 (and higher) adds Programmable Pipeline API.

OpenGL-ES (GLES)

OpenGL ES is OpenGL for Embedded Systems for mobile phones, PDAs, and video game consoles, basically for devices with limited computation capability. It consists of well-defined subsets of desktop OpenGL. Desktop graphics card drivers typically did not support the OpenGL-ES API directly. However, as of 2010 graphics card manufacturers introduced ES support in their desktop drivers and this makes the ES term in the specification confusing. OpenGL ES 2.0 is based on OpenGL 2.0 with the fixed function API removed.

WebGL

WebGL is a Programmable Pipeline API, with constructs that are semantically similar to those of the underlying OpenGL ES 2.0 API. It stays very close to the OpenGL ES 2.0 specification, with some concessions made for what developers expect with memory-managed languages such as JavaScript. See WebGL and OpenGL.

Programmable Pipeline, Shaders and GLSL

The Programmable Pipeline requires a Program which is “equivalent” to the functions provided by the Fixed Function Pipeline. These programs are called Shaders. The programming language for the shaders used to be assembly language but as the complexity increased, high-level languages for GPU programming emerged, one of which is called OpenGL Shading Language (GLSL). Like any program, the Shader program needs to be compiled and linked. However, the Shader code is loaded to the GPU, compiled and linked at runtime using APIs provided by OpenGL.

Conclusion

So, modern OpenGL is great, except it makes learning graphics programming harder (much harder). It is generally easier to teach new graphics programmers using the Fixed Function Pipeline. Ideas behind Shaders are pretty complicated and the minimum required knowledge in basic 3D programming (vertex creation, transformation matrices, lighting etc.) is substantial. There is a lot more code to write and many more places to screw up. A classic fixed function OpenGL programmer was oblivious to most of these nasty details.

“Then” was Fixed Function Pipeline and “Now” is Programmable Pipeline. Much of what was learned then must be abandoned now. Programmability wipes out almost all of the fixed function pipeline, so the knowledge does not transfer well. To make matters worse, OpenGL has started to deprecate fixed functionality. In OpenGL 3.2, the Core Profile lacks these fixed-function concepts. The compatibility profile keeps them around.

The transition in terms of code and philosophy is detailed in OpenGL – VBO, Shader, VAO.


https://cognitivewaves.wordpress.com/2015/04/24/opengl-then-and-now/


OPENGL – VBO, SHADER, VAO

Tutorial with example

As posted in OpenGL-Then and Now, there has been a paradigm shift in rendering. This is a comprehensive look at the transition from “Then” (Immediate Mode, Fixed Functon Pipeline) to “Now” (Retained Mode, Programmable Pipeline). The simple example will incrementally transition from the legacy approach to the current paradigm.

Introduction
Example
Render
    Immediate using glBegin and glEnd
    Vertex Array to specify vertex data in client memory
    Vertex Buffer Object (VBO) to store vertex data in GPU memory
    Shader to define the program to execute on the GPU
    Shader with Vertex Buffer Object (VBO) to invoke the Programmable Pipeline
    Shader with Verrtex Array Object (VAO) an advanced concept of the Programmable Pipeline
Conclusion

Introduction

A short overview of the underlying concept will help understand the basis of the transition to modern OpenGL. Graphics Processing Unit (GPU) is hardware dedicated (and optimized) for graphics rendering. Modern OpenGL aims for better utilization of the GPU. So the transition is in these broad areas.

LEGACY OPENGLMODERN OPENGL
Specifying vertex data with attributes like coordinates, colors, normals, textures, etc.
Main/Client memoryGPU memory (Vertex Buffer Objects)
Processing of the vertex data with hardware/software along the rendering pipeline
Fixed functionsUser defined programs (Shaders) in the GPU


Example

The example draws lines and triangles. Different colors are used for each of the entities so that problems can be identified easily.

  • The same vertex data, coordinates and color attribute, is used for rendering in various methods.
  • The Cartesian coordinate system axes are drawn in RGB at the origin 0,0,0 (a common notation to represent X-axis in red, Y-axis in Green and Z-axis in Blue).
  • A pyramid with its base in grey and sides in shades of yellow/orange.
  • Key stroke commands are provided to rotate, pan and zoon. The list of commands are shown in the output windows.

Further, the example in order to focus on the concept keeps the necessary API calls in a sequence. It is NOT optimized for performance. The example code can be accessed on GitHub at https://github.com/cognitivewaves/OpenGL-Render.

 

Immediate

The simplest way to “experience” OpenGL is to draw in immediate mode using per vertex attribute specification between glBegin and glEnd.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
static void drawImmediate()
{
     // Draw x-axis in red
     glColor3d(ac[0], ac[1], ac[2]);
     glBegin(GL_LINES);
         glVertex3f(av[0], av[1], av[2]);
         glVertex3f(av[3], av[4], av[5]);
     glEnd();
 
     // Draw y-axis in green
     glColor3d(ac[3], ac[4], ac[5]);
     glBegin(GL_LINES);
         glVertex3f(av[0], av[1], av[2]);
         glVertex3f(av[6], av[7], av[8]);
     glEnd();
 
     // Draw z-axis in blue
     glColor3d(ac[6], ac[7], ac[8]);
     glBegin(GL_LINES);
         glVertex3f(av[0], av[1], av[2]);
         glVertex3f(av[9], av[10], av[11]);
     glEnd();
 
     // Draw pyramid
     glBegin(GL_TRIANGLES);
         glColor3d(pc[0], pc[1], pc[2]);
 
         glVertex3f(pv[0], pv[1], pv[2]);       // 0
         glVertex3f(pv[3], pv[4], pv[5]);       // 1
         glVertex3f(pv[6], pv[7], pv[8]);       // 2
 
         glVertex3f(pv[6], pv[7],  pv[8]);      // 2
         glVertex3f(pv[9], pv[10], pv[11]);     // 3
         glVertex3f(pv[0], pv[1],  pv[2]);      // 0
 
         glColor3f(pc[3], pc[4], pc[5]);
 
         glVertex3f(pv[0],  pv[1],  pv[2]);     // 0
         glVertex3f(pv[9],  pv[10], pv[11]);    // 3
         glVertex3f(pv[12], pv[13], pv[14]);    // 4
 
         glColor3f(pc[6], pc[7], pc[8]);
 
         glVertex3f(pv[9],  pv[10], pv[11]);    // 3
         glVertex3f(pv[6],  pv[7],  pv[8]);     // 2
         glVertex3f(pv[12], pv[13], pv[14]);    // 4
 
         glColor3f(pc[9], pc[10], pc[11]);
 
         glVertex3f(pv[6],  pv[7],  pv[8]);     // 2
         glVertex3f(pv[3],  pv[4],  pv[5]);     // 1
         glVertex3f(pv[12], pv[13], pv[14]);    // 4
 
         glColor3f(pc[12],  pc[13], pc[14]);
 
         glVertex3f(pv[3],  pv[4],  pv[5]);     // 1
         glVertex3f(pv[0],  pv[1],  pv[2]);     // 0
         glVertex3f(pv[12], pv[13], pv[14]);    // 4
     glEnd();
}

Vertex Array

A slightly better way to specify vertex data in immediate mode is using Vertex Arrays (not to be confused with Vertex Array Objects discussed later). The vertex data is stored in array format in client memory. The respective attribute association is specified with glVertexPointer and glColorPointer. Data is transferred to the GPU in bulk for every frame.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void drawVertexArray()
{
     glEnableClientState(GL_VERTEX_ARRAY);
     glEnableClientState(GL_COLOR_ARRAY);
 
     // Set axes data
     glVertexPointer(nVertexComponents, GL_FLOAT, 0, ave);
     glColorPointer(nColorComponents, GL_FLOAT, 0, ace);
 
     // Draw axes
     glDrawArrays(GL_LINES, 0, nLines*nVerticesPerLine);
 
     // Set pyramid data
     glVertexPointer(nVertexComponents, GL_FLOAT, 0, pve);
     glColorPointer(nColorComponents, GL_FLOAT, 0, pce);
 
     // Draw pyramid
     glDrawArrays(GL_TRIANGLES, 0, nFaces*nVerticesPerFace);
 
     glDisableClientState(GL_VERTEX_ARRAY);
     glDisableClientState(GL_COLOR_ARRAY);
}

Extensions

APIs for VBO, Shader, VAO etc. are dependent on OpenGL extensions which have to be loaded dynamically. Though it is a nifty concept, it is not like linking to a dynamic library. Pointers to the required functions have to be loaded explicitly. There are OpenGL Loading Libraries which abstracts (and hides) the loading mechanism. But in the example, it is done manually to get a feel for it. See glext.h and glextload.h in the source code.

Vertex Buffer Object (VBO)

An even better way is to store the vertex data directly on the GPU. These GPU memory pools are called Vertex Buffer Objects. This is achieved by setting a current buffer with glBindBuffer and copying the contents from client memory to the VBO with glBufferData. The GPU can then access the data directly and will save the cost of transferring from client memory to GPU memory every frame. The attribute association is again specified with glVertexPointer and glColorPointer.

⚠ Note the “reuse” of the APIs  glVertexPointer and  glColorPointer to specify the vertex attributes now copied in the VBOs. I say “reuse” because the 4th parameter  pointer has a different meaning depending on the context. So in this case, the same APIs implicitly sets the vertex and color because a VBO is bound prior to the call.

If a non-zero named buffer object is bound to the GL_ARRAY_BUFFER target (see glBindBuffer) while a vertex array is specified, pointer is treated as a byte offset into the buffer object’s data store.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static void drawVertexBufferObject()
{
     LoadGLExtensions();
 
     vboIds = new GLuint[3];
     glGenBuffers(3, vboIds);
 
     glEnableClientState(GL_VERTEX_ARRAY);
     glEnableClientState(GL_COLOR_ARRAY);
 
     // Set axes data
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);  // coordinates
     glBufferData(GL_ARRAY_BUFFER, sizeof (ave), ave, GL_STATIC_DRAW);
     glVertexPointer(nCoordsComponents, GL_FLOAT, 0, 0);
 
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[1]);  // color
     glBufferData(GL_ARRAY_BUFFER, sizeof (ace), ace, GL_STATIC_DRAW);
     glColorPointer(nColorComponents, GL_FLOAT, 0, 0);
 
     // Draw axes
     glDrawArrays(GL_LINES, 0, nLines*nVerticesPerLine);
 
     // Set pyramid data
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[2]);  // coordinates
     glBufferData(GL_ARRAY_BUFFER, sizeof (pve), pve, GL_STATIC_DRAW);
     glVertexPointer(nCoordsComponents, GL_FLOAT, 0, 0);
 
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[3]);  // color
     glBufferData(GL_ARRAY_BUFFER, sizeof (pce), pce, GL_STATIC_DRAW);
     glColorPointer(nColorComponents, GL_FLOAT, 0, 0);
 
     // Draw pyramid
     glDrawArrays(GL_TRIANGLES, 0, nFaces*nVerticesPerFace);
 
     glDisableClientState(GL_VERTEX_ARRAY);
     glDisableClientState(GL_COLOR_ARRAY);
 
     // Disable the VBO
     glBindBuffer(GL_ARRAY_BUFFER, 0);
}

Shader

Shaders are user defined programs/code written in GLSL (OpenGL Shading Language) that are executed on the GPU in the rendering pipeline. Very basic shaders are used as the focus is on the overall setup rather than GLSL.

  • Vertex Shader – applies the projection and model-view matrix to each of the vertices
  • Fragment Shader – applies the color attribute specified at the vertices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const char * vertex_shader =
     "attribute vec3 aCoords;"
     "attribute vec3 aColor;"
     "uniform mat4 umvMat;"
     "uniform mat4 upMat;"
     "varying vec3 vColor;"
     "void main () {"
         "gl_Position = upMat * umvMat * vec4(aCoords, 1.0);"
         "vColor = aColor;"
     "}" ;
 
const char * fragment_shader =
     "varying vec3 vColor;"
     "void main () {"
         "gl_FragColor = vec4 (vColor, 1.0);"
     "}" ;

These programs replace the fixed functions provided in legacy OpenGL. Since these programs run on the GPU, the source written in OpenGL Shading Language (GLSL) has to be first first loaded, then complied and linked on the GPU using appropriate API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GLuint VERTEX_ATTR_COORDS = 1;
GLuint VERTEX_ATTR_COLOR = 2;
 
static void initShaders()
{
     GLuint vs = glCreateShader (GL_VERTEX_SHADER);
     glShaderSource (vs, 1, &vertex_shader, NULL);
     glCompileShader (vs);
 
     GLuint fs = glCreateShader (GL_FRAGMENT_SHADER);
     glShaderSource (fs, 1, &fragment_shader, NULL);
     glCompileShader (fs);
 
     program = glCreateProgram();
     glAttachShader (program, fs);
     glAttachShader (program, vs);
 
     glBindAttribLocation(program, VERTEX_ATTR_COORDS, "aCoords" );
     glBindAttribLocation(program, VERTEX_ATTR_COLOR, "aColor" );
 
     glLinkProgram (program);
 
     glUseProgram (program);
}

Shader with Vertex Buffer Object (VBO)

The shaders are loaded on the GPU, and are responsible for portions of the rendering pipeline. So it is necessary for the shader code to access the vertex data to process it. Since the shader is on the GPU, the vertex data too has to be on the GPU in VBOs. The vertex data is copied to the GPU Vertex Buffer Objects as before with glBindBuffer and glBufferData.

Then comes the most confusing part – associating the attributes (coordinates, color, etc.) in VBOs to be accessed by the shader variables. The magic happens in this one function glVertexAttribPointer which creates generic vertex attributes. It establishes a relation between the bound (current) VBO to the shader attribute variable associated (using glBindAttribLocation) with its index. See OpenGL Terminology Demystified for a detailed explanation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static void drawShaderWithVertexBufferObject()
{
     LoadGLExtensions();
 
     initShaders();
 
     // Get the variables from the shader to which data will be passed
     GLint mvloc = glGetUniformLocation(program, "umvMat" );
     GLint ploc = glGetUniformLocation(program, "upMat" );
     GLint vertexAttribCoords = glGetAttribLocation(program, "aCoords" );
     GLint vertexAttribColor = glGetAttribLocation(program, "aColor" );
     
     // Pass the model-view matrix to the shader
     GLfloat mvMat[16];
     glGetFloatv(GL_MODELVIEW_MATRIX, mvMat);
     glUniformMatrix4fv(mvloc, 1, false , mvMat);
 
     // Pass the projection matrix to the shader
     GLfloat pMat[16];
     glGetFloatv(GL_PROJECTION_MATRIX, pMat);
     glUniformMatrix4fv(ploc, 1, false , pMat);
 
     vboIds = new GLuint[4];
     glGenBuffers(4, vboIds);
 
     // Set axes data
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);  // coordinates
     glBufferData(GL_ARRAY_BUFFER, sizeof (ave), ave, GL_STATIC_DRAW);
     glVertexAttribPointer(vertexAttribCoords, nCoordsComponents, GL_FLOAT, GL_FALSE, 0, 0);
     glEnableVertexAttribArray(vertexAttribCoords);
 
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[1]);  // color
     glBufferData(GL_ARRAY_BUFFER, sizeof (ace), ace, GL_STATIC_DRAW);
     glVertexAttribPointer(vertexAttribColor, nColorComponents, GL_FLOAT, GL_FALSE, 0, 0);
     glEnableVertexAttribArray(vertexAttribColor);
 
     // Draw axes
     glDrawArrays(GL_LINES, 0, nLines*nVerticesPerLine);
 
     // Set pyramid data
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[2]);  // coordinates
     glBufferData(GL_ARRAY_BUFFER, sizeof (pve), pve, GL_STATIC_DRAW);
     glVertexAttribPointer(vertexAttribCoords, nCoordsComponents, GL_FLOAT, GL_FALSE, 0, 0);
     glEnableVertexAttribArray(vertexAttribCoords);
 
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[3]);  // color
     glBufferData(GL_ARRAY_BUFFER, sizeof (pce), pce, GL_STATIC_DRAW);
     glVertexAttribPointer(vertexAttribColor, nColorComponents, GL_FLOAT, GL_FALSE, 0, 0);
     glEnableVertexAttribArray(vertexAttribColor);
 
     // Draw pyramid
     glDrawArrays(GL_TRIANGLES, 0, nFaces*nVerticesPerFace);
 
     // Disable the VBO
     glBindBuffer(GL_ARRAY_BUFFER, 0);
}

Shader with Vertex Array Object (VAO)

VAOs provide a way to “pre-define” vertex data and its attributes. It is like creating “objects” which hold the required states to render. In this example, two VAOs are created, one for the axes and the other for the pyramid. The code defining a VAO is the same as rendering using VBOs. Instead of binding the buffer, copying data to the buffer and creating a generic vertex attribute during render, these steps are “defined” in a VAO.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static void defineVAO()
{
     vaoIds = new GLuint[2];
     glGenVertexArrays(2, vaoIds);
 
     vboIds = new GLuint[4];
     glGenBuffers(4, vboIds);
 
     GLint vertexAttribCoords = glGetAttribLocation(program, "aCoords" );
     GLint vertexAttribColor = glGetAttribLocation(program, "aColor" );
 
     // Bind VAO (set current) to define axes data
     glBindVertexArray(vaoIds[0]);
 
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);  // coordinates
     glBufferData(GL_ARRAY_BUFFER, sizeof (ave), ave, GL_STATIC_DRAW);
     glVertexAttribPointer(vertexAttribCoords, nCoordsComponents, GL_FLOAT, GL_FALSE, 0, 0);
     glEnableVertexAttribArray(vertexAttribCoords);
 
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[1]);  // color
     glBufferData(GL_ARRAY_BUFFER, sizeof (ace), ace, GL_STATIC_DRAW);
     glVertexAttribPointer(vertexAttribColor, nColorComponents, GL_FLOAT, GL_FALSE, 0, 0);
     glEnableVertexAttribArray(vertexAttribColor);
 
     // Bind VAO (set current) to define pyramid data
     glBindVertexArray(vaoIds[1]);
     
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[2]);  // coordinates
     glBufferData(GL_ARRAY_BUFFER, sizeof (pve), pve, GL_STATIC_DRAW);
     glVertexAttribPointer(vertexAttribCoords, nCoordsComponents, GL_FLOAT, GL_FALSE, 0, 0);
     glEnableVertexAttribArray(vertexAttribCoords);
 
     glBindBuffer(GL_ARRAY_BUFFER, vboIds[3]);  // color
     glBufferData(GL_ARRAY_BUFFER, sizeof (pce), pce, GL_STATIC_DRAW);
     glVertexAttribPointer(vertexAttribColor, nColorComponents, GL_FLOAT, GL_FALSE, 0, 0);
     glEnableVertexAttribArray(vertexAttribColor);
 
     // Disable VAO
     glBindVertexArray(0);
}

During render, the VAO(s) are simply bound (made current) for the GPU to access the information about the “pre-defined” VBOs and vertex attributes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static void drawShaderWithVertexArrayObject()
{
     LoadGLExtensions();
 
     initShaders();
     defineVAO();
 
     // Get the variables from the shader to which data will be passed
     GLint mvloc = glGetUniformLocation(program, "umvMat" );
     GLint ploc = glGetUniformLocation(program, "upMat" );
 
     // Pass the model-view matrix to the shader
     GLfloat mvMat[16];
     glGetFloatv(GL_MODELVIEW_MATRIX, mvMat);
     glUniformMatrix4fv(mvloc, 1, false , mvMat);
 
     // Pass the projection matrix to the shader
     GLfloat pMat[16];
     glGetFloatv(GL_PROJECTION_MATRIX, pMat);
     glUniformMatrix4fv(ploc, 1, false , pMat);
 
     // Enable VAO to set axes data
     glBindVertexArray(vaoIds[0]);
     
     // Draw axes
     glDrawArrays(GL_LINES, 0, nLines*nVerticesPerLine);
 
     // Enable VAO to set pyramid data
     glBindVertexArray(vaoIds[1]);
 
     // Draw pyramid
     glDrawArrays(GL_TRIANGLES, 0, nFaces*nVerticesPerFace);
 
     // Disable VAO
     glBindVertexArray(0);
}

Conclusion

Modern OpenGL is powerful but can be intimidating until you understand the nitty gritty details. Hope that the document is useful. Feel free to comment if you need any clarifications or further information.

https://cognitivewaves.wordpress.com/opengl-vbo-shader-vao/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值