:eyeglasses: Go library for [d]encoding glTF 2.0 files

gltf PkgGoDev Build Status Coverage Status License Mentioned in Awesome Go

A Go module for efficient and robust serialization/deserialization of glTF 2.0, a royalty-free specification for the efficient transmission and loading of 3D scenes and models by applications, also known as "the JPEG of 3D".

Main FeaturesGetting StartedContributingAbout The Project

Gopher glTF

Main Features

📜 Getting started

Data Model

qmuntal/gltf implements the whole glTF 2.0 specification. The top level element is the gltf.Document and it contains all the information to hold a gltf document in memory:

// This document does not produce any valid glTF, it is just an example.
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: []uint32{0}}},
}

Optional parameters

All optional properties whose default value does not match with the golang type zero value are defines as pointers. Take the following guidelines into account when working with optional values:

  • It is safe to not define them when writing the glTF if the desired value is the default one.
  • It is safe to expect that the optional values are not nil when reading a glTF.
  • When assigning values to optional properties it may be helpful to use these utility functions:
    • gltf.Index(1)
    • gltf.Float(0.5)

Reading a document

A gltf.Document can be decoded from any io.Reader by using gltf.Decoder:

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

When working with the file system it is more convenient to use gltf.Open as it automatically manages relative external buffers:

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

In both cases the decoder will automatically detect if the file is JSON/ASCII (gltf) or Binary (glb) based on its content.

Writing a document

A gltf.Document can be encoded to any io.Writer by using gltf.Encoder:

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

By default gltf.NewEncoder outputs a binary file, to generate a JSON/ASCII file set AsBinary to 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)

When working with the file system it is more convenient to use gltf.Save and gltf.SaveBinary as it automatically manages relative external buffers:

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

Manipulating buffer views and accessors

The package gltf/modeler defines a friendly API to read and write accessors and buffer views, abstracting away all the byte manipulation work and the idiosyncrasy of the glTF spec.

The following example creates a single colored triangle:

screenshot

doc := gltf.NewDocument()
doc.Meshes = []*gltf.Mesh{{
    Name: "Pyramid",
    Primitives: []*gltf.Primitive{{
        Indices: gltf.Index(modeler.WriteIndices(doc, []uint16{0, 1, 2})),
        Attributes: map[string]uint32{
          "POSITION": modeler.WritePosition(doc, [][3]float32{{0, 0, 0}, {0, 10, 0}, {0, 0, 10}}),
          "COLOR_0":  modeler.WriteColor(doc, [][3]uint8{{255, 0, 0}, {0, 255, 0}, {0, 0, 255}}),
        },
    }},
}}

Data interleaving

The data of the attributes that are stored in a single bufferView may be stored as an Array-Of-Structures, which may produce a rendering perfomance boost in static attributes. qmuntal/gltf/modeler facilitates the creation of interleaved accessors and buffer views with the methods WriteAttributesInterleaved, WriteAccessorsInterleaved, and WriteBufferViewInterleaved being the first one the most recommended for creating mesh primitives:

doc := gltf.NewDocument()
attrs, _ := modeler.WriteAttributesInterleaved(doc, modeler.Attributes{
  Position:       [][3]float32{{0, 0, 0}, {0, 10, 0}, {0, 0, 10}},
  Color:          [][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,
    }},
}}

Manipulating bytes

The package gltf/binary defines a friendly and efficient API to read and write bytes from buffers, abstracting away all the byte manipulation work. This package is very low level and normal users should use gltf/modeler instead as it provides another level of abstraction that understands how bytes are associated to other entities.

This package is very similary to the Go binary package, the main differences are that it is highly specialized in glTF data types and that it only have to deal with little endian encoding.

Dealing with extensions

qmuntal/gltf is designed to support dynamic extensions. By default only the core specification is decoded and the data inside the extensions objects are stored as json.RawMessage so they can be decoded by the caller or automatically encoded when saving the document.

Some of the official extensions are implemented under ext.

To decode one of the supported extensions the only required action is to import the associated package, this way the extension will not be stored as json.RawMessage but as the type defined in the extension package:

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

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

It is not necessary to call gltf.RegisterExtension for built-in extensions, as these auto-register themselves when the package is initialized.

Custom extensions

To implement a custom extension encoding, provide a struct that can be encoded as a JSON object as dictated by the spec.

To implement a custom extension decoding, call gltf.RegisterExtension at least once before decoding, providing the identifier of the extension and a function that decodes the JSON bytes to the desired struct:

const ExtensionName = "FAKE_Extension"

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

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

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

🙋 Contributing

PRs, issues, and feedback from ninja gophers are very welcomed.

About The Project

This library is a complete implementation of glTF 2.0, and its explicit aim is to provide a production-ready, idiomatic and curated API to perform any kind of glTF manipulation.

It is out of the scope to implement convertes to/from other file formats and to provide mechanisms to create and manipulate 3D geometry.

The current API is still not frozen and can suffer minor changes until it reached v1.0.

Please use the issue tracker or the if you'd like to report problems or discuss features.

Owner
Quim Muntal
Eager traveler, passionate engineer and polyglot programmer.
Quim Muntal
Comments
  • how to use modeler.WriteIndices

    how to use modeler.WriteIndices

    Hi qmuntal , I'm pretty new to this low level of glTF editing , I spent two days trying to make find the right parameters for the modeler's writeIndices All i'm trying to do now is write a simple triangle :

    package main
    
    import (
        "github.com/qmuntal/gltf"
        "github.com/qmuntal/gltf/modeler"
    )
    
    func drawTriangle() {
        doc := gltf.NewDocument()
        
        positionAccessor := modeler.WritePosition(doc, [][3]float32{{0, 0, 0}, {0, 10, 0}, {0, 0, 10}})
        indicesAccessor := modeler.WriteIndices(doc, []uint8{   0, 1, 0, 
                                                                0, 0, 1, 
                                                                0, 0, 1 })
        colorIndices := modeler.WriteColor(doc, [][3]uint8{{50, 155, 255}, {0, 100, 200}, {255, 155, 50}})
        
        doc.Meshes = []*gltf.Mesh{{
            Name: "Pyramid",
            Primitives: []*gltf.Primitive{{
                Indices: gltf.Index(indicesAccessor),
                Attributes: map[string]uint32{
                  "POSITION": positionAccessor,
                  "COLOR_0":  colorIndices,
                },
            }},
        }}
        doc.Nodes = []*gltf.Node{{Name: "Root", Mesh: gltf.Index(0)}}
        doc.Scenes[0].Nodes = append(doc.Scenes[0].Nodes, 0)
        if err := gltf.SaveBinary(doc, "./example.glb"); err != nil {
            panic(err)
        }
    }
    func main (){
        drawTriangle()
    }
    

    I tried two of the examples in the repo (the second one on the md file and example_test.go ) but they didn't work . the pyramid one on the md file is working fine and this code is trying to replicate your example but i don't know the data that i'm supposed to pass as indices . i tried

    • Normals : passing 1,0,0,1,0,0,1,0,0

    Thanks for these contributions . Lots of respect for your code .

  • Ignore EOF when decoding small .gltf files

    Ignore EOF when decoding small .gltf files

    When decoding very small files (smaller than one read chunk?), the inner decoder might return io.EOF as error, alongside the document (i.e. the document read might be valid, but it's overwritten with nil in case of io.EOF)

  • Sampler's MagFilter, MinFilter, WrapS and WrapT won't be included in encoded JSON

    Sampler's MagFilter, MinFilter, WrapS and WrapT won't be included in encoded JSON

    When Sampler's MagFilter, MinFilter, WrapS and WrapT fields are zero values, they won't be included in encoded JSON document. These fields' types have MarshalJSON implemented, but IIUC omitempty is evaluated before MarshalJSON and the values are omitted (because they're zero), so the encoded JSON won't have them. I think that there are 2 options for resolving this issue.

    • A. Remove omitempty tag from MagFilter, MinFilter, WrapS and WrapT fields
    • B. Ignore first value by assigning to blank identifier before MagLinear, MinLinear, WrapRepeat

    What do you think? I'll try to make a PR after you decide.

  • API surface that doesn't require knowledge of glTF to use

    API surface that doesn't require knowledge of glTF to use

    I would like to see an API that lets someone supply a list of vertices, zero or more material definitions, and one or more lists of vertex indices to define triangles, each with an associated material reference, and have all the nuts and bolts of assembling the buffer, bufferviews, and accessors done automatically. Simply calling .Save on that type should allow the user to then persist this model to disk.

    I don't know if that is within the scope of this library, and I felt it needed suggesting, in case it is.

  • KhronosGroup/glTF-Validator reports errors on glb file

    KhronosGroup/glTF-Validator reports errors on glb file

    KhronosGroup/glTF-Validator reports errors on glb file(see attachment test.glb.gz) generated by this package:

    Error | Message | Pointer -- | -- | -- GLB_CHUNK_LENGTH_UNALIGNED | Length of 0x00000002 chunk is not aligned to 4-byte boundaries. |   GLB_CHUNK_TOO_BIG | Chunk (0x00000002) length (1179937895) does not fit total GLB length. |   GLB_UNEXPECTED_END_OF_CHUNK_DATA | Unexpected end of chunk data. |  

    Warning | Message | Pointer -- | -- | -- GLB_UNKNOWN_CHUNK_TYPE | Unknown GLB chunk type: 0x00000002.

  • Add high level API for reading GLTF

    Add high level API for reading GLTF

    This is similar to #15 except that that was regarding a way to create a GLTF document whereas I would like a way to read GTLF document without any knowledge of GLTF.

    The ideal API for me would take a document, a mesh index, and return vertices, normals, uvs, material indices, etc.

    You could call the package scanner, reader, inspector, etc.

  • Remove usage of unsafe when checking header size

    Remove usage of unsafe when checking header size

    This PR has the decoder use binary.Size in favour of int(unsafe.Sizeof(header)), since the binary package is either way used for the parsing of the header.

    This makes the implementation a bit more robust, since it should handle architectures that might align structures differently. Though in this particular case, due to the simplicity of the struct, it might actually be a bit more of a nit picking.

    In addition, it also fixes a small typo (I assumed too small for a separate PR).

    The only other place using the unsafe package that remains is the binary/unsafe.go file. I might be up to writing a separate PR in the future that reworks that to use the more manual approach, as used in binary/binary.go, if the owner of this repo is open to the idea of getting rid of unsafe.

  • Modeler.AddImage() seems to break buffer.ByteLength

    Modeler.AddImage() seems to break buffer.ByteLength

    I tried to embed the texture image into .glb using gltf.Modeler but generated a broken file.

    in modeler.go

    	buffer.ByteLength += uint32(len(buffer.Data))
    

    I think, = should be correct, not +=.

  • Referenced buffer been overwrite

    Referenced buffer been overwrite

    I am using your project to create gltf file. I have draco precompressed geometry data like a.drc, and I referenced it in a buffer in document. When I save the document, the a.drc file will be overwrite. It size becomes zero.

    This is how I define my buffer

    buffer := gltf.Buffer{
    		URI:       "a.drc",
    		ByteLength:  123,
    	}
    

    This is how I save the doc

    if err := gltf.Save(doc, path, false); err != nil {
    	panic(err)
    }
    
  • Cannot open .glb file after conversion from .gltf

    Cannot open .glb file after conversion from .gltf

    Getting EOF error when opening a .glb file converted from .gltf using gltf.SaveBinary().

    Sample input .gltf file: https://github.com/qmuntal/gltf/files/9627951/simple.gltf.txt

    Sample script:

    
    import (
    	"log"
    
    	"github.com/qmuntal/gltf"
    )
    
    func main() {
    	const simpleGltf = "simple.gltf"
    	const outputGlb = "simple.glb"
    
    	srcDoc, err := gltf.Open(simpleGltf)
    	if nil != err {
    		log.Fatal(err)
    	}
    	err = gltf.SaveBinary(srcDoc, outputGlb)
    	if nil != err {
    		log.Fatal(err)
    	}
    
    	tgtDoc, err2 := gltf.Open(outputGlb)
    	if nil != err2 {
    		log.Fatal(err2)
    	}
    	log.Println("SUCCESS!")
    	log.Printf("DEBUG: %#v", tgtDoc)
    }
    

    NB: error seen using latest version of gltf package (v.0.22.1), but not using an older version (v.0.18.3). The output files slightly differ, it looks like the old version of the package was adding some nil "padding" at the end.

  • Incorrect behaviour when loading files containing spaces

    Incorrect behaviour when loading files containing spaces

    Take a look at the glTF sample files called Box With Spaces

    This file has a uri reference to Box With Spaces.bin. But this library performs urlencode of the URI so spaces become %20 and then fails because it cannot find a file named Box%20With%20Spaces.bin

    The url-encoding happens because of a round-trip through net/url.Parse

    A solution would be to only use the parsed URL if the scheme is not empty. i.e. the URL is http/file/etc

  • How about a Hello World?

    How about a Hello World?

    Hi,

    I'm very new to Go and gltf. Is there any chance of you (or someone else) adding a "Hello World" example to display a rotating cube? The program would read a gltf file (e.g. Box.gltf) using gltf.Open and extract the necessary vertices to display a rotating cube using the simplest vertex and frag shaders.

    My main difficulty is going from opening a gltf.Document to displaying something. An example would be nice.

    Thanks Andre

