本节将在片段着色器中应用环境光照(Ambient)
文章目录
一些概念
光照模型
环境光
概述: 在场景中无处不在、均匀分布的光线,用来模拟从周伟环境反射到物体上的光。即使没有明确的光源,物体表面仍然有一定亮度
特点:
- 不依赖光源方向
- 为整个物体提供均匀的基本亮度
- 不计算光源与物体表面的方向关系
公式:
I a = K a ⋅ I a m b i e n t I_a = K_a \cdot I_{ambient} Ia=Ka⋅Iambient
- I a I_a Ia:物体表面的环境光亮度
- k a k_a ka:环境光的反射系数(0到1之间)
- I a m b i e n t I_{ambient} Iambient:环境光强度
漫反射
概述: 描述的是粗糙表面对光的反射,反射的光线相关各个方向均匀分布,与视角无光
特点:
- 亮度取决于光源的方向和物体表面法向量之间的夹角
- 适合模拟不光滑的表面,例如木材、纸张等
- 视角变化不会影响光的强度
公式(朗伯余弦定律):
I d = k d ⋅ I l i g h t ⋅ m a x ( 0 , L ⋅ N ) I_d = k_d \cdot I_{light} \cdot max(0, L \cdot N) Id=kd⋅Ilight⋅max(0,L⋅N)
- I d I_d Id:物体表面的漫反射亮度
- k d k_d kd:漫反射的反射系数(0到1之间)
- I l i g h t I_{light} Ilight:光源强度
- L:指向光源的单位向量
- N:表面的法向量
镜面反射
概述: 镜面反射描述的是光滑表面(如金属或镜子)对光的反射,反射光集中在一个特定方向上,与视角密切相关
特点:
- 表现为高光,即表面某些点的强亮反射
- 亮度取决于观察者方向、光源方向于物体表面的关系
- 表面越光滑,高光越集中;越粗糙,高光越分散
公式(Phong反射模型):
I s = k s ⋅ I l i g h t ⋅ m a x ( 0 , R ⋅ V ) n I_s = k_s \cdot I_{light} \cdot max(0, R \cdot V)^n Is=ks⋅Ilight⋅max(0,R⋅V)n
- I s I_s Is:镜面反射亮度
- k s k_s ks:镜面反射系数(0到1之间)
- R:反射方向的单位向量
- V:观察方向的单位向量
- n:高光的锐利程度,称为"高光指数"
总结
- 环境光:提供基础的整体亮度,与光源和视角无光
- 漫反射:模拟粗糙表面的光照,依赖于光源方向,但与视角无关
- 镜面反射:模拟光滑表面的高光,依赖于光源方向和视角
这三种光照成分通常组合在一起形成Phong光照模型,用于计算场景中物体的颜色和亮度:
I = I a + I d + I s I = I_a + I_d + I_s I=Ia+Id+Is
实战
简介
怎么在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
点光源的顶点着色器源码:
vs
#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
点光源的片段着色器源码:
fs
#version 330 core
// 光源颜色
uniform vec3 lightColor;
// 输出的片段颜色
out vec4 FragColor;
void main()
{
FragColor = vec4(lightColor, 1.0);
}
shader.vs
立方体的顶点着色器源码:
vs
#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
fs
#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
cpp
#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
cpp
#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
cpp
#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
cmake
# 设置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()
最终效果
可以看到,在只有环境光的情况下,立方体是很暗的,因为点光源(右上角那个很亮的立方体)的光源还没有应用到立方体上