Advertisement

Loading data from GLTF into OpenGL buffers

Started by March 03, 2018 11:46 PM
3 comments, last by Yxjmir 6 years ago

I'm trying to load data from a .gltf file into a struct to use to load a .bin file. I don't think there is a problem with how the vertex positions are loaded, but with the indices. This is what I get when drawing with glDrawArrays(GL_LINES, ...):

image.thumb.png.f0ecac612735ef1cf03a81822a6e9133.png

Also, using glDrawElements gives a similar result. Since it looks like its drawing triangles using the wrong vertices for each face, I'm assuming it needs an index buffer/element buffer. (I'm not sure why there is a line going through part of it, it doesn't look like it belongs to a side, re-exported it without texture coordinates checked, and its not there)

I'm using jsoncpp to load the GLTF file, its format is based on JSON. Here is the gltf struct I'm using, and how I parse the file:


#define GLTF_TARGET_ARRAY_BUFFER (34962)
#define GLTF_TARGET_ELEMENT_ARRAY_BUFFER (34963)

#define GLTF_COMPONENT_TYPE_BYTE (5120)
#define GLTF_COMPONENT_TYPE_UNSIGNED_BYTE (5121)
#define GLTF_COMPONENT_TYPE_SHORT (5122)
#define GLTF_COMPONENT_TYPE_UNSIGNED_SHORT (5123)
#define GLTF_COMPONENT_TYPE_INT (5124)
#define GLTF_COMPONENT_TYPE_UNSIGNED_INT (5125)
#define GLTF_COMPONENT_TYPE_FLOAT (5126)
#define GLTF_COMPONENT_TYPE_DOUBLE (5127)

#define GLTF_PARAMETER_TYPE_BYTE (5120)
#define GLTF_PARAMETER_TYPE_UNSIGNED_BYTE (5121)
#define GLTF_PARAMETER_TYPE_SHORT (5122)
#define GLTF_PARAMETER_TYPE_UNSIGNED_SHORT (5123)
#define GLTF_PARAMETER_TYPE_INT (5124)
#define GLTF_PARAMETER_TYPE_UNSIGNED_INT (5125)
#define GLTF_PARAMETER_TYPE_FLOAT (5126)

#define GLTF_PARAMETER_TYPE_FLOAT_VEC2 (35664)
#define GLTF_PARAMETER_TYPE_FLOAT_VEC3 (35665)
#define GLTF_PARAMETER_TYPE_FLOAT_VEC4 (35666)
  
struct GLTF
{
	struct Accessor
	{
		USHORT bufferView;
		USHORT componentType;
		UINT count;
		vector<INT> max;
		vector<INT> min;
		string type;
	};
	vector<Accessor> m_accessors;

	struct Asset
	{
		string copyright;
		string generator;
		string version;
	}m_asset;

	struct BufferView
	{
		UINT buffer;
		UINT byteLength;
		UINT byteOffset;
		UINT target;
	};
	vector<BufferView> m_bufferViews;

	struct Buffer
	{
		UINT byteLength;
		string uri;
	};
	vector<Buffer> m_buffers;

	vector<string> m_Images;

	struct Material
	{
		string name;
		string alphaMode;
		Vec4 baseColorFactor;
		UINT baseColorTexture;
		UINT normalTexture;
		float metallicFactor;
	};
	vector<Material> m_materials;

	struct Meshes
	{
		string name;
		struct Primitive
		{
			vector<UINT> attributes_indices;
			UINT indices;
			UINT material;
		};
		vector<Primitive> primitives;
	};
	vector<Meshes> m_meshes;

	struct Nodes
	{
		int mesh;
		string name;
		Vec3 translation;

	};
	vector<Nodes> m_nodes;

	struct Scenes
	{
		UINT index;
		string name;
		vector<UINT> nodes;
	};
	vector<Scenes> m_scenes;

	vector<UINT> samplers;
	struct Textures
	{
		UINT sampler;
		UINT source;
	};
	vector<Textures> m_textures;

	map<UINT, string> attributes_map;
	map<UINT, string> textures_map;
};

