So I run my program and I see a flicker of my model which I think means that the original vertices have been rendered and ether the new ones have not been passed correctly or they have been miscalculated. I am trying to do mesh skinnig on the CPU side so I can understand it before I do the work for the GPU. It runs at over 33fps on my machine. I want access to the skinned model for physics.
The Model class is probably not important but hear it is
#pragma once
#include "GL/glew.h"
#include "GLFW/glfw3.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "assimp/Importer.hpp"
#include "assimp/scene.h"
#include "assimp/postprocess.h"
#include <iostream>
#include <vector>
#include "SkelMesh.h"
#include "Bones.h"
using namespace std;
class Model
{
public:
Model(glm::vec3 pos = glm::vec3(0.0f), glm::vec3 siz = glm::vec3(1.0f));
glm::vec3 position;
glm::vec3 size;
void init();
void loadModel(string path);
void render(ShaderProgram shade, glm::mat4 model);
void cleanup();
void setRotation(string name, glm::vec3 rotation);
glm::vec3 getRotation(string name);
protected:
vector<SkelMesh> meshes;
void processNode(aiNode* node, const aiScene* scene);
SkelMesh processMesh(aiMesh* mesh, const aiScene* scene);
glm::mat4 assimpToGlmMatrix(aiMatrix4x4 mat);
};
#include "Model.h"
Model::Model(glm::vec3 pos, glm::vec3 siz)
:position(pos), size(siz)
{}
void Model::init() {}
void Model::loadModel(string path) {
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate);
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
cout << "could not load Model at " << path << " " << importer.GetErrorString() << endl;
return;
}
processNode(scene->mRootNode, scene);
}
void Model::render(ShaderProgram shade, glm::mat4 model) {
shade.setUniform("model", model);
for (SkelMesh mesh : meshes) {
mesh.render(shade);
}
}
void Model::cleanup() {
for (SkelMesh mesh : meshes) {
mesh.cleanup();
}
}
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);
}
}
SkelMesh Model::processMesh(aiMesh* mesh, const aiScene* scene) {
vector<Vertex> vertices;
vector<unsigned int> induces;
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);
}
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++) {
induces.push_back(face.mIndices[j]);
}
}
for (unsigned int i = 0; i < mesh->mNumBones; i++) {
aiBone* bone = mesh->mBones[i];
glm::mat4 m = assimpToGlmMatrix(bone->mOffsetMatrix);
for (int j = 0; j < bone->mNumWeights; j++) {
unsigned int id = bone->mWeights[j].mVertexId;
float weight = bone->mWeights[j].mWeight;
}
}
return SkelMesh(vertices, induces, mesh, scene);
}
glm::mat4 Model::assimpToGlmMatrix(aiMatrix4x4 mat) {
glm::mat4 m;
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
m[x][y] = mat[y][x];
}
}
return m;
}
void Model::setRotation(string name, glm::vec3 rotation) {
meshes[0].setRotation(name, rotation);
}
glm::vec3 Model::getRotation(string name) {
return meshes[0].getRotation(name);
}
The Mesh class
#pragma once
#include "GL/glew.h"
#include "GLFW/glfw3.h"
#include "glm/glm.hpp"
#include <vector>
#include "Texture2D.h"
#include "ShaderProgram.h"
#include "Mesh.h"
#include "Bones.h"
#include "assimp/scene.h"
#include "assimp/postprocess.h"
using namespace std;
typedef struct Vertex;
class SkelMesh
{
public:
SkelMesh();
SkelMesh(vector<Vertex> verts, vector<unsigned int>indus, aiMesh* mesh, const aiScene* scene);
vector<Vertex> vertices;
vector<unsigned int> induces;
unsigned int VAO;
void setRotation(string name, glm::vec3 rotation);
glm::vec3 getRotation(string name);
void render(ShaderProgram shade);
void cleanup();
private:
aiMesh* amesh;
const aiScene* ascene;
Bones armatures;
unsigned int IBO, VBO;
void setup();
};
#include "SkelMesh.h"
vector<struct Vertex> Vertex::genList(float* vertices, int vertNum) {
vector<Vertex> ret(vertNum);
int stride = sizeof(Vertex) / sizeof(float);
for (int i = 0; i < vertNum; i++) {
ret[i].position = glm::vec3{
vertices[i * stride],
vertices[i * stride + 1],
vertices[i * stride + 2]
};
ret[i].normal = glm::vec3{
vertices[i * stride + 3],
vertices[i * stride + 4],
vertices[i * stride + 5]
};
ret[i].texCoords = glm::vec2{
vertices[i * stride + 6],
vertices[i * stride + 7]
};
}
return ret;
}
SkelMesh::SkelMesh() {
}
SkelMesh::SkelMesh(vector<Vertex> verts, vector<unsigned int>indus, aiMesh* mesh, const aiScene* scene)
:vertices(verts), induces(indus), amesh(mesh), ascene(scene)
{
setup();
}
void SkelMesh::render(ShaderProgram shade) {
armatures.update();
unsigned int diffuseIdx = 0;
unsigned int specularIdx = 0;
//shade.use();
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, induces.size(), GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &armatures.newVertices[0], GL_DYNAMIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, induces.size() * sizeof(unsigned int), &induces[0], GL_DYNAMIC_DRAW);
glBindVertexArray(0);
glActiveTexture(GL_TEXTURE0);
}
void SkelMesh::cleanup() {
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &IBO);
}
void SkelMesh::setup() {
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &IBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), &vertices[0], GL_DYNAMIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, induces.size() * sizeof(unsigned int), &induces[0], GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
glEnableVertexAttribArray(0);
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);
glBindVertexArray(0);
armatures.init(amesh, vertices, ascene);
}
void SkelMesh::setRotation(string name, glm::vec3 rotation) {
armatures.setRotation(name, rotation);
}
glm::vec3 SkelMesh::getRotation(string name) {
return armatures.getRotation(name);
}
Armatures class.
#pragma once
#include <vector>
#include <string>
#include <unordered_map>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "assimp/scene.h"
#include "assimp/postprocess.h"
#include "Mesh.h"
using namespace std;
struct boneVert {
vector<int> ids;
vector<float> weights;
};
struct Bone {
int id = 0; // position of the bone in final upload array
string name = "";
glm::mat4 offset = glm::mat4(1.0f);
vector<Bone> children = {};
};
class Bones
{
public:
vector<Vertex> origVertices;
vector<Vertex> newVertices;
vector<boneVert> boneVerts;
void init(aiMesh* mesh, vector<Vertex> inpos, const aiScene* scene);
void setRotation(string name, glm::vec3 rotation);
glm::vec3 getRotation(string name);
void update();
void updateVerts(vector<glm::mat4> boneTransforms);
private:
glm::mat4 globalInverseTransform;
unordered_map<string, glm::vec3> rots;
Bone structure;
std::vector<glm::mat4> currentPose = {};
void getPose(Bone& skeletion, std::vector<glm::mat4>& output, glm::mat4& parentTransform, glm::mat4& globalInverseTransform);
bool readSkeleton(Bone& boneOutput, aiNode* node, std::unordered_map<std::string, std::pair<int, glm::mat4>>& boneInfoTable);
glm::mat4 assimpToGlmMatrix(aiMatrix4x4 mat);
};
#include "Bones.h"
void Bones::init(aiMesh* mesh, vector<Vertex> inpos, const aiScene* scene) {
std::unordered_map<std::string, std::pair<int, glm::mat4>> boneInfo = {};
origVertices = inpos;
for (int i = 0; i < origVertices.size(); i++) {
Vertex tran1;
tran1 = origVertices.at(i);
newVertices.push_back(tran1);
boneVert tran;
boneVerts.push_back(tran);
}
for (unsigned int i = 0; i < mesh->mNumBones; i++) {
aiBone* bone = mesh->mBones[i];
glm::mat4 m = assimpToGlmMatrix(bone->mOffsetMatrix);
boneInfo[bone->mName.C_Str()] = { i, m };
rots[bone->mName.C_Str()] = glm::vec3(0, 0, 0);
float total = 0;
for (int j = 0; j < bone->mNumWeights; j++) {
unsigned int id = bone->mWeights[j].mVertexId;
float weight = bone->mWeights[j].mWeight;
boneVerts.at(id).ids.push_back(i);
total += weight;
}
for (int j = 0; j < bone->mNumWeights; j++) {
unsigned int id = bone->mWeights[j].mVertexId;
float weight = bone->mWeights[j].mWeight;
boneVerts.at(id).weights.push_back(weight / total);
}
}
glm::mat4 toinverse = assimpToGlmMatrix(scene->mRootNode->mTransformation);
globalInverseTransform = glm::inverse(toinverse);
readSkeleton(structure, scene->mRootNode, boneInfo);
}
bool Bones::readSkeleton(Bone& boneOutput, aiNode* node, std::unordered_map<std::string, std::pair<int, glm::mat4>>& boneInfoTable) {
if (boneInfoTable.find(node->mName.C_Str()) != boneInfoTable.end()) { // if node is actually a bone
boneOutput.name = node->mName.C_Str();
boneOutput.id = boneInfoTable[boneOutput.name].first;
boneOutput.offset = boneInfoTable[boneOutput.name].second;
for (int i = 0; i < node->mNumChildren; i++) {
Bone child;
readSkeleton(child, node->mChildren[i], boneInfoTable);
boneOutput.children.push_back(child);
}
return true;
}
else { // find bones in children
for (int i = 0; i < node->mNumChildren; i++) {
if (readSkeleton(boneOutput, node->mChildren[i], boneInfoTable)) {
return true;
}
}
}
return false;
}
void Bones::setRotation(string name, glm::vec3 rotation) {
glm::vec3 conv = glm::vec3(glm::radians(rotation.x), glm::radians(rotation.y), glm::radians(rotation.y));
rots[name] = conv;
}
glm::vec3 Bones::getRotation(string name) {
glm::vec3 rotations = rots[name];
return glm::vec3(glm::degrees(rotations.x), glm::degrees(rotations.y), glm::degrees(rotations.z));
}
void Bones::update() {
glm::mat4 identity(1.0f);
vector<glm::mat4> transforms;
transforms.resize(50);
getPose(structure, transforms, identity, globalInverseTransform);
updateVerts(transforms);
}
void Bones::updateVerts(vector<glm::mat4> boneTransforms) {
for (int i = 0; i < boneVerts.size(); i++) {
glm::mat4 boneTrans(0.0f);
for (int j = 0; j < boneVerts.at(i).ids.size(); j++) {
boneTrans += boneTransforms.at(boneVerts.at(i).ids.at(j)) * boneVerts.at(i).weights.at(j);
}
glm::vec3 posin = origVertices.at(i).position;
glm::vec4 posout = boneTrans * glm::vec4(posin, 1.0f);
glm::vec3 apos = glm::vec3(posout.x, posout.y, posout.z);
newVertices.at(i).position = apos;
}
}
void Bones::getPose(Bone& skeletion, std::vector<glm::mat4>& output, glm::mat4& parentTransform, glm::mat4& globalInverseTransform) {
glm::vec3 rotVal(0, 0, 0);
unordered_map<string, glm::vec3>::iterator itr;
for (itr = rots.begin(); itr != rots.end(); itr++) {
if (itr->first == skeletion.name) {
rotVal = itr->second;
break;
}
}
//glm::vec3 rotVal = rots[skeletion.name];
glm::mat4 iden(1.0f);
glm::mat4 boneRot = glm::rotate(iden, rotVal.x, glm::vec3(1, 0, 0)) * glm::rotate(iden, rotVal.y, glm::vec3(0, 1, 0)) * glm::rotate(iden, rotVal.z, glm::vec3(0, 0, 1));
glm::mat4 globalTransform = parentTransform * boneRot;
output[skeletion.id] = globalInverseTransform * globalTransform * skeletion.offset;
for (Bone& child : skeletion.children) {
getPose(child, output, globalTransform, globalInverseTransform);
}
}
glm::mat4 Bones::assimpToGlmMatrix(aiMatrix4x4 mat) {
glm::mat4 m;
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
m[x][y] = mat[y][x];
}
}
return m;
}