17cupsofcoffee / tetra

๐ŸŽฎ A simple 2D game framework written in Rust
MIT License
920 stars 63 forks source link

Mesh does not support DrawParams::color #215

Closed LittleB0xes closed 3 years ago

LittleB0xes commented 3 years ago

Here is my mesh creation

        let (pos_i, uv_i) = (Vec2::new(0.0, 0.0), Vec2::new(0.0, 0.0));
        let (pos_j, uv_j) = (Vec2::new(1.25, 1.25), Vec2::new(0.5, 0.5));
        let (pos_k, uv_k) = (Vec2::new(0.0, 2.5), Vec2::new(0.0, 1.0));
        let particle_vertices = &[
            Vertex::new(pos_i, uv_i, Color::WHITE),
            Vertex::new(pos_j, uv_j, Color::WHITE),
            Vertex::new(pos_k, uv_k, Color::WHITE),
        ];
        let particle_mesh = VertexBuffer::with_usage(ctx, particle_vertices, BufferUsage::Static)?.into_mesh();

When I draw it (and try to change the color with DrawParams)

            graphics::draw(
                ctx,
                &self.particle_mesh,
                DrawParams::new()
                    .position(p.position)
                    .origin(Vec2::new(0.0, 2.5))
                    .scale(Vec2::new(p.scale, p.scale))
                    .color(p.color)                //-> p.color is a Color
                    .rotation(p.theta),
            );   
        }

thinking my code had a problem, I performed the same test with the mesh.rs example (removing the texture). Same result, the color does not change

17cupsofcoffee commented 3 years ago

Ah, this is a docs issue rather than a code issue - DrawParams::color can't really work for a mesh the way it is implemented now ๐Ÿ˜ข I'm not sure why I wrote that you could in the Mesh docs, that's a total brain fart on my end.

A bit of background as to why - when you draw a texture, that effectively generates a Vertex for each corner of the texture. The color for each of those vertexes is taken from DrawParams::color, and the shader multiplies that by the texture's color. This is how you're able to tint textures with a color.

With a mesh however, the Vertex is specified by you, and it's uploaded to the GPU in advance. There's no way for us to pass the tint color through to it after the fact, other than either reuploading the vertex data or by passing it in as a shader uniform. Either way, you're left with a useless DrawParams::color.

I think the way to fix this would be to make it so two colors are passed to the shader - a per-vertex color and a per-mesh color. Then I can make DrawParams::color work consistently across the entire renderer.

I will probably do this in the next version of Tetra, but I need to have a bit of a look at how other frameworks handle this first to make sure I'm not doing anything silly ๐Ÿ˜„ It'd also potentially be a breaking change for any existing custom shaders, so I'll have to make it a 0.6 release. But I kinda needed to do that anyway.

In the meantime, it's possible to tint a mesh using a custom shader (pass in the color as a uniform and then multiply the vertex color by it). I appreciate that's a bit of a pain to do though.

17cupsofcoffee commented 3 years ago

Aha, Love2D does exactly what I suggested (seperate shader inputs for vertex and mesh color). So that seems like a reasonable approach.

Thank you for the report, I'll make sure that gets done soon!

LittleB0xes commented 3 years ago

So it's time for me to dive into shaders. ๐Ÿ˜„ Thank you for your clear answer

17cupsofcoffee commented 3 years ago

Once the fix is done, your original code should work fine without custom shaders, so the alternative is to just wait a few days ๐Ÿ˜› But shaders are a fun thing to learn, so feel free to play around with them in the meantime!

The steps for the workaround would something like:

LittleB0xes commented 3 years ago

For testing purpose, I used the default shader and in my particle display loop I did

        for p in self.particle_list.iter() {

            graphics::set_shader(ctx, &self.shader);
            let mut rng = rand::thread_rng();
            let red = rng.gen_range(0.0, 1.0);
            let green = rng.gen_range(0.0, 1.0);
            let blue = rng.gen_range(0.0, 1.0);

            self.shader.set_uniform(ctx, "u_red", red);
            self.shader.set_uniform(ctx, "u_green", green);
            self.shader.set_uniform(ctx, "u_blue", blue);
            graphics::draw(
                ctx,
                &self.particle_mesh,
                DrawParams::new()
                    .position(p.position)
                    .origin(Vec2::new(0.0, 2.5))
                    .scale(Vec2::new(p.scale, p.scale))
                    .rotation(p.theta),
            );

            graphics::reset_shader(ctx);

        }

My spaceship is now producing a nice flow of multicolored, sparkling particles. image

This kind of technique should be sufficient while waiting for the future evolution of Tetra. thank you for your work

17cupsofcoffee commented 3 years ago

๐ŸŽ‰ Glad you were able to get it working! I usually wouldn't recommend calling set_shader/reset_shader in a loop like that, as changing shaders flushes any batched draw calls to the GPU, but since you're using Mesh (which doesn't batch draw calls) it doesn't really matter here.

I'll leave this issue open until the proper fix is done.

17cupsofcoffee commented 3 years ago

0.5.5 has been published with a fix for this issue.

I opted to keep vertex colours and mesh colours seperate, as trying to use a unified approach didn't really work very well with batched rendering. There's now an extra parameter (u_diffuse) in the fragment shader that holds the mesh color, and it's set to white when doing other kinds of rendering. This seemed like a simple/backwards compatible approach (it's the same thing Raylib does), and I'll hopefully be able to extend it if I ever add support for 3D meshes (don't hold your breath ๐Ÿ˜†)

LittleB0xes commented 3 years ago

Oof! You told me to wait a few days ... you circle the sun much faster than me :grinning:

17cupsofcoffee commented 3 years ago

I couldn't help myself, leaving it broken till the weekend would have driven me crazy ๐Ÿ˜…

LittleB0xes commented 3 years ago

It works perfectly :pray: