当前位置: 首页 > article >正文

[OpenGL]使用OpenGL实现Phong、Blinn-Phong模型

一、简介

本文介绍了如何使用OpenGL、glsl实现PhongBlinn-Phong光照模型。本文在[OpenGL]使用OpenGL加载obj模型、绘制纹理的基础上实现PhongBlinn-Phong模型。

按照本文代码实现后,理论上可以得到以下结果:

Phong模型绘制结果:
Phong模型绘制结果

Blinn-Phong模型绘制结果:
blinn-phong模型绘制结果

二、Phong、Blinn-phong模型介绍

1.Phong模型

Phong模型中,物体表面反射光线由环境光(Ambient)、漫反射(Diffuse)和镜面反射光(Specular)三部分组成。

1).环境光 Ambient

环境光是均匀分布在整个场景中的光线,用于模拟间接光照。即使物体不直接受到光源照射,也能通过环境光获得一些基础亮度。
计算公式如下:
I a = k a ∗ L a Ia = ka * La Ia=kaLa
其中 I a Ia Ia为环境光强度, k a ka ka为环境光反射系数, L a La La为光源强度。

2).漫反射光 Diffuse

漫反射光模拟了粗糙表面上的光线反射,漫反射取决于光源与表面法向量之间的夹角。当光线垂直照射到物体表面时,漫反射光最强;当角度变小时,亮度也会减弱。
计算公式如下:
I d = k d ∗ L d ∗ m a x ( 0 , L ⃗ ⋅ N ⃗ ) Id = kd * Ld * max(0, \vec{L}\cdot\vec{N}) Id=kdLdmax(0,L N )
其中 I d Id Id为漫反射强度, k d kd kd为漫反射系数, L d Ld Ld为光源强度, L ⃗ \vec{L} L 为光源向量, N ⃗ \vec{N} N 为着色点的法向,如下图所示:

向量L,N和V示意图

3).镜面反射光 Specular

镜面反射光用于模拟光滑表面上的高光区域。镜面反射光取决于观察方向、光源方向和表面法向量之间的关系。Phong模型使用一个称为“高光系数”的值 s s s来控制高光区域的大小和亮度。计算公式如下:
I s = k s ∗ L s ∗ m a x ( 0 , R ⃗ ⋅ V ⃗ ) s Is = ks*Ls*max(0, \vec{R}\cdot\vec{V})^{s} Is=ksLsmax(0,R V )s
其中 I s Is Is为镜面反射强度, k s ks ks为镜面反射系数, L s Ls Ls为光源强度, s s s为高光系数, R ⃗ \vec{R} R 为反射光向量, V ⃗ \vec{V} V 为视点向量,如下图所示:

向量L,N,R和V示意图

2.Blinn-phong模型

Blinn-Phong光照模型是对Phong光照模型的一种改进,通过修改镜面反射部分的计算方式,使得光照效果更加逼真且计算更高效。与Phong模型类似,Blinn-Phong光照模型也由环境光、漫反射光和镜面反射光三个分量组成,但在镜面反射光的计算上有所不同。
Blinn-Phong模型的环境光和漫反射部分与Phong模型相同,此处不再赘述。只介绍与Phong模型不同的镜面反射光部分。

1).镜面反射光 Specular

在Blinn-Phong模型中,引入了半程向量(Halfway Vector) H H H来替代Phong模型中的反射向量 R R R。Blinn-Phong模型中镜面反射光的计算公式如下:
I s = k s ∗ L s ∗ m a x ( 0 , H ⃗ ⋅ V ⃗ ) s Is = ks*Ls*max(0,\vec{H}\cdot\vec{V})^s Is=ksLsmax(0,H V )s
与Phong模型中变量含义相同。唯一不同的是,半程向量 H H H为向量 L L L V V V的平分线向量,即 H ⃗ = ( L ⃗ + V ⃗ ) / 2.0 \vec{H}=(\vec{L}+\vec{V})/2.0 H =(L +V )/2.0。示意图如下:

向量L,N,H和V示意图

三、使用OpenGL实现Phong、Blinn-Phong模型

0. 环境需要

  • Linux,或者 windos下使用wsl2。
  • 安装GLFW和GLAD。请参考[OpenGL] wsl2上安装使用cmake+OpenGL教程。
  • 安装glm。glm是个可以只使用头文件的库,因此可以直接下载release的压缩文件,然后解压到include目录下。例如,假设下载的release版本的压缩文件为glm-1.0.1-light.zip。将glm-1.0.1-light.zip复制include目录下,然后执行以下命令即可解压glm源代码:
    unzip glm-1.0.1-light.zip
    
  • 需要下载 stb_image.h 作为加载.png图像的库。将 stb_image.h 下载后放入include/目录下。

1. 项目目录

项目目录

其中:

  • Mesh.hpp 包含了自定义的 Vertex, Texture, 和 Mesh 类,用于加载 obj 模型、加载图片生成纹理。
  • Shader.hpp 用于创建 shader 程序。
  • vertexShader.vertfragmentShader.frag是用于编译 shader 程序的 顶点着色器 和 片段着色器 代码。

下面介绍各部分的代码:

2. CMakeLists.txt代码

cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 14)

project(OpenGL_Phong_Model)

include_directories(include)

find_package(glfw3 REQUIRED)
file(GLOB project_file main.cpp glad.c)
add_executable(${PROJECT_NAME} ${project_file})
target_link_libraries(${PROJECT_NAME} glfw)

3. Mesh.hpp 代码

#pragma once

#include <glad/glad.h> // holds all OpenGL type declarations
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "Shader.hpp"

#include <string>
#include <vector>
using namespace std;

struct Vertex
{
    // position
    glm::vec3 Position;
    // normal
    glm::vec3 Normal;
    // texCoords
    glm::vec2 TexCoords;
};

struct Texture
{
    string path;
    GLuint Id; // 纹理 id
};

class Mesh
{
  public:
    // mesh Data
    vector<Vertex> vertices;// vertex 数据,一个顶点包括 position, normal 和 texture coord 三个信息
    vector<unsigned int> indices;// index 数据,用于拷贝到 EBO 中
    Texture texture;

    unsigned int VAO;

    Mesh(string obj_path, string texture_path = "")
    {
        // load obj
        ifstream obj_file(obj_path, std::ios::in);
        if (obj_file.is_open() == false)
        {
            std::cerr << "Failed to load obj: " << obj_path << "\n";
            return;
        }

        int position_id = 0;
        int normal_id = 0;
        int texture_coord_id = 0;
        string line;
        while (getline(obj_file, line))
        {
            std::istringstream iss(line);
            std::string prefix;
            iss >> prefix;

            if (prefix == "v") // vertex
            {
                if (vertices.size() <= position_id)
                {
                    vertices.push_back(Vertex());
                }

                iss >> vertices[position_id].Position.x;
                iss >> vertices[position_id].Position.y;
                iss >> vertices[position_id].Position.z;
                position_id++;
            }
            else if (prefix == "vn") // normal
            {
                if (vertices.size() <= normal_id)
                {
                    vertices.push_back(Vertex());
                }

                iss >> vertices[normal_id].Normal.x;
                iss >> vertices[normal_id].Normal.y;
                iss >> vertices[normal_id].Normal.z;
                normal_id++;
            }
            else if (prefix == "vt") // texture coordinate
            {
                if (vertices.size() <= texture_coord_id)
                {
                    vertices.push_back(Vertex());
                }

                iss >> vertices[texture_coord_id].TexCoords.x;
                iss >> vertices[texture_coord_id].TexCoords.y;
                texture_coord_id++;
            }
            else if (prefix == "f") // face
            {

                for (int i = 0; i < 3; ++i)
                {
                    std::string vertexData;
                    iss >> vertexData;
                    unsigned int ver, tex, nor;
                    sscanf(vertexData.c_str(), "%d/%d/%d", &ver, &tex, &nor);
                    indices.push_back(ver - 1);
                }
            }
        }
        obj_file.close();

        // load texture
        GLuint textureID;
        glGenTextures(1, &textureID); // 生成纹理 ID
        glBindTexture(GL_TEXTURE_2D, textureID); // 绑定纹理,说明接下来对纹理的操作都应用于对象 textureID 上

        // 设置纹理参数
        // 设置纹理在 S 方向(水平方向)的包裹方式为 GL_REPEAT
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        // 设置纹理在 T 方向(垂直方向)的包裹方式为 GL_REPEAT
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        // 设置纹理的缩小过滤方式,当纹理变小时,使用 GL_LINEAR (线性过滤)方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        // 设置纹理的放大过滤方式,当纹理变大时,使用 GL_LINEAR (线性过滤)方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // 加载纹理图像
        int width, height, nrChannels;
        stbi_set_flip_vertically_on_load(true);
        unsigned char *data = stbi_load(texture_path.c_str(), &width, &height, &nrChannels, 0);
        if (data)
        {
            GLenum format;
            if (nrChannels == 1)
                format = GL_RED;
            else if (nrChannels == 3)
                format = GL_RGB;
            else if (nrChannels == 4)
                format = GL_RGBA;

            // 生成纹理
            glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
            glGenerateMipmap(GL_TEXTURE_2D); // 生成 Mipmaps
        }
        else
        {
            std::cerr << "Failed to load texture: " << texture_path << "\n";
        }
        stbi_image_free(data); // 释放图像内存
        glBindTexture(GL_TEXTURE_2D, 0); // 解绑纹理

        texture.Id = textureID;
        texture.path = texture_path;

        setupMesh();
    }

