TEXTURES | Game Engine series

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
it's gonna be a long episode if you see me drinking coffee at the beginning of a video fact if you see coffee in the thumbnail of a video you know it's gonna be a nice long episode hey what's up guys my name is Chen and welcome back to my game management series so last time we talked about smart pointers references we talked about assets as well check out that video if you haven't already and today I'm gonna start off by actually creating probably our first actual asset type right like we've got shaders and we kind of consider them as assets potentially and geometry that we've kind of created in our engine but today we're actually gonna load a file from our computer it's gonna be any file that you guys create yourselves we're gonna load that in and we're gonna display it in our engine this is really exciting because if you haven't if you haven't watched my video on what game engines actually are literally the first episode of this series so if you haven't watched it I'm quite disappointed in you but in that video we talked about the fact that engines are really just these kind of tools that we have we write all this software this kind of engineering ethic goes into this software this program which takes data from our computer and transforms it into some interactive like graphics adventure that we see on the screen that's really what a game engine is and today we're finally taking in a piece of data that isn't generated from within like our engine or anything like that we're going to be learning in a texture just a normal image file and actually displaying it on our screen so that's super exciting so I have a video on textures in OpenGL so check that out as well if you haven't yet that's gonna be kind of the basis for what kind of code we write here although the code we write here today is gonna be a little bit different specifically the opengl code that we write here today I mean we're also gonna set up like I'll stand a kind of abstraction and everything like that as we've done with like different rent or primitives as I called him like you know vertex array and shader but specifically like the OpenGL code is going to be different because we'll be using a bit more of the updated API which I'm not quite sure windows 10 wants me to restart we'll stop with that by the way like seriously almost makes me want to switch to Mac you know not really but almost this whole OpenGL API is honestly such a mess that I don't even want to spend too much time talking about it but we will be using something a little bit newer than what we did like in that OpenGL series specifically to do with texture loading so I want to talk a little bit about what textures actually are and what they're used for in a game mentioned because textures are extremely powerful textures are very powerful textures are more than you might think they are you might be used to textures just being like ok well I woke up I've got an object like this bunny for example this is why I've got the bunny here so I can do things like this we bought this Widow this bunny here and I wanted to be green and I wanted to kind of maybe the ears need to be some other color and I just want it to be more than just like one solid color so how do I do that well I'll just kind of create this 3d model in a program such as blender Oh Maya or 3d max or anything like that and then I'll texture it I'll add like a credit-based belt I'll unwrap the UVs and I'll flatten it all into a texture and I'll paint that in Photoshop and I'll just have my nice little bunny and that's that's correct I mean that's probably the first use of textures that comes to mind but textures can be a lot more than that because what textures actually are what they actually are is just just a buffer of memory that we've taken and uploaded to our GPU announced taught in our vram and we can access that buffer of memory in our shader specifically in our fragment shader in a pixel shader when it's time to determine you know what color to shade a given pixel so because it's just a buffer of memory we can upload anything into it I talked a little bit about buffers like our index bar for a new vertex buffer and how like you know how vertex buffer isn't just for positions we can put any kind of data we want into their textures are exactly the same you could literally you could put anything you want into a texture and then when you sample that texture apart from getting all the specific GPU technology that goes into sampling a texture and determining like you know what color to kind of interpolate it into if we're doing magnification or minification filtering on an anisotropic filtering or any other kind of filtering when we actually sample a pixel and we determine what color to return from that texture image we get all of that bonus kind of data or if we want it and then also we just have the ability to you know shader just select that kind of specific region of a texture and be like I want to know what the value is now true in many cases the value is the color for example you know maybe bunnies fur color is brown so we'll just get that brown color out and now our bunny will be rendered as brown however we can encode any kind of data we want into into it a good really good example as a normal mapping right where what we've done is we've wanted to keep our bunny model kind of low poly so that we don't have to just have this hugely high poly very heavyweight very performance expensive like model 3d model with all these little little details instead of doing that we create a texture which is just basically a little collection of values which define more finally the normals that go into this body and that high-resolution texture with all that kind of normal data and it can actually help our bunny look a lot more detailed and a lot more realistic because when it's time for us to do our lighting calculations instead of relying on the vertex normals we can actually just go ahead and sample that texture and figure out what the normal actually is for a given pixel on kind of a per pixel basis instead of like a per kind of vertex and then interpolation and then all of that stuff so really powerful stuff and we'll cover normal mapping of course eventually in this series as well but that's just a little bit of an example and I could go on and on I mean there's you know in physically based rendering PBR entering was like you know roughness maps metal maps there's all this other stuff that you can do as well to define what areas of the body should be shiny and what areas should not be shiny right and all that is is just just a texture but really that texture is there because we want to look up a value when rendering a given pixel inside our shader and that's kind of what a texture is and again like to bring out physically based rendering again to give you one last example there are certain mathematical operations that are very difficult to do in real time so we can actually do them offline and then store the results in a lookup texture and a lot essentially in texture right and then all we have to do is or in that texture we've stored all of these already pre-calculated values we can just extract the one that we want and that's it we suddenly don't have to do that calculation of runtime that's what we do with like a brdf lot or something like that right so that's another example and we'll get into all that stuff in the future in a lot more detail so if you don't understand any of this that's totally fine the point that I'm kind of trying to drive home is the textures are so much more than just a bunch of colors that we use for directly taking those colors and then applying them to a 3d model all right so now that we've discussed just what textures I'm what they're used for in a game engine let's talk about what we're actually gonna do today specifically so what I want to do is load an image file from my computer and then I want to render that essentially on like a quad inside our engine that that's it I mean how hard could that be there's actually a lot of components that go into that first of all we need to talk we need to talk about our geometry so what is needed to display a texture on our computer spring right like what data do we need in the geometry and specifically what I'm talking about is we need texture coordinates again I don't want to really go too deep into this check out the OpenGL stories video on textures because I think that goes a lot deeper than what I'm going to do right now but essentially what we need to do is modify our vertex buffer so that it actually includes texture coordinates for every corner of our square then we also need to have a wind change our shader around so that it takes in like a sample A to D which is basically just a 2d texture slot and then we need to create a texture glass obviously that will load that file and create and upload it to our GPU and also we'll get a render ID out of that then we can just bind at runtime using an API that will create and then that I think should be it so it shouldn't be too difficult but there is a lot of work that's gonna go into this I don't want to make this episode too long so I'm going to just go through it as fast as possible it should make a lot of sense it should be really straightforward if it's not and make sure you check out that OpenGL text video and if it's still not clear to you leave some comments below and I'll try to make additional videos for things that that I might have missed that you might be um that might just need a bit more explanation so without further ado let's jump in and take a look at textures in our engine ok so the first thing I want to do is prepare this area kind of protection so what I'm even gonna touch textures yet we're just gonna everything else basically ready to go so if we take a look at what the hazel engine is at this moment all it is is this triangle I think I'll get rid of that triangle it's getting a bit annoying we've got this grid which is fantastic we might keep the grid but what I wanted maybe instead of this triangle is a nice big square where we can actually render out texture so to render a texture we need to render it on some kind of geometry so we've got a vertex buffer here which contains a square which is fantastic that's what we'll use however what I'll do is I'll hide this triangles this is us rendering the triangle on this label it yes was not clear this is us rendering our grid literally all we need to do is take this submission and instead of submitting we'll change the transform around so that instead of it being tiny I think what we're doing is we've got a position which is fine we can just leave it at the origin however the scale we did set to 0.1 we're just kind of small obviously I think 1 1 is the size of that triangle which is also a little bit small so what I'll do is I'll just copy and paste this scale transform it I'll change it so that instead of 1 as 0.1 it's 1.5 so it's a little bit bigger than just one point zero scales what scaling it up a little bit will still keep the flat color shader and the square vertex right so if we launch this right now we are setting the color to blue here so we should just get kind of like a large blue square kind of in the middle of our screen and here we go so there we go we've got this Blue Square perfect so now what we want to do is instead of rented the color blue we want to be sample each pixel from a texture so that we actually see our texture on the screen so to do that the first thing we need to do is change up this vertex buffer so that it contains texture coordinates because we have texture coordinates we actually can't render anything because we won't see we won't actually know which areas of the texture need to be sampled so for the bottom left corners is the bottom left corner we'll just use 0 0 for the for the bottom right corner it'll be 1 0 so we're just going along the positive x-axis here I'll just copy this quickly for the top right corner is going to be one for X and one for y and then finally for the top-left corner we're back to zero X and Westie o on one y now this is expanded now instead of us having three floats per vertex we have five because we have two more for our kind of 2d texture coordinate here let me just check out that's right and yeah that looks good okay so basically I mean the way the way that you can follow this is if the negative 0.5 is negative right if the 0.5 is negative then the extra corners should be 0 and if it's positive then it should be positive it should be positive 1 right so one for positive 0 for negative and that way we can quickly check to make sure that everything is correct and it is so we to expand this now obviously we still have square vertices and the size of square vertices so we don't have to touch anything here for our layout because of our beautiful layout system all we need to do is just literally add another add another attribute here there's going to be a float too and that's going to be called a text chord all right and that's it I mean if we just launch the program now it should be it should look exactly the same because we've expanded our vertex buffer obviously but we've also expanded the layout correctly and the fact that we're not taking in anything inside our vertex shader that's totally fine because obviously the position still remains at layout zero and it still is a back three so if we hit f5 to just quickly verify that everything is correct we should still see the exact same thing as before all right and now we've got the same result as before fantastic so what I want to do now is create a new shader that's going to be our texture shader so what I'll do is next to black color shadow I'll make a new shader that's going to be called texture shader and then what I'll do is literally copy and paste this entire flat color shader including the shadow creation like this now we're getting a lot of shaders now so it would be a good idea to start thinking about some kind of asset manager or some kind of shade and library so that we don't have to define all this stuff inside our sandbox app so it's definitely something that we'll talk about soon in the future so we'll have texture shader bear text tools texture shadow fragment source this is going to be extra shader this is going to be of course our texture shader fragment sauce and our texture shadow with vertex sauce so great with credit a new shader now we can edit this code and it won't affect our blue shadow our flat color shadow so we don't need to output position anymore what we do need to do is actually take in that second attribute which is our vector which is all about texture coordinates right per vertex and then we need to actually get that texture coordinate and send it to the fragment shader so that we can access it there and the way that we do that is the same way that we were sending position before we simply write out vector then V for varying honors for text board okay instead of outputting position here we're putting our text board and then this jail position that stays exactly the same so in the same way that we are put it here we need to input it here so we just use Ian's back to text cord now we have access to that texture coordinate you know our shader and now what we can do is actually see if they're even correct right this is a really important thing for us to do because it lets us verify our data what we're going to do is take those texture coordinates and the same way that we output our positions kind of has our color in some previous episode we're going to do the same thing with texture coordinates here so what we're doing is this is a vector right we're outputting it as our color specifically the first two components of our color become this texture coordinate' so the red channel and the green channel correspond to the X texture coordinate and the y texture coordinate respectively for the blue channel we've just call zero so we won't have any blue at all ever and for the Alpha we've just got one and that doesn't change obviously at all so now what should happen is if I render using this shader what I should see is a visualization of this data that I've put into my vertex buffer so this is a great way to actually verify that the data that you've actually provided is correct because obviously inside our shader we can't print anything to the console or anything like that because it's running on the GPU and so the equivalent of doing eyes just basically outputting the color on through the screen which is really cool because you can also validate it visually which i think is even more useful than if we even could print it so what we'll do now is we'll do the same rendering but instead of flat color shadow will use our it's extra shader and we'll hit a 5 and see what happens all right wonderful so now we have a really cool gradient here that it's a visualization of our data now how do we read this we know that the bottom left is 0 0 right the top left is 1 1 if we look at what we've supplied for our texture coordinates we know that the bottom left coordinates should be 0 0 right that's exactly what we see we see it being completely black which is 0 0 for the right side right so this is the bottom right we should be 1.0 in X and 0.0 and why now X is being output as we as the red Channel and then Y is being output as the green Channel so we should have 0 green and full red that's exactly what we have here in the bottom bottom right corner we've got full red not green at all now if we look at the y-axis what we should have is for the top-left corner which is this we should have no red at all and full green which is exactly what you see of course we've got full green or red and then finally for the top right corner which is 1 1 in terms of our texture coordinates we should have full red and full green which red and green mixed together give us yellow so you can see how that works now this whole thing that I've just demonstrated is super important for you to get a grasp on because this is how you're going to be visualizing any kind of data that you actually write in your shader making sure not just visualizing but validating any kind of data on your shed and making sure that what you've actually done is correct because as this series goes on and we start exploring 3d rendering and pretty lighting and physically based rendering and all these kind of rather advanced I guess shader algorithms that are going to require a lot of maths and a lot of mathematics kind of validation because we need to see mathematically not just visually here's a reflective sphere cool no we need to see visually whether or not what out what we've written and all of our maths whether or not it actually works out and that might involve looking at every stage of our equation outputting it as all of these values so that we can actually visualize them as colors on our screen and that's kind of what we're doing here so this whole thing is really really useful pretty much every engine out there is gonna have some kind of tool for validating this like for example your normals you might want to output a normal instead of Eric rendering your 3d model with full color you might just random in all normals only so then now you can see all the colors through 3d model and that way you can validate whether or not the normals that you've actually provided are correct because if you get some weird colors you'll know what's wrong so for example in this case if we messed up so instead of making this one one we made this kind of this bottom right coordinate one one if I hit at five you'll see a completely different gradient and you'll know that it's wrong okay so god is this smooth gradient right we get this weird kind of thing here where the bottom left of course is zero zero but now that what I'm right we can see is one one because it's yellow the top right seems to be the x-coordinate is on one which is correct but the y-coordinate is flipped because the y-coordinate is 0 we've got no green here at all that way we know that's wrong this is still right so really we can tell just by looking at this and this kind of comes with experience as well but if you even if you don't know what it should be you've never seen this before you don't know what's wrong just think about what it should be right because I can tell just by looking at this that this is your zero that's correct that's zero one that's correct but these two are flipped because this should be one one so we should have full green and full red which would give us yellow but we don't this is where our full yellow is all right and this should just be read-only and not green rolled so I know that these two are flipped if we look at how we render this this is like our first vertex and our second vertex our third vertex and our fourth vertex so clearly the second and third vertices their texture coordinates need to get flipped so these two which is what I just changed B to get flipped and of course we'll just do that by sending this to zero and this to one and we should get the same result as before all right I never go that's perfect so that is how you validate your data make sure you do this pretty much any time that you're doing kind of shader work again nor necessary for something as simple as just trying to sample this extra and render it on the screen but as we get a lot more advanced in this series we'll be relying on this kind of stuff a lot in fact to the point where we'll probably develop like debug filters essentially which instead of rendering our 3d scene they'll just render everything as normals only or lighting only or mana no vertex colors only or stuff like that just to see exactly what everything is and it could just could even be as advanced as you know just just render the environment map just render I don't know just friend a little ice with no huge baseline and just do this and that because that kind of stuff is really important for validating pretty much everything we do because in the world of graphics everything is quite subjective isn't it right like I mean I'll show you a little how do you know that's lit correctly especially when you're dealing with physically based rendering how do you know that it's physically based how do you know this is actually this actually may make sense like all about energy conservation series that we've done they're actually they hold true you don't know unless you kind of visualize it and validate it using something like this anyway let's get on with textures so now we've done that in the we we know that all of our text recordings are working correctly we can revolt to actually loading a texture so to do that we're going to make some new files in OpenGL I'm gonna make an OpenGL texture class and inside render I'm gonna make a texture class so we'll start with that we'll just go right click add new item we're going to add two files one's gonna be a header file called texture H and then we're gonna also add a CPP file it's gonna be called texture don't CBB texture CPP will include our precompiled header and also our texture header file and then inside our texture header file which is this one here we'll just use name space hazel here we'll make a class called texture now what I usually do is I'll split up textures based on the type that they are even though some textures like all textures will have certain properties so what I mean by that is we will have different types of textures like this could be a 2d texture it could be a cube texture it could be a lot of different things and because of that I still like to refer to things as potentially textures because textures of things that have a like a particular width and height they can be found all that kind of can happen to them right so they do have common properties so what I end up doing is creating a base class called texture which has things like you know a virtual function like get with all right which is where we'll return the width of the texture it might have it'll have to get high you know we'll have get format and other things that we don't really want to deal with today and then we'll also have stuff like bind right so we'll have virtual void fine like that so we'll kind of make this there's gonna be real super simple it's not even going to have any kind of like static credit function because it's just a texture we don't it's this is just a pure virtual kind of abstract class it's not a company instantiate it because it just represents the texture now this is going to be an actual texture it's gonna be our texture 2d so what this is is a 2d texture right and this will be a texture but this will obviously still be abstract just like alpha a text or a class because it needs an implementation by a specific renderer API which in this case will be OpenGL so we don't need to copy get with get height this function that this do some class or this yeah this class will have this class will have all the you weren't override any of these at all because they'll be implemented in another subclass however if there's anything specific to just 2d textures that for example doesn't apply to cube textures or doesn't apply to all textures we would put that those functions in here but really I think because yet with get high bind that's all we really needed we'll leave them in the superclass here inside our base class and then in our texture 2d 2d class all I'll do is I'll just have a static rep texture 2d right create now what we'll do is we'll take in a file talk in the future we'll expand this API massively because we want to be able to create textures just from memory we might want to create a solid color texture through my walk we might want to have a texture factory that will create things such as I don't know grids or gradients or solid color something like that because they can be really useful to actually test out and and another thing we might have is a narrow texture that the engine generates so so for example if texture couldn't be loaded for whatever reason the asset is missing instead of crashing or displaying black we could display like a very colorful grid maybe or even a very colorful grid that pulsates or something not and or something crazy so that when you look at when you launch your scene you know immediately are that texture is amazing like magenta is another thing that people do for like missing textures I'm using assets or anything like that so there's a lot of things that what kind of go in here not just necessarily loading textures from a file path okay so that looks pretty good to me I think that's about it all we need to do all we need to actually implement inside our texture CBP file is that create function so I'll copy the create function will include hazel one clear two things will include castlecore - and we'll also include strange which is really both of those things for this file and then in here will create the create function so texture to the create this returns a ref so a shed pointer and then we'll basically just go to like vertex array and we'll copy a sorry not opengl Bettis we're just our regular generic vertex array and we'll copy this now this would be a good time to also replace these with wraps I'm not going to do that now but that's definitely something you guys should do for homework I guess and then also something I'll do in some kind of maintenance episode will include this include two things so we'll include the renderer dot H cutter file and also I'll include platform up in jail OpenGL textures of H which is something that we're not going to wish something something that doesn't exist yet but we're about me so OpenGL texture and then we'll forward that path down like that and then of course this will return it um especially instead of returning new a new texture watertown a make shed or in jail texture routine and this will be this will take that path in like that okay so we end up with something like this and of course this we're about to create so over here inside platform open shell I'll add two new files one is going to be an OpenGL texture age so I had a file and then the other one's going to be our CPP file open jail text read they'll say V will include our PC H will include the header file back in the header file will include our texture so hazel hazel Bandra texture and then we'll make the OpenGL texture 2d plus there won't be an Open GL texture gloss just texture 2d because this will just be directly off of texture 2d the reason we have that kind of stupid generic base class is because sometimes it might not matter what the texture is it could be a texture 2d could be a texture cube we don't really care because a texture at the end of the day is still something that we can bind for example and use and then it's not really if we might not want to create two functions one for cube one foot to knee to handle something as simple as binding so that's why we have that this will have a constructor one thing I forgot by the way the vessel destructor I'll add that under second but we'll have it's taking a path then we'll have a virtual destructor opengl texture 2d and then we'll basically implement everything else so often we add that destructor so virtual texture destructor you'll just leave a blank like that or we can actually set it to equals default should be a little bit nicer and then we just need these three functions so I'll copy them paste them in here leave them as vegetable replace the equals zero with override get with and get high we can immediately just set to return width and height which will create a new minute and then bind of course we will just leave us a constant of ride okay so we're time width and height looks good to me we'll make a private set of member variables field I'll keep the path around not something we probably need too much at runtime it's useful to have this for debug bills and for development bills of your engine because for example you might want to hop reload a texture so basically what I mean by that is you recreate your texture and then you just save it and you exported again and then you want to immediately have your end pick up the fact that the files changed and then load that in so you might want to retain the path for that whether or not the path should be in the actual texture is questionable you could for example have an asset manager that handles hot reloading and thus it would maintain you know a path like a map from a path into an actual texture like instance a texture object like this we don't have an asset manager so for the minute I'm just leaving the path inside the open gel texture class so width and height I'm just becoming UN 232 T's with the name height and then finally we'll have will have the same for a renderer renderer ID okay and that's all we need so if I just go over here and use visual assist to create all the map method implementations here we have them I forgot to actually have the Havel namespace we'll have to quickly fix that up I'll just get rid of hazel from all of these areas here okay and now everything is pretty good so we'll set the path to be the path and now what we have to do is actually load out image so one thing I'll do is just quickly include glad because we need to access the OpenGL API here and then now we have the task of actually loading our image now there were a bunch of libraries that you can use you could of course not use any library and just kind of load an image write your own code Dakota now technically speaking in an engine as an engine asset you probably wouldn't rely on any of the existing image formats like a PNG or a TGA or a JPEG not sure anyone who would use a JPEG but anyway my point is you would rely on those image formats you probably make your own texture format which you would build kind of a like build time through your kind of build pipeline you create like a texture and Photoshop and save it as like a PNG or a TGA or a tiff or something like that then load that into your kind of build pipeline and build that into like a hazel texture file and then that's what you would load a runtime in your engine that way it'll be probably in like a more optimal format that you actually care about when I went on at the stage of building an asset build pipeline in our engine yet so we're just gonna load in PNG files for now but in the future that will probably be replaced so PNG files how do we load them easiest way by far is by using something called STV image s she is like a library of various a-plus-plus like helper classes essentially or normally classes but you know what I mean utility functions that help us do things in C++ for example load an image and the STP image library actually it's a single header file and it includes everything we need to load PNG images JPEGs a whole bunch of other formats so we're gonna grab that off github and use that okay so here we have the SUV library I'll leave a link to this in the description and over here we have just one file that we care about which is STB image dot H if we click on it you'll see what the file actually is is quite large seven seven and a half thousand lines but that's it that's all we need to load all of these image formats so we'll click on raw here to just get the whole file then ctrl a ctrl C to copy the whole file we'll come back in to hazel what I'll do is I'll actually put this into the vendor folder so inside hazel and then vendor I'll make a new folder called STV underscore image and then insiders to be honest core image I'll make a new item which is gonna be a header file and I'll call this the same file name as it was in STP which is STP underscore image I'll delete everything from this file and paste in what we just copied now we have the whole file here all the license information of course is just here right I mean this one is just afraid to use of course public domain which is great these are all the file types that it actually supports so you know we have TGA we have PNG I mean we even have jpg and kind of I think a limited version of the Photoshop file format as well so it also tells us what we need to do to make it work right so just somewhere we need to define this and include it right so that's what we'll do so we'll set that up now actually what I'll do is add a new item I'll call this STP image dot CPP will include our PC H of course because we have to and then we'll just copy and paste this example code and just paste it in like that ok so there we go we've got everything here we need to set up a few things though in pre make to actually get this to work properly so what I'll do is just open up this folder in a file explorer he'll go to hazel we will grab that premake file put it in here if we take a look at what we've done to other files what we need to do is make sure we compile this file this CBP file so I mean we've added it to visual studio now so it will compile but we also need to make sure that it works with when we whenever we want to regenerate a project so what I'll do is just put in project name vendor and then yeah I mean we haven't done the whole bender folders of course I'm not gonna do the whole vendor folder we've done just GLM here so we'll also do the same for STB underscore image okay so about the seam key and we'll also do the same for the header file just so that it is thing actually included in our project and then we also want to include it as an include directory so we can use it throughout our engine so I think we had GLM here we did kind of well this this little struct of everything here so I'll do STP image gear as well which will be hazel hazel vendor STV image like that and then all we have to do is inside our include directories copy and paste this hello comma and just do st be honest port image okay so that's that should be everything just to test that out really simply we could just come to our hazel the root of our hazel repository rerun generate projects everything should generate successfully and we can reload everything in visual studio to see if that worked so now two things should happen first of all this STV image should be included so we've got other stuff there's not included like this for example but you can see this stuff is if we go to STV image LCVP we should be able to hit ctrl f7 to compile just this file and inside our output you can see that this should complete successfully and there we go so now that's done and we verified that's working because we added it to the include directories we should also be able to go into OpenGL texture and just type in include and then STB image dot age like that and then we should be completely fine with that right okay beautiful so now we can actually load in that texture so how do we use this to be image well the really really the only function that we care about well there's a few but um the only one we care about is actually just SC bi load right so that takes in a console file name it outputs X&Y being the dimensions of the image to the width and height of the image that it's red it outputs the number of channels in file and there's also an additional parameter at the end called desired channels which actually lets us kind of convert the image into the format we want so if the image is like an RGBA the actual file is like an RGBA we can convert it into an RGB so we can read it as an RGB which is pretty cool but usually what I do is leave desire channels as a zero which means it's not going to change the format at all and then we'll just we'll just see what the format is and then react to that so and this returns an s2 bi you see which is just an unsigned char pointer okay so STP I you see for being all cool and we want to use their types we have basically our data which is our image data inside load we'll just do path dot C string to get the C string how about path X&Y and channels and file are output parameters so we'll do with height and channels so we now pass in a met the memory address of width height and channels so that we can get that data out of it side channels stays at zero and if we take a look at this DVI you see for fun you can see that all it is is an unsigned char ok cool so then we have that's it really if it doesn't work out it returns a null pointer so we can just do a check for a set dot up and then be like you know it fails to load image just like that to verify it that's correct and then we can continue on with everything else so scpi load unfortunately gives us our the width and height as a as a signed integer non unsigned integer so we don't have to quickly raise fine everything and then channels will kind of just hold on to we won't deal with that just yet but that will be important for determining the actual format of the image when we upload it to OpenGL ok so now the next step is to actually take this buffer of data and upload it to OpenGL I'll put it to our GPU so the first thing we'll do is actually use GL create X just to create context it'll be a 2d texture right when it read just one texture on the GPU and then we'll will actually save that ID into our render ID variable and then the next thing we need to do is actually allocate some memory on the GPU so that it can actually store all this image data so we'll use GL extra storage tree for that it asks us about what texture obviously general storage core so that what our render ID is the number of levels so if we had MIT maps which we're not going to deal with right now we would basically have to work out how many MIT maps we needed and then put those put those levels in there but we'll just deal with one for now the internal format being how OpenGL actually stores that so you can change this into what you want if we're using for example srgb you would use srgb over here we're just dealing with a normal HIV 8 texture which basically means that we're dealing with a texture that is a GB and it has 8 bits per channel and then I think that and then the width and the height rise to M with an M height and you can see OpenGL wants unsigned variables here because it wants GL sized eyes that's another good reason for us to kind of store them as unsigned integers now we've done that we can actually upload the texture before we do that though I like to set some texture parameters which pretty much all requires so Jill texture parameter I so for our texture which is a renderer ID we want to set a few parameters so specifically we need to deal with what happens when we render our texture on geometry that isn't like a one-to-one mapping so basically OpenGL will have to either shrink our texture or expand our texture so that's called minification and magnification so gel texture min filter means what kind of filtering we'll use for minification we don't need to deal with maps at the moment so we'll just use geo linear for now and we'll kind of see what happens and then we do the same thing for magnification so if we render our texture on geometry that is larger that basically the geometry will have more tap more pixels to fill than the size of our texture we need to scale our texture up what kind of filtering do we use for that scaling up we use linear filtering which will basically just linearly interpolate to work out what color we want okay and then finally now we can upload our texture so we do this by doing texture sub image 2d and we have a bunch of parameters here quite a lot actually so render ID is the first one we have a level to which level are we uploading just level zero here because it's we've defined one level and that's kind of the index of the level which is zero the X offset and y offset because this can be used to actually upload a partial texture so you can tend you could change a region of a texture that's already uploaded width and height being M with an empire the format this is the format of the data that's incoming so the image that we'll be loading will be RGB so I'll just type in GL RGB you can get this out of channels by the way and make sure that if the channel says three for example its RGB if it's four then it'll be RGB a so we can validate that as well and then finally the not finally but the type right so this is the type of data that you can each opengl it's just a bunch of unsigned bytes right that's what our unsigned char data is and then the pixels right so this is where our data pointer actually is okay that's it that's done that's all the opengl code we actually need so now what we want to do is once we've uploaded the data to the GPU we no longer need to retain it in our cpu memory in the future you can of course retain it if you want usually you'd have like a parameter being like please retain this when you create the texture but since we're not dealing with that stuff right now we can just do SD pi/3 and then and I think there's a specific function for this and I think it's STV is an image free SC bi image free which will free s DPI image free and then we'll use that data pointer and that will actually they'll actually deallocate or free that memory that was used to actually still there's a massive you okay done so inside our destructor will call GL delete textures look like one texture and it will be our renderer ID and finally for bind what we want to do is use GL bind texture unit so what this will do is bind our texture right at a particular slot or unit as OpenGL calls it so we'll just be binding a slot 0 and we'll find our and righty now this is this slot kind of refers to what kind of you know I guess texture you know we bind our texture in to because you can bind a lot of textures in fact you will have to bind a lot of textures when you render something more complex and then you'll have to access all those textures kind of at the same time in your shader so because we have texture units so that we can bind more than one texture at once typically you would want to actually set the unit so what I'll do is I'll spray it wasn't going to do this now but will do it will create a UN 32 primer which is going to be this lot right and that's what we'll find our texture to that kind of selects the slot that we get bouncer over here I'll put this into binds and then in texture duty as well I'll just put this into bind and we'll have a default parameter of zero just like that and I can do the same thing over here just so that if we don't specify a slot it binds us to slot the zero okay cool that looks pretty good to me if we go back to our OpenGL texture class I think we've got everything that we need to actually render this let's load a texture so what I've done is I've prepared a little texture here it's just gonna be a checkerboard I've put this into into the sandbox in inside the sandbox directory inside our hazel sandbox I've made a directory called assets and then textures and then inside there I've created this checkerboard dot PNG file which as you can see is just a checkerboard right pretty cool this is a really nice session tip test we can also test whether on is the right way up because the bottom left should be like a black square and the top one should be a white square cuz sometimes we can have our images our textures uploaded with a vertical flip which we which we wouldn't want and this texture is just RGB not RGB a and we can obviously validate that ourselves if we need to but that's why I've used show RGB here okay cool so now let's just make sure everything compiles first of all I'll just compile our whole solution here by hitting ctrl v to beam and I'll see if this works okay so we got a few errors here we go syntax our own texture because we haven't I think we have either you have too many parentheses here so we'll just fix that up and we also have a new line in constant so there's yeah we forgot a closing quote yeah so we'll just recompile with those changes and hopefully we'll be good all right looks good to me so now what we'll do is actually load in that texture inside our sandbox out so I'll come over here into sandbox app I'll go to the bottom one thing we'll do quickly before we do that actually is going to our hazel header file and just make sure that we're actually including this so maybe just under I'll include hazel render a texture LH then back in sandbox up I will make a hazel wrap it's gonna be a hazel texture 2d and then we'll pull it em texture then over here after we create all about shaders I will come over here and just reset our texture to be a hazel texture 2d create and and by the way you could actually store this just as a texture that's fine but we're storing it as a texture 2d so we'll supply part which will be assets textures and checkerboard checkerboard PNG and now all we really need to do to actually bind this is just before we render our actual square we just do combined and then we can put in a slot or would you skip it a zero before okay so that's everything done from the kind of texture side of things now we need to modify our shader to actually sample from the texture currently is just looking at those Tasha coordinates which is great but now what we need to do is actually sample from the texture so we'll get rid of this uniform which is just doing nothing doesn't really isn't used anyway that's how color and we'll change this to be a uniform sampler 2d right which means that this is going to be sampling a 2d texture and we'll pull this do you wanna score texture and then our color instead of this will be texture which is a function which lets us sample a texture you on the spot extra will be the sample that we're using so that'll be a texture and then we need to basically provide it with a set with a 2d coordinate of we're in the texture to sample from so that is what our texture coordinates are used for and that's what we'll use here okay and that's it that's done right that's all we need to do everything should work it's really really simple so now what we need to do and the kind of last unresolved thing is this actual sampler right because this sampler is not set it's a uniform it needs to be set we need to set it from the tube from from the CPU so what we'll do is the same way we kind of set this up you can see that we bound it and then we set our you color will do the same thing I'll just copy these two lines of code we don't need to do this every frame though let us put it over here we'll grab this texture shader will bind it and then instead of uploading a uniform float 3 I'll just move this over a little bit so you guys can see instead of uploading a uniform flow 3 will upload a uniform int and what this is is our texture uniform which is a sampler 2d but a sampler 2d is just simply an integer and specifically it is the texture sloshed to sample from so because we bound our texture at slot 0 right that's why we upload 0 as the uniform int here because that's kind of the mapping that we have now again usually just to complicate things a bit more usually you would figure out all of the all of these slots when you create your shader so when you create your shader you see for example there are 4 textures you just add a pond shader creation just just assign slots to everything so this first the first uniform will be slot 0 then the first uniform texture the first uniform sampler will be slot 0 that slot 1 there's a lot - that's all three down and dust have forgotten about then when you want to render a particular particular texture and you want to bind it to that uniform name it knows the slot that it has to be and when you bind your texture that's where it actually gets a slot from from the shader because that way you'll never have any kind of our woops I set the wrong slot you don't want to do that stuff manually your engine will usually handle that automatically for you and that's something that we'll do probably around the time that we actually create a proper material system ok so that should be it if I go ahead and just hit f5 everything should work now make sure we're using the right shader which will be our texture shader make sure we're mining the texture yeah everything should work let's hit f5 and see what happens ok so we did get a compile error now I did forget that this doesn't actually share the Krait used to return a pointer this doesn't return a pointer which is really nice because now all we can do is just say that M texture equals texture career C that's beautiful we don't need to do dot reset and set the actual value we just assign it to that shared pointer cuz already as a share pointer right it's a hazel graph which is awesome and clean up our code quite a lot so let's hit a 5 once again and we'll see what happens and check this out so what we have is our texture now there's two issues with it first of all if we look at the original texture blacks supposed to be on the bottom but on top because that action has been better be flipped and that's just because OpenGL actually expects our texture to be given from a bottom to up fashion whereas STB image by default reads it from kind of top to bottom instead of bottom to top so we need to flip it the other thing is it looks very blurred it doesn't look as sharp as this now you'll notice here that I'm actually at eight hundred and sixty nine percent so I've zoomed in a lot and the reason that is still so sharp is because it's not actually filtering it linearly it's just it's not doing any kind of filtering at all so we need to actually change our filtering type to get it to be sharp so that's what we'll do first of all so we're go to OpenGL texture dot CPP we'll change this and we don't really care about the minification filter thus if we make it smaller the magnification filter is what happens when it gets scaled up so we'll change it from GL inning out two GL nearest and see what that looks like if we hit f5 everything should now be very sharp because it should just now be snapping to the nearest pixel instead of linearly interpolating and basically resulting in a blurred image linear linear filtering is great for like images for example but definitely not for a for something like this where it's just two colors so there we go that looks perfectly sharp now we just have the problem of the vertical flip in the future obviously we do want to expose this to the API because you should be able to set what kind of filtering is used and whether that's like this kind of just scaling kind of filtering when we're minification and magnification filtering and isotopic filtering anything else like that should be configurable so that we'll make it to our API in the future but for now we're just gonna leave it like this the last thing is to flip there's actually a really nice function called flip vertically on Lord I don't even remember it's STV I set flip vertically on load and it's just an int flag true if should flip so we set that to one before we load our data and if we hit a five it should now flip it into a format that OpenGL expects which is bottom to top so if we look at what the result is you can see we have our black square on the bottom our white square on top which is exactly what our image looks like in well I mean I'm using Visual Studio code to preview images because that's how I roll but in any as well you probably see it this way so that is a texture on the screen pretty cool stuff didn't take that many lines of code either it's pretty simple to do and we now support textures which is amazing now there are a few other things I could talk about such as like blending if you try and load a texture with alpha for example because we haven't covered blending at all we haven't even enabled blending it's just it's you're not gonna see that alpha transparency at all there's all these filtering things is also supporting RGB and RGB a what about srgb are there's so many things and we will expand on this because as I said textures are extremely complicated but I hope you guys enjoyed this rather long video if you did you can hit the like button you can also help support this series and everything I do on YouTube by going over to patreon icon force actually the channel this is now my full-time job making videos for you guys and I want to do the best job that I possibly can I also want to stay in this job as long as possible without having to look for like work for a company so if you could help support a series that would be absolutely incredible because it's people like you that make this stuff possible as a reward you will get access to many things such as exclusive videos just for patrons you'll get access to a development branch where I've actually done all this stuff already I'll show some footage from the development branch because this week I actually finally published the kind of animation code that ideas so that we actually have skeletal animation working which i think is pretty exciting so as I kind of developed hazel in my spare time and that's eventually where these videos will head towards you kind of get access to that code just immediately which i think is really cool next time we've certainly got a lot to talk about I'll see you guys then good bye [Music]
Info
Channel: The Cherno
Views: 27,546
Rating: undefined out of 5
Keywords: thecherno, thechernoproject, cherno, c++, programming, gamedev, game development, learn c++, c++ tutorial, game engine, how to make a game engine, game engine series, textures
Id: qEfohFgQ1-I
Channel Id: undefined
Length: 51min 47sec (3107 seconds)
Published: Mon Aug 19 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.