+3 votes

Hello,

I spent a lot of time with finding best way but i still don't know how to get all of these:

  • i need to import assets(only meshes) from blender to godot and automatically assign my godot materials to these assets (or manual set materials ONCE)

  • i need to set up additional node trees (new scenes) with these assets to make it usable like a prefabs

  • i always need to update assets in blender (its geometry) without loose material assignments and without damage a scenes with this assets

Every trick with better collada exporter or godot-blender-exporter (escn files) and a few import settings in godot don't works good.

I have experience in Unity, and there is clear workflow:

1) make a model i Blender, export it as FBX
2) import that model to Unity
3) create own material in Unity
4) place imported model to scene and manually set own material to it
5) add everything that i need to work with this model
5) create w prefab

after this every time i need to change mesh it don't destroy my prefab and material assigment

Can anyboady help me with that? What workflow and import/ export settings/ formats will works like my Unity example workflow?

asked Apr 14 in Engine by beetbeet (28 points)

3 Answers

0 votes

"asset workflow hell", you describe it very well unfortunately.

Here is my workflow:

Blender 2.8 BETA

  • Export object in Blender with the default collada exporter (better exporter don't work in Blender 2.8)
  • In Godot, go to "Import" tab, choose a preset "with separate objets", press "reimport" so that Godot creates the "file.mesh"
  • In Godot, double-click on the file.dae, new inherited to create a new scene.
  • Save as object_name.tscn.
  • Right-click the scene root, break inheritance so that it removes the links with the ".dae" file
  • Click on the mesh, load the "file.mesh" that have been generated, so that the scene is using the external file
  • Save
  • Same thing for materials and animations (I can only export one animation per Collada file apparently).
  • For the animations, you need to load them individually into the "AnimationPlayer" node.

If you need to update any component, do this process again with the new .dae export, go into the component you want to update (mesh, animation, material), load the new file.

(I'm sure I could improve the workflow, I'm pretty new at this).

answered Apr 15 by Joel_127 (189 points)
edited Apr 15 by Joel_127

hello, thank you so much, i think it is a solution :) but it is sad that in godot there is no option to have "changing and instancing no problem" imported 3d data like in unity or unreal :/ (except obj)

0 votes

I use better collada exporter (I still use Blender 2.79).

First: My game runs in GLES2 and is also targeted on mobile devices. So maybe my requirements are very different from yours.

When I create models in Blender, I try to use material names which I also want to keep in Godot that way. So I also reuse materials from other models. And try to avoid using the same names for different materials accross different models. (Avoid "Material.001")

I also normally directly use the textures from the godot project directory. (At least same name / path structure may be advisable.)

