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

本节将在片段着色器中应用环境光照(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()

最终效果

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

相关推荐
Mongxin_Chan25 分钟前
【Cpp】指针与引用
c++·算法
SSL_lwz1 小时前
P11290 【MX-S6-T2】「KDOI-11」飞船
c++·学习·算法·动态规划
熬夜学编程的小王1 小时前
【C++篇】从基础到进阶:全面掌握C++ List容器的使用
开发语言·c++·list·双向链表·迭代器失效
悄悄敲敲敲1 小时前
C++:智能指针
开发语言·c++
zhangpz_1 小时前
c ++零基础可视化——vector
c++·算法
萨达大1 小时前
23种设计模式-模板方法(Template Method)设计模式
java·c++·设计模式·软考·模板方法模式·软件设计师·行为型设计模式
刀鋒偏冷1 小时前
ninja: error: ‘/opt/homebrew/Cellar/opensslxxx/xx/lib/libssl.dylib
c++
理论最高的吻2 小时前
98. 验证二叉搜索树【 力扣(LeetCode) 】
数据结构·c++·算法·leetcode·职场和发展·二叉树·c
沈小农学编程2 小时前
【LeetCode面试150】——202快乐数
c++·python·算法·leetcode·面试·职场和发展
ZZZ_O^O2 小时前
【动态规划-卡特兰数——96.不同的二叉搜索树】
c++·学习·算法·leetcode·动态规划