Go package for decoding and encoding TARGA image format

tga tga is a Go package for decoding and encoding TARGA image format. It supports RLE and raw TARGA images with 8/15/16/24/32 bits per pixel, monochro

Sep 26, 2022
A library to read, write, and transform Stereolithography (.stl) files in Go.

stl A library to read, write, and transform Stereolithography (.stl) files in Go. It is used in the command line STL manipulation tool stltool. Featur

Sep 26, 2022
Decode embedded EXIF meta data from image files.

goexif Provides decoding of basic exif and tiff encoded data. Still in alpha - no guarantees. Suggestions and pull requests are welcome. Functionality

Dec 17, 2022
Process audio files with pipelined DSP framework
Process audio files with pipelined DSP framework

phono is a command for audio processing. It's build on top of pipelined DSP framework. Installation Prerequisites: lame to enable mp3 encoding To link

Oct 4, 2022
Merge multiple pprof profile files into a single file

pprof-merge Merges multiple pprof profile files into one. Installation $ go get github.com/rakyll/pprof-merge Usage $ pprof-merge profile1.data profi

Dec 15, 2022
Paprika is a toolbox for creating short clips from multiple png files.
Paprika is a toolbox for creating short clips from multiple png files.

Paprika Paprika is a toolbox for creating short clips from multiple png files. Requirements This program is mainly a wrapper around ffmpeg. As such, y

