I'm trying to load a GLTF file using TinyGLTF:
#include <tiny_gltf.h>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GLFW/glfw3.h>
#include <vector>
#include <string>
namespace tn = tinygltf;
void key(GLFWwindow* w, int key, int scancode, int action, int mods) {
switch(action) {
case GLFW_PRESS: {
if(key == GLFW_KEY_ESCAPE)
glfwSetWindowShouldClose(w, GLFW_TRUE);
} break;
case GLFW_RELEASE: break;
default: break;
}
}
const GLchar* vertexShaderSource = R"(
#version 460 core
#extension all: warn
#pragma optimize(off)
#pragma debug(on)
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec2 tPosition;
layout (location = 2) uniform mat4x4 transform;
layout (location = 3) uniform mat4x4 view;
layout (location = 4) uniform mat4x4 projection;
out vec2 textureCoordinate;
void main() {
textureCoordinate = tPosition;
gl_Position = projection * view * transform * vec4(vPosition, 1.0);
}
)";
const GLchar* fragmentShaderSource = R"(
#version 460 core
#extension all: warn
layout (binding = 0) uniform sampler2D s;
in vec2 textureCoordinate;
out vec4 color;
void main() {
// color = texture(s, textureCoordinate);
color = vec4(1.0);
}
)";
void fillArrayBuffers(const tn::Model& model, GLuint programID, GLuint vertexArrayID) {
int defaultSceneIndex = model.defaultScene;
const tn::Scene& defaultScene = model.scenes[defaultSceneIndex];
for(int nodeIndex : defaultScene.nodes) {
for(const tn::Primitive& primitive : model.meshes[model.nodes[nodeIndex].mesh].primitives) {
for(GLuint bindingIndex = -1; const auto& [attrib, attribAccessorIndex] : primitive.attributes) {
if(attrib == "POSITION") {
bindingIndex = glGetAttribLocation(programID, "vPosition");
}
if(attrib.starts_with("TEXCOORD")) {
bindingIndex = glGetAttribLocation(programID, "tPosition");
}
tn::Accessor accessor = model.accessors[attribAccessorIndex];
tn::BufferView bv = model.bufferViews[accessor.bufferView];
tn::Buffer buf = model.buffers[bv.buffer];
GLuint arrayBufferID;
glCreateBuffers(1, &arrayBufferID);
glBindBuffer(bv.target, arrayBufferID);
glBufferStorage(bv.target, bv.byteLength, std::data(buf.data) + bv.byteOffset, GL_MAP_READ_BIT);
glVertexArrayVertexBuffer(vertexArrayID, bindingIndex, arrayBufferID, bv.byteOffset, accessor.ByteStride(bv));
glVertexArrayAttribFormat(vertexArrayID, bindingIndex, accessor.count, accessor.componentType, accessor.normalized,
accessor.byteOffset);
glEnableVertexArrayAttrib(vertexArrayID, bindingIndex);
} // end for
}
}
}
void fillElementBuffers(const tn::Model& model, std::vector<GLuint>& elementBufferIDs) {
int defaultSceneIndex = model.defaultScene;
const tn::Scene& defaultScene = model.scenes[defaultSceneIndex];
for(int nodeIndex : defaultScene.nodes) {
const tn::Node& node = model.nodes[nodeIndex];
int meshIndex = node.mesh;
const tn::Mesh& mesh = model.meshes[meshIndex];
for(const tn::Primitive& primitive : mesh.primitives) {
tn::Accessor accessor = model.accessors[primitive.indices];
tn::BufferView bv = model.bufferViews[accessor.bufferView];
tn::Buffer buf = model.buffers[bv.buffer];
GLuint id;
glCreateBuffers(1, &id);
glBindBuffer(bv.target, id);
elementBufferIDs.push_back(id);
glBufferStorage(bv.target, bv.byteLength, std::data(buf.data) + bv.byteOffset, GL_MAP_READ_BIT);
}
}
}
int main(int argc, const char* argv[]) {
int ret = glfwInit();
assert(ret == GLFW_TRUE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_CONTEXT_ROBUSTNESS, GLFW_LOSE_CONTEXT_ON_RESET);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
int win_width = 580, win_height = 325;
GLFWwindow* window = glfwCreateWindow(win_width, win_height, "sth", nullptr, nullptr);
glfwMakeContextCurrent(window);
GLenum err = glewInit();
assert(GLEW_OK == err);
glfwSetKeyCallback(window, key);
tn::Model model;
std::string errors;
std::string warnings;
bool ret_ = tn::TinyGLTF{}.LoadASCIIFromFile(&model, &errors, &warnings, "./models/Triangle/glTF/Triangle.gltf");
assert(ret_);
assert(std::empty(errors));
assert(std::empty(warnings));
GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShaderID, 1, &vertexShaderSource, nullptr);
glCompileShader(vertexShaderID);
GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShaderID, 1, &fragmentShaderSource, nullptr);
glCompileShader(fragmentShaderID);
GLuint programID = glCreateProgram();
glAttachShader(programID, vertexShaderID);
glAttachShader(programID, fragmentShaderID);
glLinkProgram(programID);
glUseProgram(programID);
GLuint vertexArrayID;
glGenVertexArrays(1, &vertexArrayID);
glBindVertexArray(vertexArrayID);
int defaultSceneIndex = model.defaultScene;
const tn::Scene& defaultScene = model.scenes[defaultSceneIndex];
std::vector<GLuint> elementBufferIDs;
fillArrayBuffers(model, programID, vertexArrayID);
fillElementBuffers(model, elementBufferIDs);
assert(glGetError() == GL_NO_ERROR);
constexpr GLfloat value[] = {0.247, 0.45, 0.45, 1.0};
while(!glfwWindowShouldClose(window)) {
glClearBufferfv(GL_COLOR, 0, value);
glUseProgram(programID);
glBindVertexArray(vertexArrayID);
for(const tn::Mesh& m : model.meshes) {
for(const auto& primitive : m.primitives) {
tn::Accessor ac = model.accessors[primitive.indices];
tn::BufferView bv = model.bufferViews[ac.bufferView];
tn::Buffer buf = model.buffers[bv.buffer];
glDrawElements(primitive.mode, ac.count, ac.componentType,
(const void*)(std::data(buf.data) + bv.byteOffset + ac.byteOffset));
}
}
glfwPollEvents();
glfwSwapBuffers(window);
}
}
...but I failed to do so. The glGetError()
returns no error but the screen output is just the cleared color. Any pointer?
You don't set uniforms for transform matrices, so replace
gl_Position = projection * view * transform * vec4(vPosition, 1.0);
by
gl_Position = vec4(vPosition, 1.0);
to draw triangle mesh as the model defines it.
---
Model is stored in one buffer. It consists of vertex positions and indices. BufferView
is used to point concrete kind of data. Data of model need to be copied into GL buffers. Then properties of BufferView
are utilized: pointer to the beginning of buffer, offset to concrete data and its length. After model's data was copied, i.e. vertex positions and indices, you should render using the whole data of these buffers, with no any offsets.
First mistake is in:
glVertexArrayVertexBuffer(vertexArrayID, bindingIndex, arrayBufferID, bv.byteOffset /*<<<*/, accessor.ByteStride(bv));
should be replaced by:
glVertexArrayVertexBuffer(vertexArrayID, bindingIndex, arrayBufferID, 0, accessor.ByteStride(bv));
The second culprit is:
glDrawElements(primitive.mode, ac.count, ac.componentType,
(const void*)(std::data(buf.data) + bv.byteOffset + ac.byteOffset) /*<<<*/);
replace by:
glDrawElements(primitive.mode, ac.count, ac.componentType, 0);
by these modifications, data is read from the beginning of buffers, one with vertex positions and the second one with indices. You simply confused the method of accessing model's data with pointing data of GL buffers to be used when rendering.