GLTF m_gltf; // This is actually in the Mesh class

bool Mesh::Load(string sFilename)
{
  string sFileAsString;
  stringstream sStream;
  ifstream fin(sFilename);
  
  sStream << fin.rdbuf();
  fin.close();
  
  sFileAsString = sStream.str();
  
  Json::Reader r;
  Json::Value root;
  if (!r.parse(sFileAsString, root))
  {
    string errors = r.getFormatedErrorMessages();
    if (errors != "")
    {
      // TODO: Log errors
      return false;
    }
  }
  
  if (root.isNull())
    return false;

  Json::Value object;
  Json::Value value;
  
  // Load Accessors array, these are referenced by attributes with their index value
  object = root.get("accessors", Json::Value()); // store object with key "accessors", if not found it will default to Json::Value()
  if (!object.isNull())
  {
    for (Json::ValueIterator it = object.begin(); it != object.end(); it++)
    {
      GLTF::Accessor accessor;
      
      value = (*it).get("bufferView", Json::Value());
      if (!value.isNull())
        accessor.bufferView = value.asUINT();
      else
        return false;
      
      value = (*it).get("componentType", Json::Value());
      if (!value.isNull())
        accessor.componentType = value.asUINT();
      else
        return false;
      
      value = (*it).get("count", Json::Value());
      if (!value.isNull())
        accessor.count = value.asUINT();
      else
        return false;
      
      value = (*it).get("type", Json::Value());
      if (!value.isNull())
        accessor.type = value.asString();
      else
        return false;
      
      m_gltf.accessors.push_back(accessor);
    }
  }
  else
    return false;
  
  object = root.get("bufferViews", Json::Value());
  if(!object.isNull())
  {
    for (Json::ValueIterator it = object.begin(); it != object.end(); it++)
    {
      GLTF::BufferView bufferView;
      
      value = (*it).get("buffer", Json::Value());
      if(!value.isNull())
        bufferView.buffer = value.asUInt();
      else
        return false;
      
      value = (*it).get("byteLength", Json::Value());
      if(!value.isNull())
        bufferView.byteLength = value.asUInt();
      else
        return false;
      
      value = (*it).get("byteOffset", Json::Value());
      if(!value.isNull())
        bufferView.byteOffset = value.asUInt();
      else
        return false;
  
      value = (*it).get("target", Json::Value());
      if(!value.isNull())
        bufferView.target = value.asUInt();
      else
        return false;
      
      m_gltf.m_bufferViews.push_back(bufferView);
    }
  }
  else
    return false;
  
  object = root.get("buffers", Json::Value());
  if(!object.isNull())
  {
    for (Json::ValueIterator it = object.begin(); it != object.end(); it++)
    {
      GLTF::Buffer buffer;
      
      value = (*it).get("byteLength", Json::Value());
      if(!value.isNull())
        buffer.byteLength = value.asUInt();
      else
        return false;
      
      // Store the filename of the .bin file
      value = (*it).get("uri", Json::Value());
      if(!value.isNull())
        buffer.uri = value.asString();
      else
        return false;
    }
  }
  else
    return false;
  
  object = root.get("meshes", Json::Value());
  if(!object.isNull())
  {
    for(Json::ValueIterator it = object.begin(); it != object.end(); it++)
    {
      GLTF::Meshes mesh;
      
      value = (*it).get("primitives", Json::Value());
      for(Json::ValueIterator value_it = value.begin(); value_it != value.end(); value_it++)
      {
        GLTF::Meshes::Primitive primitive;
        
        Json::Value attributes;
        attributes = (*value_it).get("attributes", Json::Value());
        vector<string> memberNames = attributes.getMemberNames();
        for(size_t i = 0; i < memberNames.size(); i++)
        {
          Json::Value member;
          member = attributes.get(memeberNames[i], Json::Value());
          if(!member.isNull())
          {
            primitive.attributes_indices.push_back(member.asUInt());
            m_gltf.attributes_map[member.asUInt()] = memberNames[i]; // Each of these referes to an accessor by indice, so each indice should be unique, and they are when loading a cube
          }
          else
            return false;
        }
        
        // Indice of the accessor used for indices
        Json::Value indices;
        indices = (*value_it).get("indices", Json::Value());
        primitive.indices = indices.asUInt();
        
        mesh.primitives.push_back(primitive);
      }
      
      m_gltf.m_meshes.push_back(mesh);
    }
  }
  
  vector<float> vertexData;
  vector<USHORT> indiceData;
  
  int vertexBufferSizeTotal = 0;
  int elementBufferSizeTotal = 0;
  
  GLTF::Meshes mesh = m_gltf.m_meshes[0];
  vector<GLTF::Meshes::Primitive> primitives = mesh.primitives; // trying to make the code easier to read
  for (size_t p = 0; p < primitive.size(); p++)
  {
    vector<UINT> attributes = primitives[p].attributes_indices;
    for(size_t a = 0; a < attributes.size(); a++)
    {
      GLTF::Accessor accessor = m_gltf.m_accessors[attributes[a]];
      GLTF::BufferView bufferView = m_gltf.m_bufferViews[accessor.bufferView];
      UINT target = bufferView.target;
      if(target == GLTF_TARGET_ARRAY_BUFFER)
        vertexBufferSizeTotal += bufferView.byteLength;
    }
    
    UINT indice = primitives[p].indices;
    GLTF::BufferView bufferView = m_gltf.m_bufferViews[indice];
    UINT target = bufferView.target;
    if(target == GLTF_TARGET_ELEMENT_ARRAY_BUFFER)
      elementBufferSizeTotal += bufferView.byteLength;
  }
  
  // These have already been generated
  glBindVertexArray(g_pGame->m_VAO);
  glBindBuffer(GL_ARRAY_BUFFER, g_pGame->m_VBO);
  glBufferData(GL_ARRAY_BUFFER, vertexBufferSizeTotal, nullptr, GL_STATIC_DRAW);
  
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_pGame->m_EBO);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementBufferSizeTotal, nullptr, GL_STATIC_DRAW);
  
  int offset = 0;
  int offset_indice = 0;
  
  for (size_t p = 0; p < primitive.size(); p++)
  {
    vector<UINT> attributes = primitives[p].attributes_indices;
    
    int pos = sFilename.find_last_of('\\') + 1;
    string sFolder = sFilename.substr(0, pos);
    
    for (size_t a = 0; a < attributes.size(); a++)
    {
      LoadBufferView(sFolder, attributes[a], data, offset);
    }
    
    UINT indice = primitives[p].indices;
    GLTF::BufferView bufferView_indice = m_gltf.m_bufferViews[indice];
    UINT target_indice = bufferView_indice.target;
    
    bool result = LoadBufferView(sFolder, indice, data, offset_indice);
    if(!result)
      return false;
  }
  
  return true;
}

bool Mesh::LoadBufferView(string sFolder, UINT a, vector<float> &vertexData, vector<float> &indiceData, int &offset_indice)
{
  ifstream fin;
  GLTF::Accessor accessor = m_gltf.m_accessors[a];
  GLTF::BufferView bufferView = m_gltf.m_bufferViews[accessor.bufferView];
  GLTF::Buffer buffer = m_gltf.m_buffers[bufferView.buffer];
  
  const size_t count = accessor.count;
  UINT target = bufferView.target;
  
  int elementSize;
  int componentSize;
  int numComponents;
  
  string sFilename_bin = sFolder + buffer.uri;
  fin.open(sFilename_bin, ios::binary);
  if (fin.fail())
  {
    return false;
  }
  
  fin.seekg(bufferView.byteOffset, ios::beg);
  
  switch (accessor.componentType)
	{
	case GLTF_COMPONENT_TYPE_BYTE:
		componentSize = sizeof(GLbyte);
		break;
	case GLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
		componentSize = sizeof(GLubyte);
		break;
	case GLTF_COMPONENT_TYPE_SHORT:
		componentSize = sizeof(GLshort);
		break;
	case GLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
		componentSize = sizeof(GLushort);
		break;
	case GLTF_COMPONENT_TYPE_INT:
		componentSize = sizeof(GLint);
		break;
	case GLTF_COMPONENT_TYPE_UNSIGNED_INT:
		componentSize = sizeof(GLuint);
		break;
	case GLTF_COMPONENT_TYPE_FLOAT:
		componentSize = sizeof(GLfloat);
		break;
	case GLTF_COMPONENT_TYPE_DOUBLE:
		componentSize = sizeof(GLfloat);
		break;
	default:
		componentSize = 0;
		break;
	}

	if (accessor.type == "SCALAR")
		numComponents = 1;
	else if (accessor.type == "VEC2")
		numComponents = 2;
	else if (accessor.type == "VEC3")
		numComponents = 3;
	else if (accessor.type == "VEC4")
		numComponents = 4;
	else if (accessor.type == "MAT2")
		numComponents = 4;
	else if (accessor.type == "MAT3")
		numComponents = 9;
	else if (accessor.type == "MAT4")
		numComponents = 16;
	else
		return false;
  
  vector<float> fSubdata;
  
  // I'm pretty sure this is one of the problems, or related to it. If I use vector<USHORT> only half of the vector if filled, if I use GLubyte, the entire vector is filled, but the data might not be right
  vector<GLubyte> nSubdata;
  
  elementSize = (componentSize) * (numComponents);
  
  // Only fill the vector I'm using
  if (accessor.type == "SCALAR")
  {
    nSubdata.resize(count * numComponents);
    fin.read(reinterpret_cast<char*>(&nSubdata[0]), count/* * elementSize*/); // I commented this out since I'm not sure which size the .bin is storing the indice values, and I kept getting runtime errors, no matter what type I used for nSubdata
  }
  else
  {
    fSubdata.resize(count * numComponents);
	fin.read(reinterpret_cast<char*>(&fSubdata[0]), count * elementSize);
  }
  
  switch (target)
  {
    case GLTF_TARGET_ARRAY_BUFFER:
      {
        vertexData.insert(vertexData.end(), fSubdata.begin(), fSubdata.end());
        
        glBindBuffer(GL_ARRAY_BUFFER, g_pGame->m_VBO);
        glBufferSubData(GL_ARRAY_BUFFER, offset, fSubdata.size() * componentSize, &fSubdata[0]);

        int attribute_index = 0; // I'm only loading vertex positions, the only attribute stored in the files for now
        
        glEnableVertexAttribArray(attribute_index);
        glVertexAttribPointer(0, numComponents, GL_FLOAT, GL_FALSE, componentSize * numComponents, (void*)(offset));
      }break;
      case GLTF_TARGET_ELEMENT_ARRAY_BUFFER:
      {
        indiceData.insert(indiceData.end(), nSubdata.begin(), nSubdata.end());
        
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_pGame->m_EBO);
        // This is another area where I'm not sure of the correct values, but if componentSize is the correct size for the type being used it should be correct glBufferSubData is expecting the size in bytes, right?
        glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, nSubdata.size() * componentSize, &nSubdata[0]);
        }break;
    default:
      return false;
  }
  
  if (accessor.type == "SCALAR")
    offset += nSubdata.size() * componentSize;
  else
    offset += fSubdata.size() * componentSize;
  
  fin.close();
  
  return true;
}

