main.cpp
#include <iostream>
#include <stdlib.h>
#include <vector>
#include <string>
#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 <math.h>
#include <learnopengl/shader_m.h>
#include <learnopengl/camera.h>
#include <learnopengl/model.h>
#define PI 3.14159265
#include <iostream>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include "SkinnedMesh.h"
#include "SkinningTechnique.h"
#include "math_3d.h"
void RenderScene(SkinningTechnique*& m_pEffect, SkinnedMesh& m_mesh, GLFWwindow*& window);
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow* window);
const unsigned int SCR_WIDTH = 1920; //纹理图大小,分辨率越大,从纹理中采样出来得到的结果越清晰,锯齿越少。
const unsigned int SCR_HEIGHT = 1080;
// camera
Camera camera(glm::vec3(0.0f, 3.0f, -1.0f));
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true;
// timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;
float time_start = 0.0f;
int main()
{
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
int count = 2;
// glfw window creation
// --------------------
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;
}
glfwMakeContextCurrent(window);
//glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
glEnable(GL_DEPTH_TEST);
SkinningTechnique* m_pEffect = new SkinningTechnique();
const char* vertexShader = "./shaders/myskinning.vs";
const char* fragmentShader = "./shaders/myskinning.fs";
m_pEffect->Init(vertexShader, fragmentShader);
glUseProgram(m_pEffect->m_shaderProg); // bind shader programme
SkinnedMesh m_mesh;
// LoadMesh
if (!m_mesh.LoadMesh("../Content/boblampclean.md5mesh")) {
cout << "Mesh load failed" << endl;
return false;
};
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
while (!glfwWindowShouldClose(window))
{
RenderScene(m_pEffect, m_mesh, window);
}
}
void RenderScene(SkinningTechnique* &m_pEffect, SkinnedMesh & m_mesh, GLFWwindow* &window)
{
glUseProgram(m_pEffect->m_shaderProg); // bind shader programme
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
vector<Matrix4f> Transforms;
float RunningTime = glfwGetTime() - time_start;
m_mesh.BoneTransform(RunningTime, Transforms);
for (uint i = 0; i < Transforms.size(); i++)
{
m_pEffect->SetBoneTransform(i, Transforms[i]);
}
glEnable(GL_DEPTH_TEST);
glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
//glm::mat4 view = camera.GetViewMatrix();
glm::mat4 view = glm::lookAt(glm::vec3(0.0f, 0.0f, 5.0f),
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(0.0f, 1.0f, 0.0f));
glUniformMatrix4fv(glGetUniformLocation(m_pEffect->m_shaderProg, "projection"), 1, GL_FALSE, &projection[0][0]);
glUniformMatrix4fv(glGetUniformLocation(m_pEffect->m_shaderProg, "view"), 1, GL_FALSE, &view[0][0]);
//float angle = i;
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(0.0f, -1.2f, 0.0f));
model = glm::rotate(model, glm::radians(-90.0f), glm::vec3(1.0, 0.0, 0.0));
model = glm::scale(model, glm::vec3(0.04f, 0.04f, 0.04f));
glUniformMatrix4fv(glGetUniformLocation(m_pEffect->m_shaderProg, "model"), 1, GL_FALSE, &model[0][0]);
m_mesh.Render(); // draw call
glfwSwapBuffers(window);
glfwPollEvents();
}
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(yoffset);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(RIGHT, deltaTime);
}
#pragma once
#ifndef _TECHNIQUE_H
#define _TECHNIQUE_H
#include "math_3d.h"
class SkinningTechnique
{
public:
static const unsigned int MAX_BONES = 100;
SkinningTechnique() = default;
void Init(const char* vertexShader, const char* fragmentShader);
void SetBoneTransform(unsigned int Index, const Matrix4f& Transform);
unsigned int m_shaderProg; // handle of created Shader program
private:
unsigned int m_boneLocation[MAX_BONES]; //记录每个骨骼在shader中的位置,在顶点着色器中对应的是
};
#endif /* _TECHNIQUE_H */
#include <limits.h>
#include <string>
#include "SkinningTechnique.h"
#include <learnopengl/shader_m.h>
using namespace std;
#define ARRAY_SIZE_IN_ELEMENTS(m) sizeof(m)/sizeof(m[0])
#define SNPRINTF _snprintf_s
/* Shader Progamme 初始化,其中Shader这个类我就不多说了,主要就是链接2个着色器,
感兴趣的可以看https://learnopengl-cn.github.io/01%20Getting%20started/05%20Shaders/
*/
void SkinningTechnique::Init(const char* vertexShader, const char* fragmentShader)
{
Shader ourShader(vertexShader, fragmentShader);
m_shaderProg = ourShader.ID; // 获得生成的Shader Programm的句柄,方便后面一系列操作
//
for (unsigned int i = 0; i < ARRAY_SIZE_IN_ELEMENTS(m_boneLocation); i++) {
char Name[128];
memset(Name, 0, sizeof(Name));
SNPRINTF(Name, sizeof(Name), "gBones[%d]", i);
// 获得对应骨骼在m_boneLocation[i]Shader中的Position
m_boneLocation[i] = glGetUniformLocation(m_shaderProg, Name);
}
}
/* 将变换矩阵传进 vertex shader
Matrix4f是一个4*4的矩阵类,可以用glm::mat4 来替代,这样下面的
glUniformMatrix4fv中的第三个参数(是否转置)就设为GL_FALSE,因为glm::mat4是列优先的
Transform存的是每个骨骼的变换矩阵矩阵后面会说。
*/
void SkinningTechnique::SetBoneTransform(unsigned int Index, const Matrix4f& Transform)
{
assert(Index < MAX_BONES);
//Transform.Print();
// GL_TRUE 表示需要转置成 列主序
//glUniformMatrix4fv(m_boneLocation[Index], 1, GL_TRUE, (const GLfloat*)Transform);
glUniformMatrix4fv(m_boneLocation[Index], 1, GL_TRUE, &Transform.m[0][0]);
}
然后是最重要的SkinnedMesh类,用来解析模型、配置骨骼变换矩阵用的
/*
Copyright 2020 Demon XU
*/
#ifndef _SKINNED_MESH_H
#define _SKINNED_MESH_H
//#include <GL/glew.h>
#include <glad/glad.h>
#include <map>
#include <vector>
#include "math_3d.h"
#include <assimp/Importer.hpp> // C++ importer interface
#include <assimp/scene.h> // Output data structure
#include <assimp/postprocess.h> // Post processing flags
#include "texture.h"
typedef unsigned int uint;
using namespace std;
#include <ImageMagick-6/Magick++.h>
class SkinnedMesh
{
public:
SkinnedMesh();
~SkinnedMesh();
bool LoadMesh(const string& Filename);
void Render();
unsigned int NumBones() const
{
return m_NumBones;
}
void BoneTransform(float TimeInSeconds, vector<Matrix4f>& Transforms);
private:
#define NUM_BONES_PER_VEREX 4
// 骨骼信息: 骨骼偏移矩阵 将顶点位置从Mesh的局部空间移动到该特定骨骼的骨骼空间中
struct BoneInfo
{
Matrix4f BoneOffset;
Matrix4f FinalTransformation;
BoneInfo()
{
BoneOffset.SetZero();
FinalTransformation.SetZero();
}
};
// 存储每个顶点所对应的骨骼数据
struct VertexBoneData
{
uint IDs[NUM_BONES_PER_VEREX]; // 存储每个顶点所对应的骨骼ID, 设每个顶点最多被4个骨骼所影响
float Weights[NUM_BONES_PER_VEREX]; // 存储每个顶点所对应的骨骼权重,分别对应IDs中的骨骼,最终weights之和为1
VertexBoneData()
{
memset(IDs, 0, sizeof(IDs));
memset(Weights, 0, sizeof(Weights));
};
void AddBoneData(uint BoneID, float Weight);
};
void CalcInterpolatedScaling(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim);
void CalcInterpolatedRotation(aiQuaternion& Out, float AnimationTime, const aiNodeAnim* pNodeAnim);
void CalcInterpolatedPosition(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim);
uint FindScaling(float AnimationTime, const aiNodeAnim* pNodeAnim);
uint FindRotation(float AnimationTime, const aiNodeAnim* pNodeAnim);
uint FindPosition(float AnimationTime, const aiNodeAnim* pNodeAnim);
const aiNodeAnim* FindNodeAnim(const aiAnimation* pAnimation, const string NodeName);
void ReadNodeHeirarchy(float AnimationTime, const aiNode* pNode, const Matrix4f& ParentTransform);
bool InitFromScene(const aiScene* pScene, const string& Filename);
void InitMesh(uint MeshIndex,
const aiMesh* paiMesh,
vector<Vector3f>& Positions,
vector<Vector3f>& Normals,
vector<Vector2f>& TexCoords,
vector<VertexBoneData>& Bones,
vector<unsigned int>& Indices);
void LoadBones(uint MeshIndex, const aiMesh* paiMesh, vector<VertexBoneData>& Bones);
bool InitMaterials(const aiScene* pScene, const string& Filename);
void Clear();
#define INVALID_MATERIAL 0xFFFFFFFF
enum VB_TYPES {
INDEX_BUFFER,
POS_VB,
NORMAL_VB,
TEXCOORD_VB,
BONE_VB,
NUM_VBs
};
GLuint m_VAO;
GLuint m_Buffers[NUM_VBs];
struct MeshEntry {
MeshEntry()
{
NumIndices = 0;
BaseVertex = 0;
BaseIndex = 0;
MaterialIndex = INVALID_MATERIAL;
}
unsigned int NumIndices;
unsigned int BaseVertex; // BaseVertex is where the subcomponent starts in the vertex buffers
unsigned int BaseIndex; // BaseIndex is where the subcomponent starts inside the index buffer
unsigned int MaterialIndex;
};
/* 由于我们的模型可以由多个(子组件)Mesh组成,每个Mesh都有自己的纹理,因此我们有一个称为m_Entries的vector,其中包含Material的Index以及Mesh的位置。
NumIndices是子组件中的索引数,BaseVertex是子组件在顶点缓冲区中开始的位置,而BaseIndex是子组件在索引缓冲区中开始的位置
(因为所有子组件一个接一个地存储在相同的缓冲区中)。在渲染网格的子组件之前,我们需要绑定其纹理,然后提交子组件顶点的draw命令。*/
vector<MeshEntry> m_Entries;
vector<MyTexture*> m_Textures;
map<string, uint> m_BoneMapping; // Maps a bone name to its index
uint m_NumBones;
vector<BoneInfo> m_BoneInfo;
Matrix4f m_GlobalInverseTransform; // 骨骼空间->局部空间的变换矩阵,
const aiScene* m_pScene;
Assimp::Importer m_Importer;
};
#endif /* OGLDEV_SKINNED_MESH_H */
#include "SkinnedMesh.h"
#define POSITION_LOCATION 0
#define TEX_COORD_LOCATION 1
#define NORMAL_LOCATION 2
#define BONE_ID_LOCATION 3
#define BONE_WEIGHT_LOCATION 4
#define ARRAY_SIZE_IN_ELEMENTS(a) (sizeof(a)/sizeof(a[0]))
#define ZERO_MEM(a) memset(a, 0, sizeof(a))
#define SAFE_DELETE(p) if (p) { delete p; p = NULL; }
#define ASSIMP_LOAD_FLAGS (aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_JoinIdenticalVertices)
// 对每个顶点,链接与其对应的4个骨头的参数(骨骼的ID weight)
void SkinnedMesh::VertexBoneData::AddBoneData(uint BoneID, float Weight)
{
// ARRAY_SIZE_IN_ELEMENTS(IDs) == NUM_BONES_PER_VEREX = 4
for (uint i = 0; i < ARRAY_SIZE_IN_ELEMENTS(IDs); i++) {
if (Weights[i] == 0.0) {
IDs[i] = BoneID;
Weights[i] = Weight;
return;
}
}
// should never get here - more bones than we have space for
assert(0);
}
SkinnedMesh::SkinnedMesh()
{
m_VAO = 0;
ZERO_MEM(m_Buffers);
m_NumBones = 0;
m_pScene = NULL;
}
SkinnedMesh::~SkinnedMesh()
{
Clear();
}
void SkinnedMesh::Clear()
{
for (uint i = 0; i < m_Textures.size(); i++) {
SAFE_DELETE(m_Textures[i]);
}
if (m_Buffers[0] != 0) {
glDeleteBuffers(ARRAY_SIZE_IN_ELEMENTS(m_Buffers), m_Buffers);
}
if (m_VAO != 0) {
glDeleteVertexArrays(1, &m_VAO);
m_VAO = 0;
}
}
bool SkinnedMesh::LoadMesh(const string& Filename)
{
// Release the previously loaded mesh (if it exists)
Clear();
// Create the VAO
glGenVertexArrays(1, &m_VAO);
glBindVertexArray(m_VAO);
// Create the buffers for the vertices attributes
glGenBuffers(ARRAY_SIZE_IN_ELEMENTS(m_Buffers), m_Buffers);
bool Ret = false;
m_pScene = m_Importer.ReadFile(Filename.c_str(), ASSIMP_LOAD_FLAGS);
if (m_pScene) {
// mTransformation : 子节点 相对 父节点的变换矩阵,对应根节点而言,其对应的是mesh的local space->根节点骨骼空间的变换矩阵
m_GlobalInverseTransform = m_pScene->mRootNode->mTransformation;
m_GlobalInverseTransform.Inverse();
Ret = InitFromScene(m_pScene, Filename);
}
else {
printf("Error parsing '%s': '%s'\n", Filename.c_str(), m_Importer.GetErrorString());
}
// Make sure the VAO is not changed from the outside
glBindVertexArray(0);
return Ret;
}
bool SkinnedMesh::InitFromScene(const aiScene* pScene, const string& Filename)
{
m_Entries.resize(pScene->mNumMeshes);
m_Textures.resize(pScene->mNumMaterials);
vector<Vector3f> Positions;
vector<Vector3f> Normals;
vector<Vector2f> TexCoords;
vector<VertexBoneData> Bones;
vector<uint> Indices;
uint NumVertices = 0; // count the total vertices
uint NumIndices = 0; // count the total indices
// Count the number of vertices and indices
for (uint i = 0; i < m_Entries.size(); i++) {
m_Entries[i].MaterialIndex = pScene->mMeshes[i]->mMaterialIndex;
m_Entries[i].NumIndices = pScene->mMeshes[i]->mNumFaces * 3;
m_Entries[i].BaseVertex = NumVertices; // 顶点开始位置
m_Entries[i].BaseIndex = NumIndices; // 索引开始位置
NumVertices += pScene->mMeshes[i]->mNumVertices; // 增加顶点总数, 加载完此Mesh的NumVertices后应有的偏移量, 下个循环 m_Entries[i].BaseVertex = NumVertices
NumIndices += m_Entries[i].NumIndices; // 增加索引总数, 加载完此Mesh的NumIndices后应有的偏移量, 下个循环BaseIndex = 此NumIndices
}
// Reserve space in the vectors for the vertex attributes and indices
Positions.reserve(NumVertices);
Normals.reserve(NumVertices);
TexCoords.reserve(NumVertices);
Bones.resize(NumVertices); // 为什么分配顶点个数个骨骼数组呢?因为方便每个顶点查看骨骼信息(每个顶点对应一个VertexBoneData)
Indices.reserve(NumIndices);
// Initialize the meshes in the scene one by one
for (uint i = 0; i < m_Entries.size(); i++) {
const aiMesh* paiMesh = pScene->mMeshes[i]; // 处理第i个Mesh
InitMesh(i, paiMesh, Positions, Normals, TexCoords, Bones, Indices);
}
if (!InitMaterials(pScene, Filename)) {
return false;
}
// Generate and populate the buffers with vertex attributes and the indices
glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[POS_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(Positions[0]) * Positions.size(), &Positions[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(POSITION_LOCATION);
glVertexAttribPointer(POSITION_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[TEXCOORD_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(TexCoords[0]) * TexCoords.size(), &TexCoords[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(TEX_COORD_LOCATION);
glVertexAttribPointer(TEX_COORD_LOCATION, 2, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[NORMAL_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(Normals[0]) * Normals.size(), &Normals[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(NORMAL_LOCATION);
glVertexAttribPointer(NORMAL_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, m_Buffers[BONE_VB]);
glBufferData(GL_ARRAY_BUFFER, sizeof(Bones[0]) * Bones.size(), &Bones[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(BONE_ID_LOCATION);
glVertexAttribIPointer(BONE_ID_LOCATION, 4, GL_INT, sizeof(VertexBoneData), (const GLvoid*)0);
glEnableVertexAttribArray(BONE_WEIGHT_LOCATION);
glVertexAttribPointer(BONE_WEIGHT_LOCATION, 4, GL_FLOAT, GL_FALSE, sizeof(VertexBoneData), (const GLvoid*)16);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Buffers[INDEX_BUFFER]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices[0]) * Indices.size(), &Indices[0], GL_STATIC_DRAW);
return glGetError() == GL_NO_ERROR;
}
void SkinnedMesh::InitMesh(uint MeshIndex,
const aiMesh* paiMesh,
vector<Vector3f>& Positions,
vector<Vector3f>& Normals,
vector<Vector2f>& TexCoords,
vector<VertexBoneData>& Bones, //Bone data for every vertex, size = vertex.
vector<uint>& Indices)
{
const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);
// Populate the vertex attribute vectors
for (uint i = 0; i < paiMesh->mNumVertices; i++) {
const aiVector3D* pPos = &(paiMesh->mVertices[i]);
const aiVector3D* pNormal = &(paiMesh->mNormals[i]);
const aiVector3D* pTexCoord = paiMesh->HasTextureCoords(0) ? &(paiMesh->mTextureCoords[0][i]) : &Zero3D;
Positions.push_back(Vector3f(pPos->x, pPos->y, pPos->z));
Normals.push_back(Vector3f(pNormal->x, pNormal->y, pNormal->z));
TexCoords.push_back(Vector2f(pTexCoord->x, pTexCoord->y));
}
LoadBones(MeshIndex, paiMesh, Bones);
// Populate the index buffer
// 用于Element Array Buffer,EBO中存储的内容就是顶点位置的索引indices
for (uint i = 0; i < paiMesh->mNumFaces; i++) {
const aiFace& Face = paiMesh->mFaces[i];
assert(Face.mNumIndices == 3);
Indices.push_back(Face.mIndices[0]);
Indices.push_back(Face.mIndices[1]);
Indices.push_back(Face.mIndices[2]);
}
}
void SkinnedMesh::LoadBones(uint MeshIndex, const aiMesh* pMesh, vector<VertexBoneData>& Bones) //Bones的个数为NumVertices
{
for (uint i = 0; i < pMesh->mNumBones; i++) {
uint BoneIndex = 0;
string BoneName(pMesh->mBones[i]->mName.data);
// 如果在该骨骼mapping中没有找到该骨骼名字,则添加一个mapping
if (m_BoneMapping.find(BoneName) == m_BoneMapping.end()) {
// Allocate an index for a new bone
BoneIndex = m_NumBones;
m_NumBones++;
BoneInfo bi;
m_BoneInfo.push_back(bi);
m_BoneInfo[BoneIndex].BoneOffset = pMesh->mBones[i]->mOffsetMatrix; // mOffesetMatrix: 从mesh的局部空间变换到骨骼空间的矩阵
m_BoneMapping[BoneName] = BoneIndex;
}
// 如果遍历到已存在的Bonename,则获取其BoneIndex
else {
BoneIndex = m_BoneMapping[BoneName];
}
// 处理每一个骨骼中的所有weights(单个weight影响单个顶点)
for (uint j = 0; j < pMesh->mBones[i]->mNumWeights; j++) {
/*
由于顶点ID与单个Mesh相关,并且我们将所有Mesh的顶点Position(或Normals、Texcoords)存储在单个矢量中(在vector中存在BaseVertex偏移量)
因此我们将当前aiMesh的基本顶点ID与mWeights数组的顶点ID相加,以获得绝对顶点ID
*/
uint VertexID = m_Entries[MeshIndex].BaseVertex + pMesh->mBones[i]->mWeights[j].mVertexId;
float Weight = pMesh->mBones[i]->mWeights[j].mWeight;
Bones[VertexID].AddBoneData(BoneIndex, Weight);
}
}
}
bool SkinnedMesh::InitMaterials(const aiScene* pScene, const string& Filename)
{
// Extract the directory part from the file name
string::size_type SlashIndex = Filename.find_last_of("/");
string Dir;
if (SlashIndex == string::npos) {
Dir = ".";
}
else if (SlashIndex == 0) {
Dir = "/";
}
else {
Dir = Filename.substr(0, SlashIndex);
}
bool Ret = true;
// Initialize the materials
for (uint i = 0; i < pScene->mNumMaterials; i++) {
const aiMaterial* pMaterial = pScene->mMaterials[i];
m_Textures[i] = NULL;
if (pMaterial->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
aiString Path;
if (pMaterial->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
string p(Path.data);
if (p.substr(0, 2) == ".\\") {
p = p.substr(2, p.size() - 2);
}
string FullPath = Dir + "/" + p;
m_Textures[i] = new MyTexture(GL_TEXTURE_2D, FullPath.c_str());
if (!m_Textures[i]->Load()) {
printf("Error loading texture '%s'\n", FullPath.c_str());
delete m_Textures[i];
m_Textures[i] = NULL;
Ret = false;
}
else {
printf("%d - loaded texture '%s'\n", i, FullPath.c_str());
}
}
}
}
return Ret;
}
void SkinnedMesh::Render()
{
glBindVertexArray(m_VAO);
for (uint i = 0; i < m_Entries.size(); i++) {
const uint MaterialIndex = m_Entries[i].MaterialIndex;
assert(MaterialIndex < m_Textures.size());
if (m_Textures[MaterialIndex]) {
m_Textures[MaterialIndex]->Bind(GL_TEXTURE0);
}
glDrawElementsBaseVertex(GL_TRIANGLES,
m_Entries[i].NumIndices,
GL_UNSIGNED_INT,
(void*)(sizeof(uint) * m_Entries[i].BaseIndex),
m_Entries[i].BaseVertex);
}
// Make sure the VAO is not changed from the outside
glBindVertexArray(0);
}
uint SkinnedMesh::FindPosition(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
for (uint i = 0; i < pNodeAnim->mNumPositionKeys - 1; i++) {
if (AnimationTime < (float)pNodeAnim->mPositionKeys[i + 1].mTime) {
return i;
}
}
assert(0);
return 0;
}
uint SkinnedMesh::FindRotation(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
assert(pNodeAnim->mNumRotationKeys > 0);
for (uint i = 0; i < pNodeAnim->mNumRotationKeys - 1; i++) {
if (AnimationTime < (float)pNodeAnim->mRotationKeys[i + 1].mTime) {
return i;
}
}
assert(0);
return 0;
}
uint SkinnedMesh::FindScaling(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
assert(pNodeAnim->mNumScalingKeys > 0);
for (uint i = 0; i < pNodeAnim->mNumScalingKeys - 1; i++) {
if (AnimationTime < (float)pNodeAnim->mScalingKeys[i + 1].mTime) {
return i;
}
}
assert(0);
return 0;
}
void SkinnedMesh::CalcInterpolatedPosition(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
if (pNodeAnim->mNumPositionKeys == 1) {
Out = pNodeAnim->mPositionKeys[0].mValue;
return;
}
uint PositionIndex = FindPosition(AnimationTime, pNodeAnim);
uint NextPositionIndex = (PositionIndex + 1);
assert(NextPositionIndex < pNodeAnim->mNumPositionKeys);
float DeltaTime = (float)(pNodeAnim->mPositionKeys[NextPositionIndex].mTime - pNodeAnim->mPositionKeys[PositionIndex].mTime);
float Factor = (AnimationTime - (float)pNodeAnim->mPositionKeys[PositionIndex].mTime) / DeltaTime;
assert(Factor >= 0.0f && Factor <= 1.0f);
const aiVector3D& Start = pNodeAnim->mPositionKeys[PositionIndex].mValue;
const aiVector3D& End = pNodeAnim->mPositionKeys[NextPositionIndex].mValue;
aiVector3D Delta = End - Start;
Out = Start + Factor * Delta;
}
void SkinnedMesh::CalcInterpolatedRotation(aiQuaternion& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
// we need at least two values to interpolate...
if (pNodeAnim->mNumRotationKeys == 1) {
Out = pNodeAnim->mRotationKeys[0].mValue;
return;
}
uint RotationIndex = FindRotation(AnimationTime, pNodeAnim);
uint NextRotationIndex = (RotationIndex + 1);
assert(NextRotationIndex < pNodeAnim->mNumRotationKeys);
float DeltaTime = (float)(pNodeAnim->mRotationKeys[NextRotationIndex].mTime - pNodeAnim->mRotationKeys[RotationIndex].mTime);
float Factor = (AnimationTime - (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime;
assert(Factor >= 0.0f && Factor <= 1.0f);
const aiQuaternion& StartRotationQ = pNodeAnim->mRotationKeys[RotationIndex].mValue;
const aiQuaternion& EndRotationQ = pNodeAnim->mRotationKeys[NextRotationIndex].mValue;
aiQuaternion::Interpolate(Out, StartRotationQ, EndRotationQ, Factor);
Out = Out.Normalize();
}
void SkinnedMesh::CalcInterpolatedScaling(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
if (pNodeAnim->mNumScalingKeys == 1) {
Out = pNodeAnim->mScalingKeys[0].mValue;
return;
}
uint ScalingIndex = FindScaling(AnimationTime, pNodeAnim);
uint NextScalingIndex = (ScalingIndex + 1);
assert(NextScalingIndex < pNodeAnim->mNumScalingKeys);
float DeltaTime = (float)(pNodeAnim->mScalingKeys[NextScalingIndex].mTime - pNodeAnim->mScalingKeys[ScalingIndex].mTime);
float Factor = (AnimationTime - (float)pNodeAnim->mScalingKeys[ScalingIndex].mTime) / DeltaTime;
assert(Factor >= 0.0f && Factor <= 1.0f);
const aiVector3D& Start = pNodeAnim->mScalingKeys[ScalingIndex].mValue;
const aiVector3D& End = pNodeAnim->mScalingKeys[NextScalingIndex].mValue;
aiVector3D Delta = End - Start;
Out = Start + Factor * Delta;
}
void SkinnedMesh::ReadNodeHeirarchy(float AnimationTime, const aiNode* pNode, const Matrix4f& ParentTransform)
{
string NodeName(pNode->mName.data);
const aiAnimation* pAnimation = m_pScene->mAnimations[0];
Matrix4f NodeTransformation(pNode->mTransformation);
const aiNodeAnim* pNodeAnim = FindNodeAnim(pAnimation, NodeName);
if (pNodeAnim) {
// Interpolate scaling and generate scaling transformation matrix
aiVector3D Scaling;
CalcInterpolatedScaling(Scaling, AnimationTime, pNodeAnim);
Matrix4f ScalingM;
ScalingM.InitScaleTransform(Scaling.x, Scaling.y, Scaling.z);
// Interpolate rotation and generate rotation transformation matrix
aiQuaternion RotationQ;
CalcInterpolatedRotation(RotationQ, AnimationTime, pNodeAnim);
Matrix4f RotationM = Matrix4f(RotationQ.GetMatrix());
// Interpolate translation and generate translation transformation matrix
aiVector3D Translation;
CalcInterpolatedPosition(Translation, AnimationTime, pNodeAnim);
Matrix4f TranslationM;
TranslationM.InitTranslationTransform(Translation.x, Translation.y, Translation.z);
// Combine the above transformations
// NodeTransformation 指 在骨骼空间下的变换
NodeTransformation = TranslationM * RotationM * ScalingM;
}
Matrix4f GlobalTransformation = ParentTransform * NodeTransformation;
if (m_BoneMapping.find(NodeName) != m_BoneMapping.end()) {
uint BoneIndex = m_BoneMapping[NodeName];
// BoneOffset: 子节点局部空间->子节点骨骼空间的变换矩阵, GlobalTransformation:子节点骨骼空间回溯到 根节点骨骼空间的“总变换矩阵”
// m_GlobalInverseTransform:根节点骨骼空间->mesh局部空间变换矩阵,最后FinalTransformation对于的是mesh的局部空间
m_BoneInfo[BoneIndex].FinalTransformation = m_GlobalInverseTransform * GlobalTransformation * m_BoneInfo[BoneIndex].BoneOffset;
}
for (uint i = 0; i < pNode->mNumChildren; i++) {
ReadNodeHeirarchy(AnimationTime, pNode->mChildren[i], GlobalTransformation);
}
}
void SkinnedMesh::BoneTransform(float TimeInSeconds, vector<Matrix4f>& Transforms)
{
Matrix4f Identity;
Identity.InitIdentity();
float TicksPerSecond = (float)(m_pScene->mAnimations[0]->mTicksPerSecond != 0 ? m_pScene->mAnimations[0]->mTicksPerSecond : 25.0f);
float TimeInTicks = TimeInSeconds * TicksPerSecond;
float AnimationTime = fmod(TimeInTicks, (float)m_pScene->mAnimations[0]->mDuration);
ReadNodeHeirarchy(AnimationTime, m_pScene->mRootNode, Identity);
Transforms.resize(m_NumBones);
for (uint i = 0; i < m_NumBones; i++) {
Transforms[i] = m_BoneInfo[i].FinalTransformation;
}
}
const aiNodeAnim* SkinnedMesh::FindNodeAnim(const aiAnimation* pAnimation, const string NodeName)
{
for (uint i = 0; i < pAnimation->mNumChannels; i++) {
const aiNodeAnim* pNodeAnim = pAnimation->mChannels[i];
if (string(pNodeAnim->mNodeName.data) == NodeName) {
return pNodeAnim;
}
}
return NULL;
}
最后是Texture类,比较简单,加载纹理用的,.h和.cpp我放在一起方便看了
#pragma once
#ifndef _TEXTURE_H
#define _TEXTURE_H
#include <string>
#include <glad/glad.h>
//#include <ImageMagick-6/Magick++.h>
class MyTexture
{
public:
MyTexture(GLenum TextureTarget, const std::string& FileName);
bool Load();
void Bind(GLenum TextureUnit);
private:
std::string m_fileName;
GLenum m_textureTarget;
GLuint m_textureObj;
//Magick::Image m_image;
//Magick::Blob m_blob;
};
#endif // ! _TEXTURE_H
// .cpp
#include <iostream>
#include "texture.h"
#include <stb_image.h>
MyTexture::MyTexture(GLenum TextureTarget, const std::string& FileName)
{
m_textureTarget = TextureTarget;
m_fileName = FileName;
}
bool MyTexture::Load()
{
glGenTextures(1, &m_textureObj);
int width, height, nrComponents;
unsigned char* data = stbi_load(m_fileName.c_str(), &width, &height, &nrComponents, 0);
if (data)
{
GLenum format;
if (nrComponents == 1)
format = GL_RED;
else if (nrComponents == 3)
format = GL_RGB;
else if (nrComponents == 4)
format = GL_RGBA;
glBindTexture(m_textureTarget, m_textureObj);
glTexImage2D(m_textureTarget, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
stbi_image_free(data);
return true;
}
else
{
std::cout << "Texture failed to load at path: " << m_fileName << std::endl;
stbi_image_free(data);
return false;
}
}
void MyTexture::Bind(GLenum TextureUnit)
{
glActiveTexture(TextureUnit);
glBindTexture(m_textureTarget, m_textureObj);
}