Jump to content
  • Advertisement
Yxjmir

OpenGL Loading data from GLTF into OpenGL buffers

Recommended Posts

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

Share this post


Link to post
Share on other sites
Advertisement

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

Share this post


Link to post
Share on other sites

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!