these are the draw calls, I only use one at a time, but neither is currently display properly, g_pGame->m_indices is the same as indiceData vector, and vertexCount contains the correct vertex count, but I forgot to copy the lines of code containing where I set them, which is at the end of Mesh::Load(), I double checked the values to make sure.

glBindVertexArray(g_pGame->m_VAO);

glDrawElements(GL_LINES, g_pGame->m_indices.size(), GL_UNSIGNED_BYTE, (void*)0); // Only shows with GL_UNSIGNED_BYTE

glDrawArrays(GL_LINES, 0, g_pGame->m_vertexCount);

So, I'm asking what type should I use for the indices? it doesn't seem to be unsigned short, which is what I selected with the Khronos Group Exporter for blender. Also, am I reading part or all of the .bin file wrong?

Test.gltf

Test.bin

So, I played around with the exporter this morning (I also included UV coordinates), and it seems to work, but only for simple models using glDrawElements. I also, made sure to use the same type for the indices, obviously. I think it might just be a problem with the exporter, maybe it only works with models made a specific way. I didn't show it before, but only some of the faces were rendering, I probably selected unsigned byte by accident for the exporter, and was trying to read the data back into a unsigned short vector.

image.thumb.png.d6ad004bd3b3d9ade8517b9a56c0f0df.png

