OpenGL-Learning-5-Advanced OpenGL

这篇主要是一些可编程管线中的常用高级特性,包括shader的一些内建数据类型,接口块以及缓冲数据块应用。

添加到OpenGL常用的概念解释里面:
vertex代表的是一个顶点,但是不只有顶点坐标数据,更有可能包含法线、纹理坐标以及颜色等其他一堆东西。这一个单元就叫做vertex。

advanced data

主要讨论了可以利用buffer的一些处理函数,利用texture来存储大量数据再进行处理

  • OpenGL里面的buffer只不过是管理一段内存空间而已
  • 最常用的就是GL_ARRAY_BUFFER,我们也可以使用LG_ELEMENT_ARRAY_BUFFER
  • 与其用glBufferData红色这一个函数绑定所有的数据,我们可以通过glBufferSubData红色来填充特定的区域,需要置顶buffer类型,偏移以及数据、数据大小。新特性就是,我们可以插入、更新buffer memory的特定位置数据
  • glmapbuffer也可以用,在不用临时存储数据的情况下,就将数据映射给data。比如直接从文档里复制数据给buffer 的内存。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    float positions[] = { ... };
    float normals[] = { ... };
    float tex[] = { ... };
    // fill buffer
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &positions);
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &normals);
    glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &tex);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));
    glVertexAttribPointer(
    2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));
  • 这样做的直接好处就是,将不同类型的数据作为整体直接给buffer处理。

  • buffers被填充数据以后,现在也可以很方便的将数据分享给其他buffer,利用 glCopyBufferSubData红色:
    void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset,GLintptr writeoffset, GLsizeiptr size);

  • 如果要进行都是vertex array buffers的两个不同的buffer读写,怎么办?我们不能同时绑定两个buffers 给同样的buffer 对象。OpenGL为了处理这个,给了另外两个buffer targets:GL_COPY_READ_BUFFER 和GL_COPY_WRITE_BUFFER蓝色。

    1
    2
    3
    4
    float vertexData[] = { ... };
    glBindBuffer(GL_COPY_READ_BUFFER, vbo1);
    glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
    glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, sizeof(vertexData));

advanced glsl

GLSL built-in variables

我们需要从其他地方拿到数据时,我们必须传递数据,一般通过vertex attributes,uniforms以及samplers

vertex shader variables

  1. gl_Position
    which is the clip-space output position vector of the vertex shader,the output vector of the vertex shader
  2. GL_POINTS gl_PointSize
    By describing the point’s size in the vertex shader you can influence this point value per vertex.
  3. gl_VertexID
    The integer variable gl_VertexID蓝色 holds the current ID of the vertex we’re drawing. When doing indexed rendering (with glDrawElements红色) this variable holds the current index of the vertex we’re drawing. When drawing without indices (via glDrawArrays红色) this variable holds the number of the currently processed vertex since the start of the render call.

    Fragment shader variables

  4. gl_FragCoord
    在深度测试里面见过它???
    gl_FragCoord的值是屏幕坐标系里的坐标,800x600分别是xy的范围;
    之前提到,gl_FragCoord的z分量等于那个特定片元的深度值。我们同样可以利用它的x y分量来干点事。
    fragment shader:
    1
    2
    3
    4
    5
    6
    7
    void main()
    {
    if(gl_FragCoord.x < 400)
    FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    else
    FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    }