    // render the mesh
    void Draw(Shader &shader)
    {
        // draw mesh
        glActiveTexture(GL_TEXTURE0);                                    // 激活 纹理单元0
        glBindTexture(GL_TEXTURE_2D, texture.Id);                        // 绑定纹理,将纹理texture.id 绑定到 纹理单元0 上
        glUniform1i(glGetUniformLocation(shader.ID, "texture1"), 0); // 将 shader 中的 texture1 绑定到 纹理单元0

        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, static_cast<unsigned int>(indices.size()), GL_UNSIGNED_INT, 0);
        glBindTexture(GL_TEXTURE_2D, 0);
        glBindVertexArray(0);
    }

  private:
    // render data
    unsigned int VBO, EBO;

    // initializes all the buffer objects/arrays
    void setupMesh()
    {
        // create buffers/arrays
        glGenVertexArrays(1, &VAO);
        glGenBuffers(1, &VBO);
        glGenBuffers(1, &EBO);

        glBindVertexArray(VAO);
        // load data into vertex buffers
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        // A great thing about structs is that their memory layout is sequential for all its items.
        // The effect is that we can simply pass a pointer to the struct and it translates perfectly to a glm::vec3/2
        // array which again translates to 3/2 floats which translates to a byte array.
        glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

        // set the vertex attribute pointers
        // vertex Positions
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0);
        // vertex normals
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, Normal));
        // vertex texture coords
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, TexCoords));

        glBindVertexArray(0);
    }
};

4. Shader.hpp 代码

Shader.hpp 即 LearnOpenGL-入门-着色器 中的 由于管理 shader 的头文件。

#ifndef SHADER_H
#define SHADER_H

#include <glad/glad.h>
#include <glm/glm.hpp>

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

