写在前面
原文链接。原文应该是github上的一个项目,本文主要用来记录一些知识点和自己遇到的问题。
和箱子模型告别
所以,让我们导入一个由真正的艺术家所创造的模型,替代我这个天才的作品(你要承认,这些箱子可能是你看过的最漂亮的立方体了),测试一下我们的实现吧。由于我不想让我占太多的功劳,我会偶尔让别的艺术家也加入我们,这次我们将会加载Crytek的游戏孤岛危机(Crysis)中的原版纳米装(Nanosuit)。这个模型被输出为一个.obj文件以及一个.mtl文件,.mtl文件包含了模型的漫反射、镜面光和法线贴图(这个会在后面学习到),你可以在这里下载到(稍微修改之后的)模型,注意所有的纹理和模型文件应该位于同一个目录下,以供加载纹理。
现在在代码中,声明一个Model对象,将模型的文件位置传入。接下来模型应该会自动加载并(如果没有错误的话)在渲染循环中使用它的Draw函数来绘制物体,这样就可以了。不再需要缓冲分配、属性指针和渲染指令,只需要一行代码就可以了(真爽!)。接下来如果你创建一系列着色器,其中片段着色器仅仅输出物体的漫反射纹理颜色,最终的结果看上去会是这样的:
你可以在这里找到完整的源码。
这里有一个很坑的问题,可能让你得不到正确结果……事实上我也费了很多时间来debug,下面来说一下。
坑点
主要是由0号纹理单元引起的……
原文这里的代码其实是有点问题的,我们看一下他这里给出的着色器代码:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D texture_diffuse1;
void main()
{
FragColor = texture(texture_diffuse1, TexCoords);
}
再看我们在Mesh对象的Draw方法中设置的采样器名称:
很明显这里是对不上的,那么为什么我们还能看到模型呢?是因为0号纹理单元默认是一直激活的,在GLSL中的纹理采样器默认都会绑定到0号纹理单元,而我们在代码中恰好又绑定了纹理对象到这个纹理单元,所以能看到模型。那么我们可以改一下这个函数,让其激活并设置非零号纹理单元,就可以解决问题了——实际上并不能,你可以看一下texture对象的构造函数,其内调用了glBindTexture函数,也就是说只要使用了纹理对象,0号纹理单元上就一定绑定了某个纹理对象,那么在GLSL中进行采样时就一定要小心,采样的结果可能并不是黑色!出于这个原因我决定修改命名规则,我们使用sampler2D数组,同时传递两个整数给gpu,告诉它我们使用了多少个漫反射贴图和镜面光贴图。
第二个坑点是图片y轴的翻转问题。我们在纹理章节已经学过了:OpenGL要求y轴0.0坐标是在图片的底部的,但是图片的y轴0.0坐标通常在顶部。那么我们利用这个函数翻转图片的y轴:
但是还记得我们在导入obj文件的时候设置了什么吗?
aiProcess_FlipUVs将在处理的时候翻转y轴的纹理坐标。如果你感觉贴图很奇怪的话,可能就是这里的问题,两个地方都翻转了等于没有翻转。
回到主题
修改后的代码如下:
m e s h : mesh: mesh:
#pragma once
#ifndef MESH_H
#define MESH_H
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "shader.h"
#include <vector>
#include "texture.h"
#include <string>
#include <iostream>
using std::vector;
using std::string;
using std::cout;
using std::max;
struct Vertex
{
// 位置 法向量 纹理坐标
glm::vec3 Position;
glm::vec3 Normal;
glm::vec2 TexCoords;
};
class Mesh
{
public:
/* 网格数据 */
vector<Vertex> vertices;
vector<unsigned int> indices;
vector<Texture> textures;
/* 函数 */
Mesh(const vector<Vertex>& vertices, const vector<unsigned int>& indices, const vector<Texture>& textures);
void Draw(const Shader& shader);
private:
/* 渲染数据 */
unsigned int VAO, VBO, EBO;
void setupMesh();
};
#endif // !MESH_H
#include "mesh.h"
Mesh::Mesh(const vector<Vertex>& vertices, const vector<unsigned int>& indices, const vector<Texture>& textures) :
vertices(vertices), indices(indices), textures(textures)
{
setupMesh();
}
void Mesh::setupMesh()
{
// 初始化VAO VBO EBO
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
// 绑定VAO
glBindVertexArray(VAO);
// 绑定VBO 复制顶点数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);
// 绑定EBO 复制索引数据
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);
// 顶点位置
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
glEnableVertexAttribArray(0);
// 顶点法线
// C++内置的offsetof函数 能自动返回结构对象中 某变量距离结构体对象首地址的偏移值:
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
glEnableVertexAttribArray(1);
// 顶点纹理坐标
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
glEnableVertexAttribArray(2);
// 解绑VAO
glBindVertexArray(0);
}
unsigned int maxDiffuseNr = 0;
unsigned int maxSpecularNr = 0;
void Mesh::Draw(const Shader& shader)
{
// 当前漫反射纹理和镜面光纹理的编号
unsigned int diffuseNr = 0;
unsigned int specularNr = 0;
for (unsigned int i = 0; i < textures.size(); i++)
{
// 激活纹理单元 并绑定
textures[i].use(i);
// 获取纹理序号和类型
string number;
string type = textures[i].getName();
if (type == "texture_diffuse")
number = std::to_string(diffuseNr++);
else if (type == "texture_specular")
number = std::to_string(specularNr++);
shader.setInt("material." + type + "[" + number + "]", i);
}
shader.setInt(string("material.") + "texture_diffuse_num", diffuseNr);
shader.setInt(string("material.") + "texture_specular_num", specularNr);
maxDiffuseNr = max(maxDiffuseNr, diffuseNr);
maxSpecularNr = max(maxSpecularNr, specularNr);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
// always good practice to set everything back to defaults once configured.
glActiveTexture(GL_TEXTURE0);
}
m o d e l : model: model:
#pragma once
#ifndef MODEL_H
#define MODEL_H
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include "mesh.h"
#include <unordered_map>
using std::unordered_map;
class Model
{
/* 函数 */
public:
Model(const string& path)
{
loadModel(path);
}
void Draw(const Shader& shader);
private:
/* 模型数据 */
vector<Mesh> meshes;
/* 模型数据所在目录 */
string directory;
/* 记录已经加载过的纹理 */
vector<Texture> texturesLoaded;
/* 纹理文件到索引的哈希表 */
unordered_map<string, unsigned int> texturesHashTable;
void loadModel(const string& path);
void processNode(aiNode* node, const aiScene* scene);
Mesh processMesh(aiMesh* mesh, const aiScene* scene);
vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, const string& typeName);
};
#endif // !MODEL_H
#include "model.h"
#include <iostream>
using std::cout;
using std::endl;
extern unsigned int maxDiffuseNr;
extern unsigned int maxSpecularNr;
bool firstDraw = false;
void Model::Draw(const Shader& shader)
{
for (unsigned int i = 0; i < meshes.size(); i++)
meshes[i].Draw(shader);
if (!firstDraw)
{
cout << "First Draw finish\n";
cout << "Max diffuse texture number is " << maxDiffuseNr << '\n';
cout << "Max specular texture number is " << maxSpecularNr << '\n';
firstDraw = 1;
}
}
void Model::loadModel(const string& path)
{
Assimp::Importer import;
// 读取模型文件 第二个参数用于后期处理
const aiScene* scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
// 是否读取成功
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode)
{
cout << "ERROR::ASSIMP::" << import.GetErrorString() << endl;
return;
}
// 获取文件目录
directory = path.substr(0, path.find_last_of('/'));
// 从场景根节点开始递归处理所有节点
processNode(scene->mRootNode, scene);
// 打印obj的信息
cout << "Loading success!\n";
cout << "This model have " << meshes.size() << " meshes\n\n";
}
void Model::processNode(aiNode* node, const aiScene* scene)
{
// 处理节点的所有网格
for (unsigned int i = 0; i < node->mNumMeshes; i++)
{
// 依据索引从场景对象中拿到真正的数据
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
meshes.push_back(processMesh(mesh,scene));
}
// 递归处理子节点
for (unsigned int i = 0; i < node->mNumChildren; i++)
processNode(node->mChildren[i], scene);
}
Mesh Model::processMesh(aiMesh* mesh, const aiScene* scene)
{
// 一个Mesh对象包括 Vertex(位置 法向量 纹理坐标) 绘制所需的索引数组 绘制所需的纹理
vector<Vertex> vertices;
vector<unsigned int> indices;
vector<Texture> textures;
// 处理顶点位置 法线 纹理坐标
for (unsigned int i = 0; i < mesh->mNumVertices; i++)
{
Vertex vertex;
// 顶点位置 该数组永远存在
vertex.Position = glm::vec3(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z);
// 法线 不一定存在
vertex.Normal = glm::vec3(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z);
// 纹理坐标 不一定存在
if (mesh->mTextureCoords[0])
vertex.TexCoords = glm::vec2(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y);
else
vertex.TexCoords = glm::vec2(0.0f, 0.0f);
vertices.push_back(vertex);
}
// 处理索引
for (unsigned int i = 0; i < mesh->mNumFaces; i++)
{
// 处理该网格的每个面
aiFace face = mesh->mFaces[i];
for (unsigned int j = 0; j < face.mNumIndices; j++)
indices.push_back(face.mIndices[j]);
}
// 处理材质
if (mesh->mMaterialIndex >= 0)
{
// 索引场景对象获得该网格对应的材质数据
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
vector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
vector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
}
return Mesh(vertices, indices, textures);
}
vector<Texture> Model::loadMaterialTextures(aiMaterial* mat, aiTextureType type, const string& typeName)
{
vector<Texture> textures;
// 类型为type的纹理个数
for (unsigned int i = 0; i < mat->GetTextureCount(type); i++)
{
aiString textureFilePosition;
// 得到纹理文件的位置 存储在textureFilePosition中
mat->GetTexture(type, i, &textureFilePosition);
string str(textureFilePosition.C_Str());
auto it = texturesHashTable.find(str);
if (it == texturesHashTable.end())
{
// 未加载过
texturesHashTable[str] = texturesLoaded.size();
// 读取obj时已经翻转了纹理坐标 此处不需要再翻转图片y轴了
Texture texture(typeName, directory + "/" + str, false);
texturesLoaded.push_back(texture);
textures.push_back(texture);
}
else
{
// 已经加载过了
textures.push_back(texturesLoaded[it->second]);
}
}
return textures;
}
s h a d e r : shader: shader:
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
uniform mat4 model; //模型
uniform mat4 view; //观察
uniform mat4 projection; //投影
out vec2 TexCoords;
void main()
{
TexCoords = aTexCoords;
// 注意乘法要从右向左读
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
#version 330 core
#define MAX_TEXTURE_NUM 8
struct Material
{
sampler2D texture_diffuse[MAX_TEXTURE_NUM];
sampler2D texture_specular[MAX_TEXTURE_NUM];
int texture_diffuse_num;
int texture_specular_num;
};
uniform Material material;
in vec2 TexCoords;
out vec4 FragColor;
void main()
{
FragColor = vec4(0.0,0.0,0.0,0.0);
for(int i = 0; i < material.texture_diffuse_num; i++)
{
FragColor += texture(material.texture_diffuse[i], TexCoords);
}
FragColor.a = 1.0;
}
m a i n : main: main:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include "shader.h"
#include "stb_image.h"
#include "camera.h"
#include "texture.h"
#include "model.h"
using std::cout;
//窗口回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//绘图视口 3D坐标到2D坐标的转换(映射)和这些参数(宽高)有关
glViewport(0, 0, width, height);
}
//键盘回调
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
//鼠标回调
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
//滚轮回调
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
//窗口初始大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
//物体着色器
const char* vShaderPath = "ShaderFiles/shader.vert";
const char* fShaderPath = "ShaderFiles/shader.frag";
//光源着色器
const char* lightvShaderPath = "ShaderFiles/light_shader.vert";
const char* lightfShaderPath = "ShaderFiles/light_shader.frag";
//混合颜色的插值
float mixValue = 0.2f;
//记录鼠标坐标
float lastX, lastY;
bool firstMouse = true;
//摄像机
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
//光源位置
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
int main()
{
//glfw初始化
glfwInit();
//告诉glfw我们所使用的opengl版本 此处为3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//创建窗口
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
cout << "Failed to create GLFW window\n";
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//设置窗口回调函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//键盘回调函数
glfwSetKeyCallback(window, key_callback);
//鼠标回调
glfwSetCursorPosCallback(window, mouse_callback);
//滚轮回调
glfwSetScrollCallback(window, scroll_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
cout << "Failed to initialize GLAD\n";
return -1;
}
//开启深度测试
glEnable(GL_DEPTH_TEST);
//着色器对象
Shader shaderProgram = Shader(vShaderPath, fShaderPath);
Shader lightShaderProgram = Shader(lightvShaderPath, lightfShaderPath);
// light positions
float vertices[] = {
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
};
//物体(obj)
Model modelObj("Obj/Nanosuit/nanosuit.obj");
//光源
unsigned int lightVAO;
unsigned int lightVBO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
glGenBuffers(1, &lightVBO);
glBindBuffer(GL_ARRAY_BUFFER, lightVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
//线框模式
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//这些uniform不会更新 可以放到循环外面
while (!glfwWindowShouldClose(window))
{
glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//矩阵运算
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(glm::radians(camera.Fov), SCR_WIDTH * 1.0f / SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 model(1.0f);
model = glm::scale(model, glm::vec3(0.25f, 0.25f, 0.25f));
//激活着色器
shaderProgram.use();
shaderProgram.setMat4("model", model);
shaderProgram.setMat4("view", view);
shaderProgram.setMat4("projection", projection);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
modelObj.Draw(shaderProgram);
model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 2.0f));
model = glm::rotate(model, glm::radians(180.f), glm::vec3(0.0f, 1.0f, 0.0f));
model = glm::scale(model, glm::vec3(0.25f, 0.25f, 0.25f));
shaderProgram.setMat4("model", model);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
modelObj.Draw(shaderProgram);
glfwSwapBuffers(window);
glfwPollEvents();
}
//这一步是可选的
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &lightVBO);
//glDeleteBuffers(1, &EBO);
//释放资源
glfwTerminate();
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (action == GLFW_REPEAT || action == GLFW_PRESS)
{
if (key == GLFW_KEY_ESCAPE)
{
glfwSetWindowShouldClose(window, GL_TRUE);
return;
}
switch (key)
{
case GLFW_KEY_UP:
mixValue += 0.1f;
if (mixValue >= 1.0f)
mixValue = 1.0f;
break;
case GLFW_KEY_DOWN:
mixValue -= 0.1f;
if (mixValue <= 0.0f)
mixValue = 0.0f;
break;
case GLFW_KEY_W:
camera.ProcessKeyboard(FORWARD);
break;
case GLFW_KEY_S:
camera.ProcessKeyboard(BACKWARD);
break;
case GLFW_KEY_A:
camera.ProcessKeyboard(LEFT);
break;
case GLFW_KEY_D:
camera.ProcessKeyboard(RIGHT);
break;
default:
break;
}
}
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
firstMouse = false;
lastX = xpos, lastY = ypos;
}
camera.ProcessMouseMovement(xpos - lastX, lastY - ypos);
lastX = xpos;
lastY = ypos;
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
下一步:加入镜面光贴图和光源!
s h a d e r : shader: shader:
#version 330 core
layout (location = 0) in vec3 aPos; // 位置变量的属性位置值为 0
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
uniform mat4 model; //模型
uniform mat4 view; //观察
uniform mat4 projection; //投影
out vec3 Normal;
out vec3 FragPos;
out vec2 TexCoords;
void main()
{
Normal = mat3(transpose(inverse(model))) * aNormal;
FragPos = vec3(model * vec4(aPos, 1.0));
TexCoords = aTexCoords;
// 注意乘法要从右向左读
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
#version 330 core
#define MAX_TEXTURE_NUM 8
struct Material
{
sampler2D texture_diffuse[MAX_TEXTURE_NUM];
sampler2D texture_specular[MAX_TEXTURE_NUM];
int texture_diffuse_num;
int texture_specular_num;
float shininess;
};
// 方向光
struct DirLight
{
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
// 点光源
struct PointLight
{
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
// 聚光-手电筒
struct SpotLight
{
vec3 position;
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
// 内圆锥余弦值
float cutOff;
// 外圆锥余弦值
float outerCutOff;
float constant;
float linear;
float quadratic;
};
in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoords;
out vec4 FragColor;
uniform vec3 viewPos;
uniform Material material;
uniform DirLight dirLight;
#define NR_POINT_LIGHTS 2
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform SpotLight spotLight;
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalLightSum(vec3 lightAmbient, vec3 lightDiffuse, vec3 lightSpecular, float diff, float spec);
void main()
{
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
// 定向光照
vec3 result = CalcDirLight(dirLight, norm, viewDir);
// 点光源
for(int i = 0; i < NR_POINT_LIGHTS; i++)
result += CalPointLight(pointLights[i], norm, FragPos, viewDir);
// 聚光
result += CalSpotLight(spotLight, norm, FragPos, viewDir);
FragColor = vec4(result, 1.0);
}
// 计算某个方向光源对该片段颜色的贡献
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
vec3 lightDir = normalize(-light.direction);
// 漫反射着色
float diff = max(dot(normal,lightDir),0.0);
// 镜面光着色
vec3 reflectDir = reflect(-lightDir,normal);
float spec = pow(max(dot(viewDir,reflectDir),0.0),material.shininess);
// 合并
return CalLightSum(light.ambient, light.diffuse, light.specular, diff, spec);
}
// 计算某个点光源对该片段颜色的贡献
vec3 CalPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// 漫反射着色
float diff = max(dot(normal,lightDir),0.0);
// 镜面光着色
vec3 reflectDir = reflect(-lightDir,normal);
float spec = pow(max(dot(viewDir,reflectDir),0.0),material.shininess);
// 距离
float dis = length(light.position - fragPos);
// 衰减
float attenuation = 1.0 / (light.constant + light.linear * dis + light.quadratic * dis *dis);
// 合并
return CalLightSum(light.ambient, light.diffuse, light.specular, diff, spec) * attenuation;
}
// 计算某个聚光灯源对该片段颜色的贡献
vec3 CalSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
vec3 lightDir = normalize(light.position - fragPos);
// 漫反射着色
float diff = max(dot(normal,lightDir),0.0);
// 镜面光着色
vec3 reflectDir = reflect(-lightDir,normal);
float spec = pow(max(dot(viewDir,reflectDir),0.0),material.shininess);
// 距离
float dis = length(light.position - fragPos);
// 衰减
float attenuation = 1.0 /(light.constant + light.linear * dis + light.quadratic * dis * dis);
// 内外光切角插值 实现平滑过度效果
float cosTheta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((cosTheta - light.outerCutOff) / epsilon, 0.0, 1.0);
// 合并
return CalLightSum(light.ambient, light.diffuse, light.specular, diff, spec) * attenuation * intensity;
}
// 计算环境光、漫反射、镜面光的颜色和
// 没有计算衰减
vec3 CalLightSum(vec3 lightAmbient, vec3 lightDiffuse, vec3 lightSpecular, float diff, float spec)
{
vec3 diffuse = vec3(0.0,0.0,0.0);
for(int i = 0; i < material.texture_diffuse_num; i++)
{
diffuse += texture(material.texture_diffuse[i], TexCoords).rgb;
}
vec3 ambient = lightAmbient * diffuse;
diffuse = lightDiffuse * diff * diffuse;
vec3 specular = vec3(0.0,0.0,0.0);
for(int i = 0; i < material.texture_specular_num; i++)
{
specular += texture(material.texture_specular[i], TexCoords).rgb;
}
specular = lightSpecular * spec * specular;
return ambient + diffuse + specular;
}
m a i n : main: main:
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include "shader.h"
#include "stb_image.h"
#include "camera.h"
#include "light.h"
#include "texture.h"
#include "model.h"
using std::cout;
//窗口回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
//绘图视口 3D坐标到2D坐标的转换(映射)和这些参数(宽高)有关
glViewport(0, 0, width, height);
}
//键盘回调
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
//鼠标回调
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
//滚轮回调
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
//窗口初始大小
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
//物体着色器
const char* vShaderPath = "ShaderFiles/shader.vert";
const char* fShaderPath = "ShaderFiles/shader.frag";
//光源着色器
const char* lightvShaderPath = "ShaderFiles/shader.vert";
const char* lightfShaderPath = "ShaderFiles/light_shader.frag";
//混合颜色的插值
float mixValue = 0.2f;
//记录鼠标坐标
float lastX, lastY;
bool firstMouse = true;
//摄像机
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
//光源位置
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);
int main()
{
//glfw初始化
glfwInit();
//告诉glfw我们所使用的opengl版本 此处为3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//创建窗口
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
cout << "Failed to create GLFW window\n";
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//设置窗口回调函数
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
//键盘回调函数
glfwSetKeyCallback(window, key_callback);
//鼠标回调
glfwSetCursorPosCallback(window, mouse_callback);
//滚轮回调
glfwSetScrollCallback(window, scroll_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
cout << "Failed to initialize GLAD\n";
return -1;
}
//开启深度测试
glEnable(GL_DEPTH_TEST);
//着色器对象
Shader shaderProgram = Shader(vShaderPath, fShaderPath);
Shader lightShaderProgram = Shader(lightvShaderPath, lightfShaderPath);
// light positions
float vertices[] = {
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
0.5f, -0.5f, -0.5f,
0.5f, -0.5f, 0.5f,
0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, 0.5f,
-0.5f, -0.5f, -0.5f,
-0.5f, 0.5f, -0.5f,
0.5f, 0.5f, -0.5f,
0.5f, 0.5f, 0.5f,
0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, 0.5f,
-0.5f, 0.5f, -0.5f,
};
//物体(obj)
Model modelObj("Obj/Nanosuit/nanosuit.obj");
//光源
unsigned int lightVAO;
unsigned int lightVBO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
glGenBuffers(1, &lightVBO);
glBindBuffer(GL_ARRAY_BUFFER, lightVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
DirLight dirLight(glm::vec3(-0.2f, -1.0f, -0.3f), glm::vec3(0.05f, 0.05f, 0.05f), glm::vec3(0.4f, 0.4f, 0.4f), glm::vec3(0.5f, 0.5f, 0.5f));
glm::vec3 pointLightPositions[] = {
glm::vec3(-2.0f,4.0f,1.0f),
glm::vec3(2.0f,1.0f,0.0f)
};
PointLight pointLights[] = {
PointLight(pointLightPositions[0],glm::vec3(0.05f,0.05f,0.05f),glm::vec3(0.8f,0.8f,0.8f),glm::vec3(1.0f,1.0f,1.0f)),
PointLight(pointLightPositions[1],glm::vec3(0.05f,0.05f,0.05f),glm::vec3(0.8f,0.8f,0.8f),glm::vec3(1.0f,1.0f,1.0f)),
};
SpotLight spotLight(camera.Position, camera.Front, glm::vec3(0.2f, 0.2f, 0.2f), glm::vec3(0.5f, 0.5f, 0.5f), glm::vec3(1.0f, 1.0f, 1.0f));
//线框模式
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
//这些uniform不会更新 可以放到循环外面
shaderProgram.use();
shaderProgram.setFloat("material.shininess", 32.0f);
setLightAllAttribute(shaderProgram, "dirLight", &dirLight);
setLightAllAttribute(shaderProgram, "pointLights", pointLights, 2);
lightShaderProgram.use();
lightShaderProgram.setVec3("lightColor", glm::vec3(1.0, 1.0, 1.0));
while (!glfwWindowShouldClose(window))
{
glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//矩阵运算
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 projection = glm::perspective(glm::radians(camera.Fov), SCR_WIDTH * 1.0f / SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 model(1.0f);
model = glm::scale(model, glm::vec3(0.25f, 0.25f, 0.25f));
//激活着色器
shaderProgram.use();
shaderProgram.setMat4("model", model);
shaderProgram.setMat4("view", view);
shaderProgram.setMat4("projection", projection);
shaderProgram.setVec3("viewPos", camera.Position);
spotLight.position = camera.Position;
spotLight.direction = camera.Front;
setLightAllAttribute(shaderProgram, "spotLight", &spotLight);
modelObj.Draw(shaderProgram);
model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 2.0f));
model = glm::rotate(model, glm::radians(180.f), glm::vec3(0.0f, 1.0f, 0.0f));
model = glm::scale(model, glm::vec3(0.25f, 0.25f, 0.25f));
shaderProgram.setMat4("model", model);
modelObj.Draw(shaderProgram);
lightShaderProgram.use();
lightShaderProgram.setMat4("view", view);
lightShaderProgram.setMat4("projection", projection);
glBindVertexArray(lightVAO);
for (unsigned int i = 0; i < 2; i++)
{
glm::mat4 lightModel(1.0f);
lightModel = glm::translate(lightModel, pointLightPositions[i]);
lightModel = glm::scale(lightModel, glm::vec3(0.2f, 0.2f, 0.2f));
lightShaderProgram.setMat4("model", lightModel);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
glfwSwapBuffers(window);
glfwPollEvents();
}
//这一步是可选的
glDeleteVertexArrays(1, &lightVAO);
glDeleteBuffers(1, &lightVBO);
//glDeleteBuffers(1, &EBO);
//释放资源
glfwTerminate();
return 0;
}
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (action == GLFW_REPEAT || action == GLFW_PRESS)
{
if (key == GLFW_KEY_ESCAPE)
{
glfwSetWindowShouldClose(window, GL_TRUE);
return;
}
switch (key)
{
case GLFW_KEY_UP:
mixValue += 0.1f;
if (mixValue >= 1.0f)
mixValue = 1.0f;
break;
case GLFW_KEY_DOWN:
mixValue -= 0.1f;
if (mixValue <= 0.0f)
mixValue = 0.0f;
break;
case GLFW_KEY_W:
camera.ProcessKeyboard(FORWARD);
break;
case GLFW_KEY_S:
camera.ProcessKeyboard(BACKWARD);
break;
case GLFW_KEY_A:
camera.ProcessKeyboard(LEFT);
break;
case GLFW_KEY_D:
camera.ProcessKeyboard(RIGHT);
break;
default:
break;
}
}
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
firstMouse = false;
lastX = xpos, lastY = ypos;
}
camera.ProcessMouseMovement(xpos - lastX, lastY - ypos);
lastX = xpos;
lastY = ypos;
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
甚至我都必须要承认这个可能是比一直使用的箱子要好看多了。使用Assimp,你能够加载互联网上的无数模型。有很多资源网站都提供了多种格式的免费3D模型供你下载。但还是要注意,有些模型会不能正常地载入,纹理的路径会出现问题,或者Assimp并不支持它的格式。