直接将屏幕分成两个颜色区域:
图 learn-5-001

  1. gl_FrontFacing
    input variable in the fragment shader,
    gl_FrontFacing蓝色variable is a bool that is true if the fragment is part of a front face。
    fs:下面shader只在GL_FACE_CULL关闭的情况下讨论,开启的话背面肯定是不渲染的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #version 330 core
    out vec4 FragColor;
    in vec2 TexCoords;
    uniform sampler2D frontTexture;
    uniform sampler2D backTexture;
    void main()
    {
    if(gl_FrontFacing)
    FragColor = texture(frontTexture, TexCoords);
    else
    FragColor = texture(backTexture, TexCoords);
    }
  2. gl_FragDepth
    gl_FragCoord是一个输入数据,可用来读取得到当前片元windows 坐标系的坐标。但是这是一个只读的variable,但是我们可以来写入这个片元的深度值。用的就是gl_FragDepth蓝色。
    如果shader没有给gl_FragDepth写入一个值的话,将自动从gl_FragCoord.z获取深度值。
    这块没看懂,没有具体的例子,有机会跟着案例好好尝试一下。。。

    Interface Blocks

    在从vertex向fragment传递东西的时候,可以通过结构体来传递。嗯,之前写很多unityshader的时候,这个其实比较常用,所以其实很简单。

    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
    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec2 aTexCoords;
    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;
    out VS_OUT
    {
    vec2 TexCoords;
    } vs_out;
    void main()
    {
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    vs_out.TexCoords = aTexCoords;
    }
    //////
    #version 330 core
    out vec4 FragColor;
    in VS_OUT
    {
    vec2 TexCoords;
    } fs_in;
    uniform sampler2D texture;
    void main()
    {
    FragColor = texture(texture, fs_in.TexCoords);
    }

Uniform buffer objects

定义一个uniform buffer object绿色容许我们申明全局的uniform
variables,在不同的shader 中使用相同的数据。

1
2
3
4
5
6
7
8
9
10
11
12
#version 330 core
layout (location = 0) in vec3 aPos;
layout (std140) uniform Matrices
{
mat4 projection;
mat4 view;
};
uniform mat4 model;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}

很多时候,对于每一个使用的shader来说,每个渲染循环,我们设置一个projection和view矩阵。实际上我们只需要存储这些矩阵一次。这时,这玩意不是起了很好的作用么。
layout (std140) uniform Matrices说明了当前定义的这个玩意使用特定的memory layout。也就是说,他设置了uniform block layout绿色。

Uniform block layout

layout (std140) uniform ExampleBlock
{
float value;
vec3 vector;
mat4 matrix;
float values[3];
bool boolean;
int integer;
};

Using uniform buffers

1
2
3
4
5
unsigned int uboExampleBlock;
glGenBuffers(1, &uboExampleBlock);
glBindBuffer(GL_UNIFORM_BUFFER, uboExampleBlock);
glBufferData(GL_UNIFORM_BUFFER, 152, NULL, GL_STATIC_DRAW); // allocate 150 bytes of memory
glBindBuffer(GL_UNIFORM_BUFFER, 0);

Now whenever we want to update or insert data into the buffer, we bind to uboExampleBlock and use glBufferSubData to update its memory.
图 learn-5-002
we can bind multiple uniform buffers to different binding points.

example

假设我们现在需要输入project view model三个矩阵,实际上只有model经常变化,所以我们举个例子来说下这玩意怎么用:

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
float cubeVertices[] = {
// positions
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
6
};
// cube VAO
unsigned int cubeVAO, cubeVBO;
glGenVertexArrays(1, &cubeVAO);
glGenBuffers(1, &cubeVBO);
glBindVertexArray(cubeVAO);
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

// configure a uniform buffer object
// ---------------------------------
// first. We get the relevant block indices
unsigned int uniformBlockIndexRed = glGetUniformBlockIndex(shaderRed.ID, "Matrices");
unsigned int uniformBlockIndexGreen = glGetUniformBlockIndex(shaderGreen.ID, "Matrices");
unsigned int uniformBlockIndexBlue = glGetUniformBlockIndex(shaderBlue.ID, "Matrices");
unsigned int uniformBlockIndexYellow = glGetUniformBlockIndex(shaderYellow.ID, "Matrices");
// then we link each shader's uniform block to this uniform binding point
glUniformBlockBinding(shaderRed.ID, uniformBlockIndexRed, 0);
glUniformBlockBinding(shaderGreen.ID, uniformBlockIndexGreen, 0);
glUniformBlockBinding(shaderBlue.ID, uniformBlockIndexBlue, 0);
glUniformBlockBinding(shaderYellow.ID, uniformBlockIndexYellow, 0);
// Now actually create the buffer
unsigned int uboMatrices;
glGenBuffers(1, &uboMatrices);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
// define the range of the buffer that links to a uniform binding point
glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboMatrices, 0, 2 * sizeof(glm::mat4));

