OpenGL Advance

OpenGL Advance

深度测试

image-20240805105725325

开启深度测试
glEnable(GL_DEPTH_TEST);

每次清空深度缓存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

设定深度缓存对比函数
glDepthFunc(GL_LESS);

image-20240805110153360

深度值推导

image-20240805110451436 image-20240805111959130 image-20240805113424885

模板缓存

image-20240805113950797 image-20240805114233315
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
glEnable(GL_STENCIL_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

glStencilMask(OxFF);允许模板缓存写入数据
glStencilMask(0x00);禁止模板缓存写入数据
glStencilFunc(GL_EQUAL, 1, 0xFF)
参数1:什么情况下通过模板测试GL_NEVER,GL_LESS, GL_LEQUAL, GL_GREATER,
GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, GL_ALWAYS
参数2:对比的数据对象
参数3:对比之前,缓存内数据与对比数据都要跟它做一次与操作

glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)
在模板测试之后,根据不同的测试情况,做出不同反应
sfail:如果本Fragment模板测试没通过,怎么做
dpfail:如果本Fragment模板测试通过,深度测试没通过,怎么做
dppass:如果本Fragment模板测试跟深度测试都通过了,怎么做
image-20240805120819073

高光边缘(选中效果)原理

image-20240805122519342
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
void rend()
{
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
glStencilMask(0xFF);

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

glm::vec3 cubePositions[] = {
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};

glm::vec3 pointLightPositions[] = {
glm::vec3(0.7f, 0.2f, 2.0f),
glm::vec3(2.3f, -3.3f, -4.0f),
glm::vec3(-4.0f, 2.0f, -12.0f),
glm::vec3(0.0f, 0.0f, -3.0f)
};

_camera.update();
_projMatrix = glm::perspective(glm::radians(45.0f), (float)_width / (float)_height, 0.1f, 100.0f);
glm::mat4 _modelMatrix(1.0f);
_modelMatrix = glm::translate(_modelMatrix, glm::vec3(0.0f, 0.0f, -3.0f));

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _textureBox);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, _textureSpec);

_shader_scene.start();
_shader_scene.setVec3("view_pos", _camera.getPosition());

//传入物体材质属性
_shader_scene.setInt("myMaterial.m_specular", 1);
_shader_scene.setFloat("myMaterial.m_shiness", 32);

_shader_scene.setMatrix("_viewMatrix", _camera.getMatrix());
_shader_scene.setMatrix("_projMatrix", _projMatrix);

// directional light
_shader_scene.setVec3("_dirLight.m_direction", glm::vec3(-0.2f, -1.0f, -0.3f));
_shader_scene.setVec3("_dirLight.m_ambient", glm::vec3(0.05f, 0.05f, 0.05f));
_shader_scene.setVec3("_dirLight.m_diffuse", glm::vec3(0.4f, 0.4f, 0.4f));
_shader_scene.setVec3("_dirLight.m_specular", glm::vec3(0.5f, 0.5f, 0.5f));
// point light 1
_shader_scene.setVec3("_pointLight[0].m_pos", pointLightPositions[0]);
_shader_scene.setVec3("_pointLight[0].m_ambient", glm::vec3(0.05f, 0.05f, 0.05f));
_shader_scene.setVec3("_pointLight[0].m_diffuse", glm::vec3(0.8f, 0.8f, 0.8f));
_shader_scene.setVec3("_pointLight[0].m_specular", glm::vec3(1.0f, 1.0f, 1.0f));
_shader_scene.setFloat("_pointLight[0].m_c", 1.0f);
_shader_scene.setFloat("_pointLight[0].m_l", 0.09);
_shader_scene.setFloat("_pointLight[0].m_q", 0.032);
// point light 2
_shader_scene.setVec3("_pointLight[1].m_pos", pointLightPositions[1]);
_shader_scene.setVec3("_pointLight[1].m_ambient", glm::vec3(0.05f, 0.05f, 0.05f));
_shader_scene.setVec3("_pointLight[1].m_diffuse", glm::vec3(0.8f, 0.8f, 0.8f));
_shader_scene.setVec3("_pointLight[1].m_specular", glm::vec3(1.0f, 1.0f, 1.0f));
_shader_scene.setFloat("_pointLight[1].m_c", 1.0f);
_shader_scene.setFloat("_pointLight[1].m_l", 0.09);
_shader_scene.setFloat("_pointLight[1].m_q", 0.032);
// point light 3
_shader_scene.setVec3("_pointLight[2].m_pos", pointLightPositions[2]);
_shader_scene.setVec3("_pointLight[2].m_ambient", glm::vec3(0.05f, 0.05f, 0.05f));
_shader_scene.setVec3("_pointLight[2].m_diffuse", glm::vec3(0.8f, 0.8f, 0.8f));
_shader_scene.setVec3("_pointLight[2].m_specular", glm::vec3(1.0f, 1.0f, 1.0f));
_shader_scene.setFloat("_pointLight[2].m_c", 1.0f);
_shader_scene.setFloat("_pointLight[2].m_l", 0.09);
_shader_scene.setFloat("_pointLight[2].m_q", 0.032);
// point light 4
_shader_scene.setVec3("_pointLight[3].m_pos", pointLightPositions[3]);
_shader_scene.setVec3("_pointLight[3].m_ambient", glm::vec3(0.05f, 0.05f, 0.05f));
_shader_scene.setVec3("_pointLight[3].m_diffuse", glm::vec3(0.8f, 0.8f, 0.8f));
_shader_scene.setVec3("_pointLight[3].m_specular", glm::vec3(1.0f, 1.0f, 1.0f));
_shader_scene.setFloat("_pointLight[3].m_c", 1.0f);
_shader_scene.setFloat("_pointLight[3].m_l", 0.09);
_shader_scene.setFloat("_pointLight[3].m_q", 0.032);
// spotLight
_shader_scene.setVec3("_spotLight.m_pos", _camera.getPosition());
_shader_scene.setVec3("_spotLight.m_direction", _camera.getDirection());
_shader_scene.setVec3("_spotLight.m_ambient", glm::vec3(0.0f, 0.0f, 0.0f));
_shader_scene.setVec3("_spotLight.m_diffuse", glm::vec3(1.0f, 1.0f, 1.0f));
_shader_scene.setVec3("_spotLight.m_specular", glm::vec3(1.0f, 1.0f, 1.0f));
_shader_scene.setFloat("_spotLight.m_c", 1.0f);
_shader_scene.setFloat("_spotLight.m_l", 0.09);
_shader_scene.setFloat("_spotLight.m_q", 0.032);
_shader_scene.setFloat("_spotLight.m_cutOff", glm::cos(glm::radians(12.5f)));
_shader_scene.setFloat("_spotLight.m_outCutOff", glm::cos(glm::radians(15.0f)));


//////绘制方盒
for (int i = 0; i < 10; i++)
{
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0x00);
if (i == 4)
{
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
}
_modelMatrix = glm::mat4(1.0f);
_modelMatrix = glm::translate(_modelMatrix, cubePositions[i]);
_modelMatrix = glm::rotate(_modelMatrix, glm::radians(i * 20.0f), glm::vec3(0.0f, 1.0f, 0.0f));Fre

_shader_scene.setMatrix("_modelMatrix", _modelMatrix);
glBindVertexArray(VAO_cube);
glDrawArrays(GL_TRIANGLES, 0, 36);
}

_shader_scene.end();

//绘制高光边缘
_shader_color.start();
_shader_color.setMatrix("_viewMatrix", _camera.getMatrix());
_shader_color.setMatrix("_projMatrix", _projMatrix);

//////渲染高光边缘
for (int i = 0; i < 10; i++)
{
glStencilFunc(GL_NEVER, 1, 0xFF);
glStencilMask(0x00);
if (i == 4)
{
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
}
_modelMatrix = glm::mat4(1.0f);
_modelMatrix = glm::translate(_modelMatrix, cubePositions[i]);
_modelMatrix = glm::rotate(_modelMatrix, glm::radians(i * 20.0f), glm::vec3(0.0f, 1.0f, 0.0f));
_modelMatrix = glm::scale(_modelMatrix, glm::vec3(1.1f, 1.1f, 1.1f));

_shader_color.setMatrix("_modelMatrix", _modelMatrix);
glBindVertexArray(VAO_cube);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
_shader_color.end();

glStencilFunc(GL_ALWAYS, 1, 0xFF);
//////绘制太阳
_shader_sun.start();
_shader_sun.setMatrix("_viewMatrix", _camera.getMatrix());
_shader_sun.setMatrix("_projMatrix", _projMatrix);

for (int i = 0; i < 4; i++)
{
_modelMatrix = glm::mat4(1.0f);
_modelMatrix = glm::translate(_modelMatrix, pointLightPositions[i]);
_modelMatrix = glm::scale(_modelMatrix, glm::vec3(0.2f, 0.2f, 0.2f));
_shader_sun.setMatrix("_modelMatrix", _modelMatrix);
glBindVertexArray(VAO_sun);
glDrawArrays(GL_TRIANGLES, 0, 36);
}

_shader_sun.end();
}

Blending 效果

透明的原理

RGBA格式A这个值,即ALPHA

Alpha定义:本色块,在与ColorBuffer当中的颜色产生混合的时候,所占有的比例

颜色混合公式:

Color = source* alpha + destination * (1-alpha)

source:要绘制的物体本Fragment的像素颜色值
alpha:要绘制的物体本Fragment的Alpha值
destination:ColorBuffer里面目标点的颜色值

接口

glEnable(GL_BLEND);

第一步,计算出源颜色与目标颜色

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);

第二步,设置计算后的SRC颜色与DST颜色之间的计算方式

glBlendEquation(GLenum mode)

1
2
3
4
5
GL_FUNC_ADD: the default, adds both colors to each other: Cresult = Src + Dst.
GL_FUNC_SUBTRACT: subtracts both colors from each other: Cresult = Src - Dst.
GL_FUNC_REVERSE_SUBTRACT: subtracts both colors, but reverses order: Cresult = Dst - Src.
GL_MIN: takes the component-wise minimum of both colors: Cresult = min(Dst, Src).
GL_MAX: takes the component-wise maximum of both colors: Cresult = max(Dst, Src).
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
void rend()
{
//数据
std::vector<glm::vec3> _window_pos
{
glm::vec3(-1.5f, 0.0f, -0.48f),
glm::vec3(1.5f, 0.0f, 0.51f),
glm::vec3(0.0f, 0.0f, 0.7f),
glm::vec3(-0.3f, 0.0f, -2.3f),
glm::vec3(0.5f, 0.0f, -0.6f)
};

std::map<float, glm::vec3> _window_sort;
for (int i = 0; i < _window_pos.size(); i++)
{
float _dist = glm::length(_camera.getPosition() - _window_pos[i]);
_window_sort[_dist] = _window_pos[i];
}

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);


_camera.update();
_projMatrix = glm::perspective(glm::radians(45.0f), (float)_width / (float)_height, 0.1f, 100.0f);
glm::mat4 _modelMatrix(1.0f);
_modelMatrix = glm::translate(_modelMatrix, glm::vec3(0.0f, 0.0f, -3.0f));

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _textureBox);

_shader.start();
_shader.setMatrix("_modelMatrix", _modelMatrix);
_shader.setMatrix("_viewMatrix", _camera.getMatrix());
_shader.setMatrix("_projMatrix", _projMatrix);

//绘制地面
glBindVertexArray(VAO_plane);
glDrawArrays(GL_TRIANGLES, 0, 6);

//绘制方盒
glBindVertexArray(VAO_cube);
glDrawArrays(GL_TRIANGLES, 0, 36);

//绘制窗体
for (std::map<float, glm::vec3>::reverse_iterator _it = _window_sort.rbegin(); _it != _window_sort.rend(); _it++)
{
_modelMatrix = glm::mat4(1.0f);
_modelMatrix = glm::translate(_modelMatrix, _it->second);
_shader.setMatrix("_modelMatrix", _modelMatrix);
glBindTexture(GL_TEXTURE_2D, _textureWindow);
glBindVertexArray(VAO_window);
glDrawArrays(GL_TRIANGLES, 0, 6);
}

_shader.end();
}
uint createPlane()
{
uint _VAO = 0;
uint _VBO = 0;
glGenVertexArrays(1, &_VAO);
glBindVertexArray(_VAO);

glGenBuffers(1, &_VBO);
glBindBuffer(GL_ARRAY_BUFFER, _VBO);

float planeVertices[] = {
5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, 5.0f, 0.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,

5.0f, -0.5f, 5.0f, 2.0f, 0.0f,
-5.0f, -0.5f, -5.0f, 0.0f, 2.0f,
5.0f, -0.5f, -5.0f, 2.0f, 2.0f
};
glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));

return _VAO;
}

为了避免深度缓存的问题 我们根据相机的距离先绘制最远的窗体

cullFace表面剔除

解决多余的绘制

定义面的顶点绘制顺序

image-20240805165408784 image-20240805165557730

接口

1
2
3
4
5
6
7
8
glEnable(GL_CULL_FACE);
glCullFace(GL_FRONT);
GL_BACK: Culls only the back fases.
GL_FRONT: Culls only the front faces.
GL_FRONT_AND_BACK: Culls both the front and back
faces.

glFrontFace(GL_CCW); 也可以是GL_CW

glFrontFace 定义正面是顺时针还是逆时针

1
2
3
4
5
6
7
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glEnable(GL_DEPTH_TEST);

glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW); // GL_CCW 逆时针方向为正面方向 GL_CW 顺时针为正

FramBuffer 帧缓存

帧缓存不是指一块显存,而是类似于VAO的一个组织结构,他里面包含了很多附件
(ColorBuffer,DepthBuffer,StencilBuffer),附件里面会根据不同需求开辟不同的
显存空间

长期以来,我们都是用默认的帧缓存来做渲染,默认的是0号帧缓存

创建一个属于我们自己的额外的帧缓存,并且利用它来做后处
理(Post-Process),这种渲染方式为离屏渲染(off-screen rending)

image-20240805173315575

创建帧缓存

创建帧缓存ID的方式跟vao vbo texture等一样

1
2
3
4
5
6
7
8
9
unsigned int fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)

//接下来进行附件的创建与绑定操作

glBindFramebuffer(GL_FRAMEBUFFER,0);//恢复原始的默认FrameBuffer
glDeleteFramebuffers(1,&fbo);//没用了可以删除FrameBuffer

原则:

  1. 帧缓存需要至少绑定一个附件(color depth stencil)
  2. 至少有一个ColorBuffer绑定
  3. 每个绑定的附件必须都开辟内存完毕
  4. 每个绑定的附件必须都有相同的采样标准(N*M个像素数据信息)

Texture 作为Buffer附件ColorBuffer

创建Texture:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

glTexlmage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENTO, GL_TEXTURE_2D, texture, 0);

创建Texture:

glTexlmage2D(
GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0,
GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL
);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);

RenderBuffer作为Buffer附件ColorBuffer

创建RenderBuffer

1
2
3
4
5
6
uint colorBuffer;
glGenRenderbuffers(1, &colorBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, 800, 600);

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENTO, GL_RENDERBUFFER, colorBuffer);

Texture作为ColorBuffer,RenderBuffer作为D/S Buffer

image-20240806120326003

FrameBuffer 渲染流程

image-20240806120721581

CubeMap

image-20240806150510384

s 坐标: 表示纹理坐标系中沿水平轴(X 轴)的坐标。

t 坐标: 表示纹理坐标系中沿垂直轴(Y 轴)的坐标。

r 坐标: 表示与立方体面法线方向相关的深度坐标(Z 轴)。

CubeMap接口

image-20240816183316248
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
unsigned int texturelD;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

int width, height, nrChannels;
unsigned char *data;
for(unsigned int i = 0; i < textures_faces.size(); i++)

{

data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);
glTexlmage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X +i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

}

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}