Some of the lines shouldn't be there on the house model below, I'm using the same draw call just a different shader that only uses the color green for the line instead of the texture color, other than that both shaders are the same.

image.thumb.png.568989ddbc69d0d6456799371d65ad4f.png

this is how it looks in blender, there are no lines in the doorway of the model.

image.thumb.png.836d4ca3a5684bcdb0dfcbfa98908055.png

Advertisement

I am very interested in this subject.  How did you solve your problem?

I don't think I solved completely. But after parsing the .gltf file, I read all .bin files referenced in the .gltf file into temporary buffers (not sure how to read it directly into opengl buffers, if that's possible). Create the VAOs, VBOs, EBOs based on the number of mesh primitive's attributes. Then for each node in each scene I check if it contains a mesh. If it does, then load the data into opengl buffers. The following code is inside a for loop for each attribute, in each primitive, in each mesh, that is inside a function that is called for each node, in each scene. Hope that last sentence wasn't too confusing. If it is then see the Mesh::Render() below, the for loops are almost identical.


glBindVertexArray(m_VAO[i]);
if (accessor.componentType == 5121 || accessor.componentType == 5123)
{
	vector<INT> indices(bufferSize / componentSize);
	for (size_t j = 0; j < indices.size(); j++)
	{
		indices[j] = buffers[0].data()[bufferOffset + j];
	}
	m_pIndices.push_back(indices);
					
	vector<GLsizei*> pIndexPointer(indices.size());
	for (size_t j = 0; j < indices.size(); j++)
	{
		pIndexPointer[j] = &m_pIndices[(m_pIndices.size() - 1)][0];
	}
	m_pIndexPointer.push_back(pIndexPointer);
				
	//vector<GLsizei> counts;
	//counts.push_back(indices.size());
	m_pCounts.push_back(indices.size());

	UINT drawCount = m_pIndices.size();
	m_drawCount.push_back(drawCount);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO[i]);

	vector<GLubyte> bufferData(bufferSize);
	memcpy_s(bufferData.data(), bufferSize, &buffers[b_index].data()[bufferOffset], bufferSize);

	glNamedBufferData(m_EBO[i], bufferSize, bufferData.data(), GL_STATIC_DRAW);

	m_indexCount += bufferSize / (accessor.max.size() * componentSize);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
else if (accessor.componentType == 5126)
{
	UINT attr = 0;
	if (attribute.accessor_name == "POSITION")
	{
		m_vertexCount += bufferSize / (accessor.max.size() * componentSize);
		attr = 0;
	}
	if (attribute.accessor_name == "TEXCOORD_0")
		attr = 1;
	if (attribute.accessor_name == "NORMAL")
		attr = 2;
	if (attribute.accessor_name == "JOINTS_0")
		attr = 3;
	if (attribute.accessor_name == "WEIGHTS_0")
		attr = 4;

	int vb = (i * (primitive.attributes.size() - 1)) + attribute.accessor - 1;

	glBindBuffer(GL_ARRAY_BUFFER, m_VBO[vb]);

	vector<GLfloat> bufferData(bufferSize / sizeof(GLfloat));
	memcpy_s(bufferData.data(), bufferSize, &buffers[b_index].data()[bufferOffset], bufferSize);

	glNamedBufferData(m_VBO[vb], bufferSize, bufferData.data(), GL_STATIC_DRAW);
					
	glEnableVertexAttribArray(attr);
	glVertexAttribPointer(attr, accessor.max.size(), GL_FLOAT, GL_FALSE, accessor.max.size() * componentSize, (GLvoid*)0);

	++attr_index;
}

