SubTextures - Creating a Sprite Sheet API // Game Engine series
Key Takeaways
The video demonstrates the creation of a Sprite Sheet API in a game engine series, utilizing OpenGL for rendering and defining a SubTexture class to simplify texture coordinate calculations. The API is developed using tools such as Visual Studio, GLM, and Hazel, and includes features such as static creation functions and convenience functions for creating sub-textures from coordinates.
Full Transcript
as of guys my name is the churner welcome back to my game engine series so last time we introduced sprite chips and we more or less talked about how they work they're also called texture atlases check out that video if you haven't already the most important thing that I want to do today is actually work out some kind of API for our texture atlases now this is something that I haven't really like sat down and planned because I figured it would be a probably a good process to show you guys what I was thinking and kind of just do it live instead of just being like here we go let's do this but basically we're just gonna jump in and we're gonna try and figure out a way for ourselves to I guess define and use sprite sheets now straight off the bat I want to say that this is something that at the moment the small amount of thought that I have given it which has been mostly during this introduction that I'm now giving you guys I haven't really there are a lot of ways that you could implement this and unfortunately it's one of those things that tends to be according to your preference because when you when you think about a sprite sheet what is a sprite sheet so at the core we have a texture and then some kind of index into that sprite sheet which we can use to determine the needed or the required texture coordinates to render something more specifically a textured quad so basically if we break all this down that's probably the best thing to do in pretty much any situations just to break everything down to the actual data required so what is the actual data that we need well we need a texture that's step one and then the second piece of data that we need is just the texture coordinates that we're going to put into our vertex buffer when it's time to actually draw one of these textured quads so texture and then vertex texture coordinates that's it now how we choose to calculate this and abstract this away is again one of those things is going to be really dependent on the usage so how are we planning on using this as someone who's writing the code for this quote-unquote game that we're making here how do we want to I guess deal with textures that are actually sub textures inside texture atlases well ideally we probably still want to deal with them as if they were separate texture that's probably the best way to do things so in other words we probably when we load a spreadsheet we probably want to define all of the different textures and maybe make variables out of them inside our like layer inside our like level class water wherever it may be so we load a texture we define all of the different sub textures and then when it's time for us to render because we still will be calling draw quad with an appropriate texture this time maybe we can pass in that definition of a sub texture instead of the full texture and that way we can still have just one actual texture loaded in memory on the GPU ready to be bound and then the rest is just determining some kind of like set of vertex texture coordinates that's probably the way that we're going to go but let's dive in and actually take a look practically at what that would look like so this is what we had last time we had all these particles and we had these stairs rendering just to test out everything and make sure that it is okay but right now you know let's think about this where drawer in the quad we pass in a texture what we actually probably want to do is instead of passing in like m sprite sheet we want to pass in maybe M texture stairs or something like this specifically to draw the stairs now we know if we go back to render A to D we know that the stairs themselves let me find the appropriate function that the stairs themselves here now draw a quad function that we worked on last time are at seven six that's like the coordinate of the texture inside our sprite sheet if we quickly open up our sprite sheet and we can look at that so these stairs over here we know are and I don't know what visual studio is like look at this well visual studio is like image editor is don't think I've ever used it before and I'm not digging this kind of green background but anyway attempting to explain this is a little bit hard and I can't select anything because it moves everything but whatever so these stairs as we know are the seventh sprite on the X I'm not even sure I'm looking at the whole thing and I go I can zoom out with controller and the scroll wheel seventh on the x-axis and sixth on the y-axis so that's where it is I want to be able to make a sub-region out of that and then all of this calculating that we see here is something that actually needs to be done automatically I don't want to have to deal with this I just want to be able to define everything up front have it linked to an actual spritesheet being the actual OpenGL texture that we want to bind and render with and then everything else is going to be automatically calculated for us I think another more generic way to go as well is to just make something that is essentially a sub texture so instead of texture 2d we could have sub texture 2d and that represents just one part of an actual texture this could be relatively easy to define because again like a sub texture could literally just be like a texture 2d raff like this Plus these texture coordinates which we could calculate upon like constructing because another thing is doing all of this maths every single time we draw a quad with this image it's probably not a good way to go I mean it's not required at all so generally we want to avoid doing work every frame and every cycle and every time we call this function if we can and I think that we can so I think I will actually go down that route just because I feel like it's it's generic enough that it's gonna be totally fine and it also kind of allows us to to not tie ourselves directly to the concept of sprite sheets or texture atlases because that's something that is obviously very specific to these types of games and I probably don't necessarily want to stick to that terminology so let's kind of start writing this thing and again this is something that I've literally just just come up with on the spot so if we end up doing a complete backflip then so be it so let's go into our texture class here this is it is one of those things that again doesn't need to be render API specific whatsoever so we have inside platform OpenGL and an OpenGL texture you know we have specific specifics to OpenGL textures however this concept of a sub texture doesn't need to be specific whatsoever now do I want to define the texture class in this file I'm thinking probably not the reason that I don't think I will is because this as you can see is specifically to do with like renderer objects that's also why it's inside like the renderer fold another that's like a huge indicator of what should be here but my point is these are literally things that we created for the intention of creating them using a render API and having them as objects on our GPU they're basically primitives that we use for rendering so what I'll do is I'll create a new item and I'll make sure I'm ensure all files mode because if I'm not then it's just gonna create it in the root directory with my project file which is not what I want and I will call this sub texture and I guess I'll call it sub texture 2d because well that's what it is and I do like descriptive names this is exciting we're introducing a new class class sub texture 2d now this is not like a renderer primitive or anything like that which is why like it's not gonna follow the same pattern of having like a static create function or anything like that this is something that you can just you know create normally like a normal person so we'll say Const ref texture 2d this is basically what it's wrapping this is our actual texture and this is also something that is going to end up storing so we'll say ref texture 2d texture however we may actually end up providing some kind of static creation functions just to make this a little bit easier so in other words we could basically choose to create something like as a sprite sheet and then pass in like the size of each sprite for example but ultimately as I mentioned before our goal here is to have a set of texture coordinates so I'll say text chords will have four of them we know that we need four of them let me just include GLM here as well slash GLM I'll extend this this constructor to be the most kind of general case so in other words this is basically going to be like the minimum bound and then we're gonna have a maximum bound as well let's also create a CPP file for this sub texture or stub texture 2d dot CPP will include our PCH and also our sub texture 2d once I've done that I'm just gonna go and implement this constructor now this is going to obviously set our texture to be texture and then with calculating the texture coordinates this is really where the meat is going to come into play again the core of this and I'll write this out very simply is going to be this these are supposed to be texture coordinates that you pass in if you want a sub texture it should be very simple in that sense so texture coordinates zero here are going to be min dot X and Minn dot Y if you picture this being like the bottom left corner and I'm just writing them out like this so that they match up and easy to read then the texture coordinates for the bottom right side are going to be max dot X and then min dot y then we're gonna have max X max Y and I'm gonna have min X and Max Y so now we have every combination of these and these are in fact our correct texture coordinates now this is fine and this is really all we need we could really provide like any mechanism we want to retrieve these texture coordinates we could even make render a 2d a front class however I think what I'll do if we just go back to render it it's always good to check your usage obviously so what are we actually going to need well we're going to need this kind of pointer of texture coordinates where we're going to want to copy from at the appropriate index so just by having like an index should be pretty good I think so going back here what I'll do is I'll just write a function that retrieves our texture coordinates and we'll mark them as Const here as well okay so now we have the texture coordinates and then I'll also of course need to get this texture as well ok so this is really all the sub texture class needs to be as you can see it's really simple that is at its core the data that we need to turn everything into essentially a sprite sheet and and more importantly be able to actually access those individual sprites however as I mentioned it's going to be very useful to us to be able to create these things without having to actually specify the texture coordinates and having to compute them so what I'll do here is I'll add some static functions static graph sub texture 2d create and these are actually going to basically function as helper functions for us so that we can create them as if they were a sprite sheet so I'll do create from coordinates so create from coordinates or create from chords is basically going to be what we did in the render a 2d class which I'm just all over the place with my code alright so basically this right what we did here is we created them based on a set of X&Y coordinates that we specified here and then the width and height of the shade as well as each individual sprite width and height so if we go back here all we really need here is the X and the y coordinate as well as the sprite width and the sprite high so if we have these two pieces of information and in fact I think what I'll probably do is end up converting them into vectors here vector right sighs and then I'll implement the function of course and then what this is going to do is attempt to create these min and Max variables that we need here using this information so let's go back to render A to D so that I don't have to copy too much code here I'll just copy all of this I think and delete all the stuff that I don't need such as this stuff all right so min and Max let's work them out the coordinates x and y has become the coordinates sheet width and sheet height is also just the textures width and height actually one thing I forgot that's kind of important is to pass in this texture now do I want to do it at the start or at the end I think I'll do it at the start just because it is like the most important thing let's add this here okay so remember we really only need two of these because everything else will just calculate using this so this here is the bottom left corner and then we have the top right corner these are the only ones we need so I'm just going to delete the other two here and then let's start with changing this so first of all x times sprite with X's of course going to be from our coordinate so we'll do coordinates x times sprite width which is going to be our sprite sighs I think sprite size dot X and then coming over here we have chords dot y times sprite sized or Y sheet height is going to be texture gets height and sheet width of course is going to be textured get with we can get rid of these we obviously don't need them and then we have the same thing over here again chords dot X plus 1 sprite with sheet width and then sprites height which is sprite sized oh why why of course is chord or Y and then sheet height is just texture get height alright cool so that's basically all we need now instead of having this consecration which obviously isn't even a Const expression we'll get rid of it and then really all we need to do here is create this or F sub texture 2d will say result equals create ref let me get rid of that space here create ref sub texture 2d and then we just need to pass in the texture as well as the min and Max so just to make this a little bit better and a little bit easier to read I will actually bring it out here and I'll say min and Max instead of you know instead of putting it into the constructor which I could have done as well but it's okay I think I'll just leave it like this so that it looks better and makes more sense all right let's take this and this will be our max coordinate here I think everything's correct yeah it should be and then we'll just pass in a min and Max yeah all right so again this is and we can just return this so this is just supposed to be you know think I've got a comma down all right so this is what we look like this is just supposed to be a little convenience function who basically calculated these things for us so that we can give it some simple data and just create them from coordinates instead of having to calculate the actual the actual texture coordinates on our own inside the sandbox class or inside our game because we don't really want to be doing that all right so now we just need to go into our renderer and actually change the function to work with this now technically you probably want to implement this for literally every single function that takes in texture at least so what I'll do is I'll do drop quad here I probably won't do it well I guess I should do it for everything for completeness but what we'll do is we'll change this to be a sub texture 2d and we'll leave I think we still want to tile in fact to annotate color obviously as before I'll include sub texture 2d over here and let's also copy and paste rotate a quad and we'll update this check in and the sub texture as well so we have quite a few new functions here let's go ahead and implement all of them I'll grab this in fact I think what I might do is instead of implementing them that way first of all I think what I want to do is revert our our function that we actually modified which was this one to be what it's supposed to be so if we go to like literally any other function and just get the texture coordinates that's really the only thing we changed let's paste them back here and I think we can obviously get rid of this I think that's exactly what it was so that was easy and now what I'll do is every function that had that I'm just going to copy and paste it now at this point a lot of it is the same really all we're doing is calculating texture coordinates so you could actually bring this out and make all of this into one fun call it with like the texture coordinates and stuff like that we might do that in the future I'm not gonna rush in and do that right now because it's not really necessary it is unfortunate that we have so much duplicated code though which is why we should probably do it as I mentioned before but this is this is just the way it is for now so all of this stuff it still stays the same really the only thing that changes is the texture coordinates this us trying to like find an appropriate texture and like put it into the texture slots all of that stuff that still stays the same it's just that what happens is when we actually get the texture here this is a sub texture and I'm gonna label it as such so that we don't confuse it because we still need the actual real texture to render because sub texture is kind of just a wrapper over it and in fact I'm just gonna call this sub texture as well so that we don't get confused so going back here this is just gonna call draw quad with sub texture which we'll call this overload and then what this is going to do is again these texture coordinates instead of being taken in from somewhere else we're going to take them in by calling sub texture what was this sub texture get text chords like this and then this will just be I think it counts pointer like this and we can access them in exactly the same way so this code does not change whatsoever but then texture which we don't have now we also need to retrieve and we can just do that by writing Kon strap texture 2d texture equals sub texture and get texture and that's it and you can see everything else will work fine so that's this done and then now we need to do the same thing for our rotated cause the take in textures which are these so let's copy and paste this and yes I am now definitely regretting my decision to not kind of puts into one function we might have a little maintenance episode where we both move this out into like another class like this specifically this like game that were kind of working on here and then also we'll take that opportunity to like you know clean up the render of class and do all that stuff so let me grab both of these lines of code where are they so this this and this if we go down here this becomes that this is a sub texture now and I think everything else should work fine now obviously you know we need to test this because I just implemented this off the top my head and it might be wrong but the idea is let's test this out so the idea is we have a sprite cheer we still load that in normally as we always do but now the difference is and if I go to the main hey little header file we will add in sub texture over here so that we can actually use it so sub texture 2d so we do sub texture 2d just like this and then this will be like our stairs you know our let's just do texture stairs and then the way that we're going to kind of I guess and initialize this and use it is on attach when we create our sprite sheet we'll just do texture stairs equals hazel sub texture 2d create from chords and then this will be our sprite sheet and then the coordinates here are going to be 7 6 now note that you can like create half textures and stuff like that if you wanted to because these are floats so that's totally permissible because the maths didn't like make sense here even though logically minot sprite size is 128 128 okay so that just creates that should work right because that's the size of these sprites and I think I think that it should just work so let's hit a 5 and see what happens hopefully I haven't made any mistakes okay so we're already not compiling let's take a look at this we have all these linking errors okay but yeah that's because it's in compiled text wasn't if the definition we've got to delete the other one let's run this again and there your looks like we see the stairs again I really have no way of knowing if that worked or not because it looks exactly the same let's open this again inside this wonderful image editor that we have inside visual studio and I unfortunately keep trying to zoom out with control - but that just closes everything it seems ok let's shall we go back to well you know what we don't even need this right ok so first of all what's gonna happen if we go out of bounds if I specify an index off of this then you know - the coordinates still work outside of bounds it's going to either repeat what's going to clamp it's gonna do something so that's not gonna that shouldn't really crash let's do what was this bush I don't really remember okay well let's try a new sprite how about that let's try this kind of barrel thing so it's clearly the third sprite from the bottom which means it's index 2 and then here it's 1 2 3 4 5 seven eight nine wait 0 1 2 3 4 5 6 7 8 so 8 by 2 let's try that so we should get a barrel if we change and in fact what I'll do is I'll run a boat that'll be a good test so we'll do texture stairs and the texture barrel so 8 2 right um let's copy this and paste is here 8 2 I think is what my barrel was okay so we're creating it from the spreadsheet extra barrel and then I guess I'll just render it let's friend a texture barrel and we'll render this maybe add an offset of like 1 yeah 1 should be fine because I don't think they take up the whole space I release the barrel looks like it's a bit thin so we should be fine all right there we go we have a barrel we have the stairs but clearly rendering correctly from our spreadsheet everything looks good so that is our new sub texture class and in fact one final test that I think would be useful is if we open the texture again let's try and render one of these trees like this orange tree for example because this is actually it's not a square sprite so which one is it so 0 1 I guess we'll have to give it the index 1 on the y-axis and 0 1 2 so it's 1 it's 2 by 1 and then it has to be twice the height so to do that let's go back here let's do em texture tree and then over here we will assign it to let's grab that spreadsheet so what we say true 1 is that right I always forget these numbers 0 1 2 and yeah I care - 1 I was right and then 128 by 256 tall so I guess in this scenario this would refer to not necessarily the size of each sprite but the size of this particular sprite and obviously that means that it's going to take up twice the height now this is actually now that I think about it it's probably it might work for this scenario in fact it probably won't work for anything and the reason is that obviously we multiply these coordinates so this this kind of plus one thing this is really what we want to change too right because if we change the sprite size and we multiply with that then obviously it's still going to it's just going to be a different kind of coordinate we're going to be dealing with a different offset and it's still going to be the same because we're specifying a minute max balance so that's something is that we should probably fix and the only way that we can really fix that is probably by providing a third parameter well we have sprite size in fact you know what let's set that to be one by one by default and then we'll say Const GL m and this is gonna kind of make it even more confusing but we'll maybe say cell size and cell size is gonna be kind of each grid cell how big it's actually going to be and then we've got sprite size as a default parameter so let's go and copy this we'll go back we'll have to make sure that everything's working but basically cell size now is going to be what x and y is so I'm reply I'm replacing all of the sprite sizes with cell size and sprite size on the other hand is what we're going to actually increment here so the chords get incremented by the sprite size X and the sprite size Y and really this only happens for the max parameter here okay so let's go back let's change this so the cell size is still 120 about 128 but what we do here is we set a size of 1 by 2 instead of the default of 1 by 1 so now I guess I'll just add in the tree here so we'll have our tree texture and let's draw this on the other side so we'll do negative 1 and then that's not my tree texture and then maybe our last thing we need to do actually let me just cancel that build quickly because I want to see the wrong tree let's make the height of it - right so it's twice as tall obviously otherwise it's still gonna render it on a square quad and it'll be it'll be very squished ok so what do we have here okay brilliant there we go it works so now we see this importantly like we would have seen this before as well we could have set it up by just drawing two sprites on top of each other but obviously by doing it this way it's a single quad and we're rendering the entire texture on it all specifically the entire tree texture on it we don't need to split up into two quads because that can be a little bit annoying to do right especially if it's just like a little tree texture we could even make it just so that the first quad maybe functions as a Collider or something like that so you can go behind the tree like that if you're the player there's a lot of things you could you could do here but it's cool of course we're rendering it as one quad here so there you go okay so I hope you guys enjoyed this video if you did please hit that like button you can also help support the series by going patron on 4/2 Cherno you'll get access to plenty of really cool perks such as access to the 3d development branch of hazel as well as live streams for this month of May I've actually have the price to get in on the live streams that I'm doing every single week of me developing hazel so if you guys are interested in seeing me actually work on hazel dev the 3d version of hazel and all that stuff then definitely check that out and thank you so much for your support we've got this sub texture 2d class now which is pretty exciting we can actually start putting together some kind of map and rendering it whether that be just by like having a file that maybe maps out where where each tile goes we also might do it from like a string we could even like use a noise algorithm to generate some noise and then assign different types of tiles to that I don't know we'll figure it out if you guys have suggestions of them in the comments below for what you want to see but this is good I'm happy with what we've got here we're slowly working towards extending the hazel API through actually making a game and I think that's probably one of the best ways to make a gay mention it's do you want like once you've done the core stuff and we kind of have done a lot of the core stuff in hazel thus far once you've done all of that stuff you can just like you know sit down and actually start trying to make a game with it and then filling in the blanks and that's what we're doing I think it's working really well I can't wait to see you guys in the next episode honestly we can keep working on this game I will see you next time goodbye you [Music]
Original Description
Patreon ► https://patreon.com/thecherno
Instagram ► https://instagram.com/thecherno
Twitter ► https://twitter.com/thecherno
Discord ► https://thecherno.com/discord
Series Playlist ► https://thecherno.com/engine
Watch on YouTube ↗
(saves to browser)
Sign in to unlock AI tutor explanation · ⚡30
Playlist
Uploads from The Cherno · The Cherno · 0 of 60
← Previous
Next →
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
3D Game Programming - Episode 1 - Window
The Cherno
3D Game Programming - Episode 2 - Game Loop
The Cherno
3D Game Programming - Episode 3 - Arrays
The Cherno
3D Game Programming - Episode 4 - Drawing Pixels!
The Cherno
3D Game Programming - Episode 4.5 - How Rendering Works
The Cherno
3D Game Programming - Episode 5 - Playing with Pixels!
The Cherno
3D Game Programming - Episode 6 - Performance Boosting
The Cherno
3D Game Programming - Episode 7 - FPS Counter
The Cherno
3D Game Programming - Episode 8 - Alpha Support and More
The Cherno
3D Game Programming - Episode 9 - Beginning 3D
The Cherno
3D Game Programming - Episode 10 - Floors and Animation
The Cherno
3D Game Programming - Episode 11 - Rotation
The Cherno
3D Game Programming - Episode 12 - User Input
The Cherno
3D Game Programming - Episode 13 - Render Distance Limiter!
The Cherno
3D Game Programming - Episode 14 - Basic Mouse Movement
The Cherno
3D Game Programming - Episode 15 - Textures + More!
The Cherno
3D Game Programming - Episode 16 - Walking, Crouching, Sprinting + More
The Cherno
3D Game Programming - Episode 16.5 - Exporting Runnable Jars
The Cherno
3D Game Programming - Episode 17 - Small Adjustments + Birthday!
The Cherno
3D Game Programming - Episode 17.5 - Creating an Applet
The Cherno
3D Game Programming - Episode 18 - The Beginning of Walls
The Cherno
3D Game Programming - Episode 18.1 - A Few More Things
The Cherno
Episode 18.5 - Creating an EXE File in Java
The Cherno
3D Game Programming - Episode 19 - Rendering Walls
The Cherno
3D Game Programming - Episode 20 - Continuing Walls, Fixing Bugs, and Managing Crashes
The Cherno
3D Game Programming - Episode 21 - Texturing Walls, Fixing Clipping, and Fixing the Mouse
The Cherno
3D Game Programming - Episode 22 - Random Level Generator + Properly Fixing Clipping
The Cherno
3D Game Programming - Episode 23 - Graphical User Interface (GUI) Launcher
The Cherno
3D Game Programming - Episode 24 - Making Our Launcher Work
The Cherno
3D Game Programming - Episode 25 - Writing and Reading Files
The Cherno
3D Game Programming - Episode 26 - Custom Resolutions
The Cherno
3D Game Programming - Episode 27 - Decorating the Launcher
The Cherno
3D Game Programming - Episode 28 - Continuing our Custom Launcher!
The Cherno
3D Game Programming - Episode 29 - Launching The Game
The Cherno
3D Game Programming - Episode 30 - Colour Processing In-Depth
The Cherno
3D Game Programming - Episode 31 - Sprites!
The Cherno
3D Game Programming - Episode 32 - Sprite Mapping
The Cherno
3D Game Programming - Episode 33 - High Resolution Rendering
The Cherno
3D Game Programming - Episode 34 - Entities
The Cherno
Genesis - My Game for Ludum Dare 24
The Cherno
Vlog + Ludum Dare Results
The Cherno
Game Programming - Episode 1 - Resolution
The Cherno
Game Programming - Episode 2 - Threads
The Cherno
Game Programming - Episode 3 - Game Loop
The Cherno
Game Programming - Episode 4 - Window
The Cherno
Episode 5 - Buffer Strategy
The Cherno
Game Programming - Episode 6 - Graphics Initialized
The Cherno
Game Programming - Episode 7 - Buffered Image and Rasters
The Cherno
Game Programming - Episode 8 - The Screen Class
The Cherno
Game Programming - Episode 9 - Rendering Pixels
The Cherno
Game Programming - Episode 10 - Clearing the Screen
The Cherno
Game Programming - Episode 11 - "Out of Bounds, Baby!"
The Cherno
Game Programming - Episode 12 - Negative Bounds
The Cherno
Game Programming - Episode 13 - Timer
The Cherno
Game Programming - Episode 14 - FPS Counter
The Cherno
Episode 15 - Tiles
The Cherno
Game Programming - Episode 16 - The Map
The Cherno
The Walls 2 - Minecraft PvP Survival Map
The Cherno
Game Programming - Episode 17 - Key Input
The Cherno
Game Programming - Episode 18 - Controlling The Map
The Cherno
More on: AI Systems Design
View skill →Related Reads
📰
📰
📰
📰
A complete Laravel API with clean architecture in 30 seconds, tests included
Dev.to · Loic Aron Mbassi Ewolo
Pain point #2: Django’s ORM Performance Problem
Medium · Python
Why We Stop Injecting Code Into Shopify Themes for B2B Wholesale
Dev.to · Anil Kumar
A founder once asked me why refunds kept disappearing, here is what NestJS was actually doing
Dev.to · Peace Melodi
🎓
Tutor Explanation
DeepCamp AI