golang高效读写与验证glTF 2.0格式插件库gltf的使用

Golang高效读写与验证glTF 2.0格式插件库gltf的使用

简介

gltf是一个Go模块,用于高效且健壮地序列化/反序列化glTF 2.0格式。glTF 2.0是一种免版税的规范,用于应用程序高效传输和加载3D场景和模型,也被称为"3D的JPEG"。

Gopher glTF

主要特性

  • gltf.Document: 符合glTF规范的数据模型
  • gltf.Open/gltf.Save: 支持.glTF.glb的编解码
  • qmuntal/gltf/ext: 内置扩展机制,提供主要扩展支持
  • qmuntal/gltf/modeler: 友好的读写访问器和缓冲视图的包
  • qmuntal/gltf/binary: 高效读写缓冲字节的包

快速开始

数据模型

qmuntal/gltf实现了完整的glTF 2.0规范。顶级元素是gltf.Document,它包含所有在内存中保存gltf文档的信息:

// 这个文档不会产生有效的glTF,只是一个示例
gltf.Document{
  Accessors: []*gltf.Accessor{
      {BufferView: gltf.Index(0), ComponentType: gltf.ComponentUshort, Type: gltf.AccessorScalar},
  },
  Asset: gltf.Asset{Version: "2.0", Generator: "qmuntal/gltf"},
  BufferViews: []*gltf.BufferView{
      {ByteLength: 72, ByteOffset: 0, Target: gltf.TargetElementArrayBuffer},
  },
  Buffers: []*gltf.Buffer{{ByteLength: 1033, URI: bufferData}},
  Meshes: []*gltf.Mesh{{
    Name: "Cube",
  }},
  Nodes: []*gltf.Node{{Name: "Cube", Mesh: gltf.Index(0)}},
  Scene:    gltf.Index(0),
  Scenes:   []*gltf.Scene{{Name: "Root Scene", Nodes: []int{0}}},
}

可选参数

所有默认值与Go类型零值不匹配的可选属性都定义为指针。处理可选值时请考虑以下准则:

  • 如果所需值是默认值,在写入glTF时可以安全地不定义它们
  • 在读取glTF时,可以安全地预期可选值不为nil
  • 为可选属性赋值时,使用这些实用函数可能很有帮助:
    • gltf.Index(1)
    • gltf.Float(0.5)

读取文档

可以通过使用gltf.Decoder从任何io.Reader解码gltf.Document

resp, _ := http.Get("https://example.com/static/foo.gltf")
var doc gltf.Document
gltf.NewDecoder(resp.Body).Decode(&doc)
fmt.Print(doc.Asset)

当处理文件系统时,使用gltf.Open更方便,因为它会自动管理相对外部缓冲:

doc, _ := gltf.Open("./foo.gltf")
fmt.Print(doc.Asset)

在这两种情况下,解码器都会根据内容自动检测文件是JSON/ASCII(gltf)还是Binary(glb)。

写入文档

可以通过使用gltf.Encodergltf.Document编码到任何io.Writer

var buf bytes.Buffer
gltf.NewEncoder(&buf).Encode(&doc)
http.Post("http://example.com/upload", "model/gltf+binary", &buf)

默认情况下gltf.NewEncoder输出二进制文件,要生成JSON/ASCII文件,将AsBinary设置为false:

var buf bytes.Buffer
enc := gltf.NewEncoder(&buf)
enc.AsBinary = false
enc.Encode(&doc)
http.Post("http://example.com/upload", "model/gltf+json", &buf)

当处理文件系统时,使用gltf.Savegltf.SaveBinary更方便,因为它们会自动管理相对外部缓冲:

gltf.Save(&doc, "./foo.gltf")
gltf.SaveBinary(&doc, "./foo.glb")

操作缓冲视图和访问器

gltf/modeler包定义了一个友好的API来读写访问器和缓冲视图,抽象了所有字节操作工作和glTF规范的特殊性。

以下示例创建一个单色三角形:

screenshot

doc := gltf.NewDocument()
doc.Meshes = []*gltf.Mesh{{
    Name: "Pyramid",
    Primitives: []*gltf.Primitive{{
        Indices: gltf.Index(modeler.WriteIndices(doc, []uint16{0, 1, 2})),
        Attributes: gltf.PrimitiveAttributes{
          gltf.POSITION: modeler.WritePosition(doc, [][3]float32{{0, 0, 0}, {0, 10, 0}, {0, 0, 10}}),
          gltf.COLOR_0:  modeler.WriteColor(doc, [][3]uint8{{255, 0, 0}, {0, 255, 0}, {0, 0, 255}}),
        },
    }},
}}
doc.Nodes = []*gltf.Node{{Name: "Pyramid", Mesh: gltf.Index(0)}}
doc.Scenes[0].Nodes = append(doc.Scenes[0].Nodes, 0)
gltf.Save(doc, "./test.gltf")

数据交错

存储在单个bufferView中的属性的数据可以作为结构数组存储,这可能会在静态属性中产生渲染性能提升。qmuntal/gltf/modeler通过方法WritePrimitiveAttributesWriteAccessorsInterleavedWriteBufferViewInterleaved促进了交错访问器和缓冲视图的创建,其中第一个是创建网格图元最推荐的:

doc := gltf.NewDocument()
attrs, _ := modeler.WritePrimitiveAttributes(doc,
    modeler.PrimitiveAttribute{Name: gltf.POSITION, Data: [][3]float32{{0, 0, 0}, {0, 10, 0}, {0, 0, 10}}},
    modeler.PrimitiveAttribute{Name: gltf.COLOR_0, Data: [][3]uint8{{255, 0, 0}, {0, 255, 0}, {0, 0, 255}}},
)
doc.Meshes = []*gltf.Mesh{{
    Name: "Pyramid",
    Primitives: []*gltf.Primitive{{
        Indices: gltf.Index(modeler.WriteIndices(doc, []uint16{0, 1, 2})),
        Attributes: attrs,
    }},
}}
doc.Nodes = []*gltf.Node{{Name: "Pyramid", Mesh: gltf.Index(0)}}
doc.Scenes[0].Nodes = append(doc.Scenes[0].Nodes, 0)
gltf.Save(doc, "./test.gltf")

操作字节

gltf/binary包定义了一个友好且高效的API来读写缓冲字节,抽象了所有字节操作工作。这个包非常底层,普通用户应该使用gltf/modeler,因为它提供了另一个抽象级别,理解字节如何与其他实体关联。

处理扩展

qmuntal/gltf设计用于支持动态扩展。默认情况下,只解码核心规范,扩展对象中的数据存储为json.RawMessage,因此可以由调用者解码或在保存文档时自动编码。

一些官方扩展在ext下实现。

要解码一个受支持的扩展,唯一需要的操作是导入相关的包,这样扩展将不会存储为json.RawMessage,而是作为扩展包中定义的类型:

import (
  "github.com/qmuntal/gltf"
  "github.com/qmuntal/gltf/ext/lightspunctual"
)

func main() {
  doc, _ := gltf.Open("./foo.gltf")
    if v, ok := doc.Extensions[lightspunctual.ExtensionName]; ok {
        for _, l := range v.(lightspunctual.Lights) {
            fmt.Print(l.Type)
        }
    }
}

对于内置扩展,不需要调用gltf.RegisterExtension,因为这些包在初始化时会自动注册自己。

自定义扩展

要实现自定义扩展编码,提供一个可以按照规范编码为JSON对象的struct

要实现自定义扩展解码,在解码前至少调用一次gltf.RegisterExtension,提供扩展标识符和一个将JSON字节解码到所需struct的函数:

const ExtensionName = "FAKE_Extension"

type Foo struct {
    BufferView int          `json:"bufferView"`
    Attributes gltf.Attributes  `json:"attributes"`
}

func init() {
    gltf.RegisterExtension(ExtensionName, Unmarshal)
}

func Unmarshal(data []byte) (any, error) {
    foo := new(Foo)
    err := json.Unmarshal(data, foo)
    return foo, err
}

关于项目

这个库是glTF 2.0的完整实现,其明确目标是提供一个生产就绪、符合习惯且精心设计的API来执行任何类型的glTF操作。

实现其他文件格式的转换器以及提供创建和操作3D几何的机制超出了范围。

当前API尚未冻结,在达到v1.0之前可能会发生微小变化。


更多关于golang高效读写与验证glTF 2.0格式插件库gltf的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高效读写与验证glTF 2.0格式插件库gltf的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


高效读写与验证glTF 2.0的Go库使用指南

glTF (GL Transmission Format) 是3D场景和模型的标准化格式,Go语言中可以使用github.com/qmuntal/gltf库来高效处理glTF 2.0文件。

