How To Render CIRCLES (OpenGL/Vulkan/DirectX/Metal)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
exciting title eh today we're going to be learning how to render these it's called a circle and yes i did print that so circles seem like they should be really easy to render but they're not see the thing is modern day gpus and graphics apis such as directx opengl vulcan metal all of that stuff they like to deal with triangles and that's great if you're trying to render a triangle or like a rectangle because that's composed of two triangles or even some kind of like 3d mesh that works great but circles circles are round circles are smooth circles are soft i guess i'm not sure if they're necessarily soft but i imagine they are they have this like nice smooth edge so how are you supposed to have a smooth edge if you're rendering triangles jagged line straight line triangles that just doesn't seem to work very well so that's why i thought i'd make this video we're going to learn all about how we can actually render those smooth soft circles and we're going to be achieving this by simply taking like the triangles out of the equation we're not going to try and create our circle out of actual geometry we're just going to create like a canvas for ourselves and then write a shader that will actually create a nice smooth soft circle for us but before we jump into that is there a way that we could just render the circle out of triangles and of course we can in fact that's a pretty common and popular way we could just simply create a triangle fan that goes all around our circle but then we have this problem of but how many triangles do we use because if we use too few then we'll essentially end up with like a hexagon or an octagon something that obviously has very jagged edges and to really make this smooth especially if you zoom the camera right into that or if the camera just gets really close to that geometry to make it actually look smooth you would have to use a lot of triangles now modern day gpus are pretty good at dealing with triangles but it's still not exactly ideal and what if you didn't want like a filled in circle what if you just want like an outline of a circle like a like a non-filled in circle i don't know what to call that well then instead of triangles kind of going from the center outwards you could just use lines going around the perimeter of the circle but again you have this problem of but how many lines do i use the more lines i use the kind of higher resolution that circle becomes the smoother it becomes it's kind of a similar problem that you might have if you're modeling like a sphere in blender or a 3d program or something you you want kind of more segments but that just increases the density of the mesh now that's not to say that you should never render circles with triangles or with lines in fact that's something that we actually do in hazel mostly for like debug graphics where like the the actual fidelity of the circle might not matter as much so for example here we're using it to just visualize like the bounds of a point light so that we can see how big it is and then the last kind of type of circle you might want to render is like a doughnut essentially it's like a really thick kind of outline of a circle for that you could switch to using a triangle strip instead of like a triangle fan and then you could still kind of draw that geometrically that's perfectly fine but if your goal is to have really nice smooth soft i keep saying soft round kind of circles then it's clear that this kind of geometric approach is just not really what you want to do so let's dial back circles for a little bit because they really shouldn't be this complex in fact they're one of the simplest shapes we can describe because if we were to mathematically describe them they're simply like a point and then the radius the size of the circle in fact in many ways they're actually much simpler than a rectangle that has like you know four bounds this is just a point and a radius so why is it so difficult to render well again it's because gpus love triangles but what if we just don't worry about triangles for the sake of this what if we just create for ourselves a little canvas upon which we can render a beautifully smooth and soft circle we'll kind of create this little canvas for ourselves and then we can write a custom shader like a pixel shader or a fragment shader same thing that will simply render our nice smooth circle using that kind of mathematical principle and then because it's like a custom shader we can really do anything we want there we can create a nice smooth faded transition we can make the fade really large for a stylistic effect we have all of this control because we're controlling the entire rendering of the circle within that gpu shader code and that is precisely what we're going to do now what i'm going to do for this video is i'm not going to actually go over the implementation of how to do this from start to finish instead we're going to just focus on the actual glsl shader code although if you use hlsl or msl this should be very easy to adapt we're just going to focus on that shader code using a website called shader toy and i'll go pretty deep into it and explain everything if you guys want to see some kind of actual integration video maybe using opengl or something like that where i show you how to actually take that that from shader toy and integrate it properly into an engine let me know in the comment section below but otherwise i think that this should be pretty easy to integrate pretty much anywhere all you really need to be able to do is just render quads and those quads need to be they need to have some kind of like good local space attached to them so for example in hazel the quads the vertex positions of the quads go from negative point five to positive point five so they have that kind of one by one meter like unit range being able to access that like local coordinate space within your fragment shed is going to be really important as we'll see later but those are like the draw calls that you need to issue and this is very easy and probably recommended to actually integrate into like a batch renderer i have a battery under a series on my channel definitely check that out if you haven't already there we're rendering quads but you can also easily extend that to render circles instead because the circles are just quads but using a different shader so they will need to be in like a separate draw call since they're using a different shader to the to the quads that are being rendered in the batch renderer but all of your circles can easily be batched together into a single draw call as well even if they're like filled in not filled in doughnuts whatever anyway i think that's enough talking let's dive in and take a look at how we can render circles okay so to explore this we're gonna be using a website called shader toy shader toy.com if you haven't heard of this highly recommend it lots of very exciting shaders and it's a great way to learn more about shaders and a lot of the math that goes into rendering graphics in general we're not going to be doing anything as cool as like some of the stuff that we see here that actually has the label warning because it's so complicated but over here on like the right side we have this new button we can press it and we'll just be taken to a blank new shader let me make this a little bit bigger for you guys maybe like that and we can start talking about this okay so what is shader toy how does this help us with our circles so what you're seeing here is very simple and it's it's basically the same place that we're at when we're rendering our circles it is a canvas this is simply like a full screen quad that is being drawn here that covers this entire surface and this is the fragment shader that is shading these pixels so this code over here runs for every single pixel that we have over here it uses some built-in variables that we can see in shader inputs such as like time and resolution and that is what produces these lovely colors that we are seeing here so let's kind of reduce this to basically nothing because we don't really need any of this like boilerplate code we can just set the frag color to be like a constant white or something alt enter to compile and there we have this right so pretend that this area over here is in fact that quad that i mentioned that we have to draw to act as like the canvas for our circle now the first issue that you can see here is that this is not a square right it's a rectangle it's 960 by 540 that's a 16 9 aspect ratio so really it's not it's not one to one which means that if we were to mathematically draw a circle here it would actually fill up the entire width and height and it would be more of an ellipse now let's talk a little bit about why that would happen why it would be an ellipse and like how we're even going to draw the circle in the first place so we basically want the ability obviously to draw these circles anywhere in our world whether that be some kind of screen space element like for ui purposes or if we want it to be just in the 3d world potentially or in a 2d world right it's on world space as well the way that we achieve that is by having like a game world and then just drawing this kind of uh square this quad somewhere in the world right so again if we draw this canvas in which we're going to draw a circle if we draw that at a particular place in our world then clearly the circle is also going to be there so in other words the position isn't controlled within this shader it shouldn't be that's just more of like the actual physical geometry will appear somewhere else however the way this is going to work is that inside this kind of square we're going to have a sort of local space and that local space is going to define what area is basically inside our circle and what area is not so our goal is to draw something like this right this is a very terrible circle but it's a circle that i got to do that again okay slightly better so we have this kind of circle within this quad that we're drawing and of course this is kind of our two triangles that are actually being drawn here so if that is the case then we need some kind of local coordinate system within this actual square within this quad so that we can address each pixel we can't have this be something in world space we need to be local to this actual geometry right preferably where the middle would be zero right the origin and then the width and the height shouldn't really matter but they need to be some kind of constant unit such as one so we can have negative one for the left positive one for the right and then maybe something like that now it's common inside like game engines and the way that we're doing this in hazel as well it's common to kind of stick to a unit right like you want to pick your units and you want to deal with real world units such as meters not necessarily arbitrary like numbers right because that doesn't make much sense so pretty much everything we render all the primitives in hazel are one by one meter right what that means is that when we render like a quad or a circle we want it to be one meter by one meter right so again that will apply to a circle as well so what that means is that well if these were negative one to one then that would be two meters right uh one minus minus one is two so instead we basically when we actually build up this geometry right just like we did in the batch renderer series if you if you take a look at that video series you'll see that um we basically have a range of negative 0.5 to 0.5 when we make these actual physical vertices i'm getting a little bit sidetracked here like this is this is more to do with the implementation but it's important because these are the units that we're actually going to be using the coordinate system will be using inside the shader so it's important to understand that long story short we have two triangles that we draw right to make up this kind of uh quad right and their vertices are negative point five on this corner and positive point five on this corner meaning that this is point five for x and y that's negative point five for x and y and obviously something like over here would be 0.5 positive x and negative 0.5 on y and that of course achieves a size of 1 by 1 which is what we're looking for and circles are no different since we have this kind of absolute unit range that means that if we were to use like a scale matrix to distort this so a non-uniform scale if i apply a non-uniform scale like in the transform component inside my entity in my game engine then it's going to potentially make this uh this square not a square anymore so it could scale this up right and then the vertex shader will apply that scale now the thing with this is that these local units should still stay the same right they obviously these vertices that we'll that we'll be using will still be 0.5 to 0.5 this will be 0.5 negative 0.5 meaning that our circle will end up being quite long which might actually be what you want i think you should be able to distort your circles scale them non-uniformly you know who says we need to have circles we can have ellipses too there is room to love them as well so anyway i just think it's really important to just realize that like you know these vertices or this kind of local coordinate system that we use that is kind of independent of where the vertices actually end up in our in our rendering because there's just so much at play there including the transform of the geometry but also like the view and projection of the camera but back in shader toy all we are really seeing is that final kind of canvas and so our first task has to be to like simulate something like this this negative point five or negative one to one bounds because if we don't do that then how we're going to draw our circle locally so in this case in shader twice case we have this uv coordinate in your engine or in hazel i think it's actually just a local kind of position that is passed in as a vertex attribute which is then sent into the fragment shader during the rendering pipeline right so in other words we actually send in like negative point five or negative one i think and then positive one on the other side we actually send that in as a vertex attribute that's what we use as the basis for this but in this case we can equally use a uv coordinate right because a uv coordinate if we actually take a look at this so if i just do frag color dot rg equals uv right it is just a number that is zero on this side let me just maybe get rid of the blue because it's probably a lot more common to see it like like that so if we get rid of the blue then you can see the bottom left is zero zero the right side here is one on the x-axis and then we have the green being one on the y-axis the the top left and then the top right is just one on both axes right so in other words we have a range here of zero two one but the problem with this is that it's in it's in a zero to one range not like a negative one to one range or a negative point five to point five range we want zero to be in the middle not in the bottom left corner because we're going to be using that as the origin point for our circle to convert this zero to 1 into negative 0.5 to 0.5 we can simply just subtract 0.5 right and now you can see that the middle the very middle of this actually looks like where the black begins because these colors are all negative they get clipped to zero and then here we have our actual color but let's actually convert this into a negative one to one range because that's just going to be a little bit easier to understand going forward so we could surround this by parentheses and then just multiply it by two so that takes the negative point five makes it one and it takes the positive point five also makes it one then we have that range here but you can also just do times two minus one which is probably the more kind of common way of seeing this conversion and it mathematically gives you the same result of course now the next thing we have to deal with is the aspect ratio because this kind of canvas is not a square like we would have it's a rectangle but that's also something that's very easy to deal with because we have this resolution variable over here i can easily calculate the aspect ratio here by just dividing x by i resolution y this will give me around 1.78 for this resolution here i'm literally just dividing 960 by 540. so then if i take this aspect ratio and i multiply just my x u v coordinate by it it's going to now make this in the range of negative 1.78 to positive 1.78 on the x axis and then still keep it at negative 1 to 1 on the y y-axis right so now i kind of more or less have an aspect ratio correct uv coordinate and to demonstrate this i could easily write some code that will discard uh basically any pixels or at least set them to black in this case i don't know what's behind there to discard but we'll basically set anything that is greater than uh one right to just be black and actually we'll have to do the same thing uh for less than because obviously we have negative one on the other side so let's just write that maybe let's just bring this up here and then if that if statement is true we'll just set it rgb to vec3 zero okay so needs some more uh parentheses and there we go right so now you can see we have this kind of letterboxing we have these black bars on on on either side because that is outside of the range we didn't really have to do this for the y axis because we know that's negative one to one but anyway point is that is how we can kind of make sure that we're dealing with a square in the middle which has been the entire point thus far now i realize this isn't really too relevant to circles but hopefully this is just some helpful general jell-o cell advice maybe and also some shaded toy tips so let's summarize what we've done so far because it is possibly getting complicated right so what we have now is a this uv variable which in the middle is zero zero that is the coordinate and then on each of these kind of corners it is negative one or positive one right so in this case it's negative one on the x and one on y right over here it's positive 1 on both the x and the y axis they're both both the x and the y variables inside this uv variable so now let's kind of take it back to what a circle is mathematically so the way that we define a circle is a point and then also a size so a radius now the point is irrelevant to any of this shader code why because the actual location of the geometry the transform of the geometry is what determines where this circle will be in our game world right or in whatever world it is doesn't have to be a game world so we can just forget about that because in our case that point is zero zero and it's in the middle now the radius that's important because what that's going to define is how big our circle is now as i mentioned earlier because this goes through the render pipeline we do in fact have a scale parameter that is external to all of this we could draw a quad on the screen this big or that big and it has nothing to do with the code that renders the quad it's everything to do with the transform matrix and specifically the scale within that transform component so because of that the size is kind of irrelevant because again that's external if we want a smaller circle we simply draw a smaller quad meaning that like this actual thing here is smaller so really what we're trying to do is maximize the radius within the quad right i want it to go all the way to the edge what does that mean well for a quad that has a range of negative one to one it means the radius should be one right because the diameter in that case will be two and it will perfectly fill our quad or in this case our render area that we have over here so to summarize we just want a circle where zero zero is the origin meaning that we don't even really need to consider the origin and then one is the radius it's like a unit circle it's all very very simple so now that we're aware of like the mathematical representation of this circle how do we actually draw it well when it comes down to it all we're really trying to do is look at this kind of image and then determine which pixel is inside the circle and which pixel is outside of the circle because if the per if the pixel is inside the circle then we should shade it with whatever color we want the circle to be to start off with we'll be rendering filled in circles and then if the pixel that we're processing is outside of the of the range of this circle then we probably want to just discard that pixel or in shader toy's case because we can't really blend with anything underneath without like creating a new layer and doing all of that complex stuff so we'll just render that as black but in your in your own kind of game engine you probably want to discard that pixel or like set the alpha to zero or something like that so since circles are in fact just that radius what we really need is a way to calculate the distance between this origin point and any given pixel because remember every single pixel over here in this image runs through this line of code so for every pixel we need to calculate its distance from the origin point so from zero so just its distance and then see if it is less than the radius because if it's less than the radius which is one then perfect it's inside the circle otherwise it's outside so how do we calculate the distance of each pixel well do we have some kind of coordinate system we can use and of course we do the coordinate system is that uv variable that we made so the uv variable is the coordinate of each pixel on our screen at its at its given position and that uv coordinate is just like a two-dimensional vector which if we just measure the length of the vector because 0 is the middle then we will in fact get the length of the vector being the distance between the origin and that pixel in this kind of local space so how do we get the length of a vector well there is of course a length function so let's go ahead and calculate the distance as length of uv and then what i'll do is we don't really need this anymore i might just leave it down here but what i'll do is i'll output the uh frag color as being just vec3 distance so the rgb values are just going to be this distance and this is what we end up with now i might just get rid of these uh black bars over here so you can see the full image but of course because we have this aspect ratio it still conforms to be like a proper circle and not an ellipse right if i got rid of this then that's where we get that stretching and so that's why i added it now another common way to actually calculate the length in glsl which you may have seen is by calculating the dot product of the vector with itself right which gives us this and then square rooting that result right i've seen that a lot as well now this and this is technically identical if you actually open up the glsl documentation and you take a look at what the length function does you can see that it literally like squares each component here and then square roots it right it does the same thing and of course a dot product is going to take like the first x component and then multiply it with the x component of the second vector that you're dotting and then add you know y zero times y one but when you're dotting a vector with itself then obviously it's just x times x plus y times y right which ends up being the same as x squared plus y squared and to calculate the vector's magnitude or this length you then square root that right so effectively it's doing the same thing i'm not sure if maybe some people think this is faster i honestly don't know because i'm sure that length is probably i don't know maybe they do it to save a function call length doesn't necessarily have to be calling these functions anyway i don't know the point is i think it makes a lot more sense to just write length so let's talk about this distance for a minute if you look at the image on the left you can see the middle is black right why is it black because at that point the distance is zero the distance between the middle right the origin and a pixel very close by is going to be very low that's why the value is so dark and black in the very middle zero minus zero is zero however as we get further out it lightens up now here it kind of hits white and then it remains white it's still going up it's just that the values get clamped back to one i don't think this is like an hdr buffer or anything capable of of storing numbers above one right above 255 so because of that it just kind of gets clamped but you can actually see that if i like for example uh take the color and i subtract like 0.5 from it you can see that kind of radius expands because i'm just i'm just kind of bringing down that color range so that we can actually see more colors if that makes sense even though we're just kind of looking at a different range of colors okay so now that we have this distance what can we do with it and how does it help us with our circle well clearly we seem to have already rendered a circle the problem is the color is not really constant also it seems to kind of be almost the opposite of what we want i would want the outsides to be black and then the circle in the middle to be white so to fix that the first thing we can do is actually invert this color just doing one minus length is going to make it so that now the middle is a one and then as we get further out we go down to zero instead of going up to one and then we actually go negative and again if i go to distance and i add like point five you can see that expand because we're just now visualizing kind of numbers that are that are less than zero by shifting them up to be above zero so that we can see them but the problem with this is that again we have this kind of nice smooth gradient i don't want that i just want the i just want the color to be either either this or that right but to make that happen it's really simple all i have to do is say that if the distance is greater than zero then i can for example in this case because we're setting the color to be the distance i can set the distance to be one and then we're basically done because now as soon as we get above above zero and we're in kind of positive number territory we know that we are within the bounds of our circle now if we look at this mathematically in the form of a graph let's see what's going on here so we have this graph we have let's just label this as x and y so our x-axis is going to be the distance and then our y-axis is the color because that's kind of like our result right now the thing with this is that we've actually inverted this if you go back here then you can see that we've inverted the distance so in other words uh let's maybe make this point over here zero and then we'll make this like negative 0.5 or something so what we have is we have this kind of a value here which is our distance it goes all the way until it hits one and remember one is kind of like the very center if i get rid of this it will be a little bit easier to understand one is this very center and then as we get further out we will eventually drop below zero so in other words it's kind of like we're going this way you can kind of read this graph from right to left instead of left to right but anyway point is we have this value over here this is one a distance of one because we've inverted it so that's the middle of our circle and then as we get further out this distance decreases and then eventually falls below zero so what we're kind of doing at the moment is we're saying okay well if it's above zero then this is the value i want right i just want it to be exactly one and of course we should label this as one but then as soon as we get below zero on this kind of distance axis then we immediately make it zero so this is what we've kind of created here and if we just connect the two dots here then this is like what the graph would look like we've basically gone from having a linear graph like this to something like this which is like a step function basically like a square wave so what we can actually do is instead of using an if statement for this inside glsl we can actually use this step function because there is a function called step and it matches the mathematical step function and it basically lets us just simply specify like an edge like as if this is like a cliff and this is the edge and then an actual obviously value that we're putting into this step function so if we take a look at the json documentation then you can see we have this step function over here takes in two parameters the edge which is the location the edge of the step function and x the value to be used to generate the step function so again the edge is in our case 0 because 0 is like the edge of this cliff and then what's going to happen is that if we basically cross that edge it's going to give us a 1 otherwise it's going to give us 0 that's just how the step function works so d will be like the x variable that we pass in over here that's going to be d the distance and then the edge is just simply going to be this like turning point zero so if we go back to our shader let's go ahead and use that so instead of using this if statement i'm instead going to say so we were actually setting distance to one so let's just go ahead and try and set distance to be step zero because the edge of course goes first which is a little bit confusing i never really liked that but anyway we'll put in uh the edge and then distance itself is going to be like the x uh variable and if we compile that then you can see we get exactly the same result as before but now we're using math fun times but of course the thing that i should definitely point out here is that the step function uh well that it's it's the same like it uses branches so um if you want to write code like if you know a distance uh greater than zero set distance one go ahead honestly because the implementation is basically identical the reason why i'm saying that this is math is not because it's literally multiplication you know subtraction addition that kind of stuff but because it's an actual like mathematical function that we can use for a lot more than just like that if statement and it's just a little bit more kind of generic i guess it's a little bit more used in the industry so that's basically it when it comes down to like the simplest way to basically draw like a filled in circle because this color is white and this outside is black you can also easily multiply this with any color and you will get that other color if i multiply white with orange i'm going to get orange because white is simply one now what i could do if i was actually trying to like blend this with existing like you know to composite it on top of like an existing color buffer or existing pixels then i could instead of affecting like rgb here and setting like the background to zero or whatever i could instead just have this affect the alpha channel so in other words i could set the alpha to be this and then otherwise the alpha would be zero instead right and if i did that it's not really going to work here um and of course you uh you want to actually do this right it's not really going to work here and in this case you would set the frag color the rgb to be the actual color of the circle you don't need to do any multiplication and then you would just set the alpha to be like the distance and that will be it but this is shader toy there's nothing underneath so i'll still keep treating it as rgb okay so we have a way to render a filter in circle but what if i want like a doughnut or what if i want like a really thin not filled in circle how do i do that well in that case if we go back to our diagram here i mean it's it's it's very similar to what we have now it's just that instead of allowing this to be one for the entire duration what i kind of want to do instead is make it go back down to zero i kind of want to do this right where this doesn't exist anymore and then i basically have a b1 meaning it's filled in those pixels are filled in with some kind of color for a short distance between 0 and like 0.1 and then it just goes back down to 0. so in other words if i try to write this logically so like distance if distance is greater than 0.1 right which should be that kind of border that we're looking at right so this is 0 this is like 0.1 therefore if it's greater than 0.1 let's reduce it back down to 0. if i was to try and write that like this as well it's not going to work and the reason the reason why it's not gonna work is because the distance has already been affected by that by that step function we don't want that to happen we need the actual real distance not the process distance that will that is really just used for color so what we'll do is we'll change this to be our color i'll just call it col color and then that's going to be a vec3 um of our kind of step here and then this color is what we're going to set our frag color to be now if i do something like this i can actually use distance and then maybe set the color you know to be zero and then if i take a look at that then of course it's going to give us this where the distance here again is like the kind of thinness of your circle now can we do this using a step function of course we can we just need to take that existing color and multiply it by a step and i'll wrap this in a vector3 as well we're going to take this value as the as the edge of our step function will pass in that existing distance value however if we take a look at that we still get the same result because we're kind of doing a step the other way if you take a look at this what we're really doing is we're actually going down like that instead of going up like we were before so to fix that up we actually need to reverse the result of our step function by just inverting it so one minus step and then we'll get this same result now this is our thickness parameter i might just bring it up so that it makes more sense this is going to be our thickness that's what that edge is and then what i can do for fun is just actually assign that to be the x coordinate of the mouse and we'll divide it by i resolution dot x so that we get it from pixel space into this kind of screen space and then what i can actually do is click and drag my mouse and you can see it's actually going to change the thickness of my circle pretty cool now if i set this thickness manually to something like 1 that will result in having a filled in circle okay brilliant looks really good but does it and the answer is it doesn't why because well we have this really hard edge and the reason that's happening is because everything's very like robotic there's no kind of smooth fading it's just literally a step function which we know as soon as it hits a particular value it drops right the function as we know looks like this it's really sharp it means that if we look at a group of pixels if this pixel is inside the circle it's white and the pixel next to it is just immediately black there is no kind of smooth transition or fading that we would want to reduce that aliasing and the kind of rough edge there so what can we do about it well ideally we would kind of have a bigger transition between these pixels where instead of just going from white to black instantly maybe we would go to kind of like a gray which i'll just show like this and then we'll go to black right so in other words we kind of we we smoothly fade from that one value from white to black so can we do that well yeah pretty easily actually because turns out the step function has a smoother cousin called smooth step so if we take a look at smooth step in the documentation you can see that it's it basically does like it works the same way as a step function but there are two edges instead of one and the reason there are two edges is because the function looks something like this so what you're specifying here is basically the edge at which it begins and then the edge at which this kind of smooth interpolation ends so you could like really stretch this out and make it that big where these are the two edges or you could be really like you know really kind of not that smooth at all and make make the edges closer together and therefore you get like less less of a long fade right and in fact if you make the edges equal then there is like no distinction between them which means it is basically just a regular step function so let's try that out what we'll do is we'll go back to our code and instead of using a step over here i'm going to change it to be smooth step and then now we have to decide between what kind of two distances i guess are we doing that kind of faded interpolation now zero zero is our absolute boundary right because what we're doing is we're rendering a quad and zero a distance of zero in our code equates to being like literally on the edge so we can't go any anything out outside of that range will simply not be rendered so we can't actually extend the bounds that way we have to do everything within this canvas within our render area so what that means is that this actually has to be a larger value you can't go negative so if we set it to something like i don't know 0.1 and take a look at what that looks like well we get that's quite a big distance so we get this kind of blurry look right but if we set if we set it to be something rather small then it starts to look a lot better and in hazel i think this value is set to 0.005 so if you really zoom in on that or if you look at it in full screen then you can see it actually looks really good feel free to play around with this i don't know like maybe 0.2 would be better if it's like a larger because there are more pixels just looking there are more pixels to render so my quad hd monitor this actually looks pretty good but you should be able to see the difference between using smooth step and then regular step right i'll kind of show you both examples in the video so this kind of fade parameter is also something that you could bring out so float fade and then you can kind of tweak that and yeah you can you can of course make it really big and then maybe use that as a stylistic effect as well i'm gonna leave it at point zero zero five but what about for the inside circle so if the thickness is like point one or something the inside is still jagged can we use it for that as well and the answer is of course so this becomes thickness minus fade and then thickness and now we have a nice smooth result now this is inverted right so what we can actually do is kind of change this a little bit where we just flip the edges and then suddenly it becomes less of that and more of that right and so to do that we can just basically move this minus fade to be on the second edge and then that will create this but then we can just remove that inversion so we'll just save that little one minus we don't need it anymore and we can still have our nice beautiful circle so there we go we have a nice smooth circle we can control the thickness using like the mouse or something and everything looks good there is one slight problem left though because we did in fact do this kind of smooth step now and we're subtracting fade what that means is that if we set the thickness to be one because we want this to be a perfect circle perfect filled in circle there is in fact a hole in the middle and the reason is because we're subtracting that fade so thickness is one but we're subtracting fade now to remedy this what you could do if you wanted to is just kind of add on the fade and then it will go away or you can just arbitrarily set this to like a larger number right you could have an if statement that says if it's one then maybe don't even do this calculation that's also a possible solution there's a few solutions but i think what you could do is just maybe always just add the fade to the thickness and then what that means is that like you might not be able to make it as thin as possible anymore because the thickness of zero will give you a thickness of 0.005 but you probably wouldn't be doing that anyway and then you'll be able to perfectly go to one like that now what you could also do which i realized while editing this video is instead of just adding on this fade if i get rid of that and we get that hole back instead of doing that what you could do is just simply change the way this fade is applied to the smooth step so instead of subtracting it from this side we could actually add it to this side of the smooth step so to the first edge that effectively gives us the same kind of result the same kind of behavior instead of subtracting it from here we're adding it onto here which still means that this edge is larger than this edge but it just means that we've kind of moved around the placement of it so that it it does the same thing as what i did in the video but this just obviously looks a lot cleaner now finally again with the color just just take the color and then multiply it by whatever you want so if i wanted like a nice maybe like orangey kind of color there we go i'm just simply multiplying that white by whatever i want and of course i get my color so that is how we can render some rather nice looking circles i'll leave a link in the description below to this shader toy example that i've written here so from here it should be pretty easy to take this and integrate it into any kind of game engine or just a graphics project as i mentioned if you guys want to see me integrate this into maybe like the batch renderer series or into i'll have to integrate this into get the game engine series and hazel 2d anyway so that video will definitely be coming out but either way let me know in the comments below if you enjoyed the video please don't forget to hit the like button also let me know what your thoughts are regarding rendering circles in the comments section below how are you rendering circles maybe there's like some other technique i'm not aware of let me know hopefully this video was helpful too if it was please consider helping to support the channel by going to patreon.com the channel your support is what makes videos like this one possible and you can also get access to hazel and all of its source code thank you guys for watching the video i hope you have an amazing day and i will see you next time goodbye [Music] [Music] you
Info
Channel: The Cherno
Views: 44,226
Rating: undefined out of 5
Keywords: thecherno, thechernoproject, cherno, programming, gamedev, game development, game engine, circles, how to draw circles, how to render circles, opengl, vulkan, directx, direct3d, metal, rendering, glsl, hlsl, shaders, tutorial, shadertoy
Id: xf7Y988cPRk
Channel Id: undefined
Length: 39min 55sec (2395 seconds)
Published: Wed Oct 13 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.