// store the projection matrix (we only do this once now) (note: we're not using zoom anymore by changing the FoV)
glm::mat4 projection = glm::perspective(45.0f, (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection));
glBindBuffer(GL_UNIFORM_BUFFER, 0);

// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// render
// ------
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// set the view and projection matrix in the uniform block - we only have to do this once per loop iteration.
glm::mat4 view = camera.GetViewMatrix();
glBindBuffer(GL_UNIFORM_BUFFER, uboMatrices);
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view));
glBindBuffer(GL_UNIFORM_BUFFER, 0);

// draw 4 cubes
// RED
glBindVertexArray(cubeVAO);
shaderRed.use();
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(-0.75f, 0.75f, 0.0f)); // move top-left
shaderRed.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
// GREEN
shaderGreen.use();
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(0.75f, 0.75f, 0.0f)); // move top-right
shaderGreen.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
// YELLOW
shaderYellow.use();
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(-0.75f, -0.75f, 0.0f)); // move bottom-left
shaderYellow.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
// BLUE
shaderBlue.use();
model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(0.75f, -0.75f, 0.0f)); // move bottom-right
shaderBlue.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);

// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}

// optional: de-allocate all resources once they've outlived their purpose:
// ------------------------------------------------------------------------
glDeleteVertexArrays(1, &cubeVAO);
glDeleteBuffers(1, &cubeVBO);
glfwTerminate();
return 0;
}
///////vs-shader
#version 330 core
layout (location = 0) in vec3 aPos
layout (std140) uniform Matrices
{
mat4 projection;
mat4 view;
};
uniform mat4 model;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}

///////fs-shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (std140) uniform Matrices
{
mat4 projection;
mat4 view;
};
uniform mat4 model;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
}

截至目前为止,该篇主要讨论的是,如果在OpenGL的机制下,进行数据的方便传递和使用,跟内存的使用和如果高效地进行大量数据绘制管理。第一部分说的是利用taxture来输入大量数据,绑定一个通用的buffer对象,直接将数据映射给内存就OK。第二部分说的是OpenGLshader 的一些内建的数据类型,支持更灵活地对shader进行操作,还有是如何方便地利用结构体数据类型给shader传递参数。这些方式是进行引擎开发和shader编程的时候的一些常用策略。跟之前自己简单写一些shader也是一样的。
在一些开发经验和知识梳理的基础上,最终需要形成的对于这些技巧的瓶颈概念,最终落实到效率,cpu的负荷以及显卡的内存管理上去。在整体过完OpenGL的系列后,会专门出篇关于数据、计算、交互以及网络的架构文章。因为这些因素决定了所平台的性能。最近一直在思考,工业领域要把传统的OA、SCADA、MESS等传统管理软件和三维内容结合起来,再整合云端的计算、存储能力,在不同的设备进行展示交互和管理。其实5G到来以后,会大大解放数据处理能力,实时的处理和通信会有效改善,三维的数据和交互都会变得更加便捷。
最近顺带在思考另外一个问题,就是现有的app架构的问题。现有的很多业务结构,都是以二维数据以及业务数据为核心,进行获取的时候都是浏览端进行数据请求然后网络实时返回各种数据并且进行加载,首先显存的占用不是很大,三维引擎渲染的资源也有限。但是,大块的三维顶点数据是无法进行实时传输和渲染的,三维场景下的数据,交互、信息,最典型的莫过于实时三维游戏,这部分是通过客户端实现场景数据的存储、贴图存储、本地渲染,然后交互的数据通过网络实时发送,这样就可以实现多人大场景少量数据的交互。典型的吃鸡游戏、大型网游都是这样的。这种的限制就是,有大量的场景资源数据必须存储在客户端。所以我最后的问题是,是不是5G来临以后,这部分都可以扔到服务器上去。
另外一个问题,互联网是需要大量的人工开发量,这个问题先不展开。业务的交互模式和仿真,恐怕还是要继续投入人力来做的。