天空盒理论

  1. 设置一个正方体VAO,每个方向的取值范围是-1到1,并且质心位于坐标原点

  2. 做一个CubeMap的Texture并且为六个面都设置目标贴图

  3. 在正方体VertexShader当中,把坐标直接传给FragmentShader(插值后的结果),并且去掉摄像机矩阵对于平移的影响

  4. 在FragmentShader当中利用坐标来做STR坐标,采样CubeMap给到outFrag

  5. 但是每次都必须先绘制天空盒

天空盒Shader解析

VertexShader

1
2
3
4
5
6
7
8
9
10
11
12
13
#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
TexCoords = aPos;
gl_Position = projection * view * vec4(aPos, 1.0);
}

FragmentShader

1
2
3
4
5
6
7
FragmentShader:
in vec3 TexCoords;
uniform samplerCube cubemap;
void main()
{
FragColor = texture(cubemap, TexCoords);
}

直接用物理坐标给纹理坐标赋值

天空盒优化

问题:

每次都需要优先绘制天空盒,天空盒铺满整个屏幕,所以对于性能还是有一定的损耗,如何能够将天空盒的深度都做到最深呢?

优化:

gl_Position变量在每次设置的时候,都是非NDC坐标,他跟NDC之间唯一的差距在于还没
有对xyz除以w值,在绘制天空盒的时候,将gl_Position变量设置为pos.xyww,gl_Position就会在
下一阶段计算深度值z得时候,用当前的Z除以w,即w/W,直接得到Z=1,即最大深度

1
2
3
4
5
6
7
8
9
void main()

{
TexCoords = aPos;
vec4 pos = projection * view * vec4(aPos, 1.0);
gl_Position = pos.xyww;
}

gl_Position-gl_FragCoord,

环境贴图

反射

image-20240806165529599

Shader

image-20240806165716795

Normal = mat3(transpose(inverse(model))) aNormal*

折射

image-20240806174008672

数据接口

定点数据拷贝

1
glBufferSubData(GL_ARRAY_BUFFER, 24 sizeof(data), &data);
  1. 向Buffer中进行定点数据拷贝

  2. 进行不同的数据打包方式

  3. 在不同的buffer之间拷贝数据

  4. 拷贝的数据指针

多次修改数据,会造成性能上的浪费, 每次从内存将数据拷贝到显存

glMapBuffer 虚拟指针

image-20240812102644240

顶点数据打包方式

image-20240812103621644

buffer之间的数据拷贝

1
2
3
glBindBuffer(GL_COPY_READ_BUFFER, vbo1)
glBindBuffer(GL_COPy_WRITE_BUFFER, vbo2)
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WIRTE_BBUFFER, 0, 0, 8*sizeof(float)) // 从read 0 开始读,从write 0 开始写, 数据大小

内置变量

image-20240812104147630 image-20240812104844360 image-20240812105017535
  • return;继续渲染
  • discard;丢弃

image-20240812111355755

image-20240812113518916 image-20240812113801772

接口数据块 Blocks

image-20240812113941818

UniformBlock 显存分配

全局数据块 Uniform Buffer Object

image-20240812114647190

Uniform变量在UBO显存重点存储方式:Shared,std140,packed,std430

**shared 默认方式 **

std140

image-20240812122919238

UBO 生成过程

image-20240812153754072

先绑定UBO 然后根据UBO绑定Shader

1
2
3
4
5
6
7
// UBO两种绑定方式
glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboExampleBlock); // 直接绑定
glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152); // 绑定范围

// Shader里有Light的uniform block的生命, 可以进行绑定
unsigned int lights_index = glGetUniformBlockIndex(shaderA.ID, "Lights");
glUniformBlockBinding(shaderA.ID, lights_index, 2);

core code 省略后

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
// init shader
Shader _shader;
Shader _shader_red;
Shader _shader_green;

void initShader()
{
_shader.initShader("shader/vertexShader.glsl", "shader/fragmentShader.glsl");
_shader_red.initShader("shader/redShaderv.glsl", "shader/redShaderf.glsl");
_shader_green.initShader("shader/greenShaderv.glsl", "shader/greenShaderf.glsl");
}

uint createUBO()
{
uint _ubo = 0;
glGenBuffers(1, &_ubo);
glBindBuffer(GL_UNIFORM_BUFFER, _ubo);

glBufferData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), NULL, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);

return _ubo;
}

void bindShaderData()
{
glBindBufferBase(GL_UNIFORM_BUFFER, 0, UBO);

uint redIndex = glGetUniformBlockIndex(_shader_red.getProgram(), "MAT");
glUniformBlockBinding(_shader_red.getProgram(), redIndex, 0);

uint greenIndex = glGetUniformBlockIndex(_shader_green.getProgram(), "MAT");
glUniformBlockBinding(_shader_green.getProgram(), greenIndex, 0);
}

void rend()
{
//uniform 数据填充
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(_projMatrix));
glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(_camera.getMatrix()));

_shader_red.start();
_shader_red.setMatrix("_modelMatrix", _modelMatrix);

//绘制方盒
glBindVertexArray(VAO_cube);
glDrawArrays(GL_TRIANGLES, 0, 36);
_shader_red.end();


_shader_green.start();
_modelMatrix = glm::mat4(1.0f);
_modelMatrix = glm::translate(_modelMatrix, glm::vec3(2.0f, 0, -3.0f));
_shader_green.setMatrix("_modelMatrix", _modelMatrix);

//绘制方盒
glBindVertexArray(VAO_cube);
glDrawArrays(GL_TRIANGLES, 0, 36);
_shader_green.end();
}

GeometryShader 几何着色器

几何着色器原理

image-20240813112627591

几何着色器对装配的顶点进行生成或减除操作 重新定义图元的类型

点输入

image-20240813113119032

三角形输入

image-20240813113216231

每三个一组创建一个gl_in将变量放到gl_in中,输送给Geometry Shading

Geometry 输出 重新定义了图元装配的类型

image-20240813114156720

GeometryShader

1
2
3
4
5
6
7
8
9
10
11
12
13
# version 330 core
layout (point)in;
layout (line_strip, max_vertices=2) out;

void main(){
gl_Position == gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0);
EmitVertex();

gl_Position = gl_in[0].gl_Position + vec4(0.1, 0.0, 0.0, 0.0);
Emitvertex();

EndPrimitive();
}
image-20240813115017884

gl_in

1
2
3
4
5
6
in gl_Vertex
{
vec4 gl_Position;
float gl_PointSize;
float gl_ClipDistance[]; // 剪裁距离
}gl_in[];

法向量可视化

  • STEP1 正常渲染模型
  • STEP2 使用geometryShader方式渲染模型,得到法向量

VertexShader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;

out vec3 normal;
uniform mat4 view;
uniform mat4 model;

void main()
{
gl_position = view * model * vec4(aPos, 1.0);
mat3 normalMatrix = mat3(transpose(inverse(view * model)));
normal = normailize(vec3(normalMatrix * aNormal, 0.0));
}

GeometryShader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
layout(triangles) in;
layout(line_strip, max_vertices = 6) out;

in vec3 normal;
const float MAGNITUDE = 0.4l
uniform mat4 projection;

void GenerateLine(int index)
{
gl_Position = projection * gl_in[index].gl_position;
EmitVertex();
gl_Position = projection * (gl_in[index].gl_position + vec4(normal, 0.0) * MAGNITUDE);
EmitVertex();
EndPrimitive();
}

模型读取

模型材质

image-20240813151740855

Assimp库(读取)

image-20240813151822375

Scene

  • mRootNode 场景根节点
  • mMeshs[] 模型顶点信息综合
  • mMaterials[] 模型贴图信息

Child node

部分模型信息

Mesh

  • 模型块,包含构建模型块的所有顶点信息(坐标,法线,UV,索引,纹理等)
  • 模型块数据结构 Vertex Texture Index
  • 函数:构造函数,VBO/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
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
namespace FF
{

ffBone::ffBone(int _id, std::string _name, aiMatrix4x4 _aiMatrix)
{
//m_mesh = _pMesh;
m_id = _id;
m_name = _name;
m_offsetMat = AiToGLMMat4(_aiMatrix);

m_parent = NULL;
m_parentSkeleton = NULL;
m_aiAnim = NULL;
m_aiNode = NULL;
}
ffBone::ffBone(int _id, std::string _name, glm::mat4 _matrix)
{
//m_mesh = _pMesh;
m_id = _id;
m_name = _name;
m_offsetMat = _matrix;

m_parent = NULL;
m_aiAnim = NULL;
m_aiNode = NULL;
}
glm::mat4 ffBone::getParentsTransform()
{
ffBone* _pBone = m_parent;
std::vector<glm::mat4> _matrixArr;

glm::mat4 _tmpMat = glm::mat4(1.0f);
while (_pBone)
{
_tmpMat = AiToGLMMat4(_pBone->m_aiNode->mTransformation);
_matrixArr.push_back(_tmpMat);
_pBone = _pBone->m_parent;
}

glm::mat4 _concatenatedTransform = glm::mat4(1.0f);
for (uint i = _matrixArr.size() - 1; i > -1; --i)
{
_concatenatedTransform *= _matrixArr[i];
}

return _concatenatedTransform;
}


void ffSkeleton::init(std::vector<ffBone> _boneArr, glm::mat4 _globalInverseTransform)
{
m_globalInverseTransform = _globalInverseTransform;

for (uint i = 0; i < _boneArr.size(); i++)
{
m_boneMap[_boneArr[i].m_id] = _boneArr[i];
_boneArr[i].m_parentSkeleton = this;
}
}

/*ffBone* ffSkeleton::findBoneByName(std::string _name)
{
for (uint i = 0; i < m_boneArr.size(); i++)
{
if (m_boneArr[i].m_name == _name)
{
return &m_boneArr[i];
}
}

return NULL;
}*/
void ffSkeleton::updateBoneMats()
{

}
void ffSkeleton::update() {}

ffMesh::ffMesh(std::vector<ffVertex> _vertexVec, std::vector<uint> _indexVec, std::vector<ffTexture> _texVec)
{
m_vertexVec = _vertexVec;
m_indexVec = _indexVec;
m_texVec = _texVec;

setupMesh();
}
void ffMesh::draw(Shader& _shader)
{
uint _diffuseN = 1;
uint _specularN = 1;

for (uint i = 0; i < m_texVec.size(); i++)
{
glActiveTexture(GL_TEXTURE0 + i);

//拼装shader传参字符串
std::string _typeName = m_texVec[i].m_type;
std::string _numStr;
if (_typeName == TEXTURE_DIFFUSE_STR)
{
_numStr = std::to_string(_diffuseN++);
}
if (_typeName == TEXTURE_SPECULAR_STR)
{
_numStr = std::to_string(_specularN++);
}
_shader.setFloat("myMaterial." + _typeName + _numStr, i);
glBindTexture(GL_TEXTURE_2D, m_texVec[i].m_id);
}

glActiveTexture(GL_TEXTURE0);

glBindVertexArray(m_VAO);
glDrawElements(GL_TRIANGLES, m_indexVec.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
void ffMesh::setupMesh()
{
uint _VBO = 0;
uint _EBO = 0;

glGenVertexArrays(1, &m_VAO);
glBindVertexArray(m_VAO);

glGenBuffers(1, &_VBO);
glBindBuffer(GL_ARRAY_BUFFER, _VBO);

glBufferData(GL_ARRAY_BUFFER, sizeof(ffVertex) * m_vertexVec.size(), &m_vertexVec[0], GL_STATIC_DRAW);

glGenBuffers(1, &_EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _EBO);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint) * m_indexVec.size(), &m_indexVec[0], GL_STATIC_DRAW);

glEnableVertexAttribArray(SHADER_POSITION_ANCHOR);
glVertexAttribPointer(SHADER_POSITION_ANCHOR, 3, GL_FLOAT, GL_FALSE, sizeof(ffVertex), (void*)0);

glEnableVertexAttribArray(SHADER_NORMAL_ANCHOR);
glVertexAttribPointer(SHADER_NORMAL_ANCHOR, 3, GL_FLOAT, GL_FALSE, sizeof(ffVertex), (void*)offsetof(ffVertex, m_normal));

glEnableVertexAttribArray(SHADER_TEXCOORD_ANCHOR);
glVertexAttribPointer(SHADER_TEXCOORD_ANCHOR, 2, GL_FLOAT, GL_FALSE, sizeof(ffVertex), (void*)offsetof(ffVertex, m_texCoord));

glEnableVertexAttribArray(SHADER_BONE_WEIGHT_ANCHOR);
glVertexAttribPointer(SHADER_BONE_WEIGHT_ANCHOR, 4, GL_FLOAT, GL_FALSE, sizeof(ffVertex), (void*)offsetof(ffVertex, m_weightArr));

glEnableVertexAttribArray(SHADER_BONE_ID_ANCHOR);
glVertexAttribIPointer(SHADER_BONE_ID_ANCHOR, 4, GL_INT, sizeof(ffVertex), (void*)offsetof(ffVertex, m_idArr));

glBindVertexArray(0);
}



void ffModel::loadModel(std::string _path)
{
Assimp::Importer _importer;
const aiScene* _scene = _importer.ReadFile(_path, aiProcess_Triangulate | aiProcess_FlipUVs);

if (!_scene || _scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !_scene->mRootNode)
{
std::cout << "model read fail!" << std::endl;
return;
}

m_dir = _path.substr(0, _path.find_last_of('/'));
processNode(_scene->mRootNode, _scene);
}
void ffModel::processNode(aiNode* _node, const aiScene* _scene)
{
for (uint i = 0; i < _node->mNumMeshes; i++)
{
aiMesh* _mesh = _scene->mMeshes[_node->mMeshes[i]];
m_meshVec.push_back(processMesh(_mesh, _scene, _node, m_skeleton));
}

for (uint i = 0; i < _node->mNumChildren; i++)
{
processNode(_node->mChildren[i], _scene);
}
}
ffMesh ffModel::processMesh(aiMesh* _mesh, const aiScene* _scene, const aiNode* _node)
{
std::vector<ffVertex> _vertexVec;
std::vector<uint> _indexVec;
std::vector<ffTexture> _texVec;

for (uint i = 0; i < _mesh->mNumVertices; i++)
{
ffVertex _vertex;

//位置信息读取
glm::vec3 _pos;
_pos.x = _mesh->mVertices[i].x;
_pos.y = _mesh->mVertices[i].y;
_pos.z = _mesh->mVertices[i].z;
_vertex.m_pos = _pos;

//法线
glm::vec3 _normal;
_normal.x = _mesh->mNormals[i].x;
_normal.y = _mesh->mNormals[i].y;
_normal.z = _mesh->mNormals[i].z;
_vertex.m_normal = _normal;

//纹理坐标
if (_mesh->mTextureCoords[0])
{
glm::vec2 _texCoord;
_texCoord.x = _mesh->mTextureCoords[0][i].x;
_texCoord.y = _mesh->mTextureCoords[0][i].y;
_vertex.m_texCoord = _texCoord;
}

_vertexVec.push_back(_vertex);
}

//解析index
for (uint i = 0; i < _mesh->mNumFaces; i++)
{
aiFace _face = _mesh->mFaces[i];

for (uint j = 0; j < _face.mNumIndices; j++)
{
_indexVec.push_back(_face.mIndices[j]);
}
}

//解析材质
if (_mesh->mMaterialIndex >= 0)
{
aiMaterial* _mat = _scene->mMaterials[_mesh->mMaterialIndex];

std::vector<ffTexture> _diffuseVec = loadMaterialTextures(_mat, aiTextureType_DIFFUSE, TEXTURE_DIFFUSE_STR);
_texVec.insert(_texVec.end(), _diffuseVec.begin(), _diffuseVec.end());

std::vector<ffTexture> _specularVec = loadMaterialTextures(_mat, aiTextureType_SPECULAR, TEXTURE_SPECULAR_STR);
_texVec.insert(_texVec.end(), _specularVec.begin(), _specularVec.end());
}

//解析骨骼动画
for (uint i = 0; i < _mesh->mNumBones; i++)
{
aiBone* _aiBone = _mesh->mBones[i];

//给顶点权重及骨骼id
for (uint j = 0; j < _aiBone->mNumWeights; j++)
{
aiVertexWeight _weight = _aiBone->mWeights[i];
for (uint k = 0; k < MAX_BONE_WEIGHT_PER_VERTEX; k++)
{
_vertexVec[_weight.mVertexId].m_weightArr[k] = _weight.mWeight;
_vertexVec[_weight.mVertexId].m_idArr[k] = i;
}
}

//解析骨骼构建骨骼
for (uint i = 0; i < _mesh->mNumBones; i++)
{
aiBone* _pBone = _mesh->mBones[i];
if (!_pBone)
{
continue;
}
std::string _boneName = _pBone->mName.C_Str();
glm::mat4 _boneMatrix = AiToGLMMat4(_pBone->mOffsetMatrix);

ffBone _newBone(i, _boneName, _boneMatrix);
//_newBone
}
}

return ffMesh(_vertexVec, _indexVec, _texVec);
}

std::vector<ffTexture> ffModel::loadMaterialTextures(aiMaterial* _mat, aiTextureType _type, std::string _typeName)
{
std::vector<ffTexture> _texVec;

for (uint i = 0; i < _mat->GetTextureCount(_type); i++)
{
ffTexture _tex;

aiString _path;
_mat->GetTexture(_type, i, &_path);

_tex.m_id = ffTextureMananger::getInstance()->createTexture(_path.C_Str(), m_dir);
_tex.m_path = _path.C_Str();
_tex.m_type = _typeName;

_texVec.push_back(_tex);
}
return _texVec;
}
void ffModel::draw(Shader& _shader)
{
for (uint i = 0; i < m_meshVec.size(); i++)
{
m_meshVec[i].draw(_shader);
}
}

SINGLE_INSTANCE_SET(ffTextureMananger)

uint ffTextureMananger::createTexture(std::string _path)
{
std::map<std::string, uint>::iterator _it = m_texMap.find(_path);
if (_it != m_texMap.end())
{
return _it->second;
}

ffImage* _image = ffImage::readFromFile(_path.c_str());

uint _texID = 0;
glGenTextures(1, &_texID);
glBindTexture(GL_TEXTURE_2D, _texID);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _image->getWidth(), _image->getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, _image->getData());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

delete _image;
m_texMap[_path] = _texID;
return _texID;
}
uint ffTextureMananger::createTexture(std::string _path, std::string _dir)
{
return createTexture(_dir + '/' + _path);
}
}

