OpenGL入门009——漫反射在片段着色器中的应用

本节将在片段着色器中应用漫反射

一些概念

漫反射

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

特点:

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

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

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

实战

简介

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

目的: 使用环境光

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

源码: OpenGL-Learn-Program/009-diffuse-reflection at main · 1037827920/OpenGL-Learn-Program

dependencies

主要改动shader.vs和shader.fs,其他文件看这里

shadervs

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

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

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

out vec2 TexCoord;
out vec3 Normal;
out vec3 FragPos;

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

TexCoord = vTexCoord;
Normal = mat3(transpose(inverse(model))) * vNormal;
FragPos = vec3(model * vec4(vPos, 1.0f));
}

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#version 330 core

in vec2 TexCoord;
// 表示片段的法向量,用于确定片段相对于光源的角度
in vec3 Normal;
// 表示片段在世界空间中的位置,用于确定片段相对于光源的距离
in vec3 FragPos;

// 第一个纹理
uniform sampler2D texture0;
// 第二个纹理
uniform sampler2D texture1;
// 混合比例
uniform float blendRatio;
// 立方体本身的环境光 光源颜色
uniform vec3 lightColor;
// 点光源位置
uniform vec3 lightPos;

out vec4 FragColor;

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

// 标准化法向量
vec3 norm = normalize(Normal);
// 计算片段位置和光源位置之间的方向向量
vec3 lightDir = normalize(lightPos - FragPos);
// 计算光源到片段的距离
float distance = length(lightPos - FragPos);
// 计算衰减
float attenuation = 1.0 / (distance * distance);
// 计算漫反射分量
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor * attenuation;

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

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

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

utils

修改的代码文件只有和Cube.cpp,其他可以看这里

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
225
226
#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 = { 1.0f, 0.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 vertices[] = {
// Front
-0.75f, -0.75f,0.0f, 0.0f, 0.0f, 0.0f, 0.0f,1.0f, // Bottom-left vertex
0.75f, -0.75f,0.0f, 1.0f, 0.0f, 0.0f, 0.0f,1.0f, // Bottom-right vertex
-0.75f, 0.75f,0.0f, 0.0f, 1.0f, 0.0f, 0.0f,1.0f, // Top-left vertex
0.75f, 0.75f,0.0f, 1.0f, 1.0f, 0.0f, 0.0f,1.0f, // Top-right vertex

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

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

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

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

// Bottom
-0.75f, -0.75f, -1.5f, 0.0f, 0.0f, 0.0f,-1.0f,0.0f, // Bottom-left vertex
0.75f, -0.75f, -1.5f, 1.0f, 0.0f, 0.0f,-1.0f,0.0f, // Bottom-right vertex
-0.75f, -0.75f, 0.0f, 0.0f, 1.0f, 0.0f,-1.0f,0.0f, // Top-left vertex
0.75f, -0.75f, 0.0f, 1.0f, 1.0f , 0.0f,-1.0f,0.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(vertices), vertices, 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, 8 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 纹理位置属性
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// 法向量属性
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float)));
glEnableVertexAttribArray(2);

// 恢复上下人默认的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入门009——漫反射在片段着色器中的应用
http://example.com/2024/11/20/OpenGL入门009——漫反射在片段着色器中的应用/
作者
凌云行者
发布于
2024年11月20日
许可协议