You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用TinyGLTF加载GLTF文件无法渲染,求排查指引?

渲染Triangle.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 16:07:32