一、简介
本文介绍了如何使用OpenGL、glsl实现Phong
,Blinn-Phong
光照模型。本文在[OpenGL]使用OpenGL加载obj模型、绘制纹理的基础上实现Phong
和Blinn-Phong
模型。
按照本文代码实现后,理论上可以得到以下结果:
Phong
模型绘制结果:
Blinn-Phong
模型绘制结果:
二、Phong、Blinn-phong模型介绍
1.Phong模型
在Phong
模型中,物体表面反射光线由环境光(Ambient)、漫反射(Diffuse)和镜面反射光(Specular)三部分组成。
1).环境光 Ambient
环境光是均匀分布在整个场景中的光线,用于模拟间接光照。即使物体不直接受到光源照射,也能通过环境光获得一些基础亮度。
计算公式如下:
I
a
=
k
a
∗
L
a
Ia = ka * La
Ia=ka∗La
其中
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=kd∗Ld∗max(0,L⋅N)
其中
I
d
Id
Id为漫反射强度,
k
d
kd
kd为漫反射系数,
L
d
Ld
Ld为光源强度,
L
⃗
\vec{L}
L为光源向量,
N
⃗
\vec{N}
N为着色点的法向,如下图所示:
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=ks∗Ls∗max(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为视点向量,如下图所示:
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=ks∗Ls∗max(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。示意图如下:
三、使用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.vert
和fragmentShader.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.youkuaiyun.com/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代码
6.1). 代码整体流程
- 初始化glfw,glad,窗口
- 编译 shader 程序
- 加载obj模型、纹理图片
- 设置光源和相机位置,Phong(Blinn-Phong)模型参数
- 开始绘制
- 设置MVP变换矩阵
- 释放资源
6.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);
}
7. 编译运行及结果
编译运行:
cd ./build
cmake ..
make
./OpenGL_Phong_Model
绘制结果:
Phong
模型绘制结果:
Blinn-Phong
模型绘制结果:
四、全部代码及模型文件
全部代码以及模型文件可以在使用OpenGL实现Phong、Blinn-Phong模型中下载。
五、参考
[1].Phong光照模型
[2].LearnOpenGL-高级光照
[3].计算机图形学五:局部光照模型(Blinn-Phong 反射模型)与着色方法(Phong Shading)