class Shader
{
  public:
    unsigned int ID;
    // constructor generates the shader on the fly
    // ------------------------------------------------------------------------
    Shader(const char *vertexPath, const char *fragmentPath)
    {
        // 1. retrieve the vertex/fragment source code from filePath
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;
        // ensure ifstream objects can throw exceptions:
        vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
        try
        {
            // open files
            vShaderFile.open(vertexPath);
            fShaderFile.open(fragmentPath);
            std::stringstream vShaderStream, fShaderStream;
            // read file's buffer contents into streams
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();
            // close file handlers
            vShaderFile.close();
            fShaderFile.close();
            // convert stream into string
            vertexCode = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        }
        catch (std::ifstream::failure &e)
        {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
        }
        const char *vShaderCode = vertexCode.c_str();
        const char *fShaderCode = fragmentCode.c_str();
        // 2. compile shaders
        unsigned int vertex, fragment;
        // vertex shader
        vertex = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertex, 1, &vShaderCode, NULL);
        glCompileShader(vertex);
        checkCompileErrors(vertex, "VERTEX");
        // fragment Shader
        fragment = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragment, 1, &fShaderCode, NULL);
        glCompileShader(fragment);
        checkCompileErrors(fragment, "FRAGMENT");
        // shader Program
        ID = glCreateProgram();
        glAttachShader(ID, vertex);
        glAttachShader(ID, fragment);
        glLinkProgram(ID);
        checkCompileErrors(ID, "PROGRAM");
        // delete the shaders as they're linked into our program now and no longer necessary
        glDeleteShader(vertex);
        glDeleteShader(fragment);
    }
    // activate the shader
    // ------------------------------------------------------------------------
    void use() const
    {
        glUseProgram(ID);
    }
    // utility uniform functions
    // ------------------------------------------------------------------------
    void setBool(const std::string &name, bool value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
    }
    // ------------------------------------------------------------------------
    void setInt(const std::string &name, int value) const
    {
        glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
    }
    // ------------------------------------------------------------------------
    void setFloat(const std::string &name, float value) const
    {
        glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
    }
    // ------------------------------------------------------------------------
    void setVec2(const std::string &name, const glm::vec2 &value) const
    {
        glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
    }
    void setVec2(const std::string &name, float x, float y) const
    {
        glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y);
    }
    // ------------------------------------------------------------------------
    void setVec3(const std::string &name, const glm::vec3 &value) const
    {
        glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
    }
    void setVec3(const std::string &name, float x, float y, float z) const
    {
        glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z);
    }
    // ------------------------------------------------------------------------
    void setVec4(const std::string &name, const glm::vec4 &value) const
    {
        glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
    }
    void setVec4(const std::string &name, float x, float y, float z, float w) const
    {
        glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w);
    }
    // ------------------------------------------------------------------------
    void setMat2(const std::string &name, const glm::mat2 &mat) const
    {
        glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }
    // ------------------------------------------------------------------------
    void setMat3(const std::string &name, const glm::mat3 &mat) const
    {
        glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }
    // ------------------------------------------------------------------------
    void setMat4(const std::string &name, const glm::mat4 &mat) const
    {
        glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, &mat[0][0]);
    }

  private:
    // utility function for checking shader compilation/linking errors.
    // ------------------------------------------------------------------------
    void checkCompileErrors(GLuint shader, std::string type)
    {
        GLint success;
        GLchar infoLog[1024];
        if (type != "PROGRAM")
        {
            glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n"
                          << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
        else
        {
            glGetProgramiv(shader, GL_LINK_STATUS, &success);
            if (!success)
            {
                glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n"
                          << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
            }
        }
    }
};
#endif

5. vertexShader.vert 和 fragmentShader.frag 代码

vertexShader.vert

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNor;
layout (location = 2) in vec2 aTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 vertexPos;
out vec3 vertexNor;
out vec2 textureCoord;
void main()
{
    textureCoord = aTexCoord;
    // 裁剪空间坐标系 (clip space) 中 点的位置
    gl_Position = projection * view * model * vec4(aPos, 1.0f);
    // 世界坐标系 (world space) 中 点的位置
    vertexPos = (model * vec4(aPos,1.0f)).xyz;
    // 世界坐标系 (world space) 中 点的法向
    // 该对计算公式的解释请参考 [Note:变换法线向量](https://blog.csdn.net/qq_38065509/article/details/105691559)
    vertexNor = mat3(transpose(inverse(model))) * aNor;
}

fragmentShader.frag

#version 330 core
out vec4 FragColor;

