Textures in OpenGL

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys my name is HN and welcome back to my OpenGL series so today we're gonna be talking all about textures in OpenGL and we're gonna start by defining what exactly a texture is so when most people think of textures they really just think of having an image on a 3d object in a game it doesn't have to be a 3d object of course but some kind of image that you've created in something like Photoshop or paint or paint or whatever having that on some kind of surface in your graphical application that is essentially what a texture is or at least that's how most people actually kind of think of a texture now beyond that textures can really be a lot more than just what I just described textures can be used really for a lot of things and we'll discover that when we get into some pretty complicated graphics techniques but for now I just want I just want you to think about a texture as essentially an image that we can use when we're rendering something so what I can actually do is design or create some kind of image whether that be a photograph or maybe just a squiggly line on a canvas or maybe like a kind of solid color or something like that anything like that I can create any image file on my computer and then I can actually upload it to my GPUs memory and use that in my shader for whatever kind of drawing I'm doing now this might be something as simple as just drawing a rectangle in our OpenGL program the contained that is actually rendering that texture so that we can see the texture in our game or it might be something a lot more complicated like using kind of pre-calculated mathematical values that are baked into our texture and then being able to sample them in our shader so that we can do some cool lighting effects but for now is just going to be getting an image from our computer onto a surface in our OpenGL application so let's take a look at what we've got here so if we launch our application right now just so that we can remember well we're actually at you'll see that we just had this kind of rectangle with a pulsating color now what we're going to do today is actually draw a texture on that rectangle instead of this kind of solid color now if I look at my actual directory for this OpenGL program I've made this resources folder and inside there I've made a folder called textures which just has a single image and it's gonna be this cheddar logo that I have here this is what we're actually going to draw on top of that rectangle that we have in our program so how do we do that how do we go from having a physical kind of PNG file on our computer to actually being able to sample it in our shader so that we can draw it on the screen how does that how does that work well step one is we need to load that image somehow into our CPU memory right so in this case it's a PNG now I'm not gonna get into how games use textures and what formats that all that will say for the game engine series PNG is aren't really used in game engines most game engines have their own texture format for various reasons but for now just because again this is all about loading opengl we do have a PNG image file so what we need to do is load that somehow now PNG format isn't particularly complicated but there are a few kind of there are a few different pixel formats inside there as well as primarily the option of having a compressed PNG as well and there's a bunch of decoding that we actually need to do to get the data out so what we're gonna do is we're gonna use a library for that and specifically we're gonna use something called STP image which is a very very small that's all we're gonna do we're gonna get it from github in a second here but all we need really is one file and we're gonna use that to actually load the PNG now when I say load the PNG what I mean is we give it a file path so this is my file on my computer and what it's going to do is give us basically a pointer to a buffer of rgba pixels which is what we actually want to then take to the next step which is going to be taking that pixel array and uploading it to our GPU and the way we do that of course is through opengl because opengl is our graphics specification so we use opengl to then send that data into the GPU as a texture once we've done that we can modify our shader to actually read that texture when it's drawing so the pixel shader the fragment shader is going to read from that texture memory and actually work out which pixel should be which color as per that texture and that's what that's what will happen when we actually draw that rectangle of ours so just to recap overall and we'll there are some detailed steps kind of in between that will address definitely but that is the gist of it right we load our image we create a texture in OpenGL we then have to bind that texture when it's time to render we modify our shader to all sort of kind of bind to that texture slot that we've bound our texture to and we also sample that texture in our shader and when we draw a rectangle we should see the texture so let's just go ahead and write some code so the first thing that we really need to do is load this image somehow into our CPU memory so that we can send it to OpenGL so over here what I've got is a github repository which I'll link in the description below and inside this kind of STB library which single file public domain libraries for c c++ so you can use this completely free there's something called STP image and really there's just this one file that's all we need I'm gonna open that file I'm going to click on raw here and just to make this really simple for myself I'm just going to ctrl a ctrl C so I'm copying all the text inside this file here and back inside my project I'm going to make a new source file here now I'm gonna make a folder called defender and inside there a folder called STB on the score image just so that I can kind of know that this isn't only my code I'll make a header file called STV underscore image H and delete everything from this file and just paste in what I've copied and you can see it's about seven and a half thousand lines of code yeah ok cool now if you actually read this file this is what you should do when you're when you get new file from the internet and your libraries and all that it basically says pretty simply do this write define SCB image implementation before you include this file in one see your simple plus file to create the implementation ok so it should look like this it even like gives you an example right so what we need to do to actually compile this code once and once only is we basically add a new item I'm gonna add a CPP file I'm gonna call this STV image now CPR score image dot CPP I'm going to include STV image dot age but before I do that as it says over here I'm going to define STP image implementation and that's all I need to do and if I hit ctrl f7 is going to compile all of that SUV image code there we go done simple as that now the next step is going to be to create a texture class for ourselves that we can kind of organized over all of our codes I'm going to add a new item it's going to be a header file we're going to call it texture dot H I'm also going to add a new item which is going to be our secret B file of course textured LCVP going to include texture H in here and this is just going to be now a texture class I'll include the renderer in here so that we have access to OpenGL and everything we need and we can start with creating our texture class so inside here I'll make hit render ID as we have for pretty much everything I'm also going to keep track of the actual file path about texture I'm going to define an unsigned char which is going to be our local buffer this is just going to be local storage for the texture and then finally we also need the width and the height and the bits per pixel of the actual texture all right cool and then inside here public I'm going to make a constructor which is just going to taking a path that's all we need I'll make it the structure as well so we can destroy everything when we need to a function called bind and unbind and that's pretty much it now I will make some like helpers here like get with for example which will just return the width and height just in case we want to get that later but in general I like to cut about these things as I actually need them instead of pre defining all my class because otherwise it takes quite a while to just think about well I wonder what I want this class to have when you can just extend it later now bind I'm gonna change a little bit I'm actually going to put an unsigned in slot equals 0 over here so what this is is an optional parameter if you don't specify it it will be 0 which basically allows you to specify the slot that you want to find a texture to now in OpenGL we have various slots that we can find textures to and the reason that exists is because we have the ability to actually bind more than one texture at once so on Windows typically in like with modern GPUs you have about 32 texture slots or as on mobile like on an Android or an iPhone you might have like 8 for example again this really depends on the actual GPU that you have and their implementation of Jill you can always ask OpenGL hey how many Texas law still I have but just to give you a rough ballpark on went on like on modern GPUs you have about 32 or mobile GPUs you may have eight that's roughly what it will be but of course you can just consult your platform to find out so I'm going to right click on texture and using visual assist just create all of the method implementations that's just going to create these implementations for my constructor and all of my methods I'm going to in my constructor assign file path to be path when I set basically initialize most of these things local buffer will set to null pointer width and height to zero and you should really try and do these in the same water they're defined in mr. pixels bits per pixel are set to zero and it looks pretty good so the only thing I really did was actually I did do it in order and right here was the first thing zero alright cool so there we go I've got some I'm initializing all of my variables and then what I'm gonna do here is just start loading the texture somebody's jail call GL gen textures swim generate one texture and assign it to render OID I'm then going to go ahead and bind it so we bind it by just calling GL bind texture now we specify kind of like what we do with like our vertex buffer well when where we actually bind it we specify kind of which what kind of buffer it is or what slot with running it to so in this case is going to be a GL texture 2d because it's just a two-dimensional texture we might talk about the different texture types in the future when we get to them renderer ID is the texture that we're binding now let's go ahead and actually load our image so to do that we need to include STP image so I'm just going to type in include as tip now of course this is going to be in vendor slash STP image does this to be image if I was actually properly setting up this project I might have actually included the vendor folder in the include parts for my compiler so that I weren't after the kinda vendor but this is fine now actually before we do all this let's actually load the image now the first thing I want to do is type in STV I set flip vertically onload now the reason we need to do this and then I'm going to just set this to 1 so what this does is actually flips our texture vertically so it makes it upside down the reason we need to do this is because OpenGL actually expects our text to kind of start at the bottom left not the top left right so the bottom left in OpenGL is zero zero now typically when we load images like PNG for example stores it in scan lines kind of from the top of the image to the bottom of the image so we need to actually flip it on load this really does depend on your texture format so definitely take a look at this for our use case for our PNG here we do need to flip it so just if you see that your textures upside down or whatever you can play with this flag to make sure that you get it the right way up okay the next thing we're going to do is assign M local buffer which is our local storage for our texture to BST bi load now we're gonna have to specify a file name which is gonna be path dot c string then we have pointers to X Y and channels which is just going to store all of our actual width and height and all that so we've already variables for this we just pass in the memory address and what this scpi load function will do is actually write the width and height and the bits per pixel or whatever into those actual variables for us so but we're passing pointers to these variables so that this function can actually write to them and desired channels is kind of how many desired channels we have so we want RGB a so that's four so I just want to type in four like that okay so once we've got this we've actually got all of our texture data inside this local buffer here what I need to do now is set up some actual settings I guess for our texture that we've just generated here so the way that we set any texture parameters we called GL text parameter and then the type of data with runstats so this is gonna be an eye for integer so the first parameter here is the target which is texture 2d the next slot is the actual parameter name which in this case is going to be GL texture min built up so this is the minification filter this is how our texture will actually be resampled down if it needs to be rendered smaller than it is kind of per pixel we're gonna have an in-depth episode on all of these kind of texture parameters because like dealing with MIT maps and kind of minification and magnification filtering and like you know edge clamping all that is it kind of a big topic then I don't want to just squeeze it in here so we will talk about this stuff in more detail in the future don't worry but for now you think could just kind of use these settings and I'll explain them as I go so this we're gonna set to geo linear which just going to linearly resample everything so we'll kind of still look ok it's not going to snap at the nearest pixel which is what gel nearest would do we also have to specify the magnification filter which is if we actually kind of render our texture on an area that is larger in pixels than the actual texture size so it needs to scale it up we also need to specify the clamp modes so Jill corrected gel text parameter I geo texture 2d gel texture wrap now I call them clamp most but they're just they're really wrapper eyes so wrap s the horizontal wrap so we wanted to clamp which means that we we wanted to basically not extend the area the other option is tiling so we don't want to talk about textures here so s and T is kind of like X&Y but for textures and these four parameters you need to specify if you don't specify these four parameters you're just gonna get it like a black texture which is a little bit annoying because you kind of expect them to maybe be on sensible defaults by default but they're not so make sure you specify these four otherwise you may and probably will get a black texture but of course that does depend on your OpenGL implementation okay cool next step is going to be to actually give OpenGL the data that we've read in yellow so we've got a bunch of pixel data inside and local buffer let's actually send it now to OpenGL so the way we do that is via an OpenGL called called gel text image 2d so the target is going to be GL texture 2d the level this is this is at this point you probably want to read the documentation to figure out what all of these are a lot of these are completely you're all irrelevant to us I'm gonna kind of skip past that we will look at advanced textures in the future so we'll cover them then but for now we're gonna levels be zero because this is not a multi-level texture internal format is going to be GL rgba that's how opengl is going to see our actual format RGBA not the format of the data we're supplying but how we want open shell to deal with our data internal format vs. format is a huge topic in itself which will probably even dedicate an episode to because people confuse that all the time but internal format which is the first format that's how OpenGL will store a texture data whereas format is the texture data is the format of the data you're providing OpenGL with so in other words the format of M local buffer now in recent implementations of OpenGL you should actually call this GLG ba8 okay so you should be specifying the actual per channel which is how you want OpenGL to store your texture so geogebra is kind of deprecated it will still work on most implementations but you do technically the correct modern OpenGL way to do this is actually to specify a number here which says how many bits you have per channel all right so width is going to be M width which is what we read in from our s TV I load function then we pass in M height for the height of our texture border we definitely don't want a border so is your pixel border so format is that's like the second format so that's the format of the data we're supplying in geology ba is what it is we don't use numbers here the type is the type of data so unsigned byte right so we have jail RGB a and each one of these channels is an unsigned byte and then finally we have appointed to pixels now you can make this you can actually pass in a null pointer Heroes as your or something like that if you're not ready at this stage to actually give up and share your data what that means is that you're just allocating space on the GPU because it knows how much space it needs but you're not actually providing it with data now we already have the data so I'm just gonna pass in m local buffer just like that alright great and that's really all we need so what I want to do now is unbind our texture by just calling GL buy into texture 0 could also called the unbind texture function well we of course need to specify what we want to unbind such your texture 2d since I've got this code here just going to copy and paste this into unbind as well because that's really all we need cool so now what we can do is if m local buffer right so if this buffer does actually contain data when use scpi image free to actually free this local buffer now more complicated setups you may want to retain a copy of the pixel data on your CPU because you might want to sample it or do something whatever you may want to do with that data so that's totally perfectly ok as well totally perfectly okay that's also kind of acceptable right so you may want to have something is something in the constructor that's like you know keep keep local data around or something like that we really care about that so I'm just freeing it but that's why I kind of made it a member variable and local buffer in case you kind of want to store it and that's it now finally over here inside our destructor we could delete the data man we've already freed the data in the console in the constructor so we don't have to deal with that here but we do need to actually jail delete textures to delete our actual texture from the GPU when this object yes tourism gets destroyed so we'll just chill delay textures and passing that renderer ID now bind is an interesting function because we already know how to bind textures it's really just this right but what we can do here is actually specify a texture slot and we do that by typing in GL call GL active texture now active texture takes in an integer which is Israeli jail enum which is going to be wish Act which Texas law we're currently activating so again this is kind of like the whole same machine selecting thing in open channel we basically say hey I'm gonna make GL active texture slot 16 that means that the next texture that I bind will be bound to slot 16 until I call GL active texture again with a different slot so in this case the way that we specify sloths is we use enums right so chill checked a deal gel texture 0 like that is the very first slot now we have texture 1 2 extra 2 or whatever if you actually look at what these are equal to they are of course just integers and they go all the way up to 31 right so you have 32 texture slots now this does not mean that your platform supports that made textures it's just 32 happens to be the maximum that OpenGL that the OpenGL specification allows your platform as I said mobile may have like 8 so you should query and find out how many you have if you are gonna use this many texture slots with you probably usually won't be unless you're making a really big game because there are ways to kind of you know reuse textures and we'll talk about that later but anyway we have all the attach these texture slots now in this case we are specifying an unsigned int now this will be something like 0 through 231 so what you can do is just type in geo texture 0 plus slot and then it will add the slot because if you look at these definitions you can see that they are actually consecutive numbers right they just they do just count up so that's our entire texture class that is all that we need to actually give OpenGL our texture and for us to render and all of that alright so I knew this was going to be a long episode but let's keep going and let's actually see something on the screen because that is the goal for today's video so back inside application let's set all this up so I'm going to at any point really just type in texture texture I'm going to give it the path which is res slash text / sure no logo dot PNG I'm going to include texture up the top because I haven't done that yet and scrolling back down I'm going to bind my texture no I'm not gonna pass in a parameter which means it's gonna be equal to zero by default one thing I need to do by the way with my shaders before I forget is we need to first of all fix out this awful error I made last time when in which I passed in and well basically I make this map have an unsigned ends and I just I use unsigned and a bit too much it seems because this get uniform location function I even check to see if the location is negative one here because that's totally valid that just means that it couldn't find our uniform but I'm assigning it to an unsigned in here which is obviously never going to be negative one so all of this should be an int not an unsigned int when you're dealing with uniform locations and OpenGL the type is a 32-bit integer okay so int and this should also be int and if we go back to our header file this should be int because that's how uniform cash and also this should be int and I think that's all the ends if I compile there's hope little work and yet we're all good okay that's one thing I need to clear up now one thing that we need to do is we need to actually tell our shader which texture slot to sample from and the way we do that is via a uniform now this isn't really an integer slot it's something called a sampler slot and this is a bit hazy and a bit weird basically what we need to do is send an integer uniform to our shader and that integer is the slot that we've bound a texture to that we want to sample from and then we in the shader code we can actually use that sampler or that integer that we've passed in to actually sample that texture and do what we want with it okay so that's how that works so to do that we need to write another shader uniform function here which is going to be called set uniform one eye which is for integer and then we take in int value like that I'll copy and paste this one F function and replace this with int and that's pretty much it right just make sure you replace uniform one half here with one eye okay cool back to application now that we have this we can actually set that up as well so shader set uniform one eye we're gonna call this uniform u underscore texture and we're gonna set it to zero okay because we bound our texture to slot zero so that zero needs to match whatever you're passing in here five had passed in like texture bind to or something to slot to we would be passing into here okay let's undo that alright cool so there we go pretty simple stuff now one thing we haven't done is actually credit something called texture coordinates so what we need to do is actually use something called a texture coordinate system to tell our geometry that we're rendering which part of the texture to sample from so our pixel shader goes through and renters and rasterizes our rectangle right just goes through the rectangle and draws every pixel the pixel shader of the fragment shader is responsible for determining the color of each pixel so what we actually need to do is somehow when it's up to rendering a certain pixel tell it to sample a certain area of our texture to retrieve what color the pixel should be right if you think about it's actually kind of simple it's not too complicated to understand hopefully this makes sense if you don't drop a coin below and I may I might make a detailed kind of in-depth video on how the rasterization works and how texture sampling works and all that stuff but basically what I'm saying is we need to say that hey the bottom left of our rectangle on our screen is like coordinate zero zero and the top like right is the other end of the texture which in OpenGL terms is going to be one one okay because textures don't really literally tie textures to certain sizes or we don't tie texture coordinates to a certain resolution because the texture could be 64 by 64 it could be a thousand twenty four five thousand twenty four could be like fifty seven by nineteen it could be anything right so what we need to do is basically specify for each vertex that we have of our rectangle what area of the texture it should be and then the fragment shader will interpolate between that so that if we're rendering a pixel that's like halfway between two vertices it will pick the texture coordinate it's like you know halfway between the two vertices plus a fella so to do that if we come over here to our positions we know this one is the bottom-left so that's going to be zero by zero I'm adding another vector basically I'm adding two more floats to each vertex here which are our texture coordinates now this is going to be one point zero zero point zero because it's the right side right so zero point five F is the rightmost edge of our actual rectangle and so I'm setting that to be one which means sample from the right side of the texture this is going to be one point zero one one point zero as well 0.5 0.5 is the top right and that's what we're also matching without extra coordinate and then finally we have zero and one point zero now we need to add this to our vertex buffer layout again with our API it's really easy we just do this layout to push two more floats right that's it that's all we need to do we also need to actually expand this vertex buffer to contain these extra things right so now instead of having two floats per vertex we actually have four floats per vertex so we'll increase this default and finally we need to go into our shader and basically rewrite the whole thing so in our vertex shader we now take in a texture coordinate at location one which is a vector I'll call it the text cord or something like that and now I faced with something a little bit new we need to get this texture coordinate into our actual fragment shader because check this out the way that we're going to actually sample our texture is as I said earlier by using that sampler 2d uniform right so sampler today you want to store texture which is the uniform that we set over here we set it to be zero right so we need to sample this somehow and that's this you touch uniform and the way that we do that is we actually work out the color this returns a vector for which is that kind of like our text color I'll just write this out for you guys text color is going to be texture the texture that we're sampling which is you texture and then we specify those texture coordinates right so how do we actually specify our texture coordinates because they're in the vertex shader so the way that we kind of send data between the vertex and the fragment shaders by something called a varying now in modern OpenGL this is kind of the in and out system that we were already used to similarly to how we can actually input data into the vertex typing in in and also how put data from the fragment shader by having it out we can also output data from the vertex shader into the fragment shader and the way that we do that is we type in out now this is going to be a vector of course it's our texture coordinate and I'll call this V underscore text chord V sense for varying which is what this is called and then I'll assign V Tech score to be the text chord that we take in from our vertex buffer and then similarly in the fragment shader I'm going to type in in back to V underscore text quad just like that and I'm going to use that text cord over here so that we actually sample that specific texture coordinate because remember this fragment shader runs for every pixel so we need to know the precise location in the texture to actually look up so that we can draw that pixel from the texture in our actual scene and that's what this whole sampling thing is about again I'm sure we'll discuss this in the future in more depth ok cool so now instead of assigning this to you color I'm gonna assign this to text color which is our actual texture look up color and everything looks pretty good to me now I did write a lot of code and I'm not sure if this will work as I haven't even run this program once but let's just have five and who knows maybe this will just be beautiful okay so we get a nice little OpenGL eric here if we look at the actual console we can see it's 1280 this is because this is not gel clamp is supposed to be teal clamp to edge in more modern versions so we'll just set this to be GL clamp to edge now one other thing that I noticed when I was debugging this as well is the local buffer was actually null which means we're not loading this incorrectly so let me just refresh this and figure out what's going on with our actual texture so if I so if I just refresh this it doesn't look like we've actually got the textures in here for some reason and that's of course because this results this folder wasn't inside my project directory make sure you put it into your project directory and now if I hit this refresh button you'll see we have textures in here and all that this should work better now let's hit f5 take - all right beautiful so we see the channel logo now it looks like absolute trash why does my logo look like trash well I don't know if I should end this on a cliffhanger or if I should actually finish this I know why it's not working obviously but this could be fun you know what I usually would leave this on a cliffhanger but this is just such a beautifully long episode that I think we might as well finish it it's because I don't have blending enabled and of course as you saw that channel logo did have areas of transparency so there's no transparency being rendered right now it's not blending at all that's why it's coming out weird to fix that we just need to set up a blending function and enable blending there will be an episode on blending and transparency in the future hundred percent guarantee that let's just make this work now the time being so if I close this and I go back into my application or in fact into my render I think did I do kind of OpenGL set up stuff in the render right I think I did no I didn't okay so we'll do this in the application file for now what I'm going to do is scroll up here once I have my OpenGL context it's kind of part of the open jelly initialization I guess I'm gonna set up GL blend func so I'm gonna set a blending function and this is gonna be GL sauce alpha mozzarella GL source alpha and GL one minus source alpha so what we're doing here is we're basically defining how Open GL is going to blend alpha pixels right so this really does require its own episode but essentially we're saying that for the source take the sauce alpha and when you're trying to render something on top of that take one - that source alpha which will basically get the difference of that so it's just really just it really is just a mathematical equation equation like literally 1 minus the source alpha is what the destination alpha is going to end up being again episode on that in the future and then finally we do need to also enable blending by calling GL enable and then GL blend okay and you can do this in any water doesn't really matter I kind of like doing it in this order just because we enable blending and then we kind of set up the blend function let's hit a 5 and we should see a beautiful channel logo and you can see there we go now of course it does look a bit stretched because the rectangle tool for rendering is not square because our window is not square and this is kind of based on window positions but if you can kind of get over there you can we have a beautiful channel logo rendering and that is how we use textures in OpenGL if you have any questions about textures that you want answered leave a comment below because I am gonna make a lot of related videos to textures in the future textures are a really big deal of graphics programming and all of that obviously so we are gonna talk a lot more about them in the future other than that I hope you guys enjoyed this video if you did you can hit that like button and let me know what your thoughts are about all this stuff in the comment section below as always huge thank you to all the patreon supporters that let me make videos like this for all of you if you want to support this series and see more episodes and get videos early patreon a conference at the channel and I will see you guys next time goodbye [Music]
Info
Channel: The Cherno
Views: 106,620
Rating: undefined out of 5
Keywords: thecherno, thechernoproject, cherno, c++, programming, gamedev, game development, learn c++, c++ tutorial, textures, opengl texture, graphics, opengl, gpu
Id: n4k7ANAFsIQ
Channel Id: undefined
Length: 31min 43sec (1903 seconds)
Published: Wed Feb 07 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.