qmuntal / gltf

Go library for encoding glTF 2.0 files
https://www.khronos.org/gltf/
BSD 2-Clause "Simplified" License
241 stars 32 forks source link

Is it possible to use this to export a gltf/glb model to an image? #79

Closed chrisbward closed 4 months ago

chrisbward commented 4 months ago

Hi,

Repo looks cool, I'm just getting in to 3D modelling - so please forgive me if I'm asking in the wrong place.

Is it possible to load in a model and save out a render as a jpeg/png using this library?

Cheers

Chris

qmuntal commented 4 months ago

Possible it is, but not implemented in here 😸. You will have to load the model using this library and then implement some custom logic to convert it to a 2d image.

chrisbward commented 4 months ago

thanks for the quick reply - I had a little fight with chatgippity, of which I had to debug for a while, and ended up with something that might be close?


func (gltfc *GLTFController) RenderToImageAttemptOne() {
    // Load the glTF file
    doc, err := gltf.Open("./helloworld.gltf")
    if err != nil {
        log.Fatal(err)
    }

    // Create a context for rendering
    const width = 1024
    const height = 768
    dc := gg.NewContext(width, height)

    // Loop through each scene
    for _, scene := range doc.Scenes {
        // Loop through each node in the scene
        for _, nodeIndex := range scene.Nodes {
            // Get the node
            node := doc.Nodes[nodeIndex]

            // If the node has mesh, render it
            if node.Mesh != nil {
                renderMesh(dc, *doc.Meshes[*node.Mesh], doc)
            }
        }
    }

    // Save the rendered image
    if err := dc.SavePNG("output.png"); err != nil {
        log.Fatal(err)
    }

    fmt.Println("Image rendered successfully!")
}

func renderMesh(dc *gg.Context, mesh gltf.Mesh, doc *gltf.Document) {
    logrus.Infoln("rendering the mesh")
    // Loop through each primitive in the mesh
    for _, primitive := range mesh.Primitives {
        logrus.Infoln(primitive)
        // Get the accessor for the position attribute
        // positionAccessor := primitive.GetAttribute("POSITION")
        // if positionAccessor == nil {
        //  continue
        // }
        positionIndex, found := primitive.Attributes["POSITION"]
        if !found {
            logrus.Infoln("gltfController - positionindex not found")
            continue
        }
        // Get the accessor for the position attribute
        positionAccessor := doc.Accessors[positionIndex]

        // Get the buffer view for the accessor
        bufferView := doc.BufferViews[*positionAccessor.BufferView]

        // Get the buffer data
        bufferData := make([]byte, bufferView.ByteLength)
        copy(bufferData, doc.Buffers[bufferView.Buffer].Data[bufferView.ByteOffset:])

        logrus.Infoln("buffer is", bufferData)

        // Assuming position attribute is of type "VEC3"
        positions := make([][3]float32, positionAccessor.Count)
        for i := 0; i < int(positionAccessor.Count); i++ {
            offset := int(positionAccessor.ByteOffset) + i*12 // Assuming VEC3, each component is 4 bytes
            logrus.Infoln("offset is ", offset)
            positions[i][0] = float32(bufferData[offset])
            positions[i][1] = float32(bufferData[offset+4])
            positions[i][2] = float32(bufferData[offset+8])
        }
        logrus.Infoln("positions from buffer", positions, positionAccessor.Count)
        // Render each triangle in the primitive
        // Get the indices of the primitive
        logrus.Infoln("Get the indices of the primitive")
        var indices []uint32
        if primitive.Indices != nil {
            accessor := doc.Accessors[*primitive.Indices]
            bufferView := doc.BufferViews[*accessor.BufferView]
            bufferData := doc.Buffers[bufferView.Buffer].Data

            logrus.Infoln("accessor.ComponentType is ", accessor.ComponentType)
            switch accessor.ComponentType {
            case gltf.ComponentUbyte: // gltf.ComponentTypeUnsignedByte
                logrus.Info("gltf.ComponentUbyte")
                for i := 0; i < len(bufferData); i++ {
                    indices = append(indices, uint32(bufferData[i]))
                }
            case gltf.ComponentUshort: // gltf.ComponentTypeUnsignedShort
                logrus.Info("gltf.ComponentUshort")
                for i := 0; i < len(bufferData)/2; i++ {
                    indices = append(indices, uint32(bufferData[i*2])|uint32(bufferData[i*2+1])<<8)
                }
            case gltf.ComponentUint: // gltf.ComponentTypeUnsignedInt
                logrus.Info("gltf.ComponentUint")
                for i := 0; i < len(bufferData)/4; i++ {
                    indices = append(indices, uint32(bufferData[i*4])|uint32(bufferData[i*4+1])<<8|uint32(bufferData[i*4+2])<<16|uint32(bufferData[i*4+3])<<24)
                }
            }
        } else {
            logrus.Infoln("primative.Indices was nil")
            // If indices are not specified, generate them assuming triangle fan topology
            indices = make([]uint32, len(positions))
            for i := 0; i < len(indices); i++ {
                indices[i] = uint32(i)
            }
        }
        // Render each triangle in the primitive
        logrus.Infoln("Render each triangle in the primitive")
        for i := 0; i < len(indices); i += 3 {
            logrus.Infoln("rendering triangle")
            // Get the indices of the vertices of the triangle
            i1, i2, i3 := indices[i], indices[i+1], indices[i+2]
            logrus.Infoln("indices are", i1, i2, i3)
            // Get the positions of the vertices
            p1, p2, p3 := positions[i1], positions[i2], positions[i3]
            logrus.Infoln("positions are", p1, p2, p3)

            // Render the triangle (you can use any rendering technique here)
            dc.Push()
            dc.MoveTo(float64(p1[0]), float64(p1[1]))
            dc.LineTo(float64(p2[0]), float64(p2[1]))
            dc.LineTo(float64(p3[0]), float64(p3[1]))
            dc.ClosePath()
            dc.SetRGB(1, 0, 0) // Set color to red (for example)
            dc.Fill()
            dc.Pop()
        }

    }
}

it's probably trash, but due to my lack of knowledge of this space I couldn't figure how to draw the points - the positions are outside of the range so it errors.

qmuntal commented 4 months ago

It should be something along those lines. At least your are missing to factor in the gltf.Node transforms (matrix, rotation, scale, translation). Also, you can use the github.com/qmuntal/gltf/modeler package, which facilitates reading data from buffers.

chrisbward commented 4 months ago

Sadly, I have to admit this entire domain is beyond my knowledge and experience - I will likely do this on the frontend, leveraging Threejs in an upload-asset workflow.

Thank you so much regardless!

chrisbward commented 4 months ago

Closing this off, thank you