in vec3 vertexPos;
in vec3 vertexNor;
in vec2 textureCoord;
uniform vec3 cameraPos;
uniform vec3 lightPos;
uniform vec3 k;
uniform sampler2D texture1;
void main() {

  vec3 lightColor = vec3(1.0f, 1.0f, 1.0f);

  // Ambient
  // Ia = ka * La
  float ambientStrenth = k[0];
  vec3 ambient = ambientStrenth * lightColor;

  // Diffuse
  // Id = kd * max(0, normal dot light) * Ld
  float diffuseStrenth = k[1];
  vec3 normalDir = normalize(vertexNor);
  vec3 lightDir = normalize(lightPos - vertexPos);
  vec3 diffuse =
      diffuseStrenth * max(dot(normalDir, lightDir), 0.0) * lightColor;

  // Specular (Phong)
  // Is = ks * (view dot reflect)^s * Ls

  float specularStrenth = k[2];
  vec3 viewDir = normalize(cameraPos - vertexPos);
  vec3 reflectDir = reflect(-lightDir, normalDir);
  vec3 specular = specularStrenth *
                  pow(max(dot(viewDir, reflectDir), 0.0f), 2) * lightColor;
    
  // Specular (Blinn-Phong)
  // Is = ks * (normal dot halfway)^s Ls

  //   float specularStrenth = k[2];
  //   vec3 viewDir = normalize(cameraPos - vertexPos);
  //   vec3 halfwayDir = normalize(lightDir + viewDir);
  //   vec3 specular = specularStrenth *
  //                   pow(max(dot(normalDir, halfwayDir), 0.0f), 2) *
  //                   lightColor;

  // Obejct color
  vec3 objectColor = texture(texture1, textureCoord).xyz;

  // Color = Ambient + Diffuse + Specular
  // I = Ia + Id + Is
  FragColor = vec4((ambient + diffuse + specular) * objectColor, 1.0f);
}

6. main.cpp代码

1). 代码整体流程

  1. 初始化glfw,glad,窗口
  2. 编译 shader 程序
  3. 加载obj模型、纹理图片
  4. 设置光源和相机位置,Phong(Blinn-Phong)模型参数
  5. 开始绘制
  6. 设置MVP变换矩阵
  7. 释放资源

2). main.cpp代码

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "Shader.hpp"
#include "Mesh.hpp"

#include "glm/ext.hpp"
#include "glm/mat4x4.hpp"

#include <random>
#include <iostream>
// 用于处理窗口大小改变的回调函数
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
// 用于处理用户输入的函数
void processInput(GLFWwindow *window);

// 指定窗口默认width和height像素大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

/************************************/

