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

如何使用tinygltf库在glb/glTF文件中实现基于顶点的纹理选择?

实现基于顶点的纹理选择(tinygltf + glTF)

这是个很常见的需求——glTF规范本身确实没有原生提供"顶点级纹理选择"的内置属性,但我们有几种可靠的方案来实现,结合tinygltf库可以轻松搞定。我结合你的最简示例(双三角正方形+两张纹理)和复杂场景需求,分三种方案讲解:

方案1:拆分多个Primitive(最直观,适合分组清晰的场景)

这是最简单的实现方式:把使用不同纹理的顶点组拆成独立的Primitive,每个Primitive绑定对应纹理的材质。

对于你的双三角示例:

  • 第一个三角形的顶点(3个顶点)组成一个Primitive,绑定材质A(对应第一张纹理)
  • 第二个三角形的顶点(3个顶点)组成另一个Primitive,绑定材质B(对应第二张纹理)

在tinygltf里的实现步骤:

  1. 为每个纹理创建独立的tinygltf::Material对象,设置pbrMetallicRoughness.baseColorTexture.index为对应的纹理索引。
  2. 为每个顶点组创建独立的tinygltf::Primitive对象:
    • 每个Primitive的attributes包含POSITION、TEXCOORD_0等必要属性,指向对应顶点数据的Accessor。
    • 把Primitive的material字段设置为对应材质的索引。
  3. 将这些Primitive加入同一个tinygltf::Mesh(或者不同Mesh,看需求),最终写入glTF文件。

这种方案的优势是无需自定义着色器,完全兼容glTF标准加载流程;缺点是如果顶点组数量多,会增加Draw Call数量,对性能有轻微影响。

方案2:自定义顶点属性+着色器采样(适合复杂网格,Draw Call友好)

如果你不想拆分太多Primitive,可以通过自定义顶点属性传递纹理索引,配合着色器实现动态纹理选择。

步骤1:添加自定义顶点属性

在tinygltf中,你可以添加一个自定义顶点属性(比如命名为TEXINDEX_0),每个顶点存储对应的纹理索引(用无符号字节或整数类型,节省空间):

// 假设我们有一个存储纹理索引的字节数组:uint8_t texIndices[] = {0,0,0,1,1,1};
tinygltf::Buffer buffer;
buffer.data.insert(buffer.data.end(), reinterpret_cast<const char*>(texIndices), 
                   reinterpret_cast<const char*>(texIndices) + 6);
int bufferIndex = doc.buffers.size();
doc.buffers.push_back(buffer);

// 创建BufferView
tinygltf::BufferView bufferView;
bufferView.buffer = bufferIndex;
bufferView.byteOffset = 0;
bufferView.byteLength = 6;
bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
int bufferViewIndex = doc.bufferViews.size();
doc.bufferViews.push_back(bufferView);

// 创建Accessor
tinygltf::Accessor accessor;
accessor.bufferView = bufferViewIndex;
accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
accessor.count = 6; // 6个顶点
accessor.type = TINYGLTF_TYPE_SCALAR;
accessor.minValues = {0};
accessor.maxValues = {1};
int accessorIndex = doc.accessors.size();
doc.accessors.push_back(accessor);

// 把自定义属性添加到Primitive的attributes中
primitive.attributes["TEXINDEX_0"] = accessorIndex;

步骤2:编写自定义着色器

需要修改glTF的顶点/片段着色器,使用纹理索引采样对应的纹理数组:

// 顶点着色器
#version 300 es
in vec3 POSITION;
in vec2 TEXCOORD_0;
in uint TEXINDEX_0; // 对应自定义属性
out vec2 v_texCoord;
flat out uint v_texIndex; // flat修饰避免插值

void main() {
    gl_Position = projectionMatrix * modelViewMatrix * vec4(POSITION, 1.0);
    v_texCoord = TEXCOORD_0;
    v_texIndex = TEXINDEX_0;
}

// 片段着色器
#version 300 es
precision mediump float;
uniform sampler2D u_textures[2]; // 纹理数组
in vec2 v_texCoord;
flat in uint v_texIndex;
out vec4 FragColor;

void main() {
    FragColor = texture(u_textures[v_texIndex], v_texCoord);
}

步骤3:加载时绑定纹理数组

在加载glTF文件后,你需要把tinygltf中对应的纹理对象绑定到不同的纹理单元,然后把纹理单元的位置传入着色器的u_textures uniform数组。

这种方案的优势是只需要一个Primitive,Draw Call最少;缺点是需要自定义着色器,且需要处理纹理数组的绑定逻辑,兼容性略低于方案1。

方案3:纹理图集(Texture Atlas,适合多小纹理场景)

如果你的纹理数量多且尺寸不大,可以把所有纹理合并成一张纹理图集,然后调整每个顶点的UV坐标,让它们指向图集中对应的区域。

实现步骤:

  1. 用工具(比如TexturePacker)把所有纹理合并成一张大图,记录每个纹理在图集中的UV偏移和缩放比例。
  2. 在tinygltf中,为每个顶点组调整TEXCOORD_0的UV值,对应到图集中的位置。
  3. 整个网格只需要一个Primitive和一个材质(绑定图集纹理)。

这种方案的优势是Draw Call最少,性能最优;缺点是需要预处理纹理和UV,灵活性较低(比如纹理尺寸变化时需要重新生成图集)。

总结选择建议

  • 如果你的纹理分组清晰、数量不多:优先选方案1,实现简单且兼容性好。
  • 如果是复杂网格、追求低Draw Call:选方案2,但需要自定义着色器和纹理绑定逻辑。
  • 如果是大量小纹理的场景:选方案3,性能最优但需要预处理。

内容的提问来源于stack exchange,提问作者Fezzan Mahmood

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 15:54:07