安装gltf库

go get github.com/qmuntal/gltf

基本读写操作

读取glTF文件

package main

import (
	"fmt"
	"log"
	"github.com/qmuntal/gltf"
)

func main() {
	// 读取glTF文件
	doc, err := gltf.Open("model.gltf")
	if err != nil {
		log.Fatal(err)
	}

	// 打印基本信息
	fmt.Printf("glTF version: %s\n", doc.Asset.Version)
	fmt.Printf("Generator: %s\n", doc.Asset.Generator)
	fmt.Printf("Scene count: %d\n", len(doc.Scenes))
	fmt.Printf("Node count: %d\n", len(doc.Nodes))
	fmt.Printf("Mesh count: %d\n", len(doc.Meshes))
}

写入glTF文件

func writeGLTF() {
	doc := gltf.NewDocument()
	
	// 设置资产信息
	doc.Asset = &gltf.Asset{
		Version:    "2.0",
		Generator: "My Go GLTF Generator",
	}
	
	// 添加一个简单场景
	scene := gltf.NewScene()
	doc.Scenes = append(doc.Scenes, scene)
	doc.Scene = gltf.Index(0) // 设置默认场景
	
	// 保存文件
	if err := gltf.Save(doc, "output.gltf"); err != nil {
		log.Fatal(err)
	}
}

高效处理大型glTF文件

对于大型glTF文件,可以使用流式处理:

func processLargeGLTF() {
	// 使用流式解码器
	decoder := gltf.NewDecoderFS(os.DirFS("."), "large_model.gltf")
	doc := new(gltf.Document)
	if err := decoder.Decode(doc); err != nil {
		log.Fatal(err)
	}

	// 逐网格处理
	for _, mesh := range doc.Meshes {
		// 处理每个网格...
		fmt.Printf("Processing mesh: %v\n", mesh.Name)
	}
}

glTF验证

func validateGLTF() {
	doc, err := gltf.Open("model.gltf")
	if err != nil {
		log.Fatal(err)
	}

	// 执行基本验证
	if err := doc.Validate(); err != nil {
		log.Printf("Validation errors: %v", err)
	} else {
		fmt.Println("glTF is valid")
	}

	// 更详细的验证
	validator := gltf.NewValidator()
	if err := validator.Validate(doc); err != nil {
		log.Printf("Detailed validation errors: %v", err)
	} else {
		fmt.Println("glTF passed detailed validation")
	}
}

处理二进制glTF (.glb)

func processGLB() {
	// 读取GLB文件
	doc, err := gltf.Open("model.glb")
	if err != nil {
		log.Fatal(err)
	}

	// 写入GLB文件
	doc.Asset.Generator = "My Go GLB Generator"
	if err := gltf.SaveBinary(doc, "output.glb"); err != nil {
		log.Fatal(err)
	}
}

访问几何数据

func accessGeometry(doc *gltf.Document) {
	for _, mesh := range doc.Meshes {
		for _, prim := range mesh.Primitives {
			// 获取顶点位置数据
			posAccessor := doc.Accessors[*prim.Attributes["POSITION"]]
			posView := doc.BufferViews[*posAccessor.BufferView]
			posBuffer := doc.Buffers[*posView.Buffer]
			
			// 这里可以处理原始几何数据...
			fmt.Printf("Mesh %s has %d vertices\n", mesh.Name, posAccessor.Count)
		}
	}
}

性能优化技巧

  1. 批量处理:对于大型模型,批量处理网格和纹理
  2. 内存映射:对于非常大的文件,考虑使用内存映射文件
  3. 并行处理:对独立的网格可以并行处理
func parallelProcess(doc *gltf.Document) {
	var wg sync.WaitGroup
	
	for i := range doc.Meshes {
		wg.Add(1)
		go func(mesh *gltf.Mesh) {
			defer wg.Done()
			// 并行处理每个网格
			processMesh(mesh)
		}(&doc.Meshes[i])
	}
	
	wg.Wait()
}

func processMesh(mesh *gltf.Mesh) {
	// 网格处理逻辑
}

总结

github.com/qmuntal/gltf库提供了完整的glTF 2.0支持,包括:

  • 读写.gltf和.glb文件
  • 全面的glTF验证
  • 流式处理大型文件
  • 底层数据访问

通过合理使用这些功能,可以在Go中高效处理3D模型数据。

回到顶部