Game Dev Diary

Day -364: Enter Bevy & Shaders are hard #

Woke up super excited, I resumed yesterday's video while eating breakfast. And within a couple of hours I had already finished all the Chapter 5 materials that were available at that time (up to 5.7 Path Following (Steering) - Nature of Code). I had even paused in the middle to record a voice recording titled "On the hardcoded parameters of Steering Agents" where I talked about how one could remove some of the parameters Shiffman hardcodes in his videos (like the threshold for arrival behavior). I will probably work on demonstrating that when I get to actually implementing arrival behavior so stay tuned.

This all led me to be very excited about the prospect of being able to reimplement this in a way that I consider easier to follow and reason about, Shiffman typically follows a more OOP (Object Oriented Programming) methodology which I am not the biggest fan of as I think it tends to make things more complicated and harder to follow than other methods, which we will be exploring!

But breaking the ice is hard, so instead of jumping to my computer I procrastinated by looking up more creative coding things and stumbled upon this really amazing talk Exploring generative spaces: a quickstart to generative art - GitHub Universe 2020 and that's truly when my excitement exploded and I decided, it's time to DO THIS.

Bevy. #

I wanted to use Bevy. Bevy is a truly fascinating game engine that I have been following since its inception in 2020 about a year ago that I have been extremely in love with ever since. I never got a chance to make anything with it though, and thought this was it, my opportunity to try it and learn more about game development and use my favorite game engine. If you're thinking it's a bit weird to love a game engine without ever using it, just keep reading, friend. ❤️ I hope I can impress you!

So I created a new Rust project (went with the name flocking-bevy) and I added Bevy to the dependencies, I also went with depending on Bevy's main branch.

bevy = { git = "https://github.com/bevyengine/bevy", rev = "f4cfcc0e44b91446beb49a1dbe9965cb7bcde059" }

Well first things first, I need a triangle on screen! Let's look through Bevy docs for triangle! Uh-oh... that doesn't look like a great start.

Searching for triangle in Bevy's docs result in only two options render::pipeline::PrimitiveTopology::TriangleList and render::pipeline::PrimitiveTopology::TriangleStrip.

Neither of these look like a triangle shape... What even is a primitive topology? Triangle list? Heck is a render pipeline even? I just wanted a simple triangle...

So... not a great start, and yes if you’re already experienced with other game engines you might be feeling quite disappointed something so simple is not exactly obvious, but if you hang with me for a bit longer you will see what makes Bevy shine!

So when the docs don’t help, remember one of the golden rules of Rust:

The docs have your answer, and when they don't the examples do, and when they don't the community will help you and make it easier for the next person!

So docs exhausted, into next step; let's look for an example! Inside the examples folder we see even more folders, but at the very tippity top of them one can notice the 2d folder. That's probably where we will find whatever we are after.

We find inside multiple files with sprite in their name, although most game development terms are completely foreign to me, that one I am aware roughly means something like "Render an image as an object in your game". That's not what we want. I don't really know what a texture truly is but I am fairly certain it's also a bit image-y (raster-y if you will). We need something more vector-y. I don't know what contributors.rs example is about but the one last remaining option once all of those were eliminated felt more likely to guide me than it. So I went with mesh.rs

examples/
└── 2d/
    ├── contributors.rs
    ├── many_sprites.rs
    ├── mesh.rs
    ├── sprite.rs
    ├── sprite_flipping.rs
    ├── sprite_sheet.rs
    ├── text2d.rs
    └── texture_atlas.rs

I just copy pasted the whole example into my main.rs and ran it to see what happens.

Running the mesh.rs example results in a yellow star with a black center and a soft gradient from the edges to the yellow sides to the black center

Nice! This rendered a star, I knew that roughly a lot of things in rendering are just triangles, so I bet this star will be a group of triangles because you can kind of just look at it and notice each triangle. Time to look at the code and see if we are right. Scrolling through the code quickly I notice a few hints that I was probably correct about this star being a set of triangles, in fact I notice this particular line with its documentation

// Let's define the mesh for the object we want to draw: a nice star.
// We will specify here what kind of topology is used to define the mesh,
// that is, how triangles are built from the vertices. We will use a
// triangle list, meaning that each vertex of the triangle has to be
// specified.
let mut star = Mesh::new(bevy::render::pipeline::PrimitiveTopology::TriangleList);

Oh so that’s what this topology thing was for! That makes sense, heck we are already answering some of the mysteries presented earlier! I am not really sure what’s a “mesh” however. But since I knew the whole “a lot of things are just triangles” I am fairly certain I understand what PrimitiveTopology and in particular TriangleList is for at least! It tells the engine that we want to render something that’s a set of triangles.

With a bit more scrolling I quickly stumbled upon this part though

const VERTEX_SHADER: &str = r"
#version 450
layout(location = 0) in vec3 Vertex_Position;
layout(location = 1) in vec3 Vertex_Color;
layout(location = 1) out vec3 v_Color;
layout(set = 0, binding = 0) uniform CameraViewProj {
    mat4 ViewProj;
};
layout(set = 1, binding = 0) uniform Transform {
    mat4 Model;
};
void main() {
    v_Color = Vertex_Color;
    gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0);
}
";

const FRAGMENT_SHADER: &str = r"
#version 450
layout(location = 1) in vec3 v_Color;
layout(location = 0) out vec4 o_Target;
void main() {
    o_Target = vec4(v_Color, 1.0);
}
";

Heck. And I still don’t understand shaders well enough and this uses all those weird new things that I didn’t even see with Shiffman and so have no idea about like layout? location?

And thus I went on a tangent for the rest of the day to try to understand shaders more because I want to be able to write code that I fully understand! I went over a couple of chapters from the book of shaders but quickly put it down because it almost fully ignores the existence of vertex shaders which as you can see above are the bulk of these two shaders. I watched half of Freya Holmer’s Shader Basics, Blending & Textures • Shaders for Game Devs [Part 1] but the fact it was using HLSL made me shy away from it a bit since I didn’t wanna be confused by all the differences between GLSL and HLSL (they are different shader languages, the above examples are written in GLSL; I still prefer the name gunctions by the way). However, she did give me a better understanding of what vertex shaders are, they are gunctions that operate on the geometry, you can use them to create effects like shaking for example. Then found myself watching another video series that I think was also heavily focused on fragment shaders with very little vertex focus so ended up skipping it as well.

Tried to look up the specific syntax used in these two shaders, like layout, but the official documentation is not exactly well suited for learning, at least not in the chaotic way I typically learn by jumping to the middle of a topic...

As the night grew older I realized it’s going to be a very long journey until I can fully grasp shaders, and I tried to look for examples of other shaders being used throughout Bevy and its examples but it felt like all shaders were just these two copy pasted? Thinking about it I realized these are sort of the default-bevy shaders and that they basically almost do nothing. They are boilerplate, boilerplate I can try to understand more in depth later! So as closed my eyes I decided tomorrow I will try to actually understand the rest of the mesh.rs example instead of banging my head against a shaded wall.

Good night twinkling star, I shall see you tomorrow 💛