Starting our 2D Renderer | Game Engine series

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys my name is China welcome back to my gamers in series so last time we talked about creating a separate sandbox 2d layer check out that video if you haven't already and the purpose for that was basically just to prepare ourselves for doing 3d rendering I want to get hazel into a state where it will be as clean and as simple as possible for us to just jam in 3d rendering and be able to test everything that we need to test as a result of implementing that so in other words when you start working on a new feature in some kind of code base it's really good to try and create some kind of test environment for that so that you're able to cleanly and easily kind of see if what you've done actually works or not especially when we're dealing with something like C++ and we need to watch out for memory leaks and any kind of other like little things that you wouldn't have to necessarily do in other languages I think that it's important to create as clean of an environment as possible and the thing with that as well is that because I guess you know we kind of have hazel at a spot here where it is relatively clean and simple and I think there are too many bugs at all most of you guys have fixed that using pull requests which is fantastic but there are still more things we can do right and for those of you watching who really want to contribute I'm not necessarily contribute but also kind of learn I guess and take hey we'll actually make it a little bit your own and not just copy everything I'm doing here or on github but actually kind of implement this into your own engine there are so many more things you could do before you start with your rendering one thing that comes to mind immediately pretty much is like profiling and like memory tracking right so we're not doing anything like that I have no idea if there's memory but in hazel or not I can look at visual studios diagnostic tools so I can see hazel is running over here and in the background I can kind of check out the diagnostic tools in Visual Studio gives you know consider this using about 100 mega Meg's of memory and that's not all 80 max of memory and that's not kind of increasing like at all in fact it's kind of decreased a bit so there's clearly like no evident memory like or at least not not a big one so there are tools the visual studio provides and like profiling you can do in visual studio as well but that stuff is not stuff that I would necessarily rely on they're not because it's bad but because visual studio is not gonna be the target environment for ever right when hazel eventually supports Mac and Linux when hazel eventually might support Android iOS that kind of stuff what you want is in Engine Tools to do things like that so for example I want to see what the memory usage and hazel is like and specifically Hales Hazel's memory usage right it says all this overhead from the runtime library that all the runtime I guess this Eastern and runtime that kind of stuff where we kind of create a Windows application to do all this stuff you know there's all this kind of overhead that you don't really see but specifically from hazel for example you know like I've created vertex arrays but you know all this kind of you know I've got my own sandbox 2d layer I want to know how much memory I've used up in actual hazel client space that kind of stuff is important all right I've seen graphs over time adding something like that is really important and one of the ways that you would do that is by actually taking memory allocation into your own hands so in other words basically especially in debug mode passing all memory allocations through your own code so that you can actually both record metrics on those memory allocations but also in the future make smarter decisions as to where the memory comes from so that you're not hitting the heap all the time so that's one exercise that you guys could do if you're interested in looking to custom memory allocation but specifically metrics and tracking and with profiling as well right so some kind of timing mechanism that will basically maybe by instrumentation where you can maybe add like a macro to each function or something like that and it will track how much time was spent inside that function being able to then visualize that data somehow super important and that's actually something that will add eventually whilst adding the 2d renderer because what we're gonna do initially is we're gonna implement a naive kind of 2d renderer or something that works but it's pretty basic and it's not gonna be terrible but it's just not gonna be the optimal solution it's gonna be a lot more simple though so we'll do that first and then as soon as we do that you know will record metrics from from it we'll see how fast it runs how many sprites were able to render you know will that text just about stuff we might even do font rendering or something like that although I think that will come later and then after we have that set up we're gonna look at an optimized kind of 2d bat renderer and how much of a performance difference that makes that you guys can really see that and of course to do that I'm gonna have to add some kind of profiling which I want to add like custom profile into his letter want to use physical strategist profiling tools for that specific example anyway that's kind of the roadmap of what's gonna happen but if you want to jump ahead if you want something to do right now between these weekly episodes then check out my memory allocation metrics and then check out some kind of profiling and I have a video called timing in C++ which also will benchmarking in C++ I think of probably both that will actually show you how to potentially write a little benchmark anything but I've got a much more advanced plan of course for this game engine series anyway before I continue just want to thank I was going to say our sponsors for this video which actually are all of you guys all of you lovely patrons patreon conference after show know is the best way to help support this series and everything that I'm doing here on YouTube huge thank you as always I released an exclusive video I think yesterday will based on what I'm recording this video I really say exclusive video yes today I'm gonna try and do exclusive videos a lot more so they three dollars a month to get into that by the way and I'm trying to do one awake starting from like now we'll see how that goes there no guarantees of course I'm just gonna try my best with all those work that I have to do here but ultimately you'll also get access to the hazel development branch which is like a lot more advanced version of this and basically has like full 3d renderings for if you're into 3d you're into that kind of stuff you don't want to deal with this 2d stuff which I mean it's still important and still really useful obviously we'll have to have that anyway then you can jump ahead and help support the series as well ok let's just dive in I don't want to talk too much the plan is gonna be pretty simple we're gonna create a 2d renderer so it's gonna be like a render a 2d class is gonna be fully static we're gonna have a go up reading more static approach to this because I think we have a renderer but it has some honor and storage somewhere I'm not sure about that renderer is in general should be static I might go over I I what I want to do in general is create very generic non game engine story specific videos on hazel specific videos the kind of detail you know water to your enra is how to implement like a batch render or how to do this how to do that you know font rendering will probably be its own standalone video so that you guys can see how this works outside of hazel because as the more we build up is hope this code base the more it's gonna be super specific to hazel and that's not good for me because you know the videos going to get less views basically but also that's not good for people trying to implement this in their own engine base because having all this hazel code around it complicates things because we'll start relying on like layers and you know our own kind of library of code essentially whereas if someone wants to kind of take that and implement it in their own engine that's gonna be really difficult so we'll kind of end up having both will kind of end up probably having a pretty lightly explained implementation in hazel but then also an in-depth kind of maybe OpenGL stories video just a standalone kind of video that will cover the architecture and how to implement something from scratch in a Santaland video so that's kind of my plan with these things as we start getting into more complicated matters and as I mentioned we probably like for now I'm just gonna write all the code to your end present although not that difficult to implement so I'm still gonna write all of the card in front of you guys but in the future we may need to switch to like diffs or just alike already written code or something like that because the just the volume of code is gonna grow and grow and grow and that's gonna be unsustainable anyway let's jump in and take a look at the 2d renderer okay so this is the sandbox 2d class that I was talking about this is what we've made it so far it's pretty simple and that was kind of the point to keep it as bad as possible there are a few things that I still want to fix up but we'll kind of do that as we go specifically this upload uniform float for and just in general when we start uploading matrices as well we're casting that dynamically toward OpenGL shader I think that what I might do is just have a set map for or setback for set float for whatever something like that in the base shadow class for now because my hopes was that we were going to implement a material system soon and use like uniform buffers essentially and in that case we actually wouldn't need to upload individual variables but because I think that is still far off and also I think that's a good feature to have for debugging right for example I just want to quickly set a uniform flow for a map for or something in the shader without going through material system without going through the revaruraa it's probably good idea to have that so that's kind of a - dude I'm all totally this is a tutor you know data set map for and but oh my gosh ADA set floats all right so these are two functions that I want to add so add these functions okay so I kind of want to add these two functions so that we don't have to do the dialing cost and those we kind of fixes that which will kind of look a lot better we won't have to include movie al-shater either which kind of ties everything to OpenGL of course because this includes some simplest shader which includes actually no this doesn't of course the visual studio keeps crashing I need to really fix everything last time as well I'm not sure what's going on in and have any extensions installed anyway whatever the show must go on as they say so next time I'll have like a different computer may be running this so what we'll do is we'll take a look at we kind of have this in general we have a renderer begin saying let's take a look at that because that's our initial thing we have seen data here which is a static kind of seem data kind of stuff which is fine that's what we have here that's fantastic that's kind of the same idea that will take and implement in a renderer 2d let's go ahead and just start writing the code so inside renderer and I'll make sure that I'm in sharable files mark because if I'm not it's going to be disaster yep I am I'll add a new item I'll create a CPP file called render a 2d dot CPP and I will do the same thing for our let's see here for our H file so this will be namespace hazal and then we'll have a class renderer 2d now this is gonna be super static super static is something that I just made up on the spot but basically what super stack means is this has zero storage this does not deal with storage at all this is gonna be a storage less class there's not gonna be any private like static stuff here at all it's gonna just be a set of render commands that's kind of what you want to have or at least what I want to have in this case we're gonna have to go doing it this way I'm gonna see what that ends up look so we'll have static void you know to begin scene we know that that's kind of taken an orthographic camera we know it's not going to modify that'll do that over traffic camera so I'll just take it in as a reference so we'll say also drop the camera camera and we'll have to include that all the traffic camera so begin saying we've got we'll have a static boy dancing which I don't imagine we'll do pretty much anything in a simple render scenario in a batch render it will probably upload the vertex data to the GPU but for a simple kind of primitive renderer like this I don't think it will do anything like that at all and then we'll have our actual you know primitive will say primitives so this kind of system stuff begin the same an same we probably want to have a nice and shut down as well again it is fully statics or we definitely need to basically have what the equivalent of constructor and destructor will be now that there's this huge there's this huge question that I get asked very very often which is why why static why have a renderer be static and you don't have to have a renderer be static this is not something that's like you know your renders are gonna work on as a static the whole concept of static and object and instantiation and object-oriented programming and all of this stuff that is all a language construct what matters in the end are the actual OpenGL calls and data transformation data management stuff that you're actually doing inside functions if you think about if you go back to the root level and you think about what object are in T programming is you know what is a class what is a member function it's just a static function with a pointer to a block of data that's what it is that's what that's what classes are that's how they work so ultimately by making something static all you're doing is saying that I'm not going to have a separate block of data for each class right for each instance of my render a 2d class that I need to point to every time you're saying that I will manage that block of data myself that's what you're doing which is a different way to think about it of course because that's not typically that's not typically the way that you would think about it if you're just learning on your own you're just thinking about you know you know everything has to be a class everything has to be that with that right but it's true um and I think that as you kind of probably get more into as you become a better engineer I'll say and obviously I don't know what level you guys are outside but you know if you pass this level I'm not gonna this is gonna be a waste of time for you but for those of you who are still kind of getting into software engineering maybe or don't have that much experience or wrapping your head around programming and C++ and how that all works you know in the end obviously all we're doing is we're executing CPU instructions all of these constructs that we see here like classes they help us organize our code a little a little bit better and make things certain things easier right because they take some of the management stuff away from us and essentially converted into syntax sugar that's what they're doing all right but at the end of the day what are we doing in the 2d renderer we're executing an OpenGL commands right not necessarily all we're doing I mean you know we have to do some kind of potentially some kind of maths as well but ultimately what are we doing we're managing some kind of data set which is our scene essentially right and then we're executing OpenGL commands we're taking an input data such as I want a rectangle here on the screen and then we're basically just saying that you know given this rectangle that you've given me with this data I'm going to you know upload this uniform matrix to the shader and then execute a drop call that's what we're doing right that in Norway involves classes or anything like that that is just opengl functions that were calling with data now why is this in a class in the first place mainly because it's gonna look better from an API point of view right if it's not a static class if it is like because this kutis might as well be a namespace by the way we could make a namespace called render 2d I've made it a class for no other reason just because that's it's just gonna look nicer you know there's no actual difference it's gonna look nicer it's gonna be a recognizable type you know there's not going to be any instances of it because it's all completely static but ultimately and I don't want to get too much into this and confuse you guys too much but basically what I'm saying is that a render 2d is something that's global it's a wrapper over certain GPU render commands that's what it is right the data that it has to have is is a singleton in the sense that like you only have one active scene at once you only have one render to date at once you cannot have you know multiple copies of render activity what does that even mean your friend or a 2d doesn't contain any data itself what is what is another instance of retro sweetie it's nothing right it's just an instance of render 2d would just be a a vehicle yeah I would say I don't know anyone who I'm talking about right now to be honest but I hear it would be like a method I guess of calling a function which is going to execute OpenGL commands which can be arbitrary anyway because they're not you know they're not tied to a specific OpenGL instance OpenGL inherently is something that's global alright so it makes sense to make a static renderer because first of all you know the renderer is sequential there is no such thing as I'm gonna begin a scene with one render and then bigger scene with my other end or instance that's that's not done right you do everything sequentially kind of consecutively right chronologically you could say as well you do everything line by line you begin your scene you submit your print primitives you end your scene you can do this anywhere in your entire code base it doesn't matter but ultimately that's the flow of the renderer that's how it works okay so hopefully that little explanation that makes sense completely unplanned I was just gonna write the code but then I started thinking about you know other other like messages that people have written me and and stuff like that that ultimately what we're dealing with here is you know dumb it down dumb it down to what you're actually doing OpenGL wires here and then you'll realize that you know ultimately I need some kind of data store potentially because I need to know what the active scene is maybe right I need to have like a specific shader potentially it was bound right but ultimately what this is is an incredibly kind of static kind of procedurally programmed you know a piece of code that just says that I activate my scene I do all of my necessary stuff and then I do a bunch of like OpenGL commands or potentially store things in some kind of buffer if I need to for more advanced renderer and then I just you know execute a draw cold that's what I'm doing it's super simple there's no need to complicate it and it definitely no need to instantiate a class like this whatsoever okay so for the first thing that I want to do here is basically have a function that lets me draw a quad so I want to say draw part now I could call it fill quad because it's gonna be filled in but I'm not gonna really do anything like that what it's gonna have draw quad what do you think about what we actually want to take in here as far as parameters go so I'm gonna be probably suggesting that we have a vector two which is our position normally you were I mean ultimately you probably want to deal with both back to a new vector3 the reason is we can use that third zet coordinate for kind of sorting essentially in the z axis so it's kind of like a z-index type thing where you can basically be like again rendering this last but I want it to be like at the very back of my scene so I'll set it to have like a position of negative 0.5 in the z axis by doing something like this you can kind of create layering right and you can basically control yeah you can basically create your own layers in terms of like you know I'll have a layer 0.9 point eight point seven point six negative axis right and then zero will kind of be the center of my scene and then if I want things explicitly in front I can render them and like I said of 0.5 right or something like that or you know the other way around you know because I think the way that our projection is set up at the moment is that negative values go towards the camera right whereas positive z values go away from the camera so I'll leave it as vector position for now we probably want some kind of size as well typically when you render something like this this can be a vector this will be sized and then I also want to have a vet for which basically says what our color is and again as I mentioned it's nice to have a vector E instead of a vector so I'll Pat this as well this is the skeleton for our 2d renderer here so I'll include our PCH I'll include our render or a true team a file here in main space nasal and now what's going to use a little sis but I don't have that it's told so we'll just implement everything manually let me see if I can just do that and then add a bunch of jelly brackets everywhere I could have used Visual Studio implement the functions actually no I think about it but as I care this is not going to take it too long and then the last thing that I need of course is that render a 2d namespace okay cool so now we have everything so one of the first things that we need to do for the renderer we're gonna try and blaze through this as fast as possible because the videos already like 20 miles long but basically what I want to do is the same way that we create we begin a scene here and we get a cameraman we do all of this stuff right this is going to become hazel render a 2d begin scene I'll add this to Hazel dot age as well and I can't use your sister from the file stuff to do this so we have renderer I'll also have a render HUD all right so that we have access to this and so now we have a hazel render a 2d begin scene which is great and then again to the hazel render a 2d and seen will have as well not nothing like submitting here there needs to be none this stuff is well gone you know we'll keep this around just for a bit because we all probably need to copy it but ultimately it doesn't need to exist set clay color clear that stuff will probably move to the renderer eventually as well and then the really our test here would be something like we had submit with a particular square which I wish I didn't remove so let's see we just okay well it was just yeah okay we do it with like a scale or whatever and with the color so we'll just say draw quad and then we'll take in a position I'll just keep it as 0 a size maybe of you know 1 1 or something like that and then a color which all set to let's make this like red maybe a bit kind of on the red side here with a number of one ok so we have a draw quad here now that should draw a 1 by 1 quad that's kind of what we would expect right so let's go ahead and convert all of this stuff into basically something that we can use in our to your Andhra so the way this is gonna work is I'm going to cut this card here I'm gonna pop it into around routine into your nest and then now you II bet you know you realize that hang on a minute we need state right we need to have some kind of storage for this but this is super easy to do and again does not require any kind of state instances what we do here is again we just have something like renderer to the storage right or something like that it ultimately doesn't even matter at all and you can kind of format this however you like we'll do something like this to begin with and then you just have all the stuff that you need so for example red vertex array you know this becomes our you know I'll say our vertex array and then we have and this may need to like include some things here red vertex array for example I don't know if I can actually call it that is the same as a type but we'll kind of leave it like that for now maybe so better array and then you know we don't need a better spot or anything like that what else we need a shader so you know it will cut out black color shader helpful is quadrats x-ray and then shader as well will include right and then what we do is we just take around ro 2 D we make this static so that it's kind of so there's like unique I guess to this translation unit specifically um so there's not completely global for linkage purposes and then we'll just call this what should I call this render up probably just data how many can call this daughter as well but I just called a storage because I guess that's what it is anyway so we have this right it's super simple we have as data and then any time we want something we just access it so we just say s data dot and you can make this a pointer I've made it just a normal kind of it's got a bunch F when I've just made it kind of a normal a normal kind of stack based kind of function it is static though so it's gonna exist with lifetime the whole application sorry because it doesn't really matter about initializing or like the initializing this at all you know this doesn't run code upon an initialization that needs to be done at a certain point it's just going to default initialize a bunch of shared pointers which is obviously trivial and not something we have to worry about so we kind of keep it as that if you start growing this and you have like for example of static array which has like a bunch of elements elements I know I don't know you might have stuff here that actually uses up considerable storage and you want to manage that at that point you can make this a pointer and then allocate it in a specific allocator or an arena or some like that but for now we're not gonna worry about that at all so quadrat x-ray becomes this and then you know it's same with here and here and then flat college head of course would just be s da da da flat color shadow okay so now we're kind of doing something that's a little bit better I guess this okay cool we're actually okay that's fine just looking at these vertices they look fine to me as well the position will be probably modified using just a matrix in this case but that looks pretty good to me I'm just coming through it all we have is position which is probably fine and that's what we all we need for this simple renderer okay let's just see if we can get this to work so everything else I'll kind of leave as it is another reason to have an end shutdown by the way is because you can that way you can explicitly like control the memory because at this point you know this is this stuff is not gonna get deleted or released until we actually shut it down manually so it might actually be worth kind of making this some kind of point or essentially and then explicitly controlling the memory in fact I think I will do that because I just see problems being caused in the future so this is really simple again what you do is you take this and you just initialize it here a lot of these parenthesis even though you don't have to we'll take this now becomes an arrow so just to be clear the reason that I don't want to leave it as a kind of automatically managed piece of memory is because in shutdown I specifically want to delete this the reason is that shut down here will for example destroyed vertex arrays right and it's important that this stuff happens because if you look at the constructor for and where is this I have to use this unfortunately but anyway if you use if you look at the destructor for and I've been jailed vertex array right it will actually called you a colon up and gel function which relates it from the GPU if this gets shut down as a result of this you know if this gets shut down if we look at the renderer this thing the renderer has to get shut down and while we still have an OpenGL context otherwise that code is just not gonna run it's gonna crash so because of that we need to explicitly actually call those disruptors which is what do will do delete will delete this the reference will get released hopefully nothing else is holding on to it obviously and in fact this could again just be roll pointers for that purpose as well they definitely don't need to be wraps they could be scopes in fact that probably would be better just because you know that I'm going to get passed around anywhere it's important to think about lifetime for reasons like this I think I will make this a scope the only problem is that we don't really support scope this becomes a huge kind of issue that we will probably end up talking about later because of the way the API is structured but ultimately my point is that we need to explicitly control the lifetime of this and delete it explicitly like manually by ourselves not just wait a lifetime of the program to delete them which is what would happen with half this being a pointer because there is certain OpenGL code that needs to run and obviously we need to guarantee that that happens before the OpenGL context gets released anyway all the hazal namespaces here can just get deleted in fact I think I'll just kind of look at the current selection here selection and just replace that with nothing okay cool so there we have it brilliant okay so that aside begins scene will simply upload the camera data as it kind of does before so if we look over here and we see this kind of well what the other renderer did I guess if we look at renderer and then look at the B key in the same function you know it's at the view projection matrix and then that that was set dynamically for kind of each shader if we look at our actual shader that we're dealing with which is our flat color shader let's take a look at what the student has the cases God view projection has got a transform great so if we go to let's see if we go to back to sandbox and we look at for example this will copy this line of code and then back in render a 2d and begin scene what we'll do because the shaders can get bound once writers will bind the shader which is what we're doing here on this will just be s data and then flat color data we could just call this shader honestly just because it is like that simple but leave like this now we look at what we have before I think we need platform urban gel shadow so and I go back here and again that is on the to-do list to be removed and then dynamic point a cost okay this one in all right so we buy the shader and then what I want to do is of course set a or upload uniform match for and then this will actually be so you do projection projection and then this will be the actual matrix and the matrix being camera gets purchased give you projection matrix okay so we end up with something like that and then end scene in this case doesn't nothing and then draw quad at a particular position all that stuff basically what we'll do is what do we have before it doesn't really matter I think it's got the color as well so let's take this now if we set uniforms here ultimately it kind of is important to make sure that shader is bound so what you would do is you were trying to bind it again in almost in in in the future what will do is this will actually look at what the currently back down shader is and then not call that function again if it hasn't changed the problem is that theoretically you could begin a scene and then do something in 3d land which would then rebind the shader this shouldn't happen but it might so it's safer to just leave a bind there just before you do anything but ultimately again because that binder can be expensive because it's not gonna be L call you could catch that essentially and just make sure that if it's still if the shader that you bound here is still bound then just basically ignore this call and that's also commonly done okay so upload this will be uploading from float for this will be you color and then will upload the color here and then to do to do the actual draw pole what we should do is basically what we did I think in renderer which was just that x-ray vibe and then run a command for indexed so back in here will do this and again you can see this is kind of the naive naive kind of implementation I guess well we're actually just taking the Plymouth right we're actually just immediate my kind of rendering everything but that's kind of step one I guess into getting the stuff to work so let's see what do I need I need the render command thing which I think is just running eh yep okay cool so there we go render command draw index and then it should be Pequod vertex right I'm not sure if we need to bind it though I guess we do draw index to vertex array okay yeah sure so we still have to bind it which is fine and then we'll just do draw index which is fine okay great so we should get a quad riding from this right it should be pretty simple again I've seen has nothing I'll copy this this is the vector position I'll copy just put it here because really what should happen is this should call draw quad except the difference will be that will kind of do a position the x position Y and then zero for that for that and then size color just gets folded like that so basically defaulting that to be zero if you specify a vector that corner will be zero and it will just flow through the same card obviously that looks pretty good to me so of course for ignoring position size that's something that we'll tackle in the next episode that looks pretty good to me we should try this out I guess let's just have five and well we'll see what happens okay so straight away of course we get an error and begin scene that's to be expected because of cause I didn't call a knit or shutdown anywhere so if we kind of go back here what I should do is inside application this should be maybe down in the three in around row if we look at application and what application does let's take a look here so sandbox where is application application or syrupy you can see what that does is it initializes the renderer and what the renderer should do is also and once the verona arena commander meet such as initializes the api sure what this should do is actually call render a 2d in it right so that way when we initialize the three because this is kind of like the main renderer um it will also kind of spin off and initialize the 2d renderer so render a 2d age and then we don't have a shutdown yet which we definitely need to have but when we do have a shutdown we'll kind of stick that in there okay let's hit f5 and see what happens okay so we get a blank screen with absolutely no square so let's debug that and see won't work wrong specifically I think what I want to do is check out the shader because if you've got a pretty sure everything else is correct so we are doing well we are multiplying this by transform which kind of implies that the transform needs to be set up otherwise it won't work so what I'll do because if that's coming in as 0 then our matrices all up that position is not being output correctly at all so back in renderer to do CDP what I'll do is apart from just uploading the view projection I'll also upload the transform except the transform I will just set gtlm matte for one point zero F for now let's see if that makes any difference ok and there we have our square which is now you know the camera you can see still works everything looks pretty good to me but now we have of course the square which the square color doesn't affect anything because we passed in a constant of red but I think we'll kind of leave it as that okay cool so hope you guys enjoyed this video is very simple look at the beginnings of our 2d renderer next time what we'll do is actually make it so that we can control the position and the size and so that we can render multiple in at the at the moment we can definitely random multiple squares but they'll all be at the same position at the same size but the color will be variable so we'll take a look at how we can set the transform as well to be what it needs to be in the next episode I do want to stress that this is a very simple approach to to your rendering of course we are gonna expand on this and make it a lot better than it is right now but I think it's fun of course to begin at the beginning and show you guys what a simple 2d renderer looks like and the concepts behind that before we do actually expand and make something a little bit better you guys did enjoy this video please hit that like button don't forget to help support the series on patron accomplish at the churner and I will see you guys in the next video goodbye [Music]
Info
Channel: The Cherno
Views: 17,865
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
Id: r_5fF1AxgpU
Channel Id: undefined
Length: 34min 55sec (2095 seconds)
Published: Fri Oct 18 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.