Multiple Render Targets and Framebuffer Refactor // Game Engine series

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys my name is ashana welcome back to the game engine series we are back with finally a new episode for this new year hope you guys have had an amazing break and i've had some time to think about hazel i sure have and to be honest i'm very excited for this entire year because i think this is the year that we finally start actually making content with hazel right so we're getting to a point where we can kind of hopefully after a few more like important features that get implemented in the first quarter of this year hopefully we'll be able to actually create games with hazel and build off of there which is going to be great because the content is going to start driving the development and there's also a lot of exciting stuff happening with hazel dev so ultimately lots of lots of cool stuff coming this year everyone get excited um uh but today we're gonna start off with uh this is gonna be like a bit of a mini series of episodes really so what's happening here is um basically we're going to be implementing something called mouse picking so that basically lets us just click click inside our viewport on an object and pick it and select it right which is obviously a very desirable feature for an editor because we don't want to be using the scene hierarchy panel all the time we want to just be able to just click on the thing that we want to select um and that's pretty uh that's that's pretty standard stuff really so there are a few ways of implementing that um i did actually have uh an episode uh well not only an episode i did a live stream where i just worked out how that would even work and i tried out some suggestions from what people were saying in the community and stuff um and ultimately played around with it and implemented a solution to this whole mouse picking problem i've posted a live stream here on youtube so check it out i'll have it linked up there um ultimately we're going to be kind of working in that direction because that live stream was just me working out how to actually do it in the first place and there were of course a lot of like hacks and a lot of like you know me me not not developing a proper api and not actually writing good maintainable code because i just wanted to see i just want to get the idea down in as short as time as possible that's usually how i like to work i like to i like to actually get the get the code working first and then sort it out and make it you know more kind of uh it's the right word uh i guess generic maybe or extensible to it or just essentially integrate it into the actual engine code base a little bit better last time though we actually in the game engine series not the live streaming stuff we i'll have the video linked up there we implemented the editor camera so now if we hit f5 to run this um we'll be able to see that we can move around our world and i'll show you a little bit about what i mean with the whole mouse picking thing in case you're not familiar with it so we have this uh cube it's not really a cube it's actually left right and top is made up of three quads here that are just essentially um entities that we have in 3d space now for one for all intents and purposes this is in fact in 3d space right this this is 2d 2d geometry it just happens to be two-sided because we haven't uh because we've disabled culling but um backface culling but uh this is this is essentially 3d you could say 3d geometry in a 3d world right um and obviously the camera and everything proves that so in our 3d world how do we then click on something to select it so if i want to if i want to select this kind of left face over here i want to be able to click on it and then that will of course select it inside the scene hierarchy display the relevant properties display the gizmo for me so that i can actually move it around and do all the stuff that i might want to do with it how do we implement that so again lots of different solutions but what we're going to be doing is basically a little technique here where we render we basically rasterize the entire scene so we run through a render pass we rasterize the entire scene to a frame buffer but instead of actually rasterizing and essentially writing the color value of it of every pixel we're going to write the entity id for every pixel so in other words if we were to look at the rasterized buffer we would just see a bunch of entity ids where maybe we clear it to negative one so if we click outside and we we select something it's going to be negative one and it won't be an entity but then um if we look at our entities here they have essentially ascending ideas here zero one two three so if we click on for example this one it's going to actually select this left entity and then uh the way that that will work is obviously these pixels will just all be set to zero and then these pixels over here will all be set to like one and then the camera is two so all of these pixels will be set to three uh and that'll that'll be the data that's going to be stored inside this additional texture um that we basically render data to rasterize data to okay it's a little bit difficult to explain but you'll see it in practice and it's very simple and then all we have to do is just simply work out a way to um basically convert our mouse position into some kind of coordinate within that texture and then all we have to do is sample a pixel at that coordinate and see what color it is it's not really going to be a color it's just going to be an integer that's going to be 0 1 or 3 in this case and then or negative 1 and then when we click on it we'll pick that color and then we'll just simply set that to be our selected entity again if you want to see this kind of you can't wait for the series of episodes in which we implement this um the video that i have linked up there the mouse picking one that will actually show how i implemented this and how i actually worked through it and implemented it for the first time because i had never done this before um i i did a different way of mouse picking inside hazel dev and i haven't never done this before up until that live stream and that that's kind of why live streaming is great i think and there's a lot of um there's a lot of good stuff here uh and um uh twitter tv slash the channel which i'll have linked in the description below is uh where i usually stream and it's a really good place i think to um to see kind of how i work and if you want to interact more with the community and all of that then definitely check that out okay cool anyway so that's kind of how this works um hopefully that makes sense and again the video the the live stream will clear more stuff up if you want more but the problem is that to facilitate this um we really need to think about how we get this additional data now we could have an additional render pass where we basically run through every object in our scene and then render it into that secondary buffer um or into we'll say into a buffer which we'll call our id buffer now in order to actually make that happen though we need to render our geometry but then output the value for the rasterized pixel to be the entity id so how do we get the entity id into our fragment shader for that well we could use a uniform right that would that sounds pretty logical i think but the problem with that is that this isn't a piece of geometry this isn't actually an object or this isn't three objects right what this is technically is one draw call this whole thing right because this is actually using our 2d renderer so since it's using our 2d renderer what's actually happening is we're streaming in these vertices into our vertex buffer dynamically and then just drawing them all in one batch so because that's the case we can't set a uniform per object in this case because again even though this appears to be three different objects it's really just one draw call um which is good because we don't want to waste three draws on this this is silly right we can easily batch it together but for the purposes of this color buffer or this id buffer we have a little bit of a problem okay so what can we do well uh obviously we can uh put it into the vertex buffer itself right so every vertex has an id um and that id is essentially which object that vertex belongs to which entity that vertex belongs to and that's what we're going to do okay so great so what we're going to do is again we're going to extend that vertex buffer layout we're going to add this but what about that separate pass does that mean that we have to recreate our entire batch but with an additional vertex attribute and how is that even going to work so this already sounds very messy and that's what we just that's what i i also thought that during the live stream where i worked this out for the first time anyway um so what we're going to do instead is we're not going to do an additional render pass or anything like that but it's going to add another color buffer to our another color attachment to our primary frame buffer and just do it all in the one render pass so so at the moment we have a single render pass it renders this entire scene right what we're going to do is instead of just outputting the color which is the buffer we're looking at right now this is our rasterized color we're actually going to output two things we're gonna we have two render targets we have one which is the color buffer which remains untouched but then we're gonna add another one that's gonna be the entity id and it's just gonna be a buffer with a whole bunch of entity ids right because at the end at the end of the day it's called a color attachment but really we're just dealing with numbers we're just dealing with data and these are just buffers and buffers of memory so we're just going to create another buffer attach it to our frame buffer because you can do that you can have multiple writer targets of course um and then what that's going to do is let us um it's going to it is going to let us not add another render pass it's going to make it so that we can just do it all in one render pass and not be wasteful because we don't want to run through all the objects in our scene a second time and render them to something else if we can just do it all in one pass it's a lot more efficient okay so that being said how do we then do that how do we add an additional attachment to our frame buffer and this is really where this episode is going to come in sorry for the very long backstory but um if we look at our current frame buffer class so opengl frame buffer that's our implementation you can see that it's very uh well it just kind of gives you this and that's it right so if we walk through this roughly it creates a frame buffer and then we attach a color attachment to it we attach a color buffer that's rgba 8 and rgba unsigned bytes it's basically like a an integer color here right and then it's got a depth buffer that's depth 24 stencil a this is very uh very not flexible right we don't want every frame buffer we ever create in our engine to be exactly this specification that doesn't make any sense right i mean for one when we do like maybe hdr rendering we want to maybe use floating point values here so like six rgb a16f or 32f right um we want this to be gel float what about if we uh want more than one attachment what if we don't want any color attachments because we're doing a depth pass for our shadow mapping and what if it's a depth bars it doesn't need the stencil buffer so what if i just want depth 32 right all of these kind of scenarios and there are a lot mean that we want some kind of flexible way to define exactly what our frame buff is going to be like so what how many attachments does it have what types of the uh are the attachments do we have a depth buffer that kind of stuff so let's create a system uh for that now hazeldev i've written something very similar for hazel dev right i've written something uh basically i've come up with just a nice api for us to be able to deal with this stuff and it's kind of inspired by the api that i came up with for vertex buffers so you can see that if i go into like render a 2d for example we have a little vertex buffer layout situation where we're able to just specify a whole bunch of uh types right for all of our attributes and then a name now the name doesn't really do much and we actually discussed this in the earlier stream that i had today i had another stream today which i had to abandon halfway through because i had to do some stuff but um ultimately uh we talked about this a a bit where these names are usually more kind of they more apply to like attribute semantics and stuff in hlsl because uh this kind of system is something that i wrote for sparky um and sparky supports directx and opengl spark is an old engine that i wrote about six years ago um so it doesn't these names don't really do anything for opengl they do for directx um but anyway the point is though we have a nice little list of attributes so why not do the same for these attachments right why not be able to specify a list of attachments and i'll show you what the api looks like so this is hazeldev this is our scene renderer which basically creates a whole bunch of different like render passes and stuff like that and so it obviously has this little frame buffer specification and we also create a whole bunch of frame buffers so the way that we create frame buffers is we pass in a specification right and then this specification has a list of attachments and you can see that in this case this is our geometry pass so this is like our main kind of path where we actually render all of the objects in the scene and of course this in general if you haven't seen hazel dev i'll link a video up there but uh hazel dev is obviously a lot more advanced than what we have inside hazel at the moment we're doing like 3d rendering um and all of that and this scene has like shadows and a whole bunch of different render passes and it's hdr and everything so the way that it works is we basically define this list of attachments so you can see that in this case for the geometry frame buffer we have an rgb a16f buffer an rgba16f buffer and a depth buffer right so what we're actually doing here is we're saying i want two color buffers i want them to be this type and i want a depth buffer pretty simple right and then there's some other options for like multi-sampling um and the clear color but that's kind of it or look at this um compositing buffer right this is kind of our main kind of scene composite buffer so this is an rgb a8 because obviously we're going to be displaying it on a 8-bit display most likely um so we're outputting just an rgb a8 kind of we're rendering the compositing pass to an rgba 8 texture and we're doing the tone mapping and all that stuff and you can see there's no depth buffer because we don't need one um another good example is the uh the shadow map pass right so the shadow map pass we create like a 4k texture here and you can see it's just got that depth 32f because we don't need any stencil buffers we don't need any color buffers it just needs to be a depth buffer and that's all it is right um and we've also got some other parameters in this case like no resize because we obviously don't want the texture to scale along with the um the window resizing whereas these ones we do because they're the main kind of viewport buffers so anyway that that's how it works inside hazeldev and someone um also said that what if we want uh certain texture filtering right so what if we want to have um you know linear filtering or nearest filtering or what about like wrap modes like clamp and all that stuff um repeat how do we set that up well the thing is this list of attachments is not just a bunch of texture formats right what it actually is is frame uh frame buffer attachment specification the way i've implemented it which is this which has an initialized list of frame and and stores a vector of frame buffer texture specification which is this right which um i added this during the last live stream isn't really implemented but anyway which you can see is that texture format and we have a constructor which lets us implicitly construct it just by using the texture format which is one of these right but um we can also add wrap and stuff to it which means that if we wanted to not have the default wrap of like clamp or whatever or maybe we wanted um some kind of filtering we can just add it here where we can just do like uh what is the texture wrap um repeat or something like that if we for some reason wanted it and if we had a constructor for that that would work right so we can uh kind of add more detail if we want to but if not we just uh rely on that implicit constructor and then we can literally just specify a list of formats and we get all of our frame buffers and then obviously all of our texture attachments for the frame buffer so we go and then obviously the frame buffer has to go through that list and then when it gets invalidated and actually go through and create all these textures so that's how hazeldev works i really like this system i've been using it for the past few months and yeah i think it's really clean and it works really well all right cool so let's pop in over to uh our code and start implementing something like this so again the way that i like doing this let's even i don't even know where we create the frame buffer to be honest so let's go ahead and um research for that now let's go back to edit a layer and then i want to see uh do we do it here i guess this scene do we have a scene renderer we don't have a scene renderer yet maybe the scene just does it um or not how on earth do we render anything does renderer do it how does rendering work in hazel i'm serious where on earth do we create that frame buffer frame buffer let's go ahead and find it i have not pre-written the code for this i will be referring to hazel dev tesla def's code but uh this specific code i haven't written for this episode um okay so here we oh okay it is inside of the layer so we create a frame buffer here and then i think we yeah and that that's essentially that that's the frame buffer that we eventually display in the viewport as well um and then that's the one that we resize here and that we bind before rendering okay cool okay so that's fine so when we create this frame buffer right which happens over here you can see we we actually already have that specification here but this specification doesn't have it's got frame buffer format and all that stuff i mean i don't remember talking about this stuff but ultimately what it doesn't have is again that little list that we're going to be discussing today which is going to be a list of texture formats so the first thing i want to do is specify a enum class and this is going to be our frame buffer texture format now why am i not using um texture formats and that was a good point i think that i thought about certain textures don't have formats at the moment which is not good but i think i wanted to keep this separate just because like the the way that it works is um these aren't necessarily standard textures so we also have things like depth and stencil and those aren't really like textures that you would ever create as part of the texture api but there are there are things that you would want as part of the frame buffer api so there is a bit of a difference okay so i'm literally just going to copy some of these formats and stuff from um hazel dev but i'm not going to go ahead and implement all of this stuff right here right so in fact i'm not going to add a number to these things so color and depth stencil depth 24 stencil 8 so this is the stuff that we're using at the moment and then i'll set a default as well so the reason i'm doing this is just because we if we explicitly want to want it to be this we'll do that but otherwise we'll just specify depth and it should sort it out for us okay so i haven't really done anything new here right all i've done is i've added uh and the idea here is that when we do add like for example then when we do have the need for like um rgb a16f we'll just add it here so i don't want to like overcome well nice one um so i don't want to like over complicate anything now because we definitely don't need those formats just yet um i'll i will add them as required okay so now the next thing is we create a struct called frame buffer texture specification right and this is the thing that actually contains the specification for a texture so these are just the formats right this is the actual physical specification of a texture so the format the wrap and the uh filtering right so um we'll have uh i'll make the constructor frame buffer texture specification we'll just have a default constructor and then we'll have a constructor that takes in one of these texture formats and this is going to be an easy one all this is going to do is set the texture format which we have yet to be a format right and then we'll have frame buffer texture formats format texture format okay and then again i'll leave it to do here because we probably won't get it to get to this today um this will just be the beginnings because i spent quite a quite a long time um uh and there's like some gifting and stuff going on i'll deal with you later stream sorry recording an episode i should get that stuff to appear anyway um so uh yeah so we'll deal with this stuff a little bit later maybe maybe in the next episode because i do want to complete this to an extent um with that but i don't want to waste too much time here and i want you guys to get the uh kind of main idea since this is more of an introduction okay so we've got this so this represents each individual texture that we add to our frame buffer and then finally we need the collection of them we could kind of add it here but i don't want to because we want to have some extra stuff on top of this and you'll see what i mean in a minute so this is frame buffer attachment specification and specifically this is the collective frame buffer attachment right so this is these are all of them um and the way that this kind of manifests itself is that it has a list a vector of these textures and we call these attachments um and then i'll go ahead and create like a default constructor as usual just to make sure that we have that because we're going to also add a constructor that takes in an initializer list of these texture specifications so this is what lets us kind of write it this is very similar by the way to what i did with the vertex buffer layout so if you're confused or if you want to see another example of this general like what is an initializer list how does this work um if you go to vertex buffer um then we also have you can see a buffer layout with initialized list of elements um maybe we don't need to do a const reference i'm not sure um but then again that was a list of uh enums there's a list of classes which i mean you know okay whatever we'll we won't add a const reference never know these days when you want your constant reference and when you don't but anyway um and we'll have a bunch of attachments here uh which we'll just set like this so we'll construct the vector with the attachments with the initialized list um that's it right and then obviously the last thing we have to do is have the actual attachments here so i called these attachments as well these are just our attachments so now the cool thing is this whole thing changes right now what we do is we say fpspec dot attachments equals and we specify our attachments so frame buffer texture format rgba8 and then frame buffer texture format um uh depth just depth right we don't care we just want to default the depth buffer right now we're creating this um so this obviously looks better because for for one by creating a frame buffer we're aware of what on earth it is before it was like implicitly implied and pleasantly implied that's funny that it would be this which is just ridiculous we want to actually see that here i mean it's fine to have a default potentially but um i i like to be quite verbose with my programming i guess i like to actually clearly specify what is happening because otherwise it's just a mess i think um but anyway so we have that and um now we need to actually make it do something so if we go back to opengl frame buffer this is all great but obviously we haven't changed any of the code it's still uh it still kind of remains as is so our next step is going to be to change this around so that we can actually create all the attachments necessary so when invalidate happens which might be on resize obviously resize calls and validate as you can see um we create the frame buffers that's fine this is the attachment specific stuff so attachments um so from here all the way to here we create a color buffer and a death buffer and attach them so let's go ahead and change this so first of all if um our and this this this is actually something that we need to do i think i did i did them somewhere here um we basically need to uh where did i do this okay so one one thing one thing that we have that's a bit of an advantage so one of the things we need to do is basically easily sort our attachments into into two categories depth and color right because at the moment we have a list of attachments but some of them are color some of them are depth and for some reason i decided to handle them separately right so the way that i did this was uh and the thing that's interesting here is that inside invalidate um invalidate is basically i haven't talked much about this i think but invalidate is an interesting function because invalidate means that something has changed inside our frame buffer and it needs to be it's no longer valid right so a good example is the width and height now whether or not specification with an id change a bit of a different question um because maybe we want to maintain the original width and height no matter what no matter if it gets resized so we might deal with that later but um there's two ways you could go about this you could assume that the specification that a frame buffer gets created with is set in stone meaning that if you want a different spec suddenly create a new frame buffer this frame buffer is going down with a ship it has this spec right not in terms of width and height because that's somewhat arbitrary right depends what it's for but it's somewhat arbitrary we'll say but in terms of the rest of the specifications such as like hey is this multi-sampled is this um a swap train target is it what attachment does it have you could say that's set in stone um and again you don't have to but if you do it means that inside the constructor itself you can for example sort out the attachments into those into those categories right and i think that's what i'm going to do so i could do it in invalidate but the thing is i don't necessarily think it's work that we need to do every time we resize the frame buffer because we already should know what the attachments are they're not going to change just because our frame buffer gets resized that's why some people have like you could have a resize function instead of invalidate or resize and invalidate but invalidate to me just means recreate the whole thing um so that's what we'll do here so what i've done in hazeldev and what i'll do here is i'm basically going to go through all of the attachments right which is what this is and i'm going to check to see if it's a depth format and if it is i'm going to put it into my color attachments otherwise into my depth attachment so the way that i did this was i basically had two little piles inside here i had um a vector and we'll assume that we have one depth buffer i don't even know if you can have two i'm not sure i've ever done that before usually you have one depth buffer um so uh we'll do frame buffer texture format and these are going to be our color attachment formats so you can see we've got a vector of them and then i'm just going to have a depth uh buffer format depth buffer uh depth attachment i'll call it format and this is also why i had that frame buffer none thing because i want to have a default i want to have a like an invalid state right so i want to have like what's not that it's invalid it's just that it's it's empty i want to have an empty state i want to have a depth buffer format that isn't really real and that way obviously i can check to see if that's none or not um and if it's none then it doesn't exist and i have no depth buffer so what i'll do here um and i i wrote a little function inside hazel dev called is depth format um which i just wrote inside here so let's just write it here for now static bull is depth format frame buffer texture format um and then in hazel dev this does something very simple and for us it'll be even simpler all it has to do is basically check to see is it this this is the only depth format we have at the moment just one and so we can just switch on format um the reason i'm doing a switch on an f is because we'll add more formats later like depth 32f um and so i don't want to uh i'm just kind of allowing for that i guess um okay so uh case framework texture format okay um all right so there we go so we we return true if it's that um and then otherwise we return false pretty simple stuff i mean in fact i'm going to put this into a namespace called utils because it's uh we might move it out of this class we'll see and so all i'm going to do is over here i'm going to check to see uh if so i'll do the opposite case first so if utils is depth format um and then format so if it's not a depth format i'll add it to color attachment just because it's probably we're probably only going to have one death buffer and it just makes more sense this way um so we'll do uh i guess we're gonna do in place back i don't think it'll matter too much in this case um else and this format should be uh i think we should do format texture format um else we'll set our depth attac again we should only have one technically possible to put in both i don't know if we want to protect against that but otherwise we'll set that attachment format so what this is doing is upon construction it's just sorting out that list right and seeing hey you know what you know what's your situation with the color and depth um and unfortunately i've got errors and stuff which is a little annoying um all right let's sort this out quickly and that was annoying um so it's not happy with this uh format dot texture format is what we actually want to in place right so what what we should see at this point in time is color attachment we have one rgb a8 and depth attachment we have one depth 24 cents late so it's just sorted it into basically one of two buckets um okay cool so inside invalidate now what do we do well we have to go through all of our um attachments and set them up so for uh order and what is this exactly this is a framework protection format so this is just formats right so for order um auto format inside uh and the thing is i did it i did it this way but i probably shouldn't have done it exactly this way because what about different filtering modes why did i do it this way inside hazeldev probably just for fun because i like to break things um let's instead actually take in this i think so framework protection specification right depth buffer probably not too important although it might be let's do this instead so these are our color attachment specs and depth attachment spec and i'm doing this because um i guess we can have a default one because by default we haven't set this up actually we should have by default our texture format we should set to none right this will just make sure that if we hit the default constructor um it will set the texture format to none and then we can check for that to see if for example the depth buffer doesn't exist right so this just we just do that to this right depth attachment [Music] specs i like to write it out um so color attachment specification just for my specification uh depth attachment specification okay uh yep and we checked the format okay spec that's better that's an improvement i'll probably try and merge that into hazel dev okay so we go through all of these specs right so for uh auto spec uh you can i don't know about copying it but we don't need to um color attachment specifications and then basically what we want to do here is for one see exactly what format it is and then based on the format we probably want to create it right so the way that i do it in hazel dev is i have another utility function called create textures um and i think this basically just creates a texture with the given target so this is based on whether or not it's we're not really dealing with multi-sampling at the moment but um what i do here uh is a number of things actually and i think that we might want to have yeah okay this this gets a little bit complicated in order to us in a scalable way so let's do it this way so first of all we'll check to see if we have a color attachment um and if we have a color attachment right meaning that like we we actually just have uh any color attachments whatsoever i'm going to uh first of all get the actual color attachments which we don't have at the moment and they're going to be renderer ids right so we'll create a vector of so we have a vector for specifications but now we're going to have a vector of renderer ids right and there's red already no thing neon 32t then it is uh and this is going to be our color attachments and then we're gonna have one of these for our depth attachment okay buckle in because this uh episode is about to take off it's gonna be a lot of work in a minute so um if that's the case and we'll have to rewrite this anyway but whatever if this is the case then the first thing i want to do is take our color attachments and resize them to the right size right this is just to avoid unnecessary allocation inside our backyard so now that we've re now that we've resized them let's go ahead and write another utility function called create textures right um and again this will deal with multi-sampling and stuff but not not at the moment so um we'll tell it to create maybe with a specification well i think it just needs to be either true or false so um we'll add a little bull multi-sample equals uh specification dot samples greater than one and then we'll have a little boolean for whether or not this is a multi-sample texture um and then we will uh so we're creating the textures and i guess we just need the actual data and size because that's what opengl wants so we'll do color attachments dot data i could just pass them in but anyway um color attachments dot size you'll see what this ends up being in a minute um so what this is going to be is basically uh static void create textures bull multi sampled um and then we're going to have our uint 32t which is really just a glue and probably should use urban gels type but do add id and then count so that's going to be where it stores the uh outputs um and then we do gel create textures uh we'll write another function called texture target with multi-sampled uh and then we do uh uh what so at the moment i just did that did i that's a bit weird that looks like a bug hmm anyway it should be count and out id but for some reason i just have one here inside hazel dev weird okay whatever um so static void uh sorry static gl enum texture target very easy simple function just returns uh gl texture tv multi-sample or gl texture 2d so i guess i am implementing multi-sampling in a way here oh well um okay so that looks good to me for now um while we're here we might as well add some of the other utilities that we'll need such as bind texture the reason i'm doing this is because um we i didn't want to talk about multi-sampling today but sometimes it's hard to copy like half of hazel dev's code um but ultimately uh the reason we're doing this is because the texture target right um so what we bind here um and yeah we if we use direct direct state access we don't need any of this anyway we'll write it like this for now because that's consistent with the rest of our api but the texture target is going to be the gel texture 2d or gl texture 2d multi-sample depending on whether or not it's multi-sampled and so because we don't know that um you know instead of doing branches and switching everywhere we just have some convenience functions that do that for us okay um i'll call yeah that's fine okay so we have these for now and again that just means that when we actually go through this if we have any color attachments we'll create we'll fill this color attachments vector with because we've resized it we'll actually just write into the data and we'll write all will generate a whole bunch of opengl ids for each color attachment all in one go which is good okay so that's what's happening in there hopefully that makes sense now the next step is going to be to actually create the texture so we've created the uh well we've created the textures but we haven't specified what format they're going to be or anything like that we haven't allocated the memory for them which is what we need to do so we have to go through um i'll leave that in i equals zero and we'll do size t i equals zero oh my goodness me i is less than specifications size i plus plus um and we can probably just use color attachment size it doesn't really matter this should be the same um utils bind texture and again multi-sample uh so that we support that and then we're going to bind color attachments i so color attachments remember they're our render ids right so we're binding color attachments i'm um i don't really want to call it color attachment renderer ids so i'm leaving it as that but obviously if you look at it and you see that it's uwens you should be able to tell uh okay so um next step is to uh basically create what we need based on the format so we'll do switch on color attachment uh specifications i which remember these match up so color attachments i and color attachment specifications i match up because we've created well they're going to be the same size and we are obviously going through them from zero to um to the end so we basically check to see the texture format of the current color attachment specification and if it is a frame buffer texture format rgba8 um it's basically going to be either it has to be that actually at this point in time that's the only color specification we actually have so at this point we'll uh call another utility function called attach color texture we'll give it the renderer id we'll give it the amount of samples um we'll give it gl rgba8 and of course this is coming from hazeldev by the way patreon.com to the channel if you want to get access to the complete source code of my 3d version of hazel um specification width specification height and i which is the index all right now let's go ahead and implement this function not gonna be that bad i promise um so static void attached this is the really the the big one right and again we need to expand this and include more um uh formats in the future but that's really all we'll be will be expanding everything else should just be scalable so height and index so again bull multi-sample to quickly uh create one of these uh because we actually need the amount of samples as well as whether or not it's multi-sampled so if it's multi-sampled um then we'll do gl text image 2d multi-sample gl texture 2d multi-sample samples format with isgl false okay read the documentation if you don't understand this but it's very similar to normal text image except we just supply the number of samples right internal format is going to be rgba8 as we passed in here uh over here right with height is self-explanatory and this is just fixed sample locations we just set that to false in most cases really um but again consult the documentation okay so um gl text image 2d otherwise so if it's not multi-sampled which is currently we we're not currently we don't really deal with multi-sample textures um it'll be the same except we're on a different function so not text image 2d multi-sample just text image 2d and we're passing a different target and then from here level 0 internal format is going to be format um width height is going to be yup with height um border is zero now format is the access format which we'll keep as glr gba this is uh so we have two formats internal format which is how opengl stores the data and then format which is how we access the data so what like the incoming data so sorry yeah format is the incoming data right incoming data and the storage type so uh yeah we'll just leave this um as glrgba and gl unsigned byte for now right we'll have to have a conversion function because this might be like float or int um in the future but for now we'll leave that as is and then we'll just do another pointer right um now at this point we need to set all the filtering stuff right so um [Music] the filtering and the wrap which we should have just taken in the specif the spec right because that has the format anyway but this is an opengl format now right so yeah okay so again the filtering and stuff we said we do later um uh filtering um well yeah i don't even need to write this because what we'll do is we'll go through gel text parameter um i uh we'll do texture target because again this this um uh we'll notice this won't be multi-sample because oh this is a bug inside hazel as well wait what how does that work ah apparently you don't need to set that's weird isn't it um i might have to take a look at this later um but basically we'll do texture target multi-sampled in this case it'll always be false though so there's a really probably no point running that um i'm not sure why i'm not setting parameter i for i'm not setting these like parameters for um for multi-sample textures maybe maybe because different ones apply or something well like yeah i guess minification magnification for multi-sampling is different okay whatever um and then we'll do wrap gl texture wrap so this you know we'll do rst clamp to edge so um basically you like at the moment we're just setting this to linear and clamp to edge but you know depending on a lot of things including well this is a frame offer so i'm not sure about mit maps and stuff but yeah that could be useful um anyway this stuff will be set from the frame of frame buffer specification in the future um that's kind of it again i said it wouldn't be too difficult um the next thing we need to do though so if we had two of these it would work right it would just add it to uh to two different things now why do we have the index though because the index so far has been useless was that just something that i never implemented oh whoops i didn't attach it good thing i went back so gl frame buffer texture 2d geoframe buffer right this is the attachment code i think we've already got one of these here right so let's copy it let's pop it in except this time we're going to do color attachment zero plus index right and the id is obviously going to be id and that's it and this is going to be going to go through texture target all right so that's it we've created our textures we've allocated memory for them right and then we've attached them to our frame buffer at the correct index um and that's what this does and so obviously if we run this twice we'll we'll we would have created two textures that are going to be attached at color attachment 0 and color attachment 1 which is great all right so next stop is the depth attachment right so again it's possible to not have them so we need to check it first so but it's not a vector so we could do if depth attachment specification doesn't equal sorry texture format doesn't equal none then it's valid right so we have to basically create a texture except this time we'll create one and it will go into oh oops one goes here and it will go into the depth attachment slot um and then we'll do uh yeah and we'll leave multi-sample there then we'll mine we'll bind it again whether or not it's multi-sampled and obviously depth attachment is what we're binding and then we need to basically do a switch on the format so very similar to this except there's no for loop because there's just one this is one of these so we do depth attachment specification dot texture format again in this case we only have depth depth 24 stencil eight it's possible to have other ones like depth 32f uh is another popular one so um we'll we'll set up our code for that so attach depth texture this is going to be a different one um and we'll pass in the depth attachment we'll pass in the the number of samples right which uh i thought we had but we didn't that's inside here and then for this in this case depth 24 stencil eight uh and then what else did i have uh gl depth stencil attachment because it's possible again to just have a depth attachment so we have to specify which one and this one does contain a stencil buffer um and then m specification dot width and specification.height that's it okay so what is this all about um so this is again going to be very simple very similar to um what we have before which is this so we'll copy and paste this attach depth texture um so id samples format um we'll add something else though called attachment type with height and then we don't need an index sorry multi-sampling still applies to depth buffers that's fine um and then over here the difference is um well we'll do gl text storage today a few few differences and again i don't really want to get into this too much um it should work in all cases though i think um and then uh what do we have levels so one level internal format is going to be um format and then we just have width and height uh we can leave our parameters uh and then finally uh framework for texture this remains the same except the attachment is now the attachment type so either depth um attachment or depth stencil attachment and then we have the multi-sampled and then the id and then the zero for the level in this case okay that's it um i think that is actually the end finally so yeah lots of code here suggest you guys i'll commit this obviously after the episode i suggest you read it um and maybe go if you really don't understand how it works i mean it's pretty simple it's just a lot of actual code right um but we basically just organize it in a way that is completely responsive to like a dynamic kind of um collection of specifications that it can use to actually create this this these physical uh open shell objects basically okay so that's how that works um now finally though what we need to do is we basically need to check to see if the color attachment size is greater than one because if it is then there's something else that we have to do so first of all we'll assume that for now we only support a maximum of four color attachments because the next block of code is only going to support four so we'll make sure that we have no more than four color attachments and then what i'm going to do is define our draw buffers this is something you need to do on opengl if you actually want to draw to more than one uh render target so basically what these are are these kind of color attachments that we define inside a list inside a little array like this and then we just do gl draw buffers and we're basically enabling them so for the amount that we have and we've got an array that defines four but we don't have to use all four and that's what this is doing so we basically say how many whoops how many do we have here um well what am i trying to do constructive space how many do we have here we have uh four and then what are the actual buffers so the buffers are buffers okay and that's it so that just enables them for rendering otherwise um if it's only a depth pass and i don't know if this is strictly necessary but basically else if m color attachments size uh is zero right so i guess we can do color attachments empty then um gl draw buffer gl none only death pass might be like an optimization thing maybe okay um and again this would be used for like uh just a depth pass for things like shadow mapping okay um that is it i think so we don't need any of this right we've got that inside our other functions um we should still check to see if it's complete or not obviously it's very important stuff um but that is it and then we can unbind it to make sure we don't actually we don't accidentally render to it um without me youtube wow so when we invalidate it another thing we should do by the way is make sure we clear everything because if the specification changes i mean the thing is it probably won't well yeah because what we're doing here obviously is we're going through everything again and we're we're resizing stuff so i think that you probably would want to clear this because just the actual color attachments not the specification necessarily um just the renderer ids because you've just deleted them they're now pointing to invalid ids so let's get rid of that and then let's set that depth attachment to zero as well okay um and that's that really is it i think uh when we bind it and when we do this stuff looks great to me as well oh um yeah this is something that needs to be addressed so color attachment death attachment oh we have another function called death attachments great great i'll delete this um so we don't have a color attachment anymore let's go ahead uh let's go through deleting all this stuff so delete textures now just takes in um all of them at once so color attachment oh someone just uh subscribed oh it does show up in the recordings i don't know what that does okay good cool cooler thank you for your subscription also previously lfix as well and uh sebia gifting subs as well thank you to gives yourselves to royal anyway so much stuff so much stuff to catch up on but um it's been a long episode what are we at right now 53 minutes incredible telling you guys game engines are a serious business um i don't know what i'm gonna do with these episodes um okay so we'll go through all of them and then color attachment specifications.data right so this just released them all in one go basically and then the depth attachment we can clear as necessary um i don't i don't really know what happens if you give it zero will just not delete it i guess not because i don't think hazel dev crashes because of this so um and sorry this is uh color attachments not the specifications um so let's do that here so we just delete all of them and i guess if it's zero it's not going to do anything and i guess if that's zero maybe it weren't deleted i don't know if if anything we can wrap it in an if statement obviously and just only delete it if it's actually valid if it's a if it's a thing oh i think that's it let's just run it maybe everything will work it should look exactly the same it's already failing to compile color attachment undeclared identifier uh get color attachment under id okay well this is a bit controversial because which one are you talking about um index zero uh return color attachments index and corset to make sure that index is uh less than special color attachment size um okay uh we've changed the signature of this let's go back f5 search should work the same we had a default parameter of zero um okay so far so good so we didn't get this the assertion or anything and we see our ping cube is normal right so what we should be able to do now right is uh essentially add in potentially another one of these right um and now our frame buffer should actually have two attachments i'm not sure if this will work i mean i guess it should um right and then we should be able to actually draw to that if we so so wanted to right um in fact uh should we test this out i guess we should test this out so i think what i can basically do right we've already gone this far right so if i go to the shader let's go ahead and just do layout location one red and color two we'll say and then we'll just say color two equals just red right just red maybe let's do a nicer red i don't want to just completely ruin everyone's eyes with a full red just something like this right nice little red and then what we'll do so this this should ideally everything should work should we should still see the pink cube right but then what i'm going to do is when i actually uh bind well when i actually draw the viewport what i'm going to try and do is you can see i get the texture idea give it to i'm going what if we get index 1 so that's the second color buffer so this should be red if everything has worked correctly and i believe it should have there you go okay so you can see that what we've got now is a shader that's rendering to two targets and we're able to easily create that using a pretty nice specification api i think that's my personal opinion and then obviously we're able to render to it perfectly well and read back from it perfectly well as well okay so that is the end of this extremely long episode hope you guys enjoyed it um if you did if you're watching on youtube don't forget to hit the like button and subscribe you can also help support the series by going to patreon.com which i'll have a link in the description below too as well where you can get access to all of the hazel dev source code that led up to what you see here um you know we've had this api for a while and obviously we're actually using it to do stuff like shadow mapping and um you know various other passes and compositing and whatever so um uh yeah and then obviously you'll be helping to support the uh general game engine series and my youtube channel and everything here would not be possible without all of you guys so really i'm thankful to everyone who subscribes on twitch and to everyone who um also supports on youtube thank you guys for watching uh don't forget to um don't forget to follow the stream if you want to catch more of these live streams because this is streamed live and i will see you next time goodbye [Music] you
Info
Channel: The Cherno
Views: 19,796
Rating: 4.9659576 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, mrt, framebuffer, multiple render targets, opengl, hazel, hazel engine, hazel-dev
Id: f-GbHye1VFQ
Channel Id: undefined
Length: 58min 26sec (3506 seconds)
Published: Fri Jan 22 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.