运行效果
其他内容
源代码
顶点着色器:
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
out vec2 TexCoords;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0f);
TexCoords = texCoords;
}
片段着色器:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D texture_diffuse1;
void main()
{
color = vec4(texture(texture_diffuse1, TexCoords));
}
网格:
#pragma once
// Std. Includes
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <vector>
using namespace std;
// GL Includes
#include <GL/glew.h> // Contains all the necessery OpenGL includes
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
// 顶点结构体(包含位置、法向量、纹理坐标)
struct Vertex {
// Position
glm::vec3 Position;
// Normal
glm::vec3 Normal;
// TexCoords
glm::vec2 TexCoords;
};
// 纹理结构体(包含纹理id、纹理贴图类型、)
struct Texture {
GLuint id;
string type;
aiString path;
};
// 网格类
class Mesh {
public:
/* 网格存储的数据:一系列 顶点、渲染索引顶点、纹理 */
vector<Vertex> vertices;
vector<GLuint> indices;
vector<Texture> textures;
/* 网格的构造函数,传入网格所有数据 并调用网格设置函数 */
Mesh(vector<Vertex> vertices, vector<GLuint> indices, vector<Texture> textures)
{
this->vertices = vertices;
this->indices = indices;
this->textures = textures;
// 设置网格的缓冲区
this->setupMesh();
}
// 依据传入的着色器程序渲染网格
void Draw(Shader shader)
{
// 记录对应纹理类型的数目(因为在着色器中命名的采样器也是按照类型分类的)
GLuint diffuseNr = 1;
GLuint specularNr = 1;
// 激活纹理单元、设置着色器中采样器的位置值、绑定纹理到对应纹理类型上(作用:将纹理赋值给着色器的采样器)
for (GLuint i = 0; i < this->textures.size(); i++)
{
// 激活对应着色器上的对应纹理单元
glActiveTexture(GL_TEXTURE0 + i);
// 使用stringstram方便从GL_Luint和string之间的转换
stringstream ss;
string number;
// 获得纹理的类型
string name = this->textures[i].type;
// 根据纹理类型得到并记录 对应纹理类型的数目
if (name == "texture_diffuse")
ss << diffuseNr++; // 属于漫反射贴图
else if (name == "texture_specular")
ss << specularNr++; // 属于高光贴图
number = ss.str();
// 设置着色器中采样器的位置值为i,其中(name+number).c_str() 返回 "texture_diffuseN"或"texture_specular"
glUniform1i(glGetUniformLocation(shader.Program, (name + number).c_str()), i);
// 绑定当前迭代的纹理 到对应纹理类型上,将纹理赋值给着色器中的采样器
glBindTexture(GL_TEXTURE_2D, this->textures[i].id);
}
// 设置着色器中材质的高光发光值
glUniform1f(glGetUniformLocation(shader.Program, "material.shininess"), 16.0f);
// 绑定网格的VAO
glBindVertexArray(this->VAO);
// 根据网格的索引数据进行网格的渲染
glDrawElements(GL_TRIANGLES, this->indices.size(), GL_UNSIGNED_INT, 0);
// 解绑VAO
glBindVertexArray(0);
// 一旦渲染完(使用完纹理单元,就要将激活的纹理单元恢复原状,即解绑纹理)
for (GLuint i = 0; i < this->textures.size(); i++)
{
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
private:
/* 缓冲数据:VAO VBO EBO */
GLuint VAO, VBO, EBO;
/* 设置网格的缓冲数据,即设置VAO VBO EBO,并且将顶点和索引数据传入显存 */
void setupMesh()
{
// 申请缓冲区对象的引用
glGenVertexArrays(1, &this->VAO);
glGenBuffers(1, &this->VBO);
glGenBuffers(1, &this->EBO);
// 将对象绑定到对应缓冲区类型上
glBindVertexArray(this->VAO);
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
// 将网格的顶点数据传入VBO中
glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &this->vertices[0], GL_STATIC_DRAW);
// 绑定EBO并且将网格的索引顶点数据传入EBO中
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->indices.size() * sizeof(GLuint), &this->indices[0], GL_STATIC_DRAW);
// 设置顶点数据的解析方式(每次读取一个Vertex结构体即三个GL_FLOAT值)
// 解析顶点位置数据
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)0);
// 解析顶点法向量数据
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, Normal));
// 解析顶点纹理坐标数据
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, TexCoords));
// 设置完网格解绑VAO
glBindVertexArray(0);
}
};
模型:
#pragma once
// Std. Includes
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <map>
#include <vector>
using namespace std;
// GL Includes
#include <GL/glew.h> // Contains all the necessery OpenGL includes
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <SOIL.h>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include "Mesh.h"
#include "Shader.h"
// 定义所有加载过的纹理
GLint TextureFromFile(const char* path, string directory);
class Model
{
public:
/* 构造函数(参数:模型路径) */
Model(const char* path)
{
this->loadModel(path);
}
// 使用着色器渲染模型(本质是对模型中各个网格分别调用渲染)
void Draw(Shader shader)
{
for (GLuint i = 0; i < this->meshes.size(); i++)
this->meshes[i].Draw(shader);
}
private:
/* 模型数据:网格数组、模型所在的目录地址、已加载纹理数据 */
vector<Mesh> meshes;
string directory;
vector<Texture> textures_loaded;
/* 加载模型(参数:模型路径) */
void loadModel(string path)
{
// 使用Assimp读入模型文件,返回scene对象
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);// 开启图元类型全为三角形、纹理翻转
// 检查文件是否成功读入并返回正确的scene对象
if (!scene || scene->mFlags == AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) // if is Not Zero
{
cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << endl;
return;
}
// 计算模型的目录地址(从模型地址的第0个字符到最后一个"/"字符 之前)
this->directory = path.substr(0, path.find_last_of('\\'));
// 将根节点及其所有子节点的网格加入模型的网格数组
this->processNode(scene->mRootNode, scene);
}
// 传入根节点node,将node根节点及其所有子节点的网格加入模型网格数组中
void processNode(aiNode* node, const aiScene* scene)
{
// mNumMeshes代表根节点的网格数组的长度,迭代网格数组
for (GLuint i = 0; i < node->mNumMeshes; i++)
{
// 根节点网格数组中第i个网格在scene网格数组中的索引为:node->mMeshes[i]
// 从scene网格数组scene->mMeshes中获得迭代的i个网格的实际数据
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
// 将迭代网格的实际数据读入到模型中
this->meshes.push_back(this->processMesh(mesh, scene));
}
// 遍历根节点的每一个子节点,读取其 和其子节点的网格数据
for (GLuint i = 0; i < node->mNumChildren; i++)
{
this->processNode(node->mChildren[i], scene);
}
}
// 读取Assimp类型的网格数据,返回Mesh类型的网格数据
Mesh processMesh(aiMesh* mesh, const aiScene* scene)
{
// 定义返回网格数据的属性
vector<Vertex> vertices;
vector<GLuint> indices;
vector<Texture> textures;
// 读取网格数据中的顶点位置(mNumVertices表示mesh网格中的顶点数目)
for (GLuint i = 0; i < mesh->mNumVertices; i++)
{
// 定义一个顶点类型变量
Vertex vertex;
// 定义一个中间值
glm::vec3 vector;
// mVertices存储网格的顶点位置,将网格顶点位置存储到顶点类型变量中
// glm::vec3和Assimp中向量不是很兼容,所以不能直接赋值,需要创建glm::vec3向量分别对xyz赋值
vector.x = mesh->mVertices[i].x;
vector.y = mesh->mVertices[i].y;
vector.z = mesh->mVertices[i].z;
// 赋值完glm::vec3向量然后再赋值给顶点的位置
vertex.Position = vector;
// 同样使用中间值记录网格顶点的法向量
vector.x = mesh->mNormals[i].x;
vector.y = mesh->mNormals[i].y;
vector.z = mesh->mNormals[i].z;
// 赋值给顶点的法向量
vertex.Normal = vector;
// 检查网格是否具有纹理
if (mesh->mTextureCoords[0])
{
// 定义中间值记录纹理坐标
glm::vec2 vec;
// 一个纹理可能具有多个纹理坐标,在此我们仅读取第0个纹理 即只读取一个纹理
vec.x = mesh->mTextureCoords[0][i].x;
vec.y = mesh->mTextureCoords[0][i].y;
// 将中间值赋值给顶点的纹理坐标
vertex.TexCoords = vec;
}
else
// 如果纹理不存在就赋值为0.0(需要申请内存并且初始化)
vertex.TexCoords = glm::vec2(0.0f, 0.0f);
// 将顶点数据插入顶点数组
vertices.push_back(vertex);
}
// 读取网格的索引
for (GLuint i = 0; i < mesh->mNumFaces; i++)
{
// 获取迭代的面
aiFace face = mesh->mFaces[i];
// 先获取一个个需要渲染的面即图元,然后记录每一个图元的顶点索引
for (GLuint j = 0; j < face.mNumIndices; j++)
indices.push_back(face.mIndices[j]);
}
// 获取网格的纹理(如果纹理存在)
if (mesh->mMaterialIndex >= 0)
{
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
// 1. 获得网格的漫反射贴图纹理(记录于定义的纹理数组中)
vector<Texture> diffuseMaps = this->loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");
textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
// 2. 获得网格的镜面反射贴图纹理(记录于定义的纹理数组中)
vector<Texture> specularMaps = this->loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");
textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());
}
// 返回Mesh类型的网格,使用读取的顶点数据、索引数据、纹理数据构造
return Mesh(vertices, indices, textures);
}
// 加载纹理
vector<Texture> loadMaterialTextures(aiMaterial* mat, aiTextureType type, string typeName)
{
vector<Texture> textures;
for(GLuint i = 0; i < mat->GetTextureCount(type); i++)
{
aiString str;
mat->GetTexture(type, i, &str);
// Check if texture was loaded before and if so, continue to next iteration: skip loading a new texture
GLboolean skip = false;
for(GLuint j = 0; j < textures_loaded.size(); j++)
{
if(std::strcmp(textures_loaded[j].path.C_Str(), str.C_Str()) == 0)
{
textures.push_back(textures_loaded[j]);
skip = true; // A texture with the same filepath has already been loaded, continue to next one. (optimization)
break;
}
}
if(!skip)
{ // If texture hasn't been loaded already, load it
Texture texture;
texture.id = TextureFromFile(str.C_Str(), this->directory);
texture.type = typeName;
texture.path = str;
textures.push_back(texture);
this->textures_loaded.push_back(texture); // Store it as texture loaded for entire model, to ensure we won't unnecesery load duplicate textures.
}
}
return textures;
}
};
// 读取纹理文件
GLint TextureFromFile(const char* path, string directory)
{
//Generate texture ID and load texture data
string filename = string(path);
filename = directory + '\\' + filename;
GLuint textureID;
glGenTextures(1, &textureID);
int width, height;
unsigned char* image = SOIL_load_image(filename.c_str(), &width, &height, 0, SOIL_LOAD_RGB);
// Assign texture to ID
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
// Parameters
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);
glBindTexture(GL_TEXTURE_2D, 0);
SOIL_free_image_data(image);
return textureID;
}
主程序:
// Std. Includes
#include <string>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GL includes
#include "Shader.h"
#include "Camera.h"
#include "Model.h"
// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// Other Libs
#include <SOIL.h>
// Properties
GLuint screenWidth = 800, screenHeight = 600;
// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void Do_Movement();
// Camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
bool keys[1024];
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
GLfloat deltaTime = 0.0f;
GLfloat lastFrame = 0.0f;
// The MAIN function, from here we start our application and run our Game loop
int main()
{
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed
glfwMakeContextCurrent(window);
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// Options
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
glewInit();
// Define the viewport dimensions
glViewport(0, 0, screenWidth, screenHeight);
// Setup some OpenGL options
glEnable(GL_DEPTH_TEST);
// 构造着色器程序
Shader shader("C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\vertexShader.txt", "C:\\Users\\32156\\source\\repos\\LearnOpenGL\\Shader\\fragmentShader.txt");
// 构造模型
Model ourModel("D:\\Download\\nanosuit\\nanosuit.obj");
// Draw in wireframe
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// Game loop
while (!glfwWindowShouldClose(window))
{
// Set frame time
GLfloat currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// Check and call events
glfwPollEvents();
Do_Movement();
// Clear the colorbuffer
glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 使用着色器程序
shader.Use();
// 计算各种变换矩阵
glm::mat4 projection = glm::perspective(camera.Zoom, (float)screenWidth / (float)screenHeight, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "view"), 1, GL_FALSE, glm::value_ptr(view));
// 传入着色器变换矩阵
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(0.0f, -1.75f, 0.0f)); // Translate it down a bit so it's at the center of the scene
model = glm::scale(model, glm::vec3(0.2f, 0.2f, 0.2f)); // It's a bit too big for our scene, so scale it down
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "model"), 1, GL_FALSE, glm::value_ptr(model));
// 使用着色器渲染模型
ourModel.Draw(shader);
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
#pragma region "User input"
// Moves/alters the camera positions based on user input
void Do_Movement()
{
// Camera controls
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, deltaTime);
}
// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (action == GLFW_PRESS)
keys[key] = true;
else if (action == GLFW_RELEASE)
keys[key] = false;
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
GLfloat xoffset = xpos - lastX;
GLfloat yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
#pragma endregion