Mesh Component

Exciting features and exciting times!

Who will be the first to make a voxel like world?

8 Likes

Epic Component! :defold::hugs:

7 Likes

Finally some long awaited 3D features!

6 Likes

Awesome work guys! Defold is just getting better and better.

3 Likes

Should every mesh cause a drawcall? I tried to optimize it but it seems like they always do?

Edit: Nevermind, material had local instead of world.

2 Likes

What’s wrong with this? Trying to create a dynamic cube from a triangle strip primitive. Sometimes the cube shows up right, other times vertices seem like they are out into infinity.

/gen/custom.script

local function fill_positions_strip(self, verts)

    for key, value in ipairs(verts) do
        self.positions[key] = verts[key]
    end

end

function init(self)
    
    self.res = go.get("#mesh", "vertices")
    print(self.res)
    
    self.buffer = resource.get_buffer(self.res)
    print(self.buffer)
    
    self.positions = buffer.get_stream(self.buffer, "position")
    print(self.positions)

    -- if the buffer has some data you could change these values here
    -- self.positions[1] = self.positions[1] + dt

    -- create a new buffer, since the one in the resource doesn't have enough size
    self.new_buffer = buffer.create(3 * 14, {
        { name = hash("position"),
         type=buffer.VALUE_TYPE_FLOAT32,
         count = 3 }
    })

    -- get the position stream
    self.positions = buffer.get_stream(self.new_buffer, "position")


    local verts2 = {
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 1, 0,
        1, 1, 1,
        0, 1, 0,
        0, 1, 1,
        0, 0, 1,
        1, 1, 1,
        1, 0, 1,
        1, 0, 0,
        0, 0, 1,
        0, 0, 0,
        0, 1, 0
    }


    fill_positions_strip(self, verts2)

    resource.set_buffer(self.res, self.new_buffer)

end

function update(self, dt)

end

MeshTest.zip (241.6 KB)

2 Likes

Since each stream specifies its component count, we specify the buffer size in elements.
The problem here is that you allocate a buffer with 3 * 14 elements.
And, if you don’t fill up the whole buffer, you’ll get garbage data, which may or may not show up in the renderer.

Here’s a fixed version.

local function fill_stream(stream, verts)
    for key, value in ipairs(verts) do
        stream[key] = verts[key]
    end
end

function init(self)
    self.res = go.get("#mesh", "vertices")
    
    self.buffer = resource.get_buffer(self.res)

    local verts2 = {
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 1, 0,
        1, 1, 1,
        0, 1, 0,
        0, 1, 1,
        0, 0, 1,
        1, 1, 1,
        1, 0, 1,
        1, 0, 0,
        0, 0, 1,
        0, 0, 0,
        0, 1, 0
    }

    -- create a new buffer, since the one in the resource doesn't have enough size
    self.new_buffer = buffer.create(#verts2 / 3, {
        { name = hash("position"), type=buffer.VALUE_TYPE_FLOAT32, count = 3 }
    })
    
    -- get the position stream
    self.positions = buffer.get_stream(self.new_buffer, "position")

    fill_stream(self.positions, verts2)

    resource.set_buffer(self.res, self.new_buffer)
end
8 Likes

Thank you! :innocent:

How would the same example be extended to have the normals, uvs, and colors?

I mean the part with buffer.create and then resource.set_buffer ?

local function fill_buffers_strip(self, verts)

    for key, value in ipairs(verts) do
        self.positions[key] = verts[key]
        self.normals[key] = verts[key]
    end

end