DrawCall 减少性能开销

使用Instance来做 统一将所有渲染任务压入

1
2
void glDrawArraysInstanced(GLenum mode, Glint first, GLsizei count, GLsizei primcount);
void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void * indices, FLsizei primcount); // primCount 一次性打包绘制的实例数量

gl_InstanceID 内置变量

代表正在绘制的实例编号 从0开始依次递增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec3 aColor;

out vec3 fColor;

uniform vec2 offsets[100];

void main()
{
vec2 offset = offset[gl_InstanceID];
gl_Position = vec4(aPos + offset, 0.0, 1.0);
fColor = aColor;
}

使用gl_InstanceID 会导致过多的使用uniform变量

gl_InstanceID 内置变量 优化 InstanceArray

利用顶点数组VBO进行书传书 VShader中使用layout进行MAT4的获取

1
2
3
4
5
6
7
8
9
10
11
12
13
glGenBuffers(1, &_VBO_ins);
glBindBuffer(GL_ARRAY_BUFFER, VBO_ins);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::mat4) * AMOUNT, _mMatrixArrp[0], GL_STATIC_DRAW);
// 挂载锚点绑定数据, AttriPointer最多支持vec4,所以拆开
glBIndVertexArray(_model->m_meshVec[i].m_VAO);
glEnableVertexAttribArray(3);
glVertexAttribPointer(3,4,GL_FLOAT,GL_FALSE,sizeof(glm::mat4),(void*)0);
glEnableVertexAttribArray(4);
glVertexAttribPointer(4,4,GL_FLOAT,GL_FALSE,sizeof(glm::mat4),(void*)0(sizeof(glm::vec4));
glEnableVertexAttribArray(5);
glVertexAttribPointer(5,4,GL_FLOAT,GL_FALSE,sizeof(glm::mat4),(void*)0(sizeof(glm::vec4)*2);
glEnableVertexAttribArray(6);
glVertexAttribPointer(6,4,GL_FLOAT,GL_FALSE,sizeof(glm::mat4),(void*)0(sizeof(glm::vec4)*3);

glVertexAttribDivisor(GLuint index, GLuint divisor)

index:挂载的锚点

divisor:多少个实例迭代一次, 0 代表一个顶点迭代一次, 1代表一个模型实例绘制迭代一次

Instance 实现

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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#define AMOUNT 100000

glm::mat4* _mMatrixArr = NULL;

FF::ffModel* _model = NULL;
uint _VBO_ins;
uint _texId;

Shader _shader;


Camera _camera;

glm::mat4 _projMatrix(1.0f);
int _width = 800;
int _height = 600;

void rend()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glEnable(GL_DEPTH_TEST);


_camera.update();
_projMatrix = glm::perspective(glm::radians(45.0f), (float)_width / (float)_height, 0.1f, 100.0f);

_shader.start();
_shader.setMatrix("_viewMatrix", _camera.getMatrix());
_shader.setMatrix("_projMatrix", _projMatrix);
_shader.setFloat("myMaterial.m_shiness", 32.0f);
_shader.setVec3("view_pos", _camera.getPosition());

//光照设置
_shader.setVec3("myLight.m_pos", glm::vec3(0.0f, 0.0f, 0.0f));
_shader.setVec3("myLight.m_ambient", glm::vec3(0.9f, 0.9f, 0.9f));
_shader.setVec3("myLight.m_diffuse", glm::vec3(1.0f, 1.0f, 1.0f));
_shader.setVec3("myLight.m_specular", glm::vec3(1.0f, 1.0f, 1.0f));

_shader.setFloat("myLight.c", 1.0f);
_shader.setFloat("myLight.l", 0.09f);
_shader.setFloat("myLight.q", 0.032f);

glBindTexture(GL_TEXTURE_2D, _texId);
for (uint i = 0; i < _model->m_meshVec.size(); i++)
{
glBindVertexArray(_model->m_meshVec[i].m_VAO);
glDrawElementsInstanced(GL_TRIANGLES, _model->m_meshVec[i].m_vertexVec.size(), GL_UNSIGNED_INT, 0, AMOUNT);
glBindVertexArray(0);
}

/* for (uint i = 0; i < AMOUNT; i++)
{
_shader.setMatrix("_modelMatrix", _mMatrixArr[i]);
_model->draw(_shader);
}*/

_shader.end();
}


void initShader()
{
_shader.initShader("shader/vPointShader.glsl", "shader/fPointShader.glsl");
//_shader.initShader("shader/vertexShader.glsl", "shader/fragmentShader.glsl");
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}

void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);

if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
{
_camera.move(CAMERA_MOVE::MOVE_FRONT);
}
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
{
_camera.move(CAMERA_MOVE::MOVE_BACK);
}
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
{
_camera.move(CAMERA_MOVE::MOVE_LEFT);
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
{
_camera.move(CAMERA_MOVE::MOVE_RIGHT);
}
}

void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
_camera.onMouseMove(xpos, ypos);
}

void initInstanceArray()
{
//准备模型变换矩阵
_mMatrixArr = new glm::mat4[AMOUNT];
srand(glfwGetTime());

float radius = 10.0;
float offset = 2.5f;

glm::mat4 model = glm::mat4(1.0f);
for (unsigned int i = 0; i < AMOUNT; i++)
{
glm::mat4 model = glm::mat4(1.0f);

float angle = (float)i / (float)AMOUNT * 360.0f;

float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float x = sin(angle) * radius + displacement;
displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float y = displacement * 0.4f;
displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float z = cos(angle) * radius + displacement;

model = glm::translate(model, glm::vec3(x, y, z));

float scale = (rand() % 20) / 100.0f + 0.05;
model = glm::scale(model, glm::vec3(scale));

float rotAngle = (rand() % 360);
model = glm::rotate(model, glm::radians(rotAngle), glm::vec3(0.4f, 0.6f, 0.8f));

_mMatrixArr[i] = model;
}


glGenBuffers(1, &_VBO_ins);
glBindBuffer(GL_ARRAY_BUFFER, _VBO_ins);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::mat4) * AMOUNT, &_mMatrixArr[0], GL_STATIC_DRAW);

