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

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

文章目录

一些概念

漫反射

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

特点:

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

公式(朗伯余弦定律):
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:表面的法向量

实战

简介

怎么在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

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

shadervs

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

vs 复制代码
#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

fs 复制代码
#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

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 = { 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

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()

最终效果

相关推荐
小老鼠不吃猫2 小时前
C++点云大文件读取
开发语言·c++
姚先生972 小时前
LeetCode 35. 搜索插入位置 (C++实现)
c++·算法·leetcode
CoderCodingNo3 小时前
【GESP】C++二级考试大纲知识点梳理, (4)流程图
开发语言·c++·流程图
小小unicorn3 小时前
【C++初阶】STL详解(十三)—— 用一个哈希表同时封装出unordered_map和unordered_set
java·c++·散列表
慕羽★3 小时前
详细介绍如何使用rapidjson读取json文件
linux·c++·windows·json·file·param·rapidjson
大梦百万秋4 小时前
C++中的虚拟化:通过虚拟机管理模拟服务器虚拟化
服务器·开发语言·c++
shentuyu木木木(森)4 小时前
入门STL(map/multiset)
开发语言·数据结构·c++·算法·map·multiset
这是我584 小时前
C++打小怪游戏2
c++·游戏
axxy20005 小时前
leetcode之hot100---206反转链表(C++)
c++·leetcode·链表
SRKkAI5 小时前
取子串(指针)
数据结构·c++·算法