Feb 16, 2022
Converts Apple .heic files to .jpg

heic-to-jpg Converts Apple .heic files to .jpg Install this necessary tool: sudo apt-get install libheif-examples Put all your .heic files in the heic

Dec 30, 2021
geoserver is a Go library for manipulating a GeoServer instance via the GeoServer REST API.
geoserver is a Go library for manipulating a GeoServer instance via the GeoServer REST API.

Geoserver geoserver Is a Go Package For Manipulating a GeoServer Instance via the GeoServer REST API. How to install: go get -v gopkg.in/hishamkaram/g

Dec 22, 2022
General purpose library for reading, writing and working with OpenStreetMap data

osm This package is a general purpose library for reading, writing and working with OpenStreetMap data in Go (golang). It has the ability to read OSM

Dec 30, 2022
S2 geometry library in Go

Overview S2 is a library for spherical geometry that aims to have the same robustness, flexibility, and performance as the best planar geometry librar

Jan 8, 2023
Go package for fast high-level image processing powered by libvips C library

bimg Small Go package for fast high-level image processing using libvips via C bindings, providing a simple programmatic API. bimg was designed to be

Jan 2, 2023
Image processing library and rendering toolkit for Go.

blend Image processing library and rendering toolkit for Go. (WIP) Installation: This library is compatible with Go1. go get github.com/phrozen/blend

Nov 11, 2022
Go bindings to OpenGL Utility Library

GLU This package offers minimal bindings for GLU functions. Usage go get github.com/go-gl-legacy/glu License Copyright 2012 The go-gl Authors. All ri

Aug 18, 2018
Go binding for the cairo graphics library

go-cairo Go binding for the cairo graphics library Based on Dethe Elza's version https://bitbucket.org/dethe/gocairo but significantly extended and up

Dec 19, 2022
A library for playing with colors in go (golang).
A library for playing with colors in go (golang).

go-colorful A library for playing with colors in Go. Supports Go 1.13 onwards. Why? I love games. I make games. I love detail and I get lost in detail

Dec 30, 2022
A lightning fast image processing and resizing library for Go

govips A lightning fast image processing and resizing library for Go This package wraps the core functionality of libvips image processing library by

Jan 8, 2023
This is old and unmaintained code, ignore it. starfish is a simple, SDL based, 2D graphics and user input library for Go. If you intend to work on it, please fork from the 'devel' branch, not 'master'. Current release: 0.12.0

What is starfish? What starfish is: starfish is a simple 2D graphics and user input library for Go built on SDL. What starfish is not: While it is bui

Jun 4, 2019
Go Language Library for SVG generation
Go Language Library for SVG generation

SVGo: A Go library for SVG generation The library generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (http://www.w3.org/TR/SVG

Jan 6, 2023
Twilio Go library

twilio-go A client for accessing the Twilio API with several nice features: Easy-to-use helpers for purchasing phone numbers and sending MMS messages

Nov 30, 2022