OpenGL入门008——环境光在片段着色器中的应用

本节将在片段着色器中应用环境光照(Ambient)

一些概念

光照模型

环境光

概述: 在场景中无处不在、均匀分布的光线,用来模拟从周伟环境反射到物体上的光。即使没有明确的光源,物体表面仍然有一定亮度

特点:

  • 不依赖光源方向
  • 为整个物体提供均匀的基本亮度
  • 不计算光源与物体表面的方向关系

公式:
$$
I_a = K_a \cdot I_{ambient}
$$

  • $I_a$:物体表面的环境光亮度
  • $k_a$:环境光的反射系数(0到1之间)
  • $I_{ambient}$:环境光强度

漫反射

概述: 描述的是粗糙表面对光的反射,反射的光线相关各个方向均匀分布,与视角无光

特点:

  • 亮度取决于光源的方向和物体表面法向量之间的夹角
  • 适合模拟不光滑的表面,例如木材、纸张等
  • 视角变化不会影响光的强度

公式(朗伯余弦定律):
$$
I_d = k_d \cdot I_{light} \cdot max(0, L \cdot N)
$$

  • $I_d$:物体表面的漫反射亮度
  • $k_d$:漫反射的反射系数(0到1之间)
  • $I_{light}$:光源强度
  • L:指向光源的单位向量
  • N:表面的法向量

镜面反射

概述: 镜面反射描述的是光滑表面(如金属或镜子)对光的反射,反射光集中在一个特定方向上,与视角密切相关

特点:

  • 表现为高光,即表面某些点的强亮反射
  • 亮度取决于观察者方向、光源方向于物体表面的关系
  • 表面越光滑,高光越集中;越粗糙,高光越分散

公式(Phong反射模型):
$$
I_s = k_s \cdot I_{light} \cdot max(0, R \cdot V)^n
$$

  • $I_s$:镜面反射亮度
  • $k_s$:镜面反射系数(0到1之间)
  • R:反射方向的单位向量
  • V:观察方向的单位向量
  • n:高光的锐利程度,称为”高光指数“

总结

  • 环境光:提供基础的整体亮度,与光源和视角无光
  • 漫反射:模拟粗糙表面的光照,依赖于光源方向,但与视角无关
  • 镜面反射:模拟光滑表面的高光,依赖于光源方向和视角

这三种光照成分通常组合在一起形成Phong光照模型,用于计算场景中物体的颜色和亮度:
$$
I = I_a + I_d + I_s
$$

实战

简介

怎么在vscode上使用cmake构建项目,具体可以看这篇Windows上如何使用CMake构建项目 - 凌云行者的博客

目的: 使用环境光

  • 编译工具链:使用msys2安装的mingw-gcc
  • 依赖项:glfw3:x64-mingw-static,glad:x64-mingw-static(通过vcpkg安装)

源码: OpenGL-Learn-Program/008-ambient-light at main · 1037827920/OpenGL-Learn-Program

dependencies

lightShader.vs

点光源的顶点着色器源码:

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

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
gl_Position = projection * view * model * vec4(vPos, 1.0f);
}

lightShader.fs

点光源的片段着色器源码:

1
2
3
4
5
6
7
8
9
10
11
12
#version 330 core

// 光源颜色
uniform vec3 lightColor;

// 输出的片段颜色
out vec4 FragColor;

void main()
{
FragColor = vec4(lightColor, 1.0);
}

shader.vs

立方体的顶点着色器源码:

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

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec2 TexCoord;

void main()
{
gl_Position = projection * view * model * vec4(vPos, 1.0f);

TexCoord = vTexCoord;
}

shader.fs

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
#version 330 core

in vec2 TexCoord;

// 第一个纹理
uniform sampler2D texture0;
// 第二个纹理
uniform sampler2D texture1;
// 混合比例
uniform float blendRatio;
// 光源颜色
uniform vec3 lightColor;

out vec4 FragColor;

void main()
{
// 环境光强度
float ambientStrength = 0.2;
// 计算环境光分量
vec3 ambient = ambientStrength * lightColor;

// 最终颜色结果
vec3 lighting = ambient;

// 将两个纹理混合并乘以环境光分量
FragColor = mix(texture(texture0, TexCoord), texture(texture1, TexCoord), blendRatio) * vec4(lighting, 1.0);
}

顶点着色器源码的输出会作为片段着色器源码的输入

utils

新增的代码文件只有Cube.h和Cube.cpp,其他可以看源码的utils目录

Cube.h

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
#pragma once
#include "windowFactory.h"

#include <glad/glad.h>
#include "shader.h"
#include <vector>
#include <cmath>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

using std::vector;
using std::string;