for (uint i = 0; i < _model->m_meshVec.size(); i++)
{
glBindVertexArray(_model->m_meshVec[i].m_VAO);

glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);

glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));

glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4) * 2));

glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4) * 3));


glVertexAttribDivisor(3, 1);
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1);

glBindVertexArray(0);
}
_texId = FF::ffTextureManager::getInstance()->createTexture("res/rock/rock.png");
}
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Core", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);

if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}

glViewport(0, 0, _width, _height);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);


glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouse_callback);



_camera.lookAt(glm::vec3(0.0f, 0.0f, 8.0f), glm::vec3(0.0f, 0.0f, -1.0f), glm::vec3(0.0f, 1.0f, 0.0f));
_camera.setSpeed(0.01f);

_model = new FF::ffModel("res/rock/rock.obj");




initInstanceArray();

initShader();

while (!glfwWindowShouldClose(window))
{
processInput(window);
rend();

glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}

Blin-Phong 光照实现

半程向量

image-20240815185752239 image-20240815190032563

反射光和人眼的夹角大于九十度 导致光线被计算忽略 造成偏差

实现效果

image-20240816171731033

Gamma矫正

显示器亮度与电压的关系

CRT显示器是通过电压对屏幕亮度进行调节,但是电压的增长与亮度的增长并不是线性相关
的关系,大概会是一个2.2次幂的关系

现代的显示器为了兼容CRT也做了一些类似的设置保留

所以如果想显示真实场景就需要校正

image-20240816145138210

SRGB颜色空间

经过gamma校正后的颜色所构成的颜色空间,即为sRGB颜色空间

颜色:(0.5,0.0,0.0)1/2.2=(0.5,0.0,0.0)0.45=(0.73,0.0,0.0),

那么这个(0.73,0.0,0.0) 即为sRGB颜色空间当中的颜色值了

1
2
3
4
5
6
7
8
9
FUN1:
// 伽马矫正启用方式
glEnable(GL_FRAMEBUFFER_SRGB);
// 函数调用后会再每一个产生出来的Fragment颜色中,使用一次Gamma矫正 将颜色转为sRGB颜色空间的色彩 输出到显示器及进行类似幂运算

FUN2:
float gamma = 2.2;
FragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma));
// 直接再Shader当中手动加上Gamma矫正

再美术工作中 如果工具软件具有Gamma矫正 会输出SRGB颜色空间的贴图 程序解读后进行Gamma矫正会导致效果和美术生成所看到的效果不同

创作者:rgb — 做一次1/2.2次幂(Gamma) —- 美术操作 — 2.2次幂之后 — 正常
程序:读入rgb — 做一次2.2次幂 — 做一次1/2.2次幂 — 做一次2.2次幂 — 正常

1
2
3
// 使用纹理图片之前 做一次sRGB到RGB的转换,然后再做后续
float gamma = 2.2;
vec3 diffuseColor = pow(texture(diffuse, texCoords).rgb, vec3(gamma));

glTexlmage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

可以如此接口使用,在图片读进来的时候,就设置internalFormat为sRGB空间,然后会被openGL
自动转化为2.2次幂,之后就可以使用, 场景变得柔和

image-20240816144451807

ShadowMapping

image-20240816180358159

平行光

深度渲染 FrameBuffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned int depthMapFBO;
glGenFramebuffers(1, &depthMapFBO);

const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
unsigned int depthMap;
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexlmage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); // shadow Texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

// bind
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

FragmentShader 阴影部分(Pass2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#version 330 core
out vec4 FragColor;
[ -- datas -- ]
uniform sampler2D shadowMap; // 对应depthMap 找到各个点深度值

float ShadowCalculation(vec4 fragPosLightSpace)
{
vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; // 得到NDC
projCoords = projCoords * 0.5 + 0.5; // 转化为0到1
float closestDepth = texture(shadowMap, projCoords.xy).r; // (X,Y为U,V r为Z)
float currentDepth = projCoords.z;
float shadow = currentDepth > closestDepth ? 1.0 : 0.0; // 是否产生阴影

return shadow;
}
void main()
{
[ -- calculate Light --- ]
// calculate shadow
float shadow = ShadowCalculation(fs_in.FragPosLightSpace); // fs_in.FragPosLightSpace 所渲染物体在光源空间的坐标 类NDC 除W成为NDC (Position * MVP)
vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;

FragColor = vec4(lighting, 1.0);
}

实际效果

image-20240819103219491

ShadowAnce 噪声 (采样不均问题)

image-20240819103738356

深度差别太小 导致a通过测试 b未通过测试 a亮 b暗 明暗交替

Bias参数去噪

image-20240819104053607

同样但也会导致之前该是阴影的部分抬升

Peter Panning 去噪

image-20240819105405513 image-20240819105749325

过采样问题

image-20240819115445534

XY超出立方体的点处理方式 :使用GL_CLAMP_TO_BORDER的过采样方式,让出界的都采样到1.0的深度

1
2
3
4
glTexParameteri(GL_TEXTURE_2D, GL_TExTURE_WARP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TExTURE_WARP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = {1,0f. 1.0f, 1.0f, 1.0f};
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

阴影锯齿问题

深度贴图分辨率有限 采样密度不够

PCF-percentage-closer filtering

最简单的方式 多顶当前要处理的Fragment对应的深度贴图 Texel,使用周围八个Texel进行平均

1
2
3
4
5
6
7
8
9
10
11
12
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
for(int x =- 1; x <= 1; ++x)
{
for(int y =- 1; y <= 1; ++y)
{
float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r;
shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;
}
}

shadow /= 9.0;
image-20240819122032730

PointShadow

单一光源方向并不足够 房间中的光照需要优化

image-20240819153312161

每个面对应一个90度的视场角(FOV),覆盖场景的一个特定方向:前(+X),后(-X),左(-Y),右(+Y),上(+Z),下(-Z)。每个面都可以使用一个标准的透视投影矩阵进行渲染,FOV通常设置为90度,以确保没有任何失真或间隙。

首先可以想到为光源的6个方向做6个光照矩阵 6次渲染得到6张深度贴图,进行pass2

1
2
3
4
5
6
for(unsigned int i = 0; i < 6; i++)
{
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, textures[i], 0);
BindViewMatrix(lightViewMatrices[i]);
RenderScene();
}

优化思路

  • 问题描述:我们需要渲染六张图,为什么必须要做六次渲染流程呢?主要是每次都需要虚拟一个光照矩阵(ViewMatrix*ProjMatrix),然后这六个光照矩阵分别跟Vertex相乘做变换,分别渲染
  • 设想:对每个三角形来讲,有没有可能通过一次流程,传入六个光照矩阵,对每个三角形进行六次变换,输出六个三角形
  • 解决:可以通过GeometryShader可以将一个三角形输入做成6个三角形图源输出给FS

image-20240819154309044

  • 问题描述:我们在FS里拿到了六个三角形的数据(分别对应六个光照矩阵以及六个阴影深度贴图),那么我们如何指定哪个三角形渲染到哪个贴图呢?

  • 结论:在GS里,有一个内置变量,叫做gl_Layer,它可以在GS里配合每个输出的图元(三角形), 指定输送给编号为几的Texture,但是只能对CubeMap这种类型管用

  • 问题描述:我现在手里有六个面的六张深度贴图,当我进行真正渲染的时候,需要知道哪个Fragment到底去哪个贴图里读取深度,一般是跟光线连线,然后看看指向哪个面。BUT!太复杂了

  • 结论:对于六个面取纹理这个问题,CubeMap就很好地解决了呀!将光源与当前需要渲染的Fragment位置连一条向量,用这个向量直接作为STR坐标对CubeMap进行采样即可

阴影渲染流程

image-20240819154855533

CubeMap坐标原理

在二维图像纹理中 如果直接读入一张图显示会出现上下翻转。图片的(0,0)在左上角,

OpenGL的UV(0,0)在左下角 读入图片生成纹理之前需要在y反向做翻转

在CubeMap中并没有上下翻转

有一个向量R(xyz),如果使用这个R向当前的CubeMap进行采样,那么是如何被翻译到某一个
Texture的uv上面呢?

1首先我们看下在R的每个分量当中,绝对值最大的是哪个,比如是x绝对值最大,并且x还
是正数,那就说明要冲POSITIVE_x这个Texture进行取值

2计算uv坐标,此时由于冲着x正方向,那么u=z,v=y,一横一竖,但是这里就有问题了!

image-20240819161652360

image-20240819163147214

法线贴图 & TBN空间推导

如何做出物体表面凹凸不平的效果

image-20240819163345320

真实的物体表面应该是粗糙的,几乎每一个Fragment之间的法线都会有所不同

image-20240820122310435

image-20240820141840317

把一张法线贴图做成一个以(0,0)为UV点为原点的笛卡尔坐标系,指向外部的向量N为每个Fragment各自的法向量 按照右手坐标系法则求剩下的TB值 如果N看作坐标系Z轴 B为Y轴T为X轴 每一个法线RGB都可以是坐标系中的向量。

TBN 空间转换

我们目前做出来了一组切线空间的正交基,目前存在两个选择:

1在每一个FragmentShader的执行周期,将每一个法线贴图的normal转换到世界坐标系

2将每一个相关变量都转换到TBN坐标空间,然后与法线贴图的normal进行计算

1
2
3
4
5
6
7
8
9
10
11
12
13
// VS
void main()
{
vec3 T = normalize(vec3(model * vec4(aTangent, 0.0)));
vec3 B = normalize(vec3(model * vec4(aBitangent, 0.0)));
vec3 N = normalize(vec3(model * vec4(aNormal, 0.0)));
mat3 TBN = mat3(T, B, N);
}

// FS
normal = texture(normalMap, fs_in.TexCoords).rgb;
normal = normal * 2.0 - 1.0;
normal = normalize(fs_in.TBN * normal);

HDR颜色空间

OpenGL 系统会把超过1的颜色切位1 如果Frag颜色大于1的过多 就会过度曝光

如果场景当中有很多灯光,且彼此差距很大,那么很容易造成某
些Fragment亮度过高。我们有两个处理方式:

  • 调整场景当中光源的强度,使得大部分的Fragment颜色值不会超过1,但是会让场景无法表达本来的信息
  • 允许场景当中存在超过1的颜色值,但是在最后输出颜色的时候, 做统一的转换计算,转换到o-1,从而能够更大程度的保持场景当中的颜色信息

HDR(High Dynamic range)颜色空间方式,允许颜色值大于1的存在

HDR最早用于摄影的曝光拍摄,通过更多的接受光照,从而能够揭示出来光照较少部分的细节

HDR-FloatingPoint Buffers

此时我们假设渲染到某个Framebuffer上面

1
2
glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexlmage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL); // 为colorBuffer的texture建立GL_RGBA16F通道