int main()
{

    /****** 1.初始化glfw, glad, 窗口 *******/
    // glfw 初始化 + 配置 glfw 参数
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // 在创建窗口之前
    glfwWindowHint(GLFW_SAMPLES, 4); // 设置多重采样级别为4
    // glfw 生成窗口
    GLFWwindow *window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        // 检查是否成功生成窗口,如果没有成功打印出错信息并且退出
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    // 设置窗口window的上下文
    glfwMakeContextCurrent(window);
    // 配置window变化时的回调函数
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // 使用 glad 加载 OpenGL 中的各种函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    // 启用 深度测试
    glEnable(GL_DEPTH_TEST);
    // 启用 多重采样抗锯齿
    glEnable(GL_MULTISAMPLE);
    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // 使用线框模式,绘制时只绘制 三角形 的轮廓
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 使用填充模式,绘制时对 三角形 内部进行填充

    /************************************/

    /****** 2.编译 shader 程序 ******/
    Shader ourShader("../resources/vertexShader.vert", "../resources/fragmentShader.frag");
    /************************************/

    /****** 3.加载obj模型、纹理图片、Phong模型参数 ******/
    
    // Mesh ourModel("../resources/models/backpack/backpack.obj", "../resources/models/backpack/backpack.jpg"); //
    // backpack
    Mesh ourModel("../resources/models/spot/spot.obj", "../resources/models/spot/spot.png"); // dairy cow
    // Mesh ourModel("../resources/models/rock/rock.obj", "../resources/models/rock/rock.png"); // rock
    /************************************/

    /****** 4.设置光源和相机位置,Phong(Blinn-Phong)模型参数 ******/
    // I = Ia + Id + Is
    // Ia = ka * La
    // Id = kd * (normal dot light) * Ld
    // Is = ks * (reflect dot view)^s * Ls
    // 模型参数 ka, kd, ks
    float k[] = {0.1f, 0.7f, 0.2f}; // ka, kd, ks
    // 光源位置
    glm::vec3 light_pos = glm::vec3(-2.0f, 2.0f, 0.0f);
    // 相机位置
    glm::vec3 camera_pos = glm::vec3(0.0f, 0.0f, 1.5f);
    /************************************/

    /****** 5.开始绘制 ******/
    float rotate = 0.0f;

    while (!glfwWindowShouldClose(window))
    {
        rotate += 0.05f;
        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        // 清除颜色缓冲区 并且 清楚深度缓冲区
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 使用我们之前编译连接好的 shader 程序
        // 必须先使用 shaderProgram 然后才能操作 shaderProgram 中的 uniform 变量
        ourShader.use();

        /****** 6.设置 MVP 矩阵 ******/
        // model 矩阵
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f));
        model = glm::rotate(model, glm::radians(0.0f), glm::vec3(1.0f, 0.0f, 0.0f));
        model = glm::rotate(model, glm::radians(rotate), glm::vec3(0.0f, 1.0f, 0.0f));
        model = glm::rotate(model, glm::radians(0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
        model = glm::scale(model, glm::vec3(0.5f, 0.5f, 0.5f));

        // view 矩阵
        glm::mat4 view = glm::mat4(1.0f);
        view = glm::lookAt(camera_pos, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

        // projection 矩阵
        glm::mat4 projection = glm::mat4(1.0f);
        projection = glm::perspective(glm::radians(60.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        /************************************/

        ourShader.setMat4("model", model);
        ourShader.setMat4("view", view);
        ourShader.setMat4("projection", projection);
        ourShader.setVec3("k", k[0], k[1], k[2]);
        ourShader.setVec3("cameraPos", camera_pos);
        ourShader.setVec3("lightPos", light_pos);

        ourModel.Draw(ourShader);

        glfwSwapBuffers(window); // 在gfw中启用双缓冲,确保绘制的平滑和无缝切换
        glfwPollEvents(); // 用于处理所有挂起的事件,例如键盘输入、鼠标移动、窗口大小变化等事件
    }

    /************************************/
    /****** 7.释放资源 ******/
    // glfw 释放 glfw使用的所有资源
    glfwTerminate();
    /************************************/
    return 0;
}

// 用于处理用户输入的函数
void processInput(GLFWwindow *window)
{
    // 当按下 Esc 按键时调用 glfwSetWindowShouldClose() 函数,关闭窗口
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(window, true);
    }
}

// 在使用 OpenGL 和 GLFW 库时,处理窗口大小改变的回调函数
// 当窗口大小发生变化时,确保 OpenGL 渲染的内容能够适应新的窗口大小,避免图像被拉伸、压缩或出现其他比例失真的问题
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    glViewport(0, 0, width, height);
}

4. 编译运行及结果

编译运行:

cd ./build
cmake ..
make 
./OpenGL_Phong_Model

绘制结果:

Phong模型绘制结果:
Phong模型绘制结果

Blinn-Phong模型绘制结果:
blinn-phong模型渲染结果

四、全部代码及模型文件

全部代码以及模型文件可以在使用OpenGL实现Phong、Blinn-Phong模型中下载。

五、参考

[1].Phong光照模型
[2].LearnOpenGL-高级光照
[3].计算机图形学五:局部光照模型(Blinn-Phong 反射模型)与着色方法(Phong Shading)


http://www.kler.cn/news/358561.html

相关文章:

  • 深入探讨C++多线程性能优化
  • Day09-数据库服务备份恢复
  • python实战(一)——iris鸢尾花数据集分类
  • 蜜罐技术的出现究竟影响了什么
  • Android GPU Inspector分析帧数据快速入门
  • zh/FAQ/CentOSStream-CentOS Stream 常见问题
  • 探索 JavaScript 中的 AbortController API:不仅仅是中断 HTTP 请求
  • 多特征变量序列预测(一)——CNN-LSTM风速预测模型
  • 搭建一个vue3+vite框架
  • Redis-2
  • word中高亮标题、正文、表格、图表标题不同颜色用于批量排版
  • Debezium和SeaTunnel实现MySQL到Hadoop的实时数据流和全量同步
  • 赏金猎人 | 挖掘TP-Link 服务中的信息泄露漏洞
  • 前端常用的库有哪些?
  • Django学习-后台管理相关操作
  • k8s 部署 nexus3 详解
  • [k8s理论知识]2.docker基础(一)
  • Rancher—多集群Kubernetes管理平台
  • CSS进阶-布局(三)
  • [Linux Codec驱动]音频路由概念