 Hey guys, welcome back skits on series episode 24 topic today is going to be rendering Three-dimensional shells or solids using triangles. So it's not a very complicated topic. There are a lot of steps, however But I'll give you an example of what we're going for This is example 24d just to cross rendering you can see each face Well, each side has different colors our front left right top back Bottom all the different sides have different colors and it renders pretty well pretty good Framerate not so bad. So the goal today is to basically go through the steps of how you render a body like this or a scene of bodies like this okay, so Yeah, I guess first is to know that all surfaces all shell bodies can be decomposed into Consituent triangles So for example, you had a cube that's six square faces You can break each square face into two triangle faces. So you have 12 triangular faces That's the first step of this process. And so yeah, if you can render triangles in 2d you can render three-dimensional bodies essentially However, we never covered Rendering 2d triangles. We did cover rendering three-dimensional wireframes. I think it was two episodes ago. So All the math is the same in in that regard But we will talk first about how we're supposed to render two-dimensional triangles So the algorithms are, you know, diverse for this you could find plenty of them online This I think is the best and easiest one for our application and it's implemented in the set triangle Function in our code base the way this works is basically you have a triangle and then you first You have to order the vertices in some kind of vertical sense So either top to bottom or bottom to top. So this will be vertex one two three for example And what you do is you break the triangle at vertex to so you basically cut the triangle in half So you have a flat bottom and a flat top triangle right here. So you have a triangle on the top and Triangle on the bottom Then what you do is starting out at the top or the bottom you compute the slopes of those sides So for example this slope and this slope that you get despite Subtracting the X and a Y components of the two extrema of the line segments Then what you do is you just kind of trace down those slopes and draw a bunch of horizontal lines at every pixel that you see here And you do that from your starting vertex to your middle vertex in our case vertex 1 to vertex 2 and then at vertex 2 you Recompute one of your slopes because the other slopes stay the same and Then you continue down the line until you hit vertex 3. So it's a very simple algorithm and It's not too math-intensive There are some special cases for example, what if vertex 1? Or let's just say two vertices have the same Y coordinate So you either have a Flat top or a flat bottom triangle. Well in that case Your problem gets easier by half, right? You don't have to worry about the second half of the triangle But either way you have to handle that case more or less separately Okay, cool. So now that you can render two dimensional Triangles, how do you extend that to 3d objects? Well the algorithm is pretty straightforward First you have to break your body into vertices. We already have the idea of this from our previous episode about rendering wireframes But in this case, let's see you had these vertices vertex 0 1 2 3 all the way to 7 what you do is So let's say you have a triangle here at vertex 0 vertex 2 vertex 1 and Also, you have one at let's say vertex 6 vertex 5 vertex 7 For each triangle that you have there all 12 of them you have to Project all those vertices in the 3d sense onto the two-dimensional view plane and then just draw that triangle So in our case, let's say you picked this triangle v 0 v 1 v 2 you drew that with the algorithm above Then you pick this triangle Triangle v 5 v 6 v 7 and you drew that one with the algorithm above You do that for all 12 triangles And that would be it Right that that would satisfy a 3d rendering. It wouldn't be a very good rendering and why is that? well Because you'd have a catastrophe Basically, you have no semblance of which triangle should be drawn first second third fourth or last And so you may accidentally draw triangles from the foreground before drawing triangles in the background and that would mean that you'd have like this weird, you know modern art looking rendering of Faces showing through faces that ought not to be showing essentially And so to get around this there are a couple different ideas And I recommend you try to brainstorm some but the one that oh that's the two that we're going to implement I'll explain here the first one is Both to solve that problem But also to help performance and that would be to only render triangles that are facing Towards you or in other words facing in the opposite of the way that you are looking and so this requires some More definition on our part and basically what you have to do is Think about the numbering system as an encoding for the direction of the triangles face So in that in our case, we're using anti clockwise or counterclockwise vertex numbering What that means is basically Vertex zero one and two they wrap counterclockwise Around the triangle and then the normal points in that direction with the right hand rule or alternately You can take the vector from v0 to v1 and from v0 to v2 if you cross take the cross part of those The normal outward of that sweep would be the normal vector And then you'd only would draw this triangle if that normal vector opposed our view direction So if you see this is our view direction, you would only draw this triangle and not this triangle because The first triangle has a component Opposite of our view direction Whereas this one does not how can you determine that mathematically? Well use the dot product so if the dot product of that normal cross product and Our view direction is less than zero. You can draw the shape Otherwise skip it. So this would enable us to basically Added a line right here in our algorithm. Just check if that triangle is pointing the right way and It would result in half of these triangles not being rendered So it would speed up our rendering but also solve part of our problem here But not all of it But it does require additional work on our part now We have to encode if the connection to the order of the vertices when we encode the faces So now we have to encode this triangle on top as v0 v2 v1 We can't call it v0 v1 v2. That would be a clockwise numbering scheme Okay, so that will help us with the rendering speed and also the problem that we had but it still allows something to go wrong So basically it will make it so all of our convex shapes Individually will look okay. So you can draw a red cube. You can draw a yellow cube You start any kind of cube with its own colors, you know different colors different sides That's fine and it will always work out fine The problem is if you try to overlap these two convex shapes, you know in front of you like this There's a possibility that if you still render the The closer convex shape before the further convex shape You can still get the bleed problem that we had before so if you render this foreground red body Before you draw this background yellow body He obviously the yellow will show through so you can't have that So we'll talk about that in a second One little pro tip not all bodies are convex convex basically means does a body have an indentation or a depression in it? So for example the cross I showed you before is not convex neither is You know Patrick star however if you want to be clever you can cut up most bodies into like more or less convex shells With holes on them, but that's fine and basically you can draw each individual convex body Separately and that would solve some of the problems with non-convex shapes however, the second optimization that we're going to implement is going to be Kind of a way to address this problem here And what we do is we take the centroid of these objects So if this yellow object is basically further away the centroid of it will be you know a hundred meters away from us Whereas this red object would be you know ten meters away from us So what we do is we order the objects from furthest to nearest and render them in that order and That's it so that basically solves Almost all the problems you can still have some problems arising from Near objects to each other that they're a single same distance from away from you that may still be a problem, but Most problems will be resolved in this fashion and we'll go through how that looks in the code today So let's do that. I have four examples one on covering points, which is more or less irrelevant We'll cover 2d triangles. We'll cover 3d triangles With both the improvements I discussed above Okay Let's do that so in the The code we have four examples here 24 a b c and d. Let's check out 24 a really quick So what is this one? this one is rendering points so Not really relevant to what I talked about in today's episode But still an important thing for us in the future when we go to use this for CAD or FEA and want to be able to Identify different nodes of our model or points on our model this will help us do that and I'll show you the code for this. It's basically what we did in episode. I think 22 The very very similar at least so But if you recall from before and even here we have these edge structures So this edge geometry that has encoded inside it the points and edges that compose the wireframe for that cross Well in a similar way we have a point geometry structure that Embeds just the points as well as the kind of rendering you want to have so I Just showed you those triangle points I can change those triangles to be Circles by changing this four to be a one and if I run this you'll see that now those triangles have become blue circles So not too complicated not too relevant either. I'll skip this one. You're free to peruse the code at your leisure Example b this is going to be the 2d triangle rendering so this is the implementation of the algorithm I discussed before Let me show you the algorithm again just to refresh your memory This algorithm here is implemented by what I just showed you so Quickly I want to point out if you can see it on your resolution. I have some white dots here Pointing toward the vertices. That's not part of the rendering I'm not just part of my check to make sure that the triangles were in the right spots but yes, so this algorithm does drop the triangles in the proper locations and I I'll show you the code really quick. So in this case I have this set triangle Function, which is valid for all bitmaps not just screen renderings and If the pass in just a couple things you pass in the usual stuff the pointer to the The pixel information you point you put the color you want to draw the triangle in the dimensions dimensions of your image as well as the six values so you have the Three vertices X and Y location on the screen passed in R8 R9 R10 R11 R12 and R13 So down here. You can see how it's working the red triangle here. It just You pass in the frame buffer address Pass in the color you want in our case red pass in the Six values that define the triangle X and Y for each vertex and then execute that function So yeah, again that drew those triangles. So that's the basis for our entire rendering in This entire series is drawing triangles like that Okay, that's simple enough 24 C now this I will I will I will say before I run it This does not include and I'll even show you Both of those optimizations. I want to show you each step along the process. So this one will have a glaring flaw to it So basically this does not include any idea of Centroids like this But it does include Culling all triangles that are facing in the same direction as you so I'll show you how this looks Here you can see kind of the problem that we were talking about so I Have this cross is 30 some odd triangles that constructed and I've got them all Set up with normals pointing outward from the cross So I'm only drawing the the triangles on the faces that are pointing toward the viewer However, you'll notice that you know problems like this arise here or like this arise here Where this face is facing me as well as this face is facing me? however Even though this face ought to be drawn after this face is drawn The way I've set it up. It's not the case. This is one giant Non-convex body and so you have Issues like this manifesting where you basically have bleeding through just because of the way I've numbered these faces You know on my end randomly So it doesn't make sense here, but it does make sense here. You know what I mean? So how can we address this? Well, like I mentioned before with you know the Patrick analogy we can just cut off each individual side or you know spoke on this cross and Make them all separate bodies give them all a centroid and then basically Render them in the order that they are away from us towards us And that's what the other example is showing This example does do just that and I'll show you the code in a second But yeah, basically I've cut off the top left right and bottom spokes of this cross and broken them into their own bodies given them a centroid and Then wherever I'm looking from at every location around when I'm rotating around the model I check the distance from the look perspective point to the center of the body and Then I order them every single time well if I have to reorder them I do if not I leave them the way they are but if I have to reorder them I put them in order from furthest to closest So very simple problem to solve and a very simple solution to the problem However, there's still a problem if you kind of can see it in this angle here There is I can zoom in you can see here at the at the top right. I just had it No, it's a very minor problem, but There are situations where you can actually see through Let me rerun it Just in case You're right there. You see at the top right that blue bleed through because Even though I'm ordering the centroids from furthest to closest there are still Circumstances because these are very close together, you know in the in the plane of the page It's still a possibility that I can get them out of order, but it's very very very Small in how big that problem manifests. So Still a problem. That's why I said there was still like a 1% issue with this but as far as the Computational expense and how much you're getting for it There really is no better way to render these bodies than what I've shown you so far in the video Okay, so let's go into the code about this one And I'll show you how this one works. So again, there's almost no includes because they're all behind the scenes Basically, we've had this in a couple videos so far you have our Initialization for the 3d rendering and then you have the loop for the rendering then you have to have a cursor so we make our cursor here a little yellow cross and So the entire program is this start loop will start label and this loop here So we just initialize that rendering and then loop forever But the real, you know, beauty of this is taking place in our data structures, so Instead of having an edge structure or a point structure or a face structure like we have in our other examples in previous videos In this video, we have our it's called the shell list structures what I called it and basically this is what incident coding both a number of Spokes on our cross number of bodies that we're trying to be able to order You know in real time as long with only the pointer to each of those bodies So here I have a the top of the cross the bottom of the cross the right of the cross Left of the cross as well as the centroid of that body in space and of course you could compute this With math I just hard-coded to here But you could make a function that groups these points together takes the average The problem with doing that is that you may have a lot of resolution point-wise of your feature Like the average of your body may not be the average of its points You may have a lot of points. Let's see you during a person model You have a lot of points in the face for resolution And only a few points in the legs Well, the CG of your person will be in the nose somewhere it but in reality the CG is somewhere in the stomach, right? So, you know, there's different math that you could do for this But I just hard-coded the centroid of each of those bodies in space and then like we had Before we have our individual structures that define the faces on the top left right bottom of our cross and as we had before with our Wireframes if you recall from previous video, we have all the points to fight in space and now we have all the faces to find as combinations of those points along with a color And so for example the top face the top square of the cross is These nodes 13 12 14 and 15 and we decompose that into You know two triangles Each of them is it looks like our GB blue It looks like so. Yeah, that's how this is all working. This is all passed into our Render rendering and knit Function which pulls all this in and I could even show you how that works another key part about this is This value here this structure type. So I'll show you how this works really quick Okay So here's that loop function that we just we're just calling a lot of includes you can see it has the ability to rasterize Point clouds rasterize text rasterize faces rasterize edges every kind of geometric entity we can draw in This way and the only difference is what we're passing in and so you pass in that link list of the Geometric structures and then it just checks it checks did you pass in a value of one if so? It's a point cloud. Did you pass in a value of ten in binary? If so, that's a wireframe model Did you pass in a value of a thousand binary? Well, that would be just text. We had that in our previous video What about a face so a simple? Convex shape is just a face. That's 100 binary That's like example see I showed you before where you don't care so much about The order of things in the depth of the page But you do care about culling away Faces pointing away from you that would be binary 100 And if you have a list of bodies that you want to care about the order from the viewer That would be binary 101 and you can see how we could eventually add more Geometry types to our rendering. This is how I'm implementing that in assembly And so yeah, if you pass in the value of one you're a point cloud And so it processes that and it renderizes it rasterizes that point cloud Same thing with wireframes faces and lists of those shells So if you have passed in a list of those shell Convex bodies like we're doing how does that work? Well, you say have debugging code here Let's get rid of that while we're here So the first thing that happens is Centroid sort so it's a function that I wrote the other day that sorts all the shell bodies by the distance from the viewer So that's obvious. I'm not going to go into that That's just you know the distance between that centroid and the viewer remember that this is encoded in our Perspective structure here this look from These three quad words here encode where the viewer is where you're projecting upon so you can get the distance between those values and the Centroids of each body That's what this line does then it just loops through all of the Convex shell bodies and rasterizes them So yeah, very simple. That's pretty much all of it there And in doing so we're able to render That three-dimensional body. That's not itself convex, but is constructed from Convex shells essentially So yeah, that's the entire topic for today, I hope you found that interesting It's pretty cool that we can do all this and I want to show you the size of this file. It's only 14 K there's a binary here 14.3 So that's the entire Drawing pipeline in 14 kilobytes and it could be way less I did there's a lot of overhead here I can get rid of a lot of code in the same spot well different spots But the same code could be cleaned up. I'm not going to do that my goal here with the skips on series is to make things simple and Modular and easy to understand But you could get this down to probably 8k another thing you could get down and I'm not going to do this but you could do this if you cared is All this stuff down here. Let me show you all these faces When I'm encoding which vertices are part of you know, which triangle right? DQ 17 16 7 That doesn't have to be a quad word. You don't have to use 8 bytes to encode the number 17 That could be a double word that could be a word that could be even be a bite So we could get it, you know, we could shave off, you know a kilobyte of Nonsense zeros from our code if you wanted to I'm not going to do that, but you could At the end of the day, this program only takes 32 megabytes of RAM and what was it 14 kilobytes of? hard drive space, so I'm not too concerned with the The size I'm not code golfing this series, but you're able to do that. You have the code at your You know leisure to do that with so yep, I hope you enjoyed the video. That's it. I'll see you in the next one