function init(self)
    
    self.res = go.get("#mesh", "vertices")

    -- unit square
    local verts2 = {
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 1, 0,
        1, 1, 1,
        0, 1, 0,
        0, 1, 1,
        0, 0, 1,
        1, 1, 1,
        1, 0, 1,
        1, 0, 0,
        0, 0, 1,
        0, 0, 0,
        0, 1, 0
    }

    
    self.buffer_position = buffer.create(#verts2 / 3, {
        { name = hash("position"),
         type=buffer.VALUE_TYPE_FLOAT32,
        count = 3 }
    })
    self.buffer_normal = buffer.create(#verts2 / 3, {
        { name = hash("normal"),
        type=buffer.VALUE_TYPE_FLOAT32,
        count = 3 }
    })
    self.buffer_texcoord0 = buffer.create(#verts2 / 3, {
        { name = hash("texcoord0"),
        type=buffer.VALUE_TYPE_FLOAT32,
        count = 2 }
    })
    self.buffer_color0 = buffer.create(#verts2 / 3, {
        { name = hash("color0"),
        type=buffer.VALUE_TYPE_FLOAT32,
        count = 4 }
    })    

    -- get the position stream
    self.positions = buffer.get_stream(self.buffer_position, "position")
    self.normals = buffer.get_stream(self.buffer_normal, "normal")


    fill_buffers_strip(self, verts2)

    resource.set_buffer(self.res, self.buffer_position)
    resource.set_buffer(self.res, self.buffer_normal)

end

function update(self, dt)

end

This is erroring with the below (referring to resource.set_buffer(self.res, self.buffer_normal)), what am I doing wrong?

ERROR:SCRIPT: /gen/custom.script:62: Could not copy data from buffer (9).
stack traceback:
	[C]: in function 'set_buffer'
	/gen/custom.script:62: in function </gen/custom.script:10>

Beyond that then we need a way to generate normals for custom meshes… or multiple ways with the different primitive types? There are for sure existing solutions out there…

1 Like

Each buffer can have multiple streams:

    self.new_buffer = buffer.create(num_vertices, {
        { name = hash("position"), type=buffer.VALUE_TYPE_FLOAT32, count = 3 },
        { name = hash("normal"), type=buffer.VALUE_TYPE_FLOAT32, count = 3 },
        { name = hash("texcoord0"), type=buffer.VALUE_TYPE_FLOAT32, count = 2 },
        { name = hash("color0"), type=buffer.VALUE_TYPE_FLOAT32, count = 4 }
    })

Remember to match this format with the format of the original (empty) buffer:

empty.buffer:

[
    {
        "name": "position",
        "type": "float32",
        "count": 3,
        "data": []
    },
    {
        "name": "normal",
        "type": "float32",
        "count": 3,
        "data": []
    },
    {
        "name": "texcoord0",
        "type": "float32",
        "count": 2,
        "data": []
    },
    {
        "name": "color0",
        "type": "float32",
        "count": 4,
        "data": []
    }
]

Here’s the patched code:

local function fill_stream(stream, verts)
    for key, value in ipairs(verts) do
        stream[key] = verts[key]
    end
end

function init(self)
    self.res = go.get("#mesh", "vertices")
    
    self.buffer = resource.get_buffer(self.res)

    local position = {
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 1, 0,
        1, 1, 1,
        0, 1, 0,
        0, 1, 1,
        0, 0, 1,
        1, 1, 1,
        1, 0, 1,
        1, 0, 0,
        0, 0, 1,
        0, 0, 0,
        0, 1, 0
    }

    local normal = {
        0, 0, 0,
        0, 1, 0,
        1, 0, 0,
        1, 1, 0,
        1, 1, 1,
        0, 1, 0,
        0, 1, 1,
        0, 0, 1,
        1, 1, 1,
        1, 0, 1,
        1, 0, 0,
        0, 0, 1,
        0, 0, 0,
        0, 1, 0
    }


    local texcoord0 = {
        0, 0,
        0, 1,
        1, 0,
        1, 1,
        1, 1,
        0, 1,
        0, 1,
        0, 0,
        1, 1,
        1, 0,
        1, 0,
        0, 0,
        0, 0,
        0, 1,
    }

    local color0 = {
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
        1,1,1,1,
    }

    local num_vertices = #position / 3

    -- create a new buffer, since the one in the resource doesn't have enough size
    self.new_buffer = buffer.create(num_vertices, {
        { name = hash("position"), type=buffer.VALUE_TYPE_FLOAT32, count = 3 },
        { name = hash("normal"), type=buffer.VALUE_TYPE_FLOAT32, count = 3 },
        { name = hash("texcoord0"), type=buffer.VALUE_TYPE_FLOAT32, count = 2 },
        { name = hash("color0"), type=buffer.VALUE_TYPE_FLOAT32, count = 4 }
    })
    
    -- get the position stream
    local stream_position = buffer.get_stream(self.new_buffer, "position")
    local stream_normal = buffer.get_stream(self.new_buffer, "normal")
    local stream_texcoord0 = buffer.get_stream(self.new_buffer, "texcoord0")
    local stream_color0 = buffer.get_stream(self.new_buffer, "color0")

    fill_stream(stream_position, position)
    fill_stream(stream_normal, normal)
    fill_stream(stream_texcoord0, texcoord0)
    fill_stream(stream_color0, color0)

    resource.set_buffer(self.res, self.new_buffer)