void Mesh::RenderNodeMesh(GLTF::Meshes mesh, int& i)
{
	for (size_t p = 0; p < mesh.primitives.size(); p++)
	{
		GLTF::Meshes::Primitive primitive = mesh.primitives[p];

		for (size_t a = 0; a < primitive.attributes.size(); a++)
		{
			if (primitive.attributes[a].accessor_name != "POSITION")
				continue;

			GLTF::Accessor accessor = m_gltf.m_accessors[primitive.attributes[p].accessor];
			GLTF::BufferView bv = m_gltf.m_bufferViews[accessor.bufferView];

			GLTF::Material material;
			GLTF::Textures texture; // I know this and the next variable can be confusing, but I didn't know what else to name it
			Texture2D texture2D;	// Class that contains opengl texture attributes (id, width, height, min & mag filter, clip, etc.)
			if (primitive.material != -1)
			{
				material = m_gltf.m_materials[primitive.material];
				if (material.baseColorTexture != -1)
					texture = m_gltf.m_textures[material.baseColorTexture];
			}
			if (texture.source != -1)
				texture2D = ResourceManager::GetTexture(m_textures[texture.source]);
			else
				texture2D = ResourceManager::GetTexture("uv_diff"); // Just defaults to a colored uv grid

			glActiveTexture(GL_TEXTURE0 + (texture.source == -1) ? 0 : texture.source); // Not sure this works like I intended it to
			glBindTexture(GL_TEXTURE_2D, texture2D.ID);
			
			glBindVertexArray(m_VAO[i]);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO[i]);
			glMultiDrawElements(GL_TRIANGLES, &m_pCounts[0], m_indiceElementType, (const GLvoid**)&m_pIndexPointer[0], m_pCounts.size());
			glDrawElements(GL_TRIANGLES, m_pCounts[i], m_indiceElementType, (i - 1) >= 0 ? &m_pIndices[i - 1] : 0);
			break;
		}
		//++i;
		break;
	}
}

void Mesh::RenderNode(GLTF::Nodes node, int& i)
{
	if (node.pChildren.size() > 0)
	{
		for (size_t c = 0; c < node.pChildren.size(); c++)
		{
			GLTF::Nodes childNode = m_gltf.m_nodes[node.pChildren[c]];
			if ((childNode.name.find("_Spawn_Point") != string::npos) || (childNode.name.find("Physics_Bounds") != string::npos))
				continue;

			if (childNode.mesh == -1)
			{
				RenderNode(childNode, i);
			}
			else
			{
				GLTF::Meshes mesh = m_gltf.m_meshes[childNode.mesh];
				RenderNodeMesh(mesh, i);
			}
		}
	}
	else
	{
		if (node.mesh != -1)
		{
			GLTF::Meshes mesh = m_gltf.m_meshes[node.mesh];
			RenderNodeMesh(mesh, i);
		}
	}
}

void Mesh::Render()
{
	if (!m_VAO)
		return;
  
	int i = 0;
	for (size_t s = 0; s < m_gltf.m_scenes.size(); s++)
	{
		GLTF::Scenes scene = m_gltf.m_scenes[s];
		for (size_t n = 0; n < scene.nodes.size(); n++)
		{
			GLTF::Nodes node = m_gltf.m_nodes[scene.nodes[n]];
			if ((node.name.find("_Spawn_Point") != string::npos) || (node.name.find("Physics_Bounds") != string::npos))
				continue;

			RenderNode(node, i);
		}
	}
}

Again, not sure if it'll work properly for certain models, or even most models.


vector<INT> indices(bufferSize / componentSize);
for (size_t j = 0; j < indices.size(); j++)
{
	indices[j] = buffers[0].data()[bufferOffset + j];
}
m_pIndices.push_back(indices);
					
vector<GLsizei*> pIndexPointer(indices.size());
for (size_t j = 0; j < indices.size(); j++)
{
	pIndexPointer[j] = &m_pIndices[(m_pIndices.size() - 1)][0];
}
m_pIndexPointer.push_back(pIndexPointer);

the top for loop doesn't do anything, I forgot I left it in there

This topic is closed to new replies.

Advertisement