这里对于一个像素的RGBA值,每个通道使用一个GL_Float存储,并且每个分量占有16bits即2字节

当然如果我们精度要求很高,可以使用GL_RGBA32F,即一个通道32位,4字节。

HDR toneMapping

将颜色从HDR颜色空间转到RGB0-1颜色空间的过程称为toneMapping

如果简单的把HDR空间的颜色线性的映射到RGB空间,根本不会有任何的意义,反而光线暗的地方更加看不到了

我们必须要用其他的变换公式来进行二者之间的映射

Reinhard tone mapping

image-20240820145325096
1
2
3
4
5
6
7
8
9
10
// Reinhard tone mapping
uniform sampler2D hdrBuffer;
void main()
{
vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;

vec3 mapped = hdrColor / (hdrColor + vec3(1.0));

FragColor = vec4(mapped, 1.0);
}

Exposure tone mapping

image-20240820145559687 image-20240820145517202
1
2
3
4
5
6
7
8
9
10
11
12
uniform float exposure;

void main()
{

vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;

vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure);

FragColor = vec4(mapped, 1.0);

}

Bloom 效果

image-20240820150636497 image-20240820150006441

Bloom 基础做法

  1. 将场景渲染出来到FBO的colorBufferA

  2. 将灯光单独抠图出来渲染到FBO的colorBufferB

  3. 将灯光Texture单独做模糊

  4. 将Bloom灯光图合并到主场景Texture中

光源边界问题界定

我们如何界定Texture当中哪些像素是代表着光源呢?如果我们认定大于某个值为光源,那么万一遇到白色物体如何决定?

解决:在渲染的时候使用HDR颜色空间,就可以将光源设置成为大于1的颜色值, 使得光源颜色与其它物体颜色差距变大

1
2
3
4
lightColors.push_back(glm :: vec3(5.0f, 5.0f, 5.0f));
lightColors.push_back(glm :: vec3(10.0f, 0.0f, 0.0f));
lightColors.push_back(glm :: vec3(0.0f, 0.0f, 15.0f));
lightColors.push_back(glm :: vec3(0.0f, 5.0f, 0.0f));

MRT技术

如何一个PASS完成两个任务:将场景渲染到colorBufferA,将灯光渲染到colorBufferB,并且希望使用同一个FBO

解决:使用MRT技术,即(Multiple Render Target)。在FBO当中,存在多个ColorAttachment选项,我们
可以启动两个Color附件,在FS当中对这两个目标进行渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsigned int hdrFBO;
glGenFramebufrers(1, &hdrFBO);
glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO);
unsigned int colorBuffers[2];
glGenTextures(2, colorBuffers);
for (unsigned int i = 0; i < 2; i++)

glBindTexture(GL_TEXTURE_2D, colorBuffers[i]);
glTexlmage2D(
GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENTO + i, GL_TEXTURE_2D, colorBuffers[i], 0
);

将两个绘制ColorBuffer目标, 绑定到FS的两个锚定位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned int attachments[2] = { GL_COLOR_ATTACHMENTO, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, attachments);
#version 330 core
layout (location = 0) out vec4 FragColor;
layout (location = 1) out vec4 BrightColor;
uniform vec3 lighting;
void main()

FragColor = XXXX
float brightness = dot(FragColor.rgb, vec3(0.2126, 0.7152, 0.0722));
if(brightness > 1.0)
BrightColor = vec4(FragColor.rgb, 1.0);
else
BrightColor = vec4(0.0, 0.0, 0.0, 1.0);
}

实现效果

image-20240820173747275

高斯模糊

拆分降低复杂度

image-20240820152521324 image-20240820153609709