end
9 Likes

After playing with this for a bit, I have a bit of feedback:

  1. This solved this issue of creating geometry at runtime, but that geometry is still not clickable. For that to happen, we’d need to be able to create collision shapes at runtime. The buffer API sounds like a great use-case for that as well.
  2. I couldn’t get Material World Space to work. It looks like it completely disregards the world transform and everything gets rendered at (0, 0) in world space.
  3. Not a big deal, but it seems like I couldn’t get a bunch of meshes with the same material, uniforms and transform (but different buffers) to batch.
6 Likes

For 3 to work, I think the material vertex space needs to be world instead of local. Then the vertex position needs to be setup differently. Would still be nice for us to be shown the ideal way of doing this.

Yes, but I couldn’t get Material World Space to work. I selected the right streams for position and normal in the mesh component in the editor, and switched the vertex space to World Space in the material. And it seems like it completely discards the world transform on anything that I render, as if it’s sending the data in the buffer without transforming it.

And anyway, even in this state, I still get one render call per mesh.

1 Like

Re 2) When you use World Space, all vertices are transformed on the CPU, thus the world matrix for that mesh essentially becomes the identity matrix. It sounds like what you expected, but didn’t get, so I’ll take a look and try to create an example for you.

Re 3) The batching mechanism triggered only when using World Space. When using “Local Space”, each draw call uses a separate (unique) world transform, and thus cannot be batched. For that to happen, we’d have to implement instancing, which we currently haven’t had in the road map.
I added #4818 for instancing, and #4819 for physics custom meshes using the buffer format.

6 Likes

Yay, and now the links actually make sense to the community as well!

6 Likes

I remember noting that at one point the Release Notes switched over to point towards a private github repo, which means that the older links that didn’t work previously will now work for everyone!

This means that people can even go back to older release notes if they are interesting in how something was fixed :grin: (Looked quickly and it seem to be anything after and including 1.2.163

5 Likes

I have an idea for a new Defold library: functions for transforming 2d primitives into each other. Like circle -> square, square -> triangle and so on
Just if somebody has time to play with math

3 Likes

Hi, awesome feature!
I’m trying to play with your example for studing purposes:

I’ve a bit modified a shader to render textures with alpha channel.
Now I try to fix a mistake with semi-transparent quad (dice shadow), when his Z-coord is behind Z of the ground this quad renders wrong. I get an advice to modify render script with new predicate for transparent materials.

Sources: mesh.zip (313.3 KB)

Questions:

Thanks!

11 Likes

An atlas actually becomes a single texture. The problem is then to figure out the uv coordinates of the original images inside this texture.

I haven’t seen that error output before. Thanks for reporting!

3 Likes

If we could generate or use our own atlases…

I guess we can but if we use raw images then we don’t get the benefits of texture profiles.

1 Like

Updated sources: mesh2.zip (322.0 KB)

Now correct rendering of transparent textures, except a situation of cross of them

3 Likes