I export the models with "Triangulate", "apply modifiers", uncheck "copy images" (they're already there).

Then I created an import script for import on godot. (You can put that in the import settings)
On Materials I usually select storage "Mesh" and "Built in". Storage also to "Single scene".

Compression is usually enabled. But for bigger meshes (roughly >50m) I need to disable it when accuracy is needed. (I.e. when terrain tiles need to match.)

The import script has some tool functions which I apply to imported models. This has meanwhile grown to quite a big number. Such a script gets passed the imported scene and you can use gdscript to do many things each time an import takes place.

One of these functions scans the materials.
- if they contain textures then I scan the specific res:// path where I put such materials (recursively) for fitting a material name. If that doesn't succeed then I check if I find the texture file (from the resource path in the material or the surface name) and assign the correct texture path to the material.
- Certain material names are matched against known shader names (terrain, water ...) and replaced.
- If the materials are albedo only, I search for albedo materials with that surface name in my res:// path and assign them. If nothing is found, I print out warning and keep the materials (I didn't manage to save new materials from such an import script so I manually save new materials after import once when required.)

I also do a number of other processing tasks in the script. Naturally, those depend on the type of game/app.

This includes:
- Assigning of physics attributes / joints (sometimes depending on markers in the object names)
- Auto-Creating/Assigning collision shapes for certain surface names.
- Auto recreating/adjusting UVs for certain surface names.
- Auto removing "cast shadow" for certain object/surface names.
- Auto creating "shadow only" meshes for certain surfaces.
I usually set cast shadow to off for bigger terrains. Where shadow is useful (i.e. vertical walls) but the part is not a separate mesh but a part/material of a bigger mesh I create a shadow only copy of that surface. This probably is a very specific use case but has considerably helped with performance for me (especially on mobile devices and low performing PCs).

answered Apr 15 by wombatstampede (2,013 points)

could you show me example code of import script for e.g:
" Auto-Creating/Assigning collision shapes for certain surface names." ?

This script is only for a specific use case. Those collision shapes are for vertical "walls" where the lower edge is horizontals. The material has "-collide" in its name. Also there'll have to be objects with "-preset" in their names to set values like collision mask and layers.

Example (generated collision shapes are bright blue lines in marked areas):
enter image description here

This is very far from general purpose but perhaps you find some inspiration in it.

I stripped down the code did not test that:

tool
extends EditorScenePostImport

var mdt
var preset={}
var presetMatch
const MATERIAL_BASE = "res://resources/materials/"

func post_import(_scene):
    mdt = MeshDataTool.new()
    presetMatch = RegEx.new()
    presetMatch.compile("-(\\w+)=(-?[0-9.]+)")

    print("** getCollisionNode")
    getCollisionNode(_scene,_scene)
    print ("*** done ***")
    return _scene

func getCollisionNode(node,scene):
    var delList = {}
    for N in node.get_children():
        if N.get_child_count() > 0:
            getCollisionNode(N,scene)
        if N is CollisionObject:
            setBodyPresets(N)

        # build collision shapes "box" for "walls" when material contains "-collide" flag in name
        # * mesh has to consist of tris (check on blender collada export)
        # * "wall" surface "quad" consists of 2 tris
        # * "bottom" tri is required to have 2pts of equal height (y-value)

        if (N is MeshInstance): 
            var has_collide = false
            var m = N.get_mesh()
            for s in range(0,m.get_surface_count()):
                if (m.surface_get_name(s).find("-collide")>=0):
                    if m.surface_get_primitive_type(s) == Mesh.PRIMITIVE_TRIANGLES:
                        has_collide=true
                    else:
                        print("Error: wrong surface type "+String(m.surface_get_primitive_type(s))+
                                " for "+m.surface_get_name(s)+". Required: PRIMITIVE_TRIANGLES")

            if has_collide: #collide material found
                var cscount=0

                var sbs={}

                for s in range(0,m.get_surface_count()):
                    if (m.surface_get_name(s).find("-collide")>=0): 
                        var newname=m.surface_get_name(s).replace("-collide","")

                        mdt.create_from_surface(m, s)
                        for f in range(mdt.get_face_count()):
                            var v=[]
                            var cwid = 0.5 #width of collision shape (away from normal)
                            for vc in range(3):
                                v.append(mdt.get_vertex(mdt.get_face_vertex(f,vc)))
                            var minY=min(v[0].y,min(v[1].y,v[2].y))
                            var maxY=max(v[0].y,max(v[1].y,v[2].y))
                            var vxz=[]
                            for vc in range(3): #make array of 2 verts at minY
                                if abs(v[vc].y-minY)<0.01:
                                    vxz.append(v[vc])
                            if vxz.size()==2: #not "upper rectangle" (which has only 1 vert at minY)
                                var norm=mdt.get_face_normal(f)
                                #y-values are the same so this is length in xz plane
                                var xzVec=vxz[1]-vxz[0]
                                var lenxz=xzVec.length()
                                var midxzOffs=xzVec/2
                                var cshape = CollisionShape.new()
                                var bshape = BoxShape.new()
                                #node.add_shape(bshape,N.get_transform())
                                cscount=cscount+1
                                cshape.set_name(newname+"_"+String(cscount))
                                cshape.set_shape(bshape)

                                var sb = GetCollStaticBodyXY(scene,N,sbs,vxz[0].x+midxzOffs.x-(norm.x*(cwid/2)),\
                                                                vxz[0].z+midxzOffs.z-(norm.z*(cwid/2)))

                                sb.add_child(cshape)

                                #you need to set_owner to scene root to make a node visible in editor
                                cshape.set_owner(scene) 
                                bshape.set_extents(Vector3(cwid/2,(maxY-minY)/2,lenxz/2))
                                #center of new shape
                                cshape.set_translation(Vector3(vxz[0].x+midxzOffs.x-(norm.x*(cwid/2)),
                                        minY+((maxY-minY)/2),vxz[0].z+midxzOffs.z-(norm.z*(cwid/2))))

                                cshape.rotate_y(Vector2(xzVec.x,xzVec.z).angle_to(Vector2(0,-1)))

                        mdt.clear()

        # Create collision shape for a box-object inside a -colonly (StaticBody) or -rigid (RigidBody) parent
        # * object name contains "-colbox"
        # * client object rotation is not transferred (if any)
        # * shape has extents and position of box
        # * removes automatically created shapes ("shape","SphereShape","BoxShape")
        if N is MeshInstance and (N.name.find("-colbox")>=0): 
            print(node.name+" ->"+N.name)
            var newname=N.name.replace("-colbox","")

            if node is CollisionObject: #parent can deal with collision shapes
                var aab=N.get_aabb()
                var cshape=null
                var bshape=null
                #reuse/update shape if already exists, does this ever happen?
                if node.has_node(newname) and (N is CollisionShape):
                    cshape = node.get_child(newname)
                    bshape = cshape.get_shape()
                    print(" update existing")
                else:
                    cshape = CollisionShape.new()
                    bshape = BoxShape.new()
                    cshape.set_name(newname)
                    cshape.set_shape(bshape)
                    node.add_child(cshape)
                    #you need to set_owner to scene root to make a node visible in editor
                    node.get_node(newname).set_owner(scene) 
                    #print(" add new shape "+node.get_parent().get_name()+'-'+node.get_name()+"-"+newname+":"+String(node.has_node(newname)))
                bshape.set_extents(aab.size/2)
                #no rotation transferred
                cshape.set_translation(N.get_translation()+((aab.position+(aab.size/2))))
                node.remove_child(N) #maybe not required when free/queue_free is used
                N.queue_free()

                #remove shapes which are auto-added to rigidbodies(-rigid)/staticbodies (-colonly)
                #they're replaced by colbox shapes
                delList["shape"]="CollisionShape"
                delList["SphereShape"]="CollisionShape"
                delList["BoxShape"]="CollisionShape"

        #--------------- presets ----------------------------------
        #Objects with -preset in name allow to initialize selected presets (i.e. for RigidBody/StaticBody)
        #Example name: "Cube-preset-mask=1024-layers=3-mass=1000"
        if (N.name.findn("-preset")>=0): #preset values, ((N.get_type()=="MeshInstance") and )
            var matchStart=0
            var prevMatchStart=0
            var pmatch=presetMatch.search(N.name,matchStart)
            while pmatch!=null:
                matchStart=pmatch.get_end() #+1
                print("preset: "+pmatch.get_string(1)+"<=>"+pmatch.get_string(2)) #+" prevMatchstart: "+str(prevMatchStart)+" matchstart: "+str(matchStart))
                if prevMatchStart==matchStart:#check for endless loop
                    print("error in regexpmatch endless loop")
                    break
                else:
                    prevMatchStart=matchStart

                preset[pmatch.get_string(1)]=pmatch.get_string(2)
                delList[N.get_name()]=null
                pmatch=presetMatch.search(N.name,matchStart)

    #delete child nodes after iteration (to not confuse object iteration)
    for i in delList:
        delchild(node,i,delList[i])
+4 votes

I've gone though export bedevilment myself and I understand the horror you speak of. Fortunately, this just got WAY easier. Firstly, Collada and Better Collada are not the way--so stop using them, and don't look back.

In Blender 2.8, instead of using Collada, use "glTF 2" as your exporter.

First, select the objects you want to export form blender. Then File | Export | glTF 2
In the exporter, make sure you switch it from Binary to Embedded. Embedded is your friend.

Now in the exporter, check the box to only "export selected" (otherwise you'll export everything, though that could be what you want) AND turn on "apply modifiers". The other defaults are typically what you want. (Also, you should apply your scales and rotations first. And I HIGHLY suggest the origins of your mesh(es) and the origins of their controlling armatures are the exact same place in blender space.)

Export it to a folder in your Godot project you've made for just such a purpose (make something like "blenderImports", in your Godot project folder). Click the Export GLTF2.0 button at the top right. Sometimes this export take a few moments.

Now in Godot, open your new "scene", a .gltf file. Open it, click "open anyway" then save that scene as a native Godot scene in the .tscn format. Then forget about the .gltf file and work on the .tscn file from that point on.

Then, behold the glory: armatures come over, meshes come over, materials come over, Animations/actions come over (just make sure you use the little "push down" button in the NLA editor), it understands the blender Principled BSDF shader. (https://docs.blender.org/manual/en/latest/addons/io_scene_gltf2.html for documentation on what works in the Principled shader, normals come over.... angels sing!

Enjoy that everything just works. You're welcome.

answered Aug 15 by Brinux (287 points)
edited Aug 15 by Brinux

This is the best way now for Blender 2.8 to Godot 3.1. And yes..its just "works". Way better than Collada

Thanks. But its not working with shape keys. (wave modifier for example)
The wave looks like an explosion.
enter image description here

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.