高斯 Shader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
uniform bool horizontal;
uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
void main()
{
vec2 tex_offset = 1.0 / textureSize(image, 0); // gets size of single texel
vec3 result = texture(image, TexCoords).rgb * weight[0];
if(horizontal) {
for(int i = 1; i < 5; ++i) {
result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
}
}
else{
for(int i = 1; i < 5; ++i){
result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
}
}

FragColor = vec4(result, 1.0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool horizontal = true, first_iteration = true;
int amount = 10;
shaderBlur.use();
for (unsigned int i = 0; i < amount; i++)
{
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
shaderBlur.setInt("horizontal", horizontal);
glBindTexture(
GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongBuffers[!horizontal]
);
RenderQuad();
horizontal = !horizontal;
if (first_iteration)
first_iteration = false;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);

延迟渲染技术

G缓冲

G缓冲(G-buffer)是对所有用来储存光照相关的数据,并在最后的光照处理阶段中使用的所有纹理的总称。

正向渲染中照亮一个片段所需要的所有数据

  • 一个3D位置向量来计算(插值)片段位置变量供lightDirviewDir使用
  • 一个RGB漫反射颜色向量,也就是反照率(Albedo)
  • 一个3D向量来判断平面的斜率
  • 一个镜面强度(Specular Intensity)浮点值
  • 所有光源的位置和颜色向量
  • 玩家或者观察者的位置向量

正向渲染(Forward Rendering)或者正向着色法(Forward Shading),它是我们渲染物体的一种非常直接的方式,在场景中我们根据所有光源照亮一个物体,之后再渲染下一个物体,以此类推。它非常容易理解,也很容易实现,但是同时它对程序性能的影响也很大,因为对于每一个需要渲染的物体,程序都要对每一个光源每一个需要渲染的片段进行迭代,这是非常多的!因为大部分片段着色器的输出都会被之后的输出覆盖,正向渲染还会在场景中因为高深的复杂度(多个物体重合在一个像素上)浪费大量的片段着色器运行时间。

**延迟着色法(Deferred Shading)或者说是延迟渲染(Deferred Rendering)**,为了解决上述问题而诞生了,它大幅度地改变了我们渲染物体的方式。这给我们优化拥有大量光源的场景提供了很多的选择,因为它能够在渲染上百甚至上千光源的同时还能够保持能让人接受的帧率。

**延迟(Defer)推迟(Postpone)**大部分计算量非常大的渲染(像是光照)到后期进行处理的想法。它包含两个处理阶段(Pass):在第一个几何处理阶段(Geometry Pass)中,我们先渲染场景一次,之后获取对象的各种几何信息,并储存在一系列叫做G缓冲(G-buffer)的纹理中;想想位置向量(Position Vector)、颜色向量(Color Vector)、法向量(Normal Vector)和/或镜面值(Specular Value)。场景中这些储存在G缓冲中的几何信息将会在之后用来做(更复杂的)光照计算。

img

保证在G缓冲中的片段和在屏幕上呈现的像素所包含的片段信息是一样的,因为深度测试已经最终将这里的片段信息作为最顶层的片段。这样保证了对于在光照处理阶段中处理的每一个像素都只处理一次,所以我们能够省下很多无用的渲染调用。除此之外,延迟渲染还允许我们做更多的优化,从而渲染更多的光源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
while(...) // 游戏循环
{
// 1. 几何处理阶段:渲染所有的几何/颜色数据到G缓冲
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
gBufferShader.Use();
for(Object obj : Objects)
{
ConfigureShaderTransformsAndUniforms();
obj.Draw();
}
// 2. 光照处理阶段:使用G缓冲计算场景的光照
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT);
lightingPassShader.Use();
BindAllGBufferTextures();
SetLightingUniforms();
RenderQuad();
}

对于几何渲染处理阶段,我们首先需要初始化一个帧缓冲对象,我们很直观的称它为gBuffer,它包含了多个颜色缓冲和一个单独的深度渲染缓冲对象(Depth Renderbuffer Object)。对于位置和法向量的纹理,我们希望使用高精度的纹理(每分量16或32位的浮点数),而对于反照率和镜面值,使用默认的纹理(每分量8位浮点数)就够了

img

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
GLuint gBuffer;
glGenFramebuffers(1, &gBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
GLuint gPosition, gNormal, gColorSpec;

// - 位置颜色缓冲
glGenTextures(1, &gPosition);
glBindTexture(GL_TEXTURE_2D, gPosition);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gPosition, 0

// - 法线颜色缓冲
glGenTextures(1, &gNormal);
glBindTexture(GL_TEXTURE_2D, gNormal);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, gNormal, 0);

// - 颜色 + 镜面颜色缓冲
glGenTextures(1, &gAlbedoSpec);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, gAlbedoSpec, 0);

// - 告诉OpenGL我们将要使用(帧缓冲的)哪种颜色附件来进行渲染
GLuint attachments[3] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
glDrawBuffers(3, attachments);

延迟光照处理

现在我们已经有了一大堆的片段数据储存在G缓冲中供我们处置,我们可以选择通过一个像素一个像素地遍历各个G缓冲纹理,并将储存在它们里面的内容作为光照算法的输入,来完全计算场景最终的光照颜色。由于所有的G缓冲纹理都代表的是最终变换的片段值,我们只需要对每一个像素执行一次昂贵的光照运算就行了。这使得延迟光照非常高效,特别是在需要调用大量重型片段着色器的复杂场景中。

1
2
3
4
5
6
7
8
9
10
11
12
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shaderLightingPass.Use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, gPosition);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, gNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, gAlbedoSpec);
// 同样发送光照相关的uniform
SendAllLightUniformsToShader(shaderLightingPass);
glUniform3fv(glGetUniformLocation(shaderLightingPass.Program, "viewPos"), 1, &camera.Position[0]);
RenderQuad();

在渲染之前绑定了G缓冲中所有相关的纹理,并且发送光照相关的uniform变量到着色器中

Shader

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
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;

uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gAlbedoSpec;

struct Light {
vec3 Position;
vec3 Color;
};
const int NR_LIGHTS = 32;
uniform Light lights[NR_LIGHTS];
uniform vec3 viewPos;

void main()
{
// 从G缓冲中获取数据
vec3 FragPos = texture(gPosition, TexCoords).rgb;
vec3 Normal = texture(gNormal, TexCoords).rgb;
vec3 Albedo = texture(gAlbedoSpec, TexCoords).rgb;
float Specular = texture(gAlbedoSpec, TexCoords).a;

// 然后和往常一样地计算光照
vec3 lighting = Albedo * 0.1; // 硬编码环境光照分量
vec3 viewDir = normalize(viewPos - FragPos);
for(int i = 0; i < NR_LIGHTS; ++i)
{
// 漫反射
vec3 lightDir = normalize(lights[i].Position - FragPos);
vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Albedo * lights[i].Color;
lighting += diffuse;
}

FragColor = vec4(lighting, 1.0);
}

结合延迟渲染与正向渲染

缩略图

在延迟渲染方形之上正向渲染所有的光源 正常情况下渲染立方体,只是会在我们完成延迟渲染操作之后进行

首先复制出在几何渲染阶段中储存的深度信息,并输出到默认的帧缓冲的深度缓冲,然后我们才渲染光立方体。这样之后只有当它在之前渲染过的几何体上方的时候,光立方体的片段才会被渲染出来。我们可以使用glBlitFramebuffer复制一个帧缓冲的内容到另一个帧缓冲中,用来还原多重采样的帧缓冲。glBlitFramebuffer这个函数允许我们复制一个用户定义的帧缓冲区域到另一个用户定义的帧缓冲区域。

光的体积渲染

问题:如果场景当中存在多个光源,那么即便是使用延迟渲染,那么也会出现每个Fragment跟每个光产生一次渲染的性能问题!

我们能不能对每个光源的辐射范围做一个判断,如果当前的Fragment不在范围内,
就不去计算

Threshold 边界

问题:对于每个光源,如果距离计算衰减到光照为0就表示不用去计算的话,就不会找到边界了。

想法:我们可以设置一个阈值,用如下公式计算距离d,如果大于d的就忽略掉

image-20240820183016882

延迟渲染它本身并不能支持非常大量的光源,因为我们仍然必须要对场景中每一个光源计算每一个片段的光照分量。真正让大量光源成为可能的是我们能够对延迟渲染管线引用的一个非常棒的优化:光体积(Light Volumes)

image-20240820180819761

得出X即为D的阈值

程序流程

image-20240820183328522

GPU再运行if的时候 并行执行 会造成性能丢失

优化

image-20240820183651077

将所有的几何信息写在G-Buffer上面,也就是说我们可以为每一个光照做一个球,然后使用G-Buffer的信息作为绑定纹理, 只渲染每一个光照球,这个球体Fragment范围内的G-Buffer才会被渲染

光源重合问题

使用Blend功能,设置混合模式,比如我们可以设置对物体影响最强的光源为主

1
2
glBlendFuncSeparate(GL_ONE, GL ONE, GL_ONE, GL_ONE);
glBlendEquation(GL_MAX);

也可以采用相加方式

CullFace问题

球体存在正反两面 所以会被绘制两次 增大了负担 CullFace会导致进入球体内部不存在渲染

开启glCullFace(GL_FRONT) 绘制效率,且可以进入球体内部 只对球体背面进行绘制,这样提高了一倍的