使用TinyGLTF加载GLTF文件无法渲染,求排查指引?
我尝试使用TinyGLTF加载Khronos的Triangle.gltf模型,编写了如下C++代码:
#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); }
但渲染失败,glGetError()未返回任何错误,屏幕仅显示清屏颜色,以下是关键排查方向:
矩阵参数未初始化:顶点着色器中的
transform、view、projection矩阵未赋值,默认值为全0,会导致gl_Position计算结果为(0,0,0,0),三角形完全处于裁剪空间外。需手动设置这三个矩阵:transform设为单位矩阵view设为看向模型的视图矩阵(比如相机在(0,0,5)看向原点)projection设为透视/正交投影矩阵,匹配窗口宽高比
顶点属性格式配置错误:
fillArrayBuffers中glVertexArrayAttribFormat的第三个参数错误使用了accessor.count(该值是顶点数量),正确应该用每个顶点的分量数,可通过tinygltf::GetNumComponents(accessor.type)获取(比如POSITION对应TYPE_VEC3,分量数为3)。当前配置会导致GPU解析顶点数据完全错乱,无法正确读取顶点位置。绘制时未绑定元素缓冲对象(EBO):核心模式下
glDrawElements必须依赖绑定的EBO,代码中创建了elementBufferIDs但绘制时未绑定,直接传递CPU内存指针无效。需在绘制前绑定对应的EBO,且glDrawElements的最后一个参数改为EBO内的偏移量(比如(void*)ac.byteOffset)。缺失着色器编译/链接错误检查:代码仅执行了着色器编译和程序链接,但未检查是否成功。需添加:
- 编译后用
glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &status)检查顶点着色器编译状态,并用glGetShaderInfoLog获取错误信息 - 链接后用
glGetProgramiv(programID, GL_LINK_STATUS, &status)检查程序链接状态,同样获取日志
- 编译后用
VAO配置缺失绑定关联:核心模式下,调用
glVertexArrayVertexBuffer绑定缓冲后,需用glVertexArrayAttribBinding(vertexArrayID, attribLocation, bindingIndex)将属性位置与缓冲绑定点关联,否则GPU无法知道从哪个缓冲读取属性数据。BufferView的Target验证:确认
tinygltf::BufferView::target是否正确,比如元素缓冲对应GL_ELEMENT_ARRAY_BUFFER,顶点缓冲对应GL_ARRAY_BUFFER,创建缓冲时需匹配该target。
内容的提问来源于stack exchange,提问作者Adem Budak

