Hi godotforum! I'm generating Map in one Mesh with SurfaceTool. It works perfectly, but can i apply more than one Material on it? My Map is bunch of planes that represents cube-like world. Each side should have different material. I know how to do it if i do separate mesh on every plane - but my goal is to try it implement on one mesh. Is it possible?
SurfaceTool multimaterial
I don't see anything right off after looking at the API, other than potentially the set_material
function? I don't think it would work though, unfortunately.
You could maybe commit multiple meshes, each with a different material, to an ArrayMesh using commit(array_mesh)
, where each material is done in a separate commit, but I'm not sure if that would be any different than just using multiple meshes.
Multiple materials on the same mesh would still produce multiple draw calls, depending on your case it might be more performant to have a single more complex shader with multiple 'materials' implemented within it and assign/mask them based on some vertex color values(for an example) so as to avoid the multiple draw calls.
Thank you all. Got idea to make it by using one large texture and change UV offset of texture in planes. But... I think it is not worth enough, cause of future updates of tile-types and trying to scale UV in offset of 0.001s...
For now - i draw every plane as new mesh once to static geometry. It can handle so many objects. But when i did it by only one mesh - object, it was more faster and got worked by thousands if planes without fps drops.
Maybe do it like: All top planes on One mesh, all LeftSides on other and etc. But, i think it will create same untextured problems.
P.S. Also i found this in Godot Q&A:
bojidar-bg told me that simply using set_material() on SurfaceTool would do the trick:
var st = SurfaceTool.new()
st.set_material(mat1)
# add vertices...
var mesh = st.commit()
st.set_material(mat2)
# add other vertices...
st.commit(mesh) # This appends a new surface to the mesh
In my case I even use two SurfaceTools so my generator can create the mesh in one pass.That does the trick :)
But i can't figure it out logic, and is it possible to use it on many planes created by For-loop?
@hutukawa said: Thank you all. Got idea to make it by using one large texture and change UV offset of texture in planes. But... I think it is not worth enough, cause of future updates of tile-types and trying to scale UV in offset of 0.001s...
There is that downside of using a single, large texture, as it does impose some limits on how many textures you can have. That said though, I think using a single texture with UV offsets is the most performant way of doing it from a pure GPU perspective, since everything can be drawn in a single draw call.
If you want to go down this route again for any reason and are looking for a reference, I've written a voxel tutorial that may be helpful. It's a few years old and is a touch slow, but it might be useful as a reference if you get stuck on the UV part.
For now - i draw every plane as new mesh once to static geometry. It can handle so many objects. But when i did it by only one mesh - object, it was more faster and got worked by thousands if planes without fps drops.
Yeah, having a single plane as a separate mesh would be costly since it has to send all the plane data to the GPU one at a time.
However, you might be able to get a middle ground by using a separate Mesh per material, so all planes that use the same material are in the same mesh. This is kinda in between using separate meshes per plane and only a single mesh. You get the benefits of sending large amounts of data to the GPU at once (so faster than individual planes) but you also get the benefit of material's being separate.
Maybe do it like: All top planes on One mesh, all LeftSides on other and etc. But, i think it will create same untextured problems.
P.S. Also i found this in Godot Q&A: But i can't figure it out logic, and is it possible to use it on many planes created by For-loop?
You'd probably need to store the data for each different plane material separately and then commit each surface one at a time. Something like this, I think (just an example):
# Lists to hold the data needed to pass to the Surface tool. We want a list per material, per data that will be sent to the SurfaceTool (one for vertices, UV, etc).
var dirt_mat_vertices = []
var sand_mat_vertices = []
# etc
# Get the data to pass to the surface tool
# (Just an example)
for plane in world_planes:
if (plane.material == dirt):
dirt_mat_vertices.append(plane.vert_one)
dirt_mat_vertices.append(plane.vert_two)
dirt_mat_vertices.append(plane.vert_three)
dirt_mat_vertices.append(plane.vert_four)
elif (plane.material == sand):
sand_mat_vertices.append(plane.vert_one)
sand_mat_vertices.append(plane.vert_two)
sand_mat_vertices.append(plane.vert_three)
sand_mat_vertices.append(plane.vert_four)
# The key is instead of calling something like SurfaceTool.add_vertex you instead want to do cache that data per-material in a list
# Now with all the data cached, we can make the mesh
var surface_tool = SurfaceTool.new()
# setup the surface tool as needed
var mesh = null
# Process dirt
surface_tool.set_material(dirt_mat)
for i in range(0, dirt_mat_vertices):
surface_tool.add_vertex(dirt_mat_vertices[i])
mesh = surface_tool.commit()
# Process sand
surface_tool.set_material(sand_mat)
for i in range(0, sand_mat_vertices):
surface_tool.add_vertex(sand_mat_vertices[i])
mesh = surface_tool.commit(mesh)
I think something like that would work, but I have not tried it.
@TwistedTwigleg Thank you! I tried to make mesh for side plane and minimized all my Map structure to 6 objects. For now it works perfectly! But i have question about how culling in Godot works.
Hypothetically, if i have 12,000 planes as separete Mesh models with Collision (objects) vs 1 object that have same 12,000 planes but in one Mesh with Collision, what will cull better?
Is one mesh in Godot is not culling like 12,000 objects? Or culling works the same?
Culling is done on a per-MeshInstance basis, but beware of draw calls. 12,000 drawcalls is definitely excessive, even on desktop platforms. You typically want to group meshes by small clusters using MultiMeshInstances to reduce the number of draw calls while still getting effective culling. Don't group everything under a single MultiMeshInstance unless you can be reasonably certain that it will always be fully within the camera's frustum.
See the Optimization tutorials in the documentation for more information.
@hutukawa said: @TwistedTwigleg Thank you! I tried to make mesh for side plane and minimized all my Map structure to 6 objects. For now it works perfectly!
Great, I'm glad its working :smile:
But i have question about how culling in Godot works.
Hypothetically, if i have 12,000 planes as separete Mesh models with Collision (objects) vs 1 object that have same 12,000 planes but in one Mesh with Collision, what will cull better?
Is one mesh in Godot is not culling like 12,000 objects? Or culling works the same?
As Calinou mentioned, culling is done on a per-MeshInstance node basis, so a single MeshInstance node will either be culled all at once, or not culled at all.
In other words, if you have a single mesh that has all the tiles, then either the entire thing will be processed (not culled) or the entire thing will be hidden and not processed (culled). If just a little bit of it is visible, then the entire thing will be drawn since it's all in one mesh.
By comparison, 12,000 planes would be culled individually based on their visibility. If some of it is visible, because they are separate non-visible planes will be culled. However, each plane would contribute to its own draw call in this case, since each is separate, and that would be expensive from a performance perspective.
If you are worried about culling and performance, what you may want to look at doing is breaking your world into chunks, and then only drawing the planes in a single chunk at a time. Most voxel games do this, where each chunk is a mesh and the world is composed of many chunks. This hits a middle ground and is kinda the best of both worlds, as you draw each chunk at a time rather than individual planes, but you do not need to draw the entire world if just a portion of it is visible.
@TwistedTwigleg @Calinou Thank you! I might think to optimize it with multimesh Chunks in future if it needed. For now i used not more than 4000 planes on Map and there is no performance problems. Thank you all.
For multi-material i used this method: I SurfaceTool.index(), applied material and start draw new primitive in one mesh and it's works. I will add code soon, if someone needed this.