class Cube {
public:
// 构造函数
Cube(GLFWWindowFactory* window);

// 析构函数
~Cube();

// 绘制函数
void draw();

private:
// 顶点数组对象
GLuint VAO;
// 顶点缓冲对象
GLuint VBO;
// 索引缓冲对象
GLuint EBO;
// 着色器对象
Shader shader;
// 点光源着色器对象
Shader lightShader;
// 窗口对象
GLFWWindowFactory* window;
// 纹理对象
vector<GLuint> texture;

// 设置顶点数据
void setupVertices();
// 加载纹理
void loadTexture();
// 绑定纹理
void bindTexture(GLuint& textureId, const char* path);
};

Cube.cpp

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
#include "Cube.h"
#include <iostream>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

Cube::Cube(GLFWWindowFactory* window) : window(window) {
this->shader = Shader("shader.vs", "shader.fs");
this->lightShader = Shader("lightShader.vs", "lightShader.fs");
// 加载纹理
loadTexture();
// 设置顶点数据
setupVertices();
}

Cube::~Cube() {
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
}

/// Public
void Cube::draw() {
// 存储光源的位置和强度
vector<float> lightPos = { 2.0f, 1.5f, -1.55f, 1.0f };

// 使用立方体的着色器
shader.use();
// 将立方体的VAO绑定到上下文
glBindVertexArray(this->VAO);

// 设置立方体模型矩阵
auto model = glm::mat4(1.0f);
// 设置平移矩阵
model = glm::translate(model, glm::vec3(0.0f, 0.0f, -3.0f));
// 设置旋转矩阵
model = glm::rotate(model, glm::radians(20.0f), glm::vec3(1.0f, 0.0f, 0.0f));

// 将uniform变量传递给着色器
shader.setMat4("model", model);
shader.setMat4("view", this->window->getViewMatrix());
shader.setMat4("projection", this->window->getProjectionMatrix());
shader.setVec3("lightColor", 1.0f, 1.0f, 1.0f);
shader.setVec3("lightPos", lightPos[0], lightPos[1], lightPos[2]);
shader.setFloat("blendRatio", 0.5f);

// 绘制立方体
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

// 使用光源的着色器
lightShader.use();
// 光源的VAO仍然使用立方体的VAO
// 设置模型矩阵
model = glm::mat4(1.0f);
// 设置平移矩阵
model = glm::translate(model, glm::vec3(lightPos[0], lightPos[1], lightPos[2]));
// 设置缩放矩阵
model = glm::scale(model, glm::vec3(0.2f));

// 将uniform变量传递给着色器
lightShader.setMat4("model", model);
lightShader.setMat4("view", this->window->getViewMatrix());
lightShader.setMat4("projection", this->window->getProjectionMatrix());
lightShader.setVec3("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));

// 绘制光源
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
}


/// Private
// 设置顶点数据
void Cube::setupVertices() {
// 顶点数据
float vectices[] = {
// Front
-0.75f, -0.75f,0.0f, 0.0f, 0.0f, // Bottom-left vertex
0.75f, -0.75f,0.0f, 1.0f, 0.0f, // Bottom-right vertex
-0.75f, 0.75f,0.0f, 0.0f, 1.0f, // Top-left vertex
0.75f, 0.75f,0.0f, 1.0f, 1.0f, // Top-right vertex

// Back
0.75f, -0.75f, -1.5f, 0.0f, 0.0f, // Bottom-left vertex
-0.75f, -0.75f, -1.5f, 1.0f, 0.0f, // Bottom-right vertex
0.75f, 0.75f, -1.5f, 0.0f, 1.0f, // Top-left vertex
-0.75f, 0.75f, -1.5f, 1.0f, 1.0f, // Top-right vertex

// Left
-0.75f, -0.75f, -1.5f, 0.0f, 0.0f, // Bottom-left vertex
-0.75f, -0.75f, 0.0f, 1.0f, 0.0f, // Bottom-right vertex
-0.75f, 0.75f, -1.5f, 0.0f, 1.0f, // Top-left vertex
-0.75f, 0.75f, 0.0f, 1.0f, 1.0f, // Top-right vertex

// Right
0.75f, -0.75f, 0.0f, 0.0f, 0.0f, // Bottom-left vertex
0.75f, -0.75f, -1.5f, 1.0f, 0.0f, // Bottom-right vertex
0.75f, 0.75f, 0.0f, 0.0f, 1.0f, // Top-left vertex
0.75f, 0.75f, -1.5f, 1.0f, 1.0f, // Top-right vertex

// Top
-0.75f, 0.75f, 0.0f, 0.0f, 0.0f, // Bottom-left vertex
0.75f, 0.75f, 0.0f, 1.0f, 0.0f, // Bottom-right vertex
-0.75f, 0.75f, -1.5f, 0.0f, 1.0f, // Top-left vertex
0.75f, 0.75f, -1.5f, 1.0f, 1.0f, // Top-right vertex

// Bottom
-0.75f, -0.75f, -1.5f, 0.0f, 0.0f, // Bottom-left vertex
0.75f, -0.75f, -1.5f, 1.0f, 0.0f, // Bottom-right vertex
-0.75f, -0.75f, 0.0f, 0.0f, 1.0f, // Top-left vertex
0.75f, -0.75f, 0.0f, 1.0f, 1.0f // Top-right vertex
};

// 索引数据
int indices[] = {
0, 1, 2,
2, 1, 3,

4, 5, 6,
6, 5, 7,

8, 9, 10,
10, 9, 11,

12, 13, 14,
14, 13, 15,

16, 17, 18,
18, 17, 19,

20, 21, 22,
22, 21, 23
};

// 创建VAO, VBO, EBO
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);

// 绑定VAO
glBindVertexArray(VAO);

// 绑定VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 将顶点数据复制到VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vectices), vectices, GL_STATIC_DRAW);

// 绑定EBO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
// 将索引数据复制到EBO
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

// 设置顶点位置属性(这里跟顶点着色器源码强相关,每个属性有多少个元素都是看这个顶点着色器源码是怎么写的)
// 位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 纹理位置属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

// 恢复上下人默认的VAO
glBindVertexArray(0);
}

// 加载纹理
void Cube::loadTexture() {
vector<string> paths = { "teenager.png", "tex.png" };
this->texture.resize(paths.size());
// 绑定纹理
for (int i = 0; i < paths.size(); i++) {
bindTexture(this->texture[i], paths[i].c_str());
}

// 激活纹理
glActiveTexture(GL_TEXTURE0);
// 将纹理绑定到上下文
glBindTexture(GL_TEXTURE_2D, this->texture[0]);
// 激活纹理
glActiveTexture(GL_TEXTURE1);
// 将纹理绑定到上下文
glBindTexture(GL_TEXTURE_2D, this->texture[1]);

// 使用立方体着色器
this->shader.use();
// 为着色器设置uniform变量
this->shader.setInt("texture0", 0);
this->shader.setInt("texture1", 1);
}

// 绑定纹理
void Cube::bindTexture(GLuint& textureId, const char* path) {
// 生成纹理
glGenTextures(1, &textureId);
// 绑定上下文的纹理
glBindTexture(GL_TEXTURE_2D, textureId);

// 设置纹理环绕方式
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_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// 加载和生成纹理
stbi_set_flip_vertically_on_load(true);
int width, height, nrChannels;
unsigned char* data = stbi_load(path, &width, &height, &nrChannels, 0);

if (data) {
GLenum format;
if (nrChannels == 4)
format = GL_RGBA;
else if (nrChannels == 3)
format = GL_RGB;
else
format = GL_RED;

glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else {
std::cout << "Failed to load texture" << std::endl;
}

stbi_image_free(data);
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "utils/Cube.h"
#include "utils/windowFactory.h"

int main() {
// 创建一个窗口Factory对象
GLFWWindowFactory myWindow(800, 600, "This is Title");
// 创建一个矩形模型对象
Cube cube(&myWindow);

// 运行窗口,传入一个lambda表达式,用于自定义渲染逻辑
myWindow.run([&]() {
// 绘制矩形
cube.draw();
});

return 0;
}

CMakeLists.txt

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
# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.10)
# 设置项目名称
project(Ambient)

# vcpkg集成, 这里要换成你自己的vcpkg工具链文件和共享库路径
set(VCPKG_ROOT D:/software6/vcpkg/)
set(CMAKE_TOOLCHAIN_FILE ${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
set(CMAKE_PREFIX_PATH ${VCPKG_ROOT}/installed/x64-mingw-static/share)

# 查找所需的包
find_package(glad CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED)
find_package(assimp CONFIG REQUIRED)
find_package(yaml-cpp CONFIG REQUIRED)

# 搜索并收集utils文件夹下的所有源文件
file(GLOB UTILS "utils/*.cpp", "utils/*.h")

# 添加可执行文件(还要加入utils文件夹下的源文件)
add_executable(Ambient main.cpp ${UTILS})

# 链接所需的库
target_link_libraries(Ambient PRIVATE glad::glad glfw glm::glm assimp::assimp yaml-cpp::yaml-cpp)

# 检查项目是否有dependeicies目录,如果存在,则在使用add_custom_command命令在构建后将dependencies目录中的文件复制到项目的输出目录
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dependencies")
if(EXISTS ${SOURCE_DIR})
add_custom_command(TARGET Ambient POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${SOURCE_DIR} $<TARGET_FILE_DIR:Ambient>)
endif()

最终效果

可以看到,在只有环境光的情况下,立方体是很暗的,因为点光源(右上角那个很亮的立方体)的光源还没有应用到立方体上


OpenGL入门008——环境光在片段着色器中的应用
http://example.com/2024/11/20/OpenGL入门008——环境光在片段着色器中的应用/
作者
凌云行者
发布于
2024年11月20日
许可协议