Healthbars, SDFs & Lighting • Shaders for Game Devs [Part 2]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
uh okay we're going to start off by going through the assignments uh how did the assignments go was it did it go well was it too hard too easy you needed more or was it about was it good uh had some problem with the texture stuff yes the texture part is it's a little tricky because there was there was one more step involved that i didn't uh predict what actually happened uh because i forgot um i wrote the assignment without actually doing it myself but there's one more step in there that i didn't anticipate and it can be kind of confusing because of the way that we use the texture in this assignment um it lasts in 99 health exactly so basically there is an issue where if your health bar has full health or no health or rather you're above a threshold or below a threshold um the texture sampling uh does not look correct um so that was something that i didn't anticipate uh what happened it makes sense that it does happen and we should talk about that so the goal of this exercise was to make a health bar in more and more advanced ways so we start with a very very basic health part the idea is that you have some rectangular area and you want to make that fill up with some color you want to start at a red color when you're low health and end with a green color when you have a lot of health that's kind of step one so let's let's just do step one so just going to make a new shader oh i wonder if i should separate these by folder in case i share this later um there we go lesson one lesson sounds so formal i don't like the name of that stream one there we go that's better let's make a new folder stream two lecture who knows health bar then we'll make a health bar material call it health bar and then we make the health bar mesh in the assignment the um you could just use a quad the built-in unity quad and then scale it episode one that works yeah i set the texture to a sprite 2d and that solved it uh yeah that is kind of the wrong solution but i'm glad it helps yeah because you're not using it as a 2d sprite in this case um so it should probably be left as the the setting of the song before the default import setting but yes you do want to set it to clamps instead of wrap and i will talk about why so we have our we have our quad it's a very empty health bar right now so let's add our health bar uh material to it and then open the shader okay um i feel like i should just set up a default shader like i because i do this every time just fixing the brackets removing fog because i don't care about fog it's just messing up my stuff it's not not even hard to do it i just never do it um okay let's see meshedita um interpolators you can set writer to fix the brackets for you fairly quickly i don't know how to set uh auto formatting for shaders i didn't find that setting i found all the format settings for everything else but not shaders um so if you know where that is do let me know um let's use float full precision uh okay let's see what else um so this unity uh transform text macro uh it's kind of actually not needed here we're never ever gonna change the um the tiling of the of the texture so in this case i'm just going to remove that because it's useless we don't need it um and then by extension we don't need those parameters either the scale offset parameters uh so i'm just gonna delete that and also if you do that and you go back to unity and you select your thing you can see that you still have the tiling offset um parameters here even though they're not used so one thing you can do is you can add a an attribute to this i think it's no scale offset um and then if you add that then you don't have those parameters there which is good because then people are not going to be misled by unused parameters um okay so the first step of this one was a health part that should go from uh let's see there you go how far that should go from red when it's low health and green when it's full health so let's start with that and every time you do stuff like this um it's usually quite good to try to break it down into separate pieces right the um if you think about what we need here we need to um where did the document go so we need to have some mask that is increasing across this one as in it's just white where the health bar should be and black where the health bar should not be um and the term mask is going to be used like all over shaders because mask the concept of using a mask is super super useful generally what a mask is and it's usually a single float value but again it changes across all of your pixels so you have some area that is white some area that is black and then usually you use that to blend between two different states right and in this case we are blending between uh the color of the health bar and whatever color we want as background for the health bar in this case it's black so we should start out by making that mask as in we want to have a health input going from zero to one and if we change that input we should get a just a white health bar that is increasing across uh the black background right uh so we need a parameter for that so let's add that let's call it health and make it a range from zero to one i cannot keyboard right now uh start at one add the variable okay so the default shader samples the texture but we're not going to do that in this case um all right so now we want to create a mask out of this and we want this mask to change as you change health on this thing right and usually when you're talking about things that are changing across either space or time um one of the things that you should pay attention to is across what does it change um so a useful way of thinking about this um i did not have photoshop was done should have opened that earlier okay so um whenever you're thinking about these types of things where something is going to change across some space right uh the health bar we have some sort of rectangular area and the direction on which all of this is going to change is horizontally right it's going to change across this space um so so generally what that means is that you then need access to some sort of coordinate system that does change across that space the the same thing goes for i guess you can call this just a um horizontal and it's a linear change right uh but then there are other like spaces you can change uh change things across right uh if you have a distance value say you draw this thing this one is brighter in the center but it's darker in the edges right so this thing also changes the way it looks but this time it's radial so it changes as you go away from a point right um so sometimes it's useful to to keep these keep these in mind so you have like linear changes and horizontal changes and in this case it would be a radial radial change and the same goes for like gradients you would call this a radial gradient this would be a linear gradient in case you're blending between two different colors or something and then you also have angular which i hope i hope we can do why is that a fixed size chris grady until erica basics um and then you have an angular gradient like this so angular in this case you can see that it's changing by the angle to some reference points um i guess i can clarify it by doing that there we go uh just to get our terminology straight uh this is a radial gradient because everything changes by the radius uh nothing changes by the angle here if we go around this angularly the color is the same right so it doesn't change by angle same with this one this one doesn't change by radius if we go out or closer to the center it's all just the same color so the domain over which everything is changing is depending on the angle toward the center that's why this is an angular one and this is a radial one right um and quite often when you're making shaders like this you can swap these out and if all the math following that is the same everything is still going to work and so even if we finish this health bar we can then just change what like type of gradient we have if we want to make a radial health part or an angular health bar it's relatively easy to just swap between these different types of things because um shaders are just non-destructive right we don't have this baked into a texture or anything if we change the inputs we just change the outputs right um anyway i just wanted to quickly mention that um oh and obviously the um if we're talking about a gradient i didn't actually draw the gradient in this one there we go all right but in this case uh we want a linear gradient and we need some sort of coordinate space that changes across uh the x-axis right and pretty much most meshes that you make depending on how the mesh is made of course you usually have uv coordinates and we talked about that during the previous lecture um uv coordinates are usually not always two-dimensional coordinates on your uh on your in your mesh data and we can display that by outputting it as a color if you want so return float4 because our output of the fragment shader is a float form and we can just grab the uv coordinates from our interpolators so that's going to be i dot uv and then we can just add zero for the remaining channels and we're going to get these gradients um so what we're visualizing is the uv coordinates for for this mesh and you can see that it goes from black to red at the bottom and across the left side it's going from black to green and as usual the x and y axes can be visualized using color so then x corresponds to red y corresponds to green um so this is a linearly increasing gradient of red and this is a linearly increasing gradient of green that are then added together um and when you when you're dealing with colors uh red plus green is yellow yeah because generally colors add just the same way as vectors so if you have a red color then that would be one zero zero zero we can skip the alpha channel because it doesn't matter and then green would be zero one zero uh and then if you add those together it's just gonna be like any other like addition formula right you add them component by component um and then you end up with a zero one one which is yellow right so if we then visualize one of these channels uh it's a little bit easier if we just uh do a single line for that so i dot uv dot x we now see the x coordinate of the uv channels um and or the the uv uv coordinates and you can do the same for the the y coordinate then we see the y coordinate of this all right but we're going to make a health bar so that's across the x-axis so this is the gradient that we're interested in so then we can then use this in order to um we're going to use this in order to make something that changes across the space all right so let's see we do have our health value okay so what we then need to do is then create a black and white mask we want this one to be uh white where the health bar is and we want this to be black where the background is you could also flip it doesn't really matter as long as you use the mask correctly when you're blending between the two states um all right so so a very simple way of checking this is that well i guess first we can make a variable for this so float um health bar mask we can then check our x-coordinate and then we can use our health value and one thing you can do that we mentioned earlier is that you can if you make comparison operators that return a bool um that bool can also be automatically cast to floats so true would be one and false would be zero so we can turn this into a comparison so if we want this one to be white where your health value has reached a certain point then we can just compare the health to this coordinate we can check if health is less than the current x coordinate being rendered and remember the fragment shader is run for all the pixels in that we're rendering right so this is going to happen for every single pixel that is going to render on this object okay and then we can output the helper mask there we go um so right now the mask is inverted so um because i wanted white here um so we're gonna have to flip this but you can see that the uh the mask now changes depending on what health value we're using here right so better these floats than ins um in most cases i don't think it actually matters but i mean you would use whatever is appropriate i think in some cases it might compile to floats regardless but i'm not sure about that but yeah but what would be the use case for integers in this case are you thinking about like if you have a health value of like 500 or whatever i think um in cases like that what i usually like to do is that i usually like working with normalized ranges um so i like i like it when i have a range that is just zero to one because then the math becomes much much simpler on the rendering end because then we could just do this comparison if we had like a max amount of health we would need to remap that before we use it um but that's just a personal preference you can do it whatever way you want because float is either zero one i was just thinking it would be what to use because balls oh um i mean i guess it doesn't really matter in this case um like because right now we're not doing much with this anyway uh but i think there there might be some platforms where um like mixing integers and floats might be slow but i actually don't know if that's true i i just seem to remember reading something about like sticking to just using floats or using the same floating point precision and not mixing a bunch of precisions and data types like avoids having to convert between all the different things um not to mention that i'm pretty sure that integers are not supported on all platforms like on mobile or whatever uh right so so now we have the health bar mask so so this is just the the black and white mask and i also wanted to invert it so let's flip that there we go and then we also wanted to change the color of this health bar depending on how much health you have so if you don't have a lot of health we want this to be red if you have a lot of health or full health we want it to be green okay so let's let's define the uh health bar color right health part color um so the color of the health bar only depends on how much health you have where we are on the health part doesn't actually matter we're just interested in how much health do you have and you blend between these two colors right so i'm just going to hard code the colors into this so the health bar color in this case is going to be um we're going to use lerp because we're going to blend between these two colors based on how much health you have so health is then going to be the t value of the lerp and we're going to blend between um if you have low health that's red so that's going to be float three one zero zero and if you have a lot of health then it's gonna be green so flip three and zero one zero okay so now we have the color of the health wire but we're not actually using it but we could also make sure that it works if we want so i return float for if you can spell that's the difficult part sometimes okay so we got the color if you have low health it's zero it's red we increase it i can see that it goes toward green so that seems to work cool um and then we might want to set the background color right in this case where um actually i'm just gonna do vg color too long of a word and that's just going to be black in this case okay we're going to use this mask to blend between these two states so if we look at the mask again um we can use the mask itself in the t parameter of a lerp right so now the um then we will blend between the background and whatever we want here right because remember if you have a lerp then wherever the t value is zero we get one result wherever it's one we get the other result and any values in between are going to be interpolated although we're not going to use the interpolated values in this case right i wrote vector when i assign all my variables is there difference between using vector of 3 or does it have to be a conversion of use vector i have never heard of vector so i have no idea how that works but i imagine it would do some type conversion um i don't know i wouldn't expect that to compile so um i don't know okay so let's blend between those to get the final health bar color output so flip three uh out color or output color or whatever that's gonna be a lerp between the um background color because uh the first parameter of the larp is where it's black and i want the background where this is blank obviously the since the background is black right now you could take a lot of shortcuts here uh but we're not doing that just to just to show how the interpolation can work right okay so learn between the background color and the health bar color based on the health bar mask all right and then we return the output color there we go so now if we reduce the health you can see that it goes red increase it and it starts going green all right any questions so far i saw it on a forum after getting frustrated that just parentheses didn't work oh yeah so there's um it's a very like frustrating and common mistake that you do like if you have float3 uh some color uh then the way to write that color is using flip three and then you write you know some values uh this would be hdr that's really weird um okay so so a very common mistake is they sometimes forget to write the variable name and the frustrating thing with this is that this actually compiles this is valid code um and the um yeah and this is the comma operator which is a really really cursed garbage operator that has been frustrating me a lot and i don't see the point of it existing at all and apparently there are some cases when using for loops where it might be useful for whatever reason i don't know it's garbage um and and not great i have a ton of questions but none of them are useful oh uh well in that case i guess i shouldn't answer them um yeah the comma operator is cursed i'm curious about you one's question uh wait what question oh i didn't know that that was a question sorry i thought that was just a statement um how would you go about if you wanted health power that changes in chunks um so so in that case um i guess what you would do is that you would sort of um posterize the coordinate values so it would probably be something along the lines of so if you take the x-coordinate multiplied by eight um or rather you floor that multiplied by eight and then divide it by eight i believe we will now have it split up into eight parts um yeah and then if you wanted to to add that mask around it because in your example you have like a black border um you could map a repeating texture to this that lines up with all of these chunks right um but i'm not sure if you would want to have like a floating point value uh that um that can sort of fade like this even though it doesn't actually change the uh the chunks but yeah going with one minus health bar mask times background color plus health bar masks times health by color is performance-wise pretty much equivalent to the built-in larp function right um yes that is that is equivalent yes um but i i mean if you do write that then you should be using a lerp function because otherwise your code is less readable right and and yes so like you're mentioning it can overshoot with a built-in lerp function in shaders as well if you do stuff in unity then you might be you might have used the math.lerp before the unitymath.lerp is clamped but the shader larp is unclamped which means that if you go outside of zero to one uh it will extrapolate those values um so it's it's important to remember that they work a little bit differently uh all right so there we go got the smooth smooth health bar again so let's move on um all right so we have set up this uh the very basic 1a part of the assignment so far where you just have this very simple health bar that changes from one color to another across the uh the space of this one as you change the amount of health that you have the next part of the assignment was to add a threshold so that you can set where it's going to go fully read and where it's going to go fully green meaning that if it's under a certain value uh it's going to be fully read if it's um like if it's less than than 20 for instance uh then we just want that to be the the bottom color and if it's above 20 or so we want it to be fully green okay so what that means is that we basically have a two thresholds right where we want to change uh the value of this one uh then what we need to do is that um we need to basically remap the range at which we are interpolating these colors right because we have the uh or no sorry not the color but um i don't know yes the color color sorry i'm confusing myself um this one so we want to set thresholds for depending on how much health you have we want to select the the bottomless color or the the topmost color but instead of doing that at the very ends we want to remap this um okay so the way to do that is using our very familiar function inverse lerp because now we have a range of zero to one but we want to make it so that um at 0.2 we want it to be zero and at 0.8 we want it to be one right and that's the perfect use case for the inverse lamp um we don't have an inverse slope so i guess we should have um i keep just re-adding it all the time i should really make a library what an inverse lamp okay um so then we for just for the color interpolation itself we want a different t value for this alert how do you make a shader library just like you can include unity's cg include here uh you can make your own cd include file where you just write functions um so then you can just include your own like library stuff um super useful um it's also it's also it can also be really really confusing if you overdo it kind of like unity has done in hdrp and urp making your shader pretty much unreadable which is not great um all right so this is going to be the t value for the health color so we're going to inverse lerp uh between these thresholds i'm just going to hard code them now so at um 20 and at 80 percent i want to remap the health value so that it's uh wherever the health is 0.2 i want this to be zero wherever health is 0.8 i want this to be one that's how the how the inverse look function works so um so then we have the t value for that and we can input that there uh one thing to watch to watch out for though is that um like i mentioned before the lerp is not clamped so t health color is actually going to shoot over to negative values if we are below 0.2 so we might want to make sure that we clamp this which confusingly enough is called saturate that saturate will clamp it between 0 and 1. kind of kind of tempted to just like there we go oh wait the other way around it's a little cursed probably shouldn't do that okay uh so now we're clamping this between zero to one we have the remapped held color um and then let's see if it works okay so you can see that it's changing to red here and then it looks like it's stopped now so that's exactly what we want so now it's fully red even though it's changing within this range and then it starts changing above that threshold and then once it hits the top threshold it's fully green it's not fading in this range which might be useful if you want the health part to like make it feel like you still have a lot of health and that you're not like yellow uh even when you're here so it's kind of a design choice i guess but use one nonetheless to have that ability to tweak those thresholds um although now i just hard-coded it so okay the next step was to instead of making the um the background color be black like this we wanted to instead make it transparent as in not render at all there are many different ways of doing this depending on your use case uh we can either make a um an alpha blended shader or we can make a uh still leave it as an opaque shader but we just discard the pixels um so what you can do is you can use a function called clip which is a very very special function um clip will basically depending on if the value is less than or equal to zero it will discard the uh it will discard the current fragment being rendered as in it will not output at all so we can actually do this based on the health bar mask um we might need to uh actually might be only if it's less than zero so we might need to subtract some small value yes sorry so if it's less than zero um all right or probably 0.5 is probably more safe in this case because we don't have any grayscale values in between um so now it just doesn't render that part of this at all um and we can still modify this one however much we want um one of the advantages of using clip as opposed to alpha blending is that when you use clip you can sort of kinda make transparent shaders without having all of the sorting issues that you generally get with transparent shaders the problem is that you can only have um like a one bit transparency either the fragment is rendering or it's not right there are ways to sort of make things look like they're fading um so some some games use like dithered fading in order to get like gradient looking things um that that can like have partially uh partially faded stuff um but but the nice thing about this one is that it it sorts perfectly because it still writes to the depth buffer it still reads from the depth buffer um unlike the transparent shaders that just can't write to the depth buffer or they can but generally you don't want that yeah okay so that's that's just um one way of discarding fragments um but all right we should probably do the alpha blending version of this too so that was clip i can leave that in i guess and i guess if we want to optimize this i guess we should shuffle things around a little bit uh because we probably want to clip as early as possible uh when you say one bit do you mean where the alpha mask is either black or white and not different shades are great yes uh with when you're using uh with a discarding fragments there there's no partial transparency it's only either the fragment renders or it doesn't render there's no way to make it like halfway blended or whatever it's only either rendering or not so that's the the disadvantage of using clip is that you can only tell it to not render or just render it there's no way to make it like faded with alpha blending or whatever so let's do the uh transparency version we're just using alpha blending um so right now this is an opaque shader and we don't want this to have the render type opaque if we're doing something that's transparent so we want to set render type to transparent we also want to set a render queue to our queue to transparence um because that way we generally always want transparent objects to be rendered after all the opaque objects uh the reason for that is that most transparent objects still want to read from the depth buffer because if you have something that is transparent behind something you still want that to not render right or if it's intersecting with something you still want it to not render on top of other objects like that right all right so so we want this one to still be transparent uh or to make it transparent rather than opaque this also means that we probably want to change the way that it behaves in terms of the depth buffer because we don't want this one to write to the depth buffer anymore because even though we might set it to transparent at some place we don't want to write to the depth buffer there because then other transparent objects might discard their pixels and as soon as we have overlapping transparent stuff you pretty much never want to make the transparent stuff rend or right to the depth buffer because then they're going to discard each other and it's going to look like they just delete themselves as they like overlap other things it's going to be a mess um so generally you don't want them to write to the depth buffer so we add the write off okay um all right and then we also need to set the blend mode um so if you remember the way that the uh the blending stuff works is that we basically have the source color as in the color of this shader times x let's just call it x for now and then plus the destination color times y um the the stuff that you can change with the blend modes is you can change x and you can change y and you can also change the operator here it can either be plus it can be minus it can be a reverse subtract which is just flipping the arguments but generally this is the formula that you're working with um so if you can set x and y the the way that you usually do alpha blending there are other ways of doing it if you want to look into like pre-multiplied alpha blending which is a whole subject on its own i'm not going to go into that um but generally alpha blending means that you want to do source times the alpha channel of the source and you want to do destination times 1 minus the alpha channel of the source and if you look at this formula this looks almost exactly like a lerp right this is a lerp between uh source and destination right based on the source alpha as the t value which is exactly what we want to do right um so the way that you do alpha blending is they do blend src alpha 1 minus src alpha there you go so this is alpha blending and then we need to actually write to the alpha channel um because now we haven't done that yet um so i guess just to see if it works we can do a little test what is source and destination in this case source is the color output of this shader destination is the existing color in the frame buffer that we're rendering to in other words the background right so you can think of destination as the existing color of all the things we're rendering to and source is the color of this object um all right so first we should probably just make sure that this works in the first place so uh let's try uh return um float3 now look for uh let's make a red color and then we set the alpha channel to either uv.x so now we should have a horizontal fade right there we go um so you can now see that we do have partial transparency as we move across this shape right um so that works we do have something that's alpha blended now and yep so again slapping in these like return some color is pretty much just the the easiest way to debug stuff in shaders it's it's usually like good practice to just double check things every now and then like this uh just to make sure that you got everything right because sometimes in context when you have all of your code running it might be a little confusing and hard to debug um all right uh so then the goal was that we wanted to make this health bar um uh let's see alpha was zero that's why that's not visible so we wanted to make this one be fully transparent wherever the background color was here right so we can change a little bit in the way that we do this now so instead of doing a setting out color here uh and a background color we're not going to need the background color anymore so we can actually just remove those and then we can do we can simply have the health bar color in rgb and then we can use the health bar mask as the alpha channel because the mask is already black where we don't want the health bar to be visible and it's white where we want the health bar to be visible which is exactly exactly how alpha would work in this case right so if we recompile you can see that this is now transparent again and we're going to change this one like this and now it also supports partial transparency in case we want to do that right uh so if we for instance multiply this by 0.5 that means that wherever this one is one it's going to be 0.5 instead so it's going to make everything a little bit more transparent so now the health bar itself is transparent too um yeah okay so that we've done one a one b one c all right you have questions and you didn't want me to interrupt my explanation so questions go is there a way to get the lines in the healthcare fade i'm guessing that's because of floating point precision in uv coordinates what do you mean by lines in the health bar the revisible lines in the gradient when they did the transparent fade based on uv chord and x um that could be just a video encoding um but it's also a limitation of that we only have 255 steps of transparency so it i don't know if it's if it's video encoding in this case um i mean it should be visible if we just return i dot uv dot x as well right yeah the lines you're seeing is probably mostly the compression i would guess or it's just the limitation of the bits yes i can show the blend function [Music] there you go can should you use clip to discard fragments early for performance reasons when you do how can you be sure that they will actually be discarded so the way to test it is just to output a color um like some color that is very visible and then write your clip function and see if they do get discarded right um and then whether or not they will be discarded depends on what you discard them by like what is the parameter or values you use to discard right um yeah and yeah you can do that early for performance reasons absolutely um so it sort of depends on platform as well i think on some platforms using clip might be expensive probably on mobile as usual um but yeah so so you can use that for to optimize stuff out um i do that sometimes even if i have alpha blended shaders i sometimes use discard where alpha is zero um to early out in case i have like a really expensive shader um okay no more questions let's remove various stuff um is it like if i clean this up i don't know if you want this code to still be here i guess you can just watch the video if you want the old code back um let's see let's see we make sure we got this restored we do all right so uh the next step of the assignment was to use a texture for the color i'm gonna in this case i think i'm just gonna bring back the background color uh instead of the um instead of using transparency it's a little bit more um easy to see how much health you have i think um so one thing you can do here i'm just going to set it to 1 for alpha when you have something a mask that is black and white and you want to make it black where the mask is black itself given the math that given how math works when you multiply something by zero it becomes zero right if you multiply by one it remains the same so if we want the background to be black specifically we could just multiply by the um by the health bar mask right so in this case it's just going to make it black wherever the health bar mask is black where the mask is white it's just gonna remain the same color um so if we go back now you can see that it's still working um okay let's see do i have the yes i have the health bar can i reset the settings on this reset there we go so now we want to use a texture that is then going to be defining the colors that we want um so [Music] you can see that this is a texture that has some like gloss effects and some shading atop at the bottom and the color is now defined by the gradient inside of this texture instead of being something that we hard coded into the shader itself right um so yeah so let's add our health bar texture and let's go back to the shader and now we can bring back the unity default code um all right let's just return the texture to make sure that it works it does um so now the um now we got this whole texture mapped to the to the entire health bar but this is not how we want this to look right we don't want the health part to look like this um and we also um we also don't want this to um if we take the health bar color multiply it by the mask this is also not the effect we want so the goal of this one is that we want the entire health bar to be green when you're at the top we want the entire health bar to be yellow when it's here we want the entire health bar to be red when it's here and so forth right which is a little bit of a challenge because now you need to think a little bit about how does texture sampling even work um and how can we make this um sort of pick one slice of this health bar and stretch it across the entire health bar so that's kind of a challenge that you need to figure out here also because i don't like the way the compressed textures look i'm just going to go to the texture and set compression to none so we get some more colors back because i don't like i don't like ugly textures it makes your texture like four times as big um approximately um wait no not four times sorry i'm thinking of something else actually wait 32 kilobytes 5.4 kilobytes okay approximately no compression there we go um so the point of this texture is that we want to extract the color of the health bar that used to be this health bar color right um so we move our color something there so this is just to um right this was for the t value we don't need the t value anymore right because we don't need that thresholding uh so we can remove the the thresholding uh we actually don't need to blend between two colors either so what we're left with is just our texture sampling so there we go um health bar color all right so what we're doing now is that we are sampling this texture so something that is that is important to remember is that we can we can sample this texture however we want uh we don't have to just straight up use the uv coordinates um you know we can multiply this by two and we have now just changed the way that we sample this texture obviously it's this is not what we want to achieve but the the point is that the uv input to sampling a texture can be whatever you want it to be for better or worse i suppose um so so what you you need to think about here is that we want the color to be sort of picked from a vertical slice right because if you have low health we want uh we want the whole health bar to be red so even if we are sampling over here or rather if we're um even if we're sampling over here but the health is currently at this point or at this point um we want to pick the color from where the health is right um so so if you have a lot of health over here for instance like the health bar is cut off at this location we want this part to also be green um so what that tells us is you can then again start thinking about across what does it change across what does it not change um if we want to keep this glossy effect and the little shadows at the top and bottom then we need the texture sampling to change across the y-axis right but we don't want it to change across the x-axis along the x-axis we just want a single slice out of the texture so and the location at which we want that slice is your current health value so if you have a lot of health it's going to sample from the very green part if you don't have a lot of health it's going to sample from the red part so what we actually want here is that we want the x x-coordinate of the sample to be your current health but we want the y-coordinate to still be the uv y-coordinate so what we need to do is say floats too where the health is the x coordinate and i dot u v dot y which is the y y coordinate of the uvs is going to be the y coordinate and then if we go back to the health bar you can now see that we have a very murky health bar which is a fun problem that we ran into um okay so you can see that now the health bar goes from green and it goes to yellow and the whole health bar is yellow here and then the whole thing starts fading to orange and then eventually to red and then the deep dark dangerous you have very low health threat right um okay but now we have a fun interesting little puzzle why does it go murky green as soon as you have full health this is a this is because the way that the texture is sampled in unity at least that depends on the import settings of that texture um because there are different ways of sampling a texture so right now we haven't actually told unity how we want to use this texture so just to clarify a little bit um what is happening so if we just go back to the original uv sampling that we had to just draw the entire texture um if you zoom in on the edge of this you can see that there's a little sliver of green there right and if you zoom into the edge of this you can see that there's a little sliver of red the reason that this happens is because the texture thinks that you want to tile this uh it presumes that you want to have multiple of these next to each other so that it's making this very nice pretty little blend between these colors unfortunately that's not what we want so this actually has an undesirable effect for us right because when we sample a single slice out of this we're sampling the halfway blend between minimum health and maximum health right um so basically this texture presumes that we want to repeat it when we sample it so what this is called is that um you see that the texture clamping is wrapped as in it presumes that you want to repeat it um along in all directions um but what we actually want to do is that we want to clamp it as in if you go beyond one of the boundaries between 0 to 1 we don't want to repeat that we want to just stick to the edge that we left from so we just want to make those colors extend forever instead of trying to repeat this texture you can set that in the import setting of the texture there's something called wrap mode and if we set that to clamp it's now going to clamp instead of trying to repeat this texture a very useful setting that they added relatively recently finally is that you can set this per axis as well so that it clamps in one direction but you might want it to repeat in the other direction um which is really really useful and i'm glad they finally added it um anyway so in this case we don't want it to repeat because then we get this this gross blurry boundary uh in this case we wanted to clamp um i wanted to clamp in both directions um so if we had some sort of if we had a bright color at the top and a dark color at the bottom uh setting it to wrap would also destroy that effect vertically not only horizontally all right so let's see if it works we changed our texture to be clamped instead of wrapped and then we set health to maximum value and now it is fully green and it is also not murky yellow when it's completely gone which is just nice to know i guess uh in the back of your mind even though it doesn't matter but at the top it doesn't matter so so there we go i'm still mega confused about this because it strikes me as a bit silly what is the sampler doing is it sampling several points in the texture and taking the average shouldn't the sample of one be exactly in a pixel center um so would actually i'm pretty sure that that depends on opengl versus directx whether or not a sample is at the center of a pixel or at the upper left corner of a pixel i forget how it works but basically if your texture is set to bi-linear it means that it's going to blend between the different pixels so for instance if we look at this grass texture we zoom in a lot you can see that it blends between all the different pixels in this texture or texels if we want to be really esoteric about it um so you can't actually see just one sharp pixel and then it jumps to the next because this one is using bi-linear filtering so that's why everything is sort of muddied together and blended everywhere you don't have to use that but the problem is that you usually get a lot of anti and aliasing issues um so you have aliasing issues and pixelation if you don't use uh blending like this but you can turn it off if you set it to point filtering then you do get this like very pixelated uh get a single color and so forth right um but by linear will interpolate between all the neighboring pixels so so that's why um yeah so that's why it it does this when you say shouldn't the sample of one be exactly on a pixel center it like it sort of depends on how we like what color should be in the center of a pixel versus uh center of a pixel minus 0.5 like if you offset it a little bit um it gets a little messy um but yeah now i understand why it's called nearest neighbor yeah because then whenever you sample it kind of snaps to the nearest one and remember whenever you sample a texture in a shader you don't do that with integer values you don't say uh get this pixel you know um you say get the color at some floating point coordinate which could or could not be right on top of a pixel value right it makes zero sense to modify a texture by default when you've simply added it what do you mean by modify a texture oh uh the filter settings does not modify the texture at all this is purely something that happens in real time um so this only has something to do with the way that we sample it it doesn't actually modify the texture and the the default setting the default setting presumes that you want to tile textures which quite often you do want to do that right um like for instance we have the grass texture here right that we looked at last time we can set this one to clamp too but we probably don't want clamp on this grass texture because then this is what happens right as soon as we go outside of the texture it's just going to stick to the nearest edge right any other questions still can't figure out how health greater than dot x works um it's a comparison so it checks if the current health which is between 0 to 1 is greater than the current coordinate the current x coordinate on the health bar so if it is greater than that x coordinate it returns one if it's less than that it returns zero so then that gives us a black and white mask that we can use for blending in my opinion as much as possible should be explicit to avoid misunderstandings kind of have lerp in unity it's also clamping like y linear interpolation is not equal to clamp yeah but you can also get into disagreements about what that means right um it's called linear interpolation why would you expect that to extrapolate when it just says interpolate right so you could argue that it shouldn't be called interpolate um so like it kind of depends on like how you interpret that so i don't know i personally like it when they extrapolate because it can be useful i don't need super bright lights anymore since i got this camera so now my monitor is a big contributor to the color of my face which is for for better or worse not sure if this is good or bad how do i get the math to be included in all new unity projects though um yeah i guess you don't you can't right now it i might make it into a unity package at some point i kind of just didn't um but maybe maybe i should but it would be a little bit easier at least uh there's a lot of shady convention i straight up just like like vertex shaders output v to f uh fragmentators take a v2fi oh the the o and i the way that i read them is input and output yeah so i means input o means output so then if i'm in the fragment shader it's i because i want to grab the input coming in from the vertex shader right but i mean those are variable names you can name them whatever you want so if you don't like those names name them something else i think the um shaders are sort of like notoriously um annoying in that a lot of people who write shader code just use a single letter names for a lot of things which can be really annoying um but um usually usually shaders are kind of limited and isolated um like a shader very rarely has dependencies outside of that cheater so usually they're kind of self-contained and it's not usually a big issue i think single letter variable names are okay in some contexts if i'm writing an inverse lerp function i'm not going to do start value and value and whatever but in some contexts i think it's okay to just have single letter variable names but it depends on the context i think in in shaders it's so established at this point that uh you use like single letter variables for the input from the vertex shader um so a lot of people are just used to it at this point in which case if everybody used to it then probably doesn't matter that much but it's confusing as someone coming into it okay there we go yes okay it worked as i expected it would it's as i expected um all right so one of my students were talking about like how does the bilinear interpolation work does it like discard the pixel data is it like in the center or is it in the edge like how does it sample textures uh in this case i have a very very simple uh texture it's just a four by four pixels and yeah this is this is the texture basically if i turn on bilinear filtering on this one you can see that the colors are sampled in the center of each of the texels right um there are little crosses that sort of happen because of how bi-linear blending works um and then it starts interpolating to the neighboring pixels and in this case it's set to wrap right um if we set the wrap mode to clamp um it's obviously gonna clamp beyond the centers that go toward the edges so that's sort of how it's gonna like how it handles that situation so if you want to read the top left uh color just cleanly then yeah you would need a half texel offset if you want to like still use bilinear sampling but for whatever reason you want an exact sampling there any other questions before we continue with the health bar no more questions all right we just did the texture part of the health bar so that was one d um right i only have one e left and the overachieving things if you want to learn how to do that which i think would be a good uh thing to talk about so i think we should talk about this as well um all right so the next thing was that we need to make the health bar flash or pulsate or like in some way animate um the the color of it or the brightness of it whatever you want to animate uh when you have less than a certain um amount of health okay so a very useful place to go whenever you want to do stuff like this the first thing you want to do uh quite often is you want to figure out what do you actually want to achieve like if you wanted to flash how do you want it to flash uh do you want it to like have a strong flash and then fade and then strong and then fade or do you want it to be like a slowly pulsating like a sine wave flashing thing right um because those are different different curves to the flashing so so like answering that would be sort of the first first question you should ask yourself then when you want to start tweaking things a good place to like tweak stuff and like kind of figure out what type of flashing do we want is to define a curve for it so then what we can do is that we can consider the x-axis to be time and the y-axis to be some sort of brightness value right so for instance if we do the cosine of x um so now we have a brightness value that goes from one and then it goes back to negative one goes back up to 1 and so forth right so this would be a very soft pulsating thing although depending on how we want to use this we probably don't want negative values here so because presumably we have some sort of color value let's call it um let's say the blue one is the color value here let's see if we can alright so so the blue one might represent our color that we have um and then we want this like pulsating thing to affect this blue line right so how do we want this to be affected um so here you can start asking yourself well do i want to add or do i want to multiply what do i want to do here if you for instance multiply this by our sine wave up there you can see that it's exactly the same because it's one multiplied by all of that stuff um i guess in that case if we want to multiply we can just visualize it straight up we could add it as well in which case the color is now going to go up to 2 which is going to be it's going to over saturate the colors and then it's going to go all the way down to black and then all the way up to white and then continue increasing beyond that to even brighter but this is probably too strong of an effect right if it's going to fade all the way to black and then up to something that looks um you know all the way up to something that just completely over saturates everything we probably want to tone this down a little bit so what we can then do is is that if we multiply this function we can reduce the magnitude of this so if we multiply it by 0.1 for instance you can see that this now has a much more softer pulsating effect right so now it goes from 1.1 as the max value and 0.9 as the minimum value across time if you then want to change the speed at which this is happening then you can multiply the input parameter to the cosine which in this case is time right so if we multiply that by 5 for instance the frequency has now increased um so we could make a variable for this um called i don't know um so maybe we can have a for amplitude actually we can just type it in here right there we go um frequency is going to be r because why not okay so basically we have these parameters now where we can set the amplitude of this um and the amplitude we don't want it to be a one because then the flashing is going to be way too strong um so we want this to be kind of a low amplitude um and frequency kind of depends on um you know we need to look at this across time um it's kind of hard to like tweak these artistic effects without actually being in an engine but this is a very simple way that we can modify some sort of pulsating effect right okay so so let's do this in shaders so basically we have our little mathematical function um and generally the a very simple way of doing this is cosine of time times frequency times amplitude outside of the cosine and that's a very simple way that you can sort of tweak a sine wave to whatever parameters you want right so let's start out by just doing that flashing effect um so we got our health bar color and we're going to ignore the mask for now i'm just going to output the um the full health part um yeah okay so then we want this dip flash and we can then do our little sine wave again so let's do float let's call it flash uh so cosine of remember that it's underscore time dot y for the current uh time that has passed in seconds so it's the time multiplied by the frequency that we want this to be to be flashing in um we could either make this a parameter we could hard coded whatever we want um let's do times four um probably a good starting point and then we want the magnitude of this one uh the magnitude of this one um kind of um we we mostly just don't want it to subtract all the way to zero and then go like overblow the colors to like super saturated um so just some low value probably works all right so now we have this flash variable and we could return the flash to see if it works um that does not work because um this is a float value and it's going to take all of these values and spread them across an rgb a and because this is alpha blended we don't want the a value to be zero um so we are going to set the flash dot xxx for the rgb values and then alpha's gonna be one and there we go and now you can see that there's this little pulsating flashing thing it's a little subtle so maybe i should increase it just for the visualization purposes um so that seems to work it seems like we have a wave that goes up a little bit to 0.3 and it stays black so presumably it's going to negative 0.3 right because cosine is from negative 1 to 1 and then therefore it's going to go to negative values cool great so then we um then we can composite this and use this value in our health for our colors in various different ways um so um the thing we were doing in the graph was that we were simply adding this so we can do plus flash okay so let's let's see if this works it does seem to work we have a flashing health bar um and we can change the color and it's going to flash in that color range instead of the other one and so forth all right um might go a little bit too low and too high since we're mostly going to use this in the bottom range eventually um so let's reduce the range that's probably fine uh so so one thing that we should also keep in mind is that if we are adding to a color like we're doing here we're doing health bar color plus flash and remember this is a float value so this is going to be swizzled to a vector3 so it's going to make an rgb value out of the one x component of this um whenever you add or subtract from a color you will in most cases accidentally usually change the hue of that color um so sometimes you want to make sure that you still keep that hue and still have that pulsating effect so sometimes what you rather want to do is you want to change the magnitude of the color rather than adding a flat value on top of it um so so if we want to do something like multiply then multiply is the way that you scale vectors right multiplying by a vector will not change its direction and similarly if you multiply color it's not going to change its hue unless you go outside of the zero to one range in that case it will because of clamping and you have limited amounts of colors on your monitor but generally if you multiply rather than add you will retain sort of the color quality of that color however this is going from negative 0.1 to um to 0.1 but if we're multiplying we want to make sure that we still retain the color whenever this is zero we don't want to multiply by zero we just wanted to stay at the default color and so what we can do is that we can add one to that so then it's sort of going to originate at one uh we can do that either in the flash itself or um or here it doesn't really matter um so if we add one this is now instead gonna be from 0.9 to 1.1 right so if we then multiply by that it's either gonna scale it down uh by 10 or it's going to scale it up by 10 so usually if you do stuff like this the flashing itself is gonna be um the flashing itself is gonna look a little um fancier and better it's gonna retain the color quality of the texture okay there we go uh it's a little solid right now so let's increase the amplitude again there we go so now when it's flashing to be brighter you can see that it doesn't go to this pale white now it kind of retains the saturation of the color and all we're changing is the value rather than the hue and the saturation right that's kind of a nice thing of just multiplying a color oh if you want things to animate in the editor if you go to let's see it's somewhere here you can check a thing called animate materials or animated materials and that way it's just going to animate even if you don't hit play but this was not what we were supposed to do uh in the for this assignment we were supposed to make flash uh when you have less than a certain amount of health right so we don't want to do flash all the time so let's bring back some of the previous code we had we have the health break color and the health from mask there we go so what we can then do is um let's move the healthwork color um so if we want to like bring the flashing back and do health part color times equals um the flash there we go um so now only the health bar itself is flashing and the the background is still gonna remain black okay someone is asking if there's a risk that values go beyond zero to one when you multiply yes absolutely so right now you can see that the red is fully saturated so there's a bit of a edge moving up and down here right um so yes that can happen so it sort of depends on like what um what effect you want to have um if you want you can of course offset this curve to never actually shoot beyond one so if you in this case if you also subtract the amplitude then that would make sure that it doesn't go beyond one if you only want the flashing to darken but sometimes it can be like an artistic effect that you want to achieve um so it kind of depends on what you want to want to do with your stuff okay so we got this health bar we wanted to actually flash when your health is below a certain value you don't want it to flash all the time so in this case we want to add a threshold for that there are different ways of doing this it kind of depends on like how we want to set up the branching or if we want to avoid branching or whatever um so we could just make a simple if statement um so if health is less than 0.2 which is 20 health uh then we want to flash we could do that if we want um um in that case we should probably move the flash in there um there we go uh so now we've added an if statements if hell's health is less than 0.2 uh we multiply the health bar color by this flashing value right so then now you can see that it's flashing when we have low health and if we increase this it's going to stop flashing um so the gameplay equivalent of this thing is basically um you just want to warn the player when they have low health so that they spot the you know the health part and then things are dangerous right now um how expensive are if statements in the shader um so in general if your if statement varies a lot across your rendering then it can be expensive if your if statement pretty much has the same value across your entire render uh then it's not much more expensive than like an ad so it really depends on what you're doing um in this case the health value is going to be exactly the same across the entire render um so we're not going to have like divergence uh within the same rendering of this health bar um so it kind of depends on what your condition is all right me any questions about that before we move on to the to the rest uh speaking of optimization wouldn't it be cheaper just check the x value in the vertex shader uh what x value oh this one um so if you want to check the health bar mask in the x or in the vertex shader you can't really do that in this case um so so the problem is that your uh this is either going to be zero or 1 right and the vertex shader is doing this on a per vertex basis so the vertex shader can't check these values on a per pixel basis right so what's going to happen is that we're going to set this value on um it's going to set it on a per vertex basis and then it's just going to interpolate between those things um which is generally not what we want there are ways of sort of kinda moving this there uh but um oh wait hold on uh this one is no wait never mind um you can sort of move some of these things to the vertex shader if you want like we could move the flashing stuff into the vertex shader because the flashing is the same it doesn't matter like where we are across the fragments um uh if one left four vertices check and percentage has an x is going to be either zero one yes something like having this in the vertex shader um you can do that if you want um the thing is that the output color you're going to have is going to be per vertex so what's going to happen is that you will have four color samples and there's going to be one color here one color here one color here and one color here and then they're going to blend across this entire thing um because if you remember the interpolation across triangles um it's going to do bary-centric interpolations across that whole thing right uh this is for the previous one where you had two static colors to interpolate between oh in that case yes you could do that because then there was a single color across the whole thing right but in this case we don't have that because vertically it's changing colors between everything we have in the texture right okay do we move on to the overachieving stuff there it is okay there were two overachieving things in this assignment um one was to add rounded edges the other one was to add a border around the health bar i'm gonna be a radical and i'm gonna now flip these two because it's easier to do it in that order so um add rounded edges to the health bar all right so now i think it would be good to talk about a general concept that is very very useful whenever you are doing shaders it's sort of recently gotten very very popularized because there are it's a super useful thing whenever you're doing something like uh ray tracing um so there's something called assigned distance fields and sign the distance fields is sort of just a fancy way of saying a distance to something and that distance can also be negative if you're inside of it that's sort of it and checking distances to stuff is some it's something you do a lot in shader code in general but yeah but recently this is now has picked up a term sdfs so it's a very very useful thing to search for in case you want to do like a lot of distance checks um so we're going to start by looking at one of the most simplest sdfs you can work with um i don't know if we should make this in a separate tutor actually we should do that um let's make a shader sdf example create a material there we go create a quad um okay fire up our sdf example all right uh we don't need textures no properties uh i guess i i should really set up a template for this i'm sorry you have to go through this every time okay almost with a boilerplate removal of garbage i don't want neat what a shader all right so i'm going to start by just outputting the uv coordinates so uh we're just going to do a return float4 i dot uv comma 0 0. there we go okay so now we're looking at a very very simple um simple square and we have coordinates going from zero to one on both axes just like the regular uv coordinates we've been looking at in many ways we can think of this as a coordinate system where every point represents the vector going from the origin of the coordinate space going to that point right um so in some sense what we're visualizing here is essentially a vector field right every point is associated with the vector which is the point itself okay so um so like just with any other coordinate spaces um you can manipulate them if you for instance if you multiply a coordinate space you're scaling those coordinates if you are adding or subtracting you would be offsetting that space so for instance if we want to make this be 0 in the center instead of in the bottom left then we can take these coordinates and we can multiply them by multiplying them by 2 for instance so now we've scaled these coordinates now instead of 1 1 being up here 1 1 is now here as in the vector with the components 1 and 1. and then we can offset these coordinates as well uh so if we want to offset it to put 0 here we're going to subtract 1. so we go back here subtract one and then recompile we now center this coordinate space and we've also made it so that one is at the very edge here so now it's zero in the center we've got negative values on the left negative values going down here on the y axis and then positive values in this quadrant um and so forth so basically we're now remapped this from 0 to 1 to negative 1 to 1. so let's talk about what sine distance fields are let's start with a distance field it's basically the same thing but a little bit less complicated so what is a distance field just like this is a vector field that we're looking at a distance field is one where every point that we're looking at represents a distance to something so let's make a distance field for a point in this case let's make a distance field for zero the point in the center um so a way we can do that is we can get the essentially what we're doing is we're getting the distance value from uh the distance from zero to the current coordinate that we're rendering so we're getting that distance this is equivalent of just getting the length of the of that vector right so we can just do length of that uv coordinate and then let's visualize that so now what we have here is a distance field every single pixel represents the distance to the center right and this is how you make a radial gradient generally so um what like separates a distance field from a signed distance field is that it's called a signed distance because generally distances are not signed as in distances are never negative but in some cases it's useful to have that concept just like sometimes you have signed area even though area technically should never be negative sometimes it's useful to have that so one thing we can do to turn this into assign the distance field is we can just subtract some value if we subtract 0.3 for instance and then go back then what's happening here is that we have values going up to one here and then they go lower and lower lower all the way to zero here so now it's zero along this boundary but if we go further toward the center it's going to start going to negative values and since this is a distance field this would be a negative distance right hence the word signed distance field um so that's it's a it's a very very simple and common concept across uh shader cutting um so what we now have is a signed distance field for a disk right or no actually not for a disc specifically for a circle um so this is a yeah so now we have a way to distinguish if we are on the inside or on the outside if we have negative values we're on the inside of the circle if we have positive values around the outside so this is a useful concept if you want to like make any kind of like procedural shapes and shaders or if you want to use like cutoff values and whatnot so a simple use case for this is that maybe we want to draw something based on whether or not it's on the inside or outside of this thing for instance we could make a step function step is basically a threshold so we can do a step function between 0 and the distance and let's just output that and now wherever the sight distance field is negative we have black wherever it's positive we have white so and what step is doing is basically it's basically the same thing as like um something like that i don't know if the parameters are flipped i always forget just flip the parameters if it doesn't work yeah so it's a threshold check all right um and then as usual as long as you now we have a mask we can do whatever we want um like oliver's mentioning yes we can make the japanese flag out of this if we want to because then we can just lurp between these um using this mask right there are lots of interesting features of sign distance fields that make them really really powerful as soon as you want to do like procedural shapes as soon as you want to do something like um sorry i just completely forgot the thing that i was just gonna say um procedural shapes um you can do you can use it in ray tracing a lot because it's a very very good data structure or a starting point for um like both 2d and 3d are not 2d but 3d ray tracers can use sine distance field in a pretty efficient manner um anyway but the general gist of it is it is just a distance that's that's pretty much it it's a distance to something that can also be negative so that's what sign distance fields are okay so what we're looking at here is a um specifically this one it's a sign distance field for this circle but it doesn't have to be a circle there are a lot of different things you can do for instance it doesn't have to be a circle we can make a linear distance field um so for instance if we instead of doing i dot u v what if we do i dot uv dot x well this is also a distance field right instead of being a radial distance uh this is a linear distance um where these values here are negative so it's a negative distance to this zero point right and then it's a positive distance on this side um so like sine distance fields are not limited to like complex stuff they're also like very very simple um sine distance fields as well it's just that we usually don't call it sine distance field because it's such a basic operation to just have um subtract some coordinate but yes uh so that was a short primer on that um if you want to dig really deep into slime distance fields and uh ray tracing and all of that stuff uh inigo keyless has a really really good like resource on his website if you want to see like you know how do you make a signed distance feel to a triangle um to a tube or and so forth right that's the general concept okay so why does this matter um well one of the assignments was to make uh rounded corners for our health part although also i think it's funny that it seems to me like no none of you uh had the solution of just making a texture for this but quite often making a texture mask is the correct solution because it's usually really really fast to read a texture compared to doing things procedurally which sometimes has a lot of other issues like with aliasing and whatnot um but but yeah anyway so just using a texture for this uh that you draw in photoshop or something would be the really really fast and simple solution for rounded corners uh anyway let's see if we can apply this concept to um to the health bar i thought that was cheating you know what in video games everything is cheating if you can take shortcuts that's usually the best thing to to go or the best way to go but yeah obviously you would learn more not using textures so uh all right so let's see if we can apply this concept to the health bar let's see that's the sdf example health part there we go let's see in this case uh we're going to start by just ignoring all the healthcare related stuff because now all we want to set up is some way that can help us make rounded corners right um okay so let's let's go to photoshop for this because now it's really really good to like think visually about these things and try to work out exactly what you actually want to achieve and how this might help you um how you might like more easily visualize exactly what you want to do what you noticed with this sign distance field the the radial one that we had is that it looked something like something like uh something like this where we had some radial shape or gradient and then we can threshold that in order to get a clean um clean shape like this for the health bar though we don't actually want the a fully circular one uh we want it to be sort of like a a 2d capsule uh or rather um yeah like this type of shape right and if we try to think about what might this shape be a distance to that might be a good question to ask if you know what like sign distance fields are at this point um you can try to figure out like um if this is the distance to something what is this the distance to and what you might notice is that this is actually the distance to this line so what we essentially have is um a threshold for a distance field to a line segment right specifically a line segment not a line a line is infinite mathematically so sometimes it's good to clarify that specifically a line segments um so doing this we would actually get the shape that we want for the health bar right so so there we go so this is the puzzle that we need to figure out all right um so but we don't have this shape um but what we have is a rectangle and the rectangle is sort of encapsulating this whole shape like this um so then we have a bit of a like math puzzle because um while we do have the coordinate of every pixel being rendered um if we want to get the distance to this line then we need to get the the coordinate of the closest point on this line right so if we have the current coordinate of this pixel then what we would want is the coordinate of this pixel if we have the coordinate of this one we want the coordinate of this one and similarly if you're over here you want the coordinate of this and so forth right so pick any pixel here and we want the coordinate to the nearest point on this line segment so how do we get that um there is a relatively straightforward way of doing this it does get a little bit more complicated because our coordinate system is actually going from 0 to 1 on the x axis but it's also going from 0 to 1 on the y axis right um a little confusing to put it 0 to 1. and it's also going from 0 to 1 on the y axis so what we have here is it's a stretched coordinate system and this is a little unfortunate because if it's stretched then just checking distance distance values is going to get complicated right um so we we probably want to start by making sure that our coordinate system um is uniform rather than non-uniform right uh where we probably want something like this where this is one and then two and three um wherever this would be um and so forth right we want this to be uniform um rather than non-uniform so that would be kind of the first step um in this case um we did in the assignment you were to set the scale of the health bar to 0.125 on the y-axis um which is exactly an eighth so what we need to do in this case if we want to have a uniform coordinate system is that we can multiply the x coordinate by eight which would then uh scale this so that it's from zero to eight instead so then we would have four in the center uh two there and pretend this is uniform this health bar is too short aspect ratio is a little off but uh but we would have the the coordinate of eight over there um okay so that would be kind of the first step of trying to make a uniform distance here right um all right so let's let's do let's just set up those coordinates i also thought that assuming the aspect rate of the quad was cheating yeah that's fine and then the assignment became a little bit harder and that's just good because then you learned something right because then you basically need to get the scale of the of the objects and then you need to use that in your math um but generally generally the solution is the same it's just that instead of hard coding it you replace those hard hard-coded values with the scale of the object right so uh let's start out by doing the same thing we did for the other one um so we want to return float four i dot uv zero one okay so now you can see that we have this coordinate system where y is going from zero to one oh sorry x is going from zero to one across the entire thing but probably don't want to do that here because now we want a uniform one and not a non-uniform one okay um so what we then need to do is that we first need to set up the coordinate system for this so let's make that a separate variable so that we don't mess up the uvs for the rest of the rest of the code so this is going to be the uh what do we call these coordinates um i don't know what to call these chords there we go just generic coordinates uh ida uv there we go but we want to modify the x component of this we want to scale that by 8 in this case so we can do chords dot x times equals 8 and then we can output those coordinates just to verify and now it looks like the zero to one range is square right and that's what we want because now we have a uniform coordinate space and we can also verify that beyond this it actually has coordinates that go beyond one um by using the the frac uh function uh so that one basically subtracts the floored version of this coordinate uh basically just returns the fraction um so this is going to show us in case it is repeating in case it is continuously increasing linearly which it is right okay um so that seems to work um so then we now have the actual pixel coordinate so so looking back at this uh we have the coordinate of all of these pixels in a uniform coordinate space which is what we wanted initially now the next step is to figure out how do we get the points inside of here right um the y-coordinate of this one is very easy the y-coordinate is always 0.5 um because it's a line that is horizontal then y is never ever going to be anything but 0.5 um so we can just hard code 0.5 into uh where the nearest point is so that's relatively straightforward um so uh let's see let's name it the um point on line segment um so i guess we can float two uh where the y component is going to be 0.5 cool uh x component uh we're not entirely sure yet where we're going to set that to right um but if we think about the coordinates that we have um this is going to repeat eight times right i'm gonna try to make this a little bit more um close to the actual aspect ratio that we have okay so we have zero uh one two wait that's a three i was thinking three at the same time okay so now we want to we want to get the x coordinate that kind of stops here right we don't want to go further than this point um and then for the end we want the one that stops in the center of the square right but for any values in between if we are at you know 6.5 then the x coordinate that we want along this line is going to be the same it's going to be 6.5 it's the same as the x coordinate of the input pixel or the current coordinate that we have so pretty much all we need to do is to clamp the x-coordinate on the x-axis to always be greater than 0.5 0.5 but it also needs to be less than 7.5 so this is the range that we want to clamp the x-coordinate in and that way we make sure that the the coordinate we want to get out of this is always within this line segment right so the x coordinate of this one it's going to be the same as the current coordinates that we want to check with which again is uh this point right here um so that point would be uh the current coordinate that we're looking at um so the current coordinate chords dot x would be the x component of the current one if we just do chord stops dot x um it's going to be the infinite line that just goes beyond um and it doesn't clamp at all but we want to make sure that we clamp it at specifically these values right 0.5 and 7.5 um so we can clap that there's a built-in clamp function we want to clamp chords.x um from 0.5 to 7.5 again i'm just hard coding all of this for now um i believe this is the order i'm not sure if the this should go last i'm not entirely sure maybe actually let's look it up because why not cg standard library clamp um yes the value you want to clamp is the first parameter cool so we are now clamping this between 0.5 and 7.5 and so now we have the point on the line segment so what we got is both of these points so now we have this coordinate which in our code is called chords because that's the coordinate system where we can just get any points in this coordinate system and we also have the clamped point which is going to be the closest point on the line segment so all we need to do now is check the distance between these two points um so that's going to be the start of our sine distance field so we can just call that sdf and then we get the distance between the uh current coordinate and the point on the line segment and then let's output that sdf uh this was not unity in fact there we go so now you can see that we have a distance field where each pixel represents the distance to the line segment in the center here right and given the scale of our coordinate system uh where it goes from zero to one here on the x-axis jesus christ what is photoshop doing um so it goes from zero to one then we want to make sure that um or usually it's nice to normalize these things so we probably don't have to but i like doing that um so i usually like to uh make uh this go from zero to one uh and to one over here as well for the distance values um because it's nice to have normalized ranges for this because you can see that it goes to 0.5 it doesn't go to fully one um so i'm just going to multiply this by two um wait that does not go to um wait why does that not work fascinating um is it because i'm not multiplying the yes that might be because it's alpha blended and i multiply the alpha by two and apparently that affects the formula interesting i didn't know that alpha values outside of zero to one actually had an effect um okay anyway so we got the distance let's multiply by two up here instead um all right so we have our distance we scale it by two so that we have values from zero to one at the very edge um okay um so then what but what we want is we want to be able to cut off a part of this uh now we have a distance field but we're not actually doing the uh the clipping along the edges that we wanted to get our round uh health bar right um so if we want to do that then we need to do something more but just like we did with the previous one when you have a sign distance field usually you have a distance to some primitive in this case a line segment and then you subtract what can be considered a radius or a distance threshold um and in this case um the the threshold should be wherever this distance value is one right that's along this boundary so if we subtract one that means that it's now going to be zero on this boundary so now we have assigned distance field um to uh this like two-dimensional capsule shape right and then we can threshold based on zero um we could also just do threshold based on one if we want but usually this wouldn't be assigned this field in that case but now it is so then we can do the same thing we did before where we clip based on this value and so clip based on sdf and that's it so now we're going to discard pixels that are outside of this sign distance field unless i need to negate this uh yes i do now we did the opposite so let's just flip the sdf and there we go so now we have a mask that can make rounded corners on our health bar um and then all we need to do at this point is that we just need to bring the rest of the code back and we don't really need to change anything because this is already clipping the corners um so all of this is for rounded corners clipping all right so let's go back and now we got our health bar it's flashing as usual as we defined earlier and then you can still increase it and decrease it just like before and everything works as you would expect it to everything remains the same about the health by itself um yeah all right any questions about stuff so far uh if you want to run only the very corners do we need to make a composite shape uh you don't have to make a composite shape you can you can basically do the same thing that we already did but because what we were doing here was essentially clamping the x-coordinate right where we didn't allow the x-coordinate to go beyond here you could do the same thing for the y-coordinate um so then essentially it would be a rectangle and then you get the distance to that rectangle although it gets a little bit more complicated because the rectangle needs to offset a little bit away from this corner so you need to work out that math as well but yeah um then essentially what you would get is you know a shape that would be rounded along these corners okay so then we got one more thing so we should do the border now that was the that was the last part of the um assignments um basically the last one was to add a border around the health bar and the reason that i put this after doing the rounded edges is because as soon as you have a signed distance field a lot of these types of things becomes very easy to do because what we now have is a sort of a gradient or again a distance field that goes out from the center and then we have an increasing value like this um and it also does that along this whole rounded section right um so what we can then do is that previously we checked the threshold where the sine distance field is zero right but what if we threshold it where it's slightly less than zero in that case we can get another threshold that is slightly offset inwards by some distance right um so what we then get is that we can get another mask out of this uh that we can then use to manipulate the health bar we can blend in a color here so maybe we want to fill it with black or something um to add a border around the whole thing and it really depends on like what type of effect we want to achieve um so if we go back to the code here we have the sign distance field here um and then let's after clipping let's just output the sign distance field again so return float form sdf and the alpha channel one or just set the alpha channel to one and then we go back uh all right so so now it's just pure black which is what we would expect because the sine distance field has negative values here um so what we can then do is that we can make another sign distance field for the border where we take the original sign distance field and we add uh some value now let's add a little bit more just for clarity so then we can show the border sign distance field and now you can see that we have zero is offset into this health bar right um and if we then want to turn this into a mask we could just do the same thing we did before where we threshold that um so so let's make a border mask and we can threshold that based off of the border sdf probably need to negate this one too um wait why is this sad oh step requires two arguments right that wasn't clip uh and then right and then we show the border mask uh there we go although i kind of expected it to be the other way around [Music] flipped there you go so now we have a border around our health bar or we have the mask for the border but we don't actually we aren't actually showing the border uh something else we might want to do we might want to make some of these things tweakable so let's make a border size parameter because it's fun to tweak things um and probably don't want max value to be one border size we got that down um yeah so then all we need to do is add a border size here instead of 0.3 which was a hard-coded value for the thickness of this all right so we got the border sdf as a mask and we now have a parameter for the size of this border if we want it to be really thin or we want it to be thick it's kind of nice to have these uh like more easily tweakable things if you want to like artistically be able to control these things then we want to use this as a mask to add a border around the health bar okay so we have the border mask and all we need to figure out now is how we want to apply it i think that the easiest way to apply this mask is to just make it black around the border or just multiply by that although right now the mask is inverted so if we multiply the health bar it's just going to go black so we might want to flip this back to being black on the edges um oh i just remove the mask so this black on the edges and white in the center that way we can just multiply this into the existing health bar and it's going to be black around the edges um so we got the burner mask and we can just straight up multiply it in here if we want to there we go so you stack all the multiply and again when you multiply by a mask wherever the mask is zero it's going to be black wherever the mask is one it's gonna remain the same color it's gonna like be unaffected right okay there we go so now we have border to our health bar you can change the amount of health you have um you got the flashing when you're at low health um yeah so there we go uh that was pretty much um all of all of this assignment um uh you said i could remind you to talk about anti-aliasing is that still on the table or i'm running out of time we still have time to talk about that um so one simple way of doing anti-aliasing uh when you do stuff like this um is kind of like taking advantage of the way that the sign distance feel works in and of itself and because we have that distance value we can deploy a really really really useful tool in order to make this ant aliased um let's see i don't know if we can oh we can do that here um so let's find the camera there we go um wait that's the wrong one all right zoom in so now you see that the the border here is not like it's very aliased right now um so you can see that we've got all of these wandering stair steps and that's usually not great so we probably want to do something about that um so one um a really really powerful and weird thing that the um that shaders actually have access to is that you can get what's called a partial derivative of any values that you have in the fragment shader and what partial derivative means is that or sorry a screen space partial derivative um so what a partial derivative is is that it's an approximation of the rate of change of whatever the input value you give it um so if you know how quickly this sign distance field is changing in screen space you can use that as a way to kind of do a little blending depending on how far away you are or depending on how close you are and these types of partial derivatives is actually how the texture samplers figure out what mip level to pick in the textures as well so it's a really really powerful tool and there are different ways of doing it but generally the uh the idea is that you uh use a function called um f with um this is a terrible name it's a weird name uh i usually think of it as like fragment width or rate of change of the fragments or something i don't know how to rationalize this name i feel like it's a weird name um so f width is a very very simple way to get the rate of change or actually an approximation of the rate of change in screen space of some value if you do f words of the border sign distance field then we get the screen space partial derivative of the sine distance field i'm just going to call a pd for partial derivative um all right um so now we have the partial derivative so we have the rate of change a very neat thing that you can do when you have the rate of change in screen space is that if you have your original feel that you had as an input to this if you divide that by the rate of change in screen space you're gonna get that gradient go from zero to one across a single fragment um so this is pretty much exactly what we want for anti-aliasing so if we take our border sdf and divide it by the partial derivative the mask is going to make it so that we have a range of 0 to 1 along that border now we might need to flip a few things because usually things aren't exactly as we want yep um so this one is now inverted we don't want it to be inverted um so and it's also not clamped because these values are now going beyond zero to one as you can see it's like over saturating the green color here and it's inverted so first off we need to clamp it so we can saturate it which means clamp so if we go back it's now clamped and then we need to invert it because it also flipped in this case um and if you want invert if you want to invert a value in the zero to one range you do one minus and that's how you invert a color or a value and then recompile and now we're ventilating um and it's still gonna work on the edges here it's gonna look all crisp and clean um and yeah so there you go this has been very useful when making shapes but yeah it's a very very simple um simple trick oh and also the f width is actually a shortcut for um f words is f width is kind of getting the partial derivative along the x screen coordinate and the y screen coordinate and and then it's doing an approximation of the length of that vector and that's what this one is returning um you can get more accurate screen space partial derivatives if you want um so you can do that using the ddx function and the ddy function um yeah so that would give you the um the separate ones for each axis and if you want to make this more accurate you can get the i think this is the way you do it you you make this a float 2 so it's a vector 2 for the x and the y axis and then if you just get the length of that vector then this is going to be slightly more accurate than the built-in f width one slightly less performant but generally doesn't matter so if you want like a slightly more accurate partial derivative value then you can do this as well great um [Music] okay one more um one more um and i'm ending it okay well if you don't want to stick around for my stream because i say um sometimes feel free to leave you don't you don't need to be here in that case uh okay there we go i think that was all i wanted to wrap up for the health part so i just i think i've showed this to you earlier i'm gonna show it again so hopefully it's gonna work out compression is not going to destroy it my gpu is going to struggle now as well but it's not struggling at all um but yeah so so this is an example of using sine distance field or actually it's slightly more accurate to call this just a distance field but in this case this is a ray traced fractal um and so so like this is the mandelbox fractal which is a relatively like popular fractal and in this case we are using a three-dimensional sine distance field again it's a distance to an object and the object just happens to be a fractal this time so yeah so you can make all sorts of cool stuff when you have distance fields in this case the um this whole scene is a single uh triangle or two triangles uh and everything else is just done in the shader for the math here you can change the parameters of the fractal and it's all real time because there's nothing that there's no geometry here that needs to update or anything right this is a fractal there we go just wanted to show another example of what you can do with some scientists field stuff if you want like a the very short version of how this works i'm going to give you the one minute version of this so what's happening is that we have a camera and then we have two triangles that we're just drawing two triangles right in front of the camera um and they're just facing the camera at all times in fact we can go to um the scene view in unity so you can see that it's literally just a a quad in front of the camera that's it um so we're just placing these two triangles in the camera and the rest is done in the shader for this um fractal the what's happening in the shader itself is that we have some shape in this case it's a fractal right um and if we have a distance feel to this what we can do is that we can shoot array or mathematically we can kind of step into this 3d space and if we know the distance to this shape then this is going to be the closest distance for this whole thing right and given that we know that the distance from this point in space is exactly uh what we have here uh then within goddammit photoshop you know what i'm angry with you now uh then we know that this distance is safe to traverse into this whole space um so if we want to raycast then we know that the step we can take forward here is this distance right so we can step forward by that amount as if we want to rate raycast into this 3d object we can now step that distance then we can repeat that process now we have a distance here and that's going to be some somewhere around this radius so then we can step forward that amount and we know guarantee that this ray is not going to hit or intersect the object because we know that the closest distance was this right um so you can you can keep doing this you check the distance to the shape and then you step forward by that amount and so forth so this is one way of doing um ray tracing for inside distance fields and then eventually you get really close to the shape and then it's going to make smaller smaller steps and then you can count that as a hit once you once you've hit it although i bent this one which is a little weird but basically you do that for every pixel on your screen so that happens for every fragment in the fragment shader then you you keep iterating and then eventually you're going to hit something right the thing she didn't say that might be non-obvious you know the distance to the surface when sphere tracing but you don't know the directions that that's why we iterate uh even if we knew the direction to the surface that wouldn't really help that much because the the problem is that we don't know when we're going to hit this surface that's why we're doing this type of search right so it's more like we don't know how large steps we can take to the point where we go into the shape uh we want to be able to hit the surface of the shape and not go into it and then like using this type of data you can then um use various math functions to also like not only do you get the point at which it hit but you can also get the normal of that surface um so you end up getting a lot of information that you can use to like shape these objects so yeah and then you end up being able to do stuff like this so you can see that there's a little bit of a gloss highlight there's some shading on this um and there's also fog which is based on distance and so forth this is what's called raymarting right uh yes i keep mixing up ray marching and ray tracing but yeah okay so basically it's a ray on every single fragment and then we're getting this shape out of it how does this look in the scene view it looks like this it's two triangles and a camera the camera you don't really need a camera to do this i just hacked this into unity because i wanted everything to be in world space but yeah so this is what it looks like in the scene view so when you move around it's just the quad is just moving and the uh the camera is turning it looks kind of silly and that's it is there a good way to translate code on a shader toy to unity code or are they the same um some of the things are going to be different like the external stuff anything related to time the time variable probably has a different name stuff like that otherwise shaded toy is written in glsl so some of the functions have different names so i'm pretty sure they're called like vec3 rather than float3 i think uh lerp is called mix so like some of the names are going to get swapped out but generally the math is the same sound should be relatively easy to translate it i'm pretty sure the shaders in the shaded toy don't really have a vertex shader i thought they were mostly you just had a fragment shooter and two full screen or a full screen quad that kind of sucks i mean a shader toe you generally don't deal with meshes it's very much a playground for fragment shaders you might have matches there i haven't looked into it too much but um as far as i know it's mostly about input data fragment here textures and that's about it by the way the health part should take the border engine into account with a very thick border an extremely extremely low health value the bar could be entirely black even though it's not zero that is correct yes but in this case it's mostly a shade of course and i don't care about making sure that the gameplay and the readability of it is at the top so it's not a priority for me are you seeing unsafe code do shaders even support unsafe code oh no the the leak wouldn't be just from the shader itself uh it's because like assigning materials if i like instantiate materials every frame in the editor or something like that uh but inside of the shader itself there's no nothing can leak there no this one is tricky i don't know how much i should go into detail on this stuff because some of it relates to like render pipelines and i know most of you are likely not going to tap into render pipeline stuff but maybe it's not super tight to the render pipeline but kinda but you know what it's gonna be useful regardless so we have talked about a lot about the stuff that kind of just sets a color of the surface or modifies the vertices and kind of just directly manipulating and setting pixel colors generally most games do not just do this uh most games have something called lighting uh you know the whole concept of light sources uh shining onto objects making them brighter there are shadows where light is not and so forth so let's talk a little bit about lighting and some of the basic ways you can implement lighting in your shaders now in most cases a lot of the lighting stuff is going to be handled by the engine itself so this is mostly going to be a an overview of kind of the approach that you take for implementing stuff like lighting and in our case we're going to limit ourselves to one light source because as soon as you go to multiple light sources things get a little bit more complicated and then it starts tapping into stuff that depends on the render pipeline and how everything is set up um so we're gonna we're gonna go for the relatively straightforward case of um a light source so how do you do lighting on surfaces we're delving into the realms of flow storms lighting uh no because flowstorm is very much a hack i don't know if you'd want to go into that but i can show how that works and we can talk about it later if you want if you want details on how it works lighting i'm going to talk about now is kind of the uh very basic versions of how to implement lighting um if you already know how to implement lighting and how to write those types of algorithms i you're probably going to think that everything i'm going to be talking about is out of date and yes it's going to be um i'm going to show you some of the like older ways that we used to do lighting in games before we started shifting into using physically based lighting and shading however not all games use that so it's still relevant to some extent and it's also much easier to talk about and wrap your head around than going you know all in on physically based shading let's start off with a very very basic let's draw a circle there we go what a circle uh let's say you have some sort of sphere and you have some sort of light source training on this so let's make the unity style light source and in this case it's going to be a directional light so a directional light means that this light source is at infinity it is extremely far away um so for all intents and purposes the direction of the light source is going to be the same regardless of where we are right so all of these light rays are going to be parallel we don't actually have a light source that's at this distance because then we would get light rays in this direction um so in this case we're going to talk about a light that is infinitely far away kind of like the sunlight in a game usually when you do sunlight you implement it as a source that's infinitely far away so we don't care about distance it's just a direction right when we think about how to light objects like this then usually we want to get some sort of effect where it's light on the side where it hits on the side that's sort of facing the light source right and then we want that light to fade as you get to the edges of the object and a lot of this can sort of be broken down into very simple math operations uh so that's sort of what we're gonna do we're gonna look into uh how to do this um this very simple lighting i'm gonna try to repair my okay there we go okay so generally i'm over simplifying it now but generally there are um two types of lighting that can fall on an object when you're implementing a single light source like this usually you have what's called a diffuse lighting so that's lighting that's the one that usually looks something like this where the direction you're facing doesn't really affect the object in which case um this is for matte objects or for a matte shading on something as in it's not very shiny at all this is kind of just like light hitting it and um if something is facing away from the light source obviously there's not going to be any light on that the second type is specular lighting specular lighting is stuff that is almost directly reflected into your eye or camera that's usually seen as a gloss highlight um it can be a little bit blurry depending on how shiny the object is it kind of depends the gloss highlight is a little bit more complicated because that one depends on where your camera is so we're going to look into both of these two types of lighting okay um so now we can try to think about how would we mathematically represent these things um so all right so let's say we have a direction to the light source so let's make that yellow and this might seem a little reversed but usually when you are setting up um shading like this with light sources usually the direction to the light source is is is the direction to the light source and not the direction from the light source to the surface um so and this is usually called a light vector or just l um you're going to see that the just the l everywhere sometimes you might see it as w with some suffix or whatever but i like calling it l um that's just the direction to the light source and then again since we're rendering this from every pixel in the fragment shader the direction to the light source would be the same regardless of where we're doing it because in this case we're we're interpreting this as an infinitely far away uh directional light right and then we can try to think of where do we want this to be bright and where do we want it to be dark generally i presume most of you would intuit that the opposite direction should probably not have any lighting on it right this is pointing in the complete opposite direction there's no light hitting that whatsoever right um and then you can move around this and you and let's consider the perpendicular directions so if you consider the perpendicular directions to the light source uh these probably shouldn't have any light in between here but as soon as we start approaching this direction uh we want it to be more lit right uh so how much lighting should we add here well we usually want it to be brighter where it's directly facing the light and then we want the brightness to slowly decrease along this side here right and once we hit the perpendicular it should go to zero so if i start writing writing some numbers here where if we say one is maximum intensity and then we go around the circle where we want it to be zero here um then we can sort of start like thinking about what kind of intensity would we want here well uh this one is facing it quite a lot this one is facing away quite a lot so maybe 0.5 should be like somewhere here um and and so forth and then you start decreasing this somewhere here 0.25 and so forth so you get a gradient uh going from bright to dark and obviously the direction doesn't matter here it would be the same in the other direction and if you're looking at this and you've had a math course with me previously you might recognize what we're doing here we have a vector here for the direction to the light source and then we have a direction out from the surface that is kind of the slope or not the slope sorry a direction out from the surface called the normal direction so you have the normal vector and the normal vector is going to start pointing away from the light source as we traverse around this one once the normal vector is fully perpendicular like the red ones here um we we don't want any lighting at all so if we consider the dot product that is exactly what the dot product is this is like one to one with how the dot product behaves when you have two normalized vectors so we have the light direction and the normal vector and if we do the dot product between those two so the normal vector and do the dot product with the normal vector and the light vector we also want to consider the case where they're pointing away in this case we would get negative values right because that's how the dot product works we would get a negative 0.25 um and negative 0.25 and so forth or 0.5 and then here we get negative 1. um so when we're dealing with lighting we probably don't want these negative values around because we don't actually want the light source to be able to subtract from the other side of an object right um so unless that's a choice you have in your lighting style in which case that sounds cool and i encourage you to do it but in most cases we don't want negative values um so if we want to i get rid of those we can do we can just add a simple little function of zero and we do a max function of this so what this means is it's gonna pick the maximum value of either zero or n dot l um and that means that when whenever this is negative we're going to get zero um so basically it's just a way of saying clamp bottom at zero so you're gonna see this pattern a lot uh if you're searching around for shaders um then this type of back max 0 comma n.l or some other lighting function it's very very very common uh just to like clamp away the bottom values isn't this why citrate is called saturate i i have no idea why it's called saturate i think it's i think it's with saturate you can consider it a value range that is fully saturated you can say that if you have color values or whatever if one of the color violets has hit one you could say that that's a saturated value as in it's fully filled up right i think that's where it's coming from but still the name shouldn't shouldn't have been named saturate it's frustrating um all right so this is a very very very basic type of lighting and this is called lambertian lighting named after someone called lampert so this is a model for diffuse reflections this one has nothing to do with specular reflections right now it's only um this is only for the diffuse reflections uh all right so let's implement this let's try this and see how it works out let's create a new material god damn it i i need to set up a template you know what if i do this course with some other students they're gonna get the premium version of this course where i've already got a template set up they would not have to watch this anymore wait did i call it mesh input or mesh data data i think right trying to be consistent okay uh don't care about fog no fog illegal all right so this one is sampling a texture we don't really need that um let's add a no we don't need a color we're going to do that later okay so now we want to um now we need to set up the vectors we need in order to calculate the lime version right we need two things we need the normal direction and we need the light direction so let's first do the normal direction that one we've done before so it should be relatively familiar to you at this point it's kind of mostly boilerplate just to pass things from the mesh data to the fragment shader so we're gonna set up a normal direction and that's going to use the semantic normal and that's from the mesh data then in the interpolators we just slap in another interpolator and call it normal and make sure it's a float three because it's a three-dimensional vector great um so now oh we also need to pass it from the vertex shader to the fragment shader so o dot normal equals v dot normal all right and then we can just output that just to make sure it works so return look for um i dot normal and alpha channel doesn't matter because we're not using the alpha channel in this there we go let's add some objects what is fear let's add another object let's do a capsule okay and let's do a cute beautiful look at these colors so now we're visualizing the normal direction as a color and again you can sort of the think of vectors as colors it's very easy to just visualize them as such um and you can sort of tell the direction of it by the colors um so if it's pointing directly upwards the channels are 0 1 0 which means the y axis is 1 which means the green channel is 1 which means the color is going to be green so that's why the top is green on all of these because the normal is pointing upwards so we got the normal direction that one is now done um sort of there are some things we can do in case we want to be a little bit more accurate um no actually we're going to do that later because that's um that's more fun it's that local world space uh currently local space so if you want to transform this uh [Music] oh geez i always forget the name of it unity normal object or world trouble there we go thanks because it's probably better to have this in world space um right so now we need the light vector um the uh actually let's set up the variable flip three and there we go single letter variable names uh all right so we got the normal and now we need the light vector um i think it's something like light position zero and we also need to include one of unity's stuff for their lighting i forget if it's lighting or auto light um so i'm just gonna include both like a true professional there we go um so these are unity's built-in um cg code snippets where you can access some of the like some other the like unity specific data such as the light position which unity will populate automatically i presume it's in auto light since lighting is very gray and sad okay so um i thought it was light position zero or something i don't know if a world space like position zero would actually work um let me just double check unity's documentation you can tell how often i use unity's lighting i thought it was light past zero or something um oh light no wait world space light plus zero okay that is the one we want never mind yes all right um so this one is a little bit special because of the way that unity's forward renderer is set up uh so world space light position zero actually can be either a direction or a position um so if you if you read the documentation uh directional lights um me if it's a directional light being rendered it's a world space direction and that's the one we're doing now it's the infinitely far away light source that's just the direction and that means that the w component of this float4 is going to be zero for other lights the w component is going to be one and then you're going to get the actual position and not the direction um but the way the unity works is that this pass the first pass that you have in a shader is always going to be a directional light the bass pass is always a directional light you can't have point lights in the base pass technically if you want to want to do this properly you would have another pass and this pass would run for each additional light source and those can be either directional lights or point lights but we just have a base pass right now um so we're just going to presume that this is always a directional because it's always going to be a directional light so technically this is a direction okay um so this is going to be the l vector um and then i don't know if this is the direction to the light source or from the light source i'm just going to debug that so i'm going to output l um there we go and then where's the directional light okay so if we make the directional light point straight down we have a vector that's green so that seems to indicate that we're actually actually getting the light vector as in the one pointing from the surface to the light source so i turn it to the right we should be getting a blue and that we do get and then we should get a red one uh in this direction yes okay so it seems like we have the actual light vector um and the light vector is just going to be the same across the whole mesh because again it's a directional light we're not actually getting a direction to a point light source um but we now have that vector so now we have the normal vector by the light vector and now we can do the lime version shading um might want to clarify because the name is a little contradictory there um okay so this is gonna be the diffuse light um that's the that's what we're going to get out of the lime version so we do the dot products between the normal vector and the light vector and now we have the very basic lambertian lighting um and this is a gray scale this is a single float value so i'm just going to swizzle that out to a flip three and let's go back to unity and we now have lamborghini shading uh so if we rotate rotate the light source we can see that we're getting something that resembles a very very basic lighting setup um yeah all right any questions about that so far before we go into the fancier parts of it did you use the max function there nope uh it's not necessary yet because we're not doing anything else than drawing this uh but yes technically we should add the max function uh usually the problems don't show up until you start like using this value for something else um but yeah oh you can also use uh saturate if you want um because you clamping between zero and one is going to be the same thing as doing a max zero and then the dot product um so saturate also works you can do whichever one you want are we going to get into normal maps yes would max be faster than saturate i think saturate would be faster because i believe that on some gpus i think saturate has zero cost i don't know what platforms it was or if that's that applies to all the gpus the modern ones but according to unreal engines tooltip saturated supposedly free on most gpus yeah yeah as far as i understand saturate is such a simple operation that it kind of takes that path regardless and whether or not it applies it is the only thing you're changing so it just seems to me that it's a kind of a pass-through thing or it does a pass through but it also just outputs a clamped version of it the next thing we're probably going to want to do after this i would say is probably to add some sort of color to this right right now we have ignored two things we have ignored the color of the light source and we have ignored the color of the surface right now we presume the light source is simply like um just one in intensity uh the color is just white and that's it right so we probably want to implement some of the other properties of the light source so light color there we go light color zero let's add that so the diffuse light itself only describes the falloff depending on the normal but we also want to color this um and if you want to color something by some intensity you multiply it together because we don't want to add more light on top of something that already exists we want to combine this effectively the lambertian shading is a mask that we then apply to the color and the color in this case we only need rgb so xyz and that means a diffused light is now going to be a flip 3 rather than a float all right so now we got the color and we'll go back to unity and now we can change the light color and everything is going to follow we can also change the light intensity because the intensity is encoded in the color itself so it just makes the color stronger which so so everything already works out we don't need to add any like separate intensity parameter or anything so what we set up now is diffuse lighting we don't have any specular lighting though so so this is kind of a basic pretty boring shading it's not shiny at all uh it's only based on the normal direction and the light direction um so we don't get these like nice glimmering like moving specular highlights as we rotate the camera it's all kind of flat and very matte and very static right so what we need to do is add some specular lighting uh specular lighting is sort of when things start getting more complicated especially if you want to go into physically based shading which is a little too much for this course we're not going to do that i'm just going to talk about a little bit what it is after we've done this um so there are different ways of doing uh specular lighting and the two most popular ones that kind of that we've been using before the physically based era was usually something called phong lighting and then there's another one called blin phong and i think the phong lighting is probably the most intuitive one whereas the other one is a bit of a hack it just so happens that blindfold generally looks more believable whereas phong is looks a little wonky in some cases but i think we're going to start with phong lighting because fog lighting is a little bit more intuitive when you're explaining it so let's do both and then we can compare them i'm going to shuffle my notes around [Music] there we go okay what's the highest value uv x and uv y could reach uh the same value as floats so it's the same range as flip uh all right so specular lighting um again it's easier to break this down if you go into a 2d case and start thinking about that before you go into anything else um so if we have a let's see we got the light source and then we can consider some sort of point let's see this point all right so we have the light direction over there and then we got the normal direction of the surface so that's the normal at this point specifically otherwise as usual the normal varies depending on where we are in the mesh unlike the light vector in this case and the final one is that we have the view vector so uh let's say we have the camera over here there we go what a camera trying to figure out what would be a good view direction for the most illustrative example there we go probably good somewhere there it's probably going to be great um all right so we have the light vector by the normal direction and then we have the view direction so the view direction is again a direction from the surface to the camera let's make that the view vector usually just v um did actually write light normal in view no i didn't but um okay hopefully it's clear regardless um so now the fong lighting works is that it presumes a perfect reflection um so if you imagine the light vector uh you can sort of consider the what would happen if we reverse this vector we point it down toward the surface and we bounce it off if we bounce it off we're going to get this direction or something along these lines so this is going to be the reflected or the bounced light direction right i don't really know what to call this one let's call it r probably best so this is the reflected light direction and then what you do is that you do the dot products between the reflected direction and the view direction um so then what you're getting is essentially how close is the direction to the camera matching the exact exactly reflected direction um of that light ray um so what we're essentially getting is are we staring directly into the reflected light in a mirror or are we looking slightly away from that right um so essentially if we want to do uh phong shading then again not as popular as blindfold but it's usually a pretty clear example it's easy to understand so we got the dot product between uh the reflected light direction and the view factor and i'm just going to presume that all of this is going to be clamped now because generally you need to make sure they don't have negative values so just pretend that i write that everywhere because they're going to be a little messy to always type that in so fong this is uh fong specular highlights there are some more caveats to this so i'm going to get to that soon um but let's start implementing this the very basic implementation so this is our diffuse lighting and then we need the specular lighting there we go all right so what do we got what do we need we have the light vector we have the normal vector we don't have the view vector we don't have the reflected light vector so let's start with the view vector so in order to get the view vector we need two things we need the position of the current fragment and we need the position of the camera you could take the forward direction of the camera but that will not make a change across your screen even though it should um so if you just take the camera direction then that would be equivalent of using an orthographic camera but in this case we have a perspective camera so we need to get the actual fragment position and the world space position of the camera so we can start with the fragment position so let's add another interpolator for that so this is going to be a world position of the current fragment and then we need to uh pass that through the interpolator so o dot world position uh equals uh multiply the model matrix or unity um object to world and then we pass in v dot uh vertex um so we've got the world position there and then as usual i like making sure that things work so return float four uh world position that's not what i wrote um oh sorry i dot world position i'm not used to having code completion for shaders uh alpha channel doesn't matter great there we go it is very blue um so it seems to be getting green colors there move it to the origin you can see that it seems to work moves for red on the x-axis rotation independent okay seems to work close enough all right next up is we have the world position and now we need the camera position and that's built in in unity um so um world space camera position there we go so in order to get the view vector float3 view vector equals um so this is going to be the direction from the surface to the camera so we want to do camera minus the surface position there we go so now we got the view vector and as usual debug this make sure that it works so now we should get a world space vector pointing toward the camera um so if we want to try to tell if this works or not we can again look at the gizmo in the top right in unity does this direction line up with these so now it should point toward the camera which is in the x direction it should be green at the top and it should be blue on this side which it all is right so it's all matching up uh one thing we need to do though is that we need to normalize this um because now this is a vector going from the surface to the camera but we want this as a direction so normalize and now we normalize this vector let's go back now it's a little bit more calm it's not over saturated um we can see that this seems to work pretty well it's blue green red all right so the next thing is now to do the fong lighting uh no actually we need the reflection vector first so float three now we need the reflected vector reflected along the normal so uh we got the reflection vector now or we're gonna calculate the reflected vector from the light source um there's a built-in function for this uh remember remember in our math course we did like we did the math for reflecting a vector around a normal manually uh but that's built in in shaders reflect and then we're going to provide the incoming direction which is the negated light direction because the light direction is pointing away from the source but to get a reflected direction we need to to reverse it first um so we're going to reverse the light vector uh and then the normal of the surface which is n all right um so this um should now be the uh reflected light direction around the normal all right let's debug this back to unity this one is a little bit harder to debug because now we're getting a lot of colors but if we presume this direction right here uh now the camera is sort of the light direction and are these bounce to directions valid it looks like it is right um you would expect it to bounce upwards here which they're all green so that seems to make sense and they should all bounce toward um the um x-axis once they're hitting there given this angle right um so that seems to make sense and then we have negative values on the other side so now we have the reflected light vector and now uh for the actual reflection itself we need to do the dot product between the view vector and the reflected light vector so float3 um specular light equals the dot product between the view vector and the reflected vector and then also make sure to clamp it now that we have other colors involved um and so now we have the dot product within the view vector reflection vector and this should give us a very basic setup for specular light so let's try it out so we're skipping the diffuse part right now we're only showing the specular part so recompile and there we go so now we have a very matte type of specular reflection but you can see that the reflection changes uh changes shape depending on at what angle we're viewing it um and yeah but it still seems to be some sort of resemblance of a specular reflection there's one more thing that we should do here generally when you're dealing with specular reflection is you also have the concept of glossiness sometimes called roughness which is just the opposite it's basically a way of saying how smooth is this surface is it extremely smooth to the point where we have almost mirror-like reflections or is it very matte it does have a lot of like uh micro facets that are making sure that the reflection is not mirror-like so usually we represent that with a value for glossiness um so so let's let's add that as a property called gloss here we go so this is going to be the glossiness slider um and we're just going to make it a float for now um so we're going to set this at 1 because that's what we have right now the way you usually apply this in order to change the way that the highlight looks usually you use exponents for this so you take your specular light and you raise it to the power of some gloss value so specular light equals pow which is the power value a recent sum value to the power of some other value so this would be specular light to the power of gloss although i didn't have a variable for this now so class there we go you could also put that up here it doesn't really matter i just put it here now so this would be oh this is sometimes called the specular exponent um this value specifically but we're just calling it gloss for now uh slightly misleading but that's okay um all right so we recompile we can now tweak gloss uh connect weak gloss in this material so if we increase gloss you can see that the highlight gets smaller unless your cursor moves to another monitor or computer and then we get a small gloss highlight but there's one thing that you may have noticed that is happening now can one of you guess why this happens there we go quiz for the class why does it look so faceted the normal is not accounted for uh the normal is accounted for we are using the normal in the formula we got it up here uh n is the normal we're using it in the specular lighting and we're now showing the specular light double normals uh no i don't think we have any double normals the normal is not used to smooth out the surface uh not by geometry no we could do that but that's not what we're doing uh the normal is still interpolated so it depends on what you mean by that i should probably just explain it at this point sometimes you can get away with this sometimes you cannot so in this case we kind of run into a case where we can't get away with this glossy highlights is one of those cases where you sort of need to do this correctly so when you have some geometry right we have vertices and then we have normals attached to each of those then what's going to happen is that it's going to blend between these two data points we have a normalized vector here we have a normalized vector here but if we linearly blend between these two the normal we're gonna get interpolated here does actually not have a length of a one this one is actually not normalized so if we want to make this accurate we need to normalize this vector to make sure that it has a length of one so what's happening is that the normal vectors we're getting out of the vertex shader is actually slightly shorter and this is one of those cases where that shows up because now um we're like it's a very very high contrast thing and it kind of highlights this issue very clearly so that's why we're getting this faceted look where uh things are brighter here at the vertices and at the edges but when interpolating across the surface the normals are going to get slightly darker because the interpolation and the interpolators uh don't know if this is supposed to be a direction or a position or just some value right so it's just going to linearly interpolate all of this so that is why you get the um this effect where it's bright at every vertex and between the edges but then across faces it darkens so you get like very obvious um faces shown through this so it's a pretty easy fix all you need to do is you normalize the normal in the fragment shader uh so now we're normalizing this on a per pixel basis and then now it's going to be um now it's going to look fine so if we go back um recompile now it's a nice round little highlight okay so now we got a very simple glossy highlight um also works for the surface right here it's just harder to see because this one is so hard edged now we can go into compositing this right i don't know if we should composite this like putting everything together or if we should go into blin fong you would really think the normals would be normalized the thing is that the interpolators don't know what data is in normal and what data is not a normal um and in some cases you actually don't want to normalize the normals sometimes you can do stuff like uh it's a little hacky but you can actually bake um ambient occlusion data into your vertex normals um in which case you want them to be shorter um so yeah so whenever you work with vectors normalize them just in case it's always a good idea to normalize them when you have to um because normalizing is not free so um yeah it's it's good to do it where you have to if you can get away with not normalizing the normal then you might as well just you you might as well just leave it on non-normalized if it looks fine in your game right doesn't need to lsl specifically require we say float3 normal normal though um i don't know what it looks like outside of unity but in the mesh data yes you need the normal semantic but in the interpolators you don't so i think it actually allows you to type normal here but i don't think that actually makes any difference i think that's just syntactic sugar for just adding more interpolators uh but i could be wrong i assume unity's default shader do that by default uh probably the definitely the pbr ones otherwise i think it sort of depends on the platform i think on some like mobile shaders they're going to take a lot of shortcuts because you kind of have to especially if you want like physically based shading on mobile um so it really depends on what shader you're looking at what's the texture on the cubes on the bottom like these are that's a grass and some rocks i don't know if we should do blindfold or if we should continue this current thread we should probably do blindfold i think because generally fong is not really used very often and the reason for that is that when you have a planar surface and any kind of surface that is very flat when you have phong reflection with a very glossy highlight uh fong has a tendency to just be a circular shape and that is generally not very close to the way that most surfaces behave um so because most surfaces are not don't have this like perfect mirror-like reflection so generally you will get something that's more stretched along the direction to the light source um so there is a another lighting function this was fong specular lighting but there's also something called blinpong so we're going to look into blindfold now oh i don't know if we can fit blindfold into um i don't can fit that we do have time for both um we're going to go through compositing as well let's just copy this little setup so for blindfold we don't need the reflection vector we're just gonna we're just gonna delete that no more reflection vector okay i don't know how to clean this up let's add a point there we go so the way that blinn fong works is a little bit of a hack it's kind of strange the fact that it does work the way it does and it's not very intuitive um but it does look better i'm just gonna try to clean this up a little bit because otherwise it's gonna get cluttered there we go so the way that blindfold works is that it has another vector which kind of a silly name but it's called the half vector so the half vector is a vector that is an average of the light vector and the view vector um and in this case it kind of matters how we how long these vectors are so i need to fix this up a little bit there we go so the half vector um lies directly halfway in between uh the light vector and the view vector so i guess it would be here somewhere we got the this is not halfway i'm separating them a little bit just for readability uh so now we get the half vector uh so where the half vector and then um the way to get the uh the way to get the half vector is basically you take the light vector plus the view vector uh divide it by two and then normalize actually you don't need to divide by two you just add the light vector the view vector and then normalize and that's the way to get the halfway vector we could write blinn fong in terms of the existing vectors so that would be the light vector plus view vector and then we normalize that i guess this would be the normalized vector hat thing normal like that and then we take the dot product between the normal and this half vector right there uh so we do the dot product with the normal direction there we go that's it in other words this is the same thing as the dot product between the half vector and the normal vector all right so um this is called a blindfold there you go i cut off the hat for some reason that's not good all right so i got blindfold uh let's try this out so we don't need the reflected vector anymore but what we do need is the half vector so flute 3 h and that is then going to be the light vector plus the view vector and then all that normalized there we go and then basically everything else is the same except we do the dot product between the half vector and the normal vector there we go so if we go back and recompile you can see that this highlight is now anisotropic which is the technical term for different on different axes instead of uniform in all directions so it's no longer perfectly circular now it has a bit of a stretch to it right which is which is closer to the way that things actually work in reality this is very much a hack still but even though it is a hack this is closer to reality than fong as a blind phong is usually the one that people opt to use for for some of these more basic uh lighting models um yeah so so that's that's blindfold otherwise it looks very similar in many ways there's one more thing we should probably do and that is if um there's a bit of an edge case when your camera goes at an extreme angle where the light source is sort of behind you can see that we get to get this tiny little 2d spotlight looking thing at the very edge there um and this is because the we we currently don't cull this light depending on if we're behind something or not um so we can actually use this uh use the lime version to uh remove this if we want to um so the lime version being the diffused one where the light source is starting to fade away from you um you can use that in order to just cut it off so that it doesn't go to the back side so if we want to prevent that um we can multiply the specular light by this lambortion which is currently not separated so we might want to do that let's just call it lambert and that's this part and then the diffuse light is lambert but it's also multiplied by the light color while lambert is just the falloff itself right um so we're going to do we're going to multiply the specular light by lambert uh greater than zero there we go because this one is going to evaluate to a bull which is going to get converted to a floating point value which is either zero or one um so if it's zero uh we're going to remove all of this it gets multiplied by zero and it just disappears if it's greater than one there's gonna be one and it just multiplies by one in which case um it's just going to look the the same way it did before so if we go back recompile we no longer have that weird spotlight looking thing so now it actually disappears on the back side okay so so far we've talked about the specular highlight in isolation but usually you want to composite these together because right now the diffused lighting is gone the light color is not taken into account anymore and everything's just kind of just it's just this is an isolation right but we want to put this in a context where we have where we have more stuff right i'm going to remove that um actually you might want to keep that for posterity um okay right so this is specifically lin phong this is used for phone um all right uh there's actually one more thing we could sort of talk about the way that gloss works is kind of annoying um yeah let's talk about this now because it's easier to tell what's happening um so you might be able to tell that if i set the gloss to one or sorry two or something reasonable this is a value of seven if i pull this up to 15 it's somewhere around here if i pull it up to 42 it's somewhere around here and you can go very high on this before you get like a very tiny spotlight so this gloss value is at 571. um so it's kind of annoying to deal with this value range because you have a huge value range that's very non-linear um so usually when i have gloss values i like to remap this value into an exponential curve um because it's kind of annoying to have to like deal with these huge values and the very small numbers um so um yeah so let's let's change this to instead of being a float change this into a range between zero and one and then we want this to be remapped to some other range uh all right so if we want to remap this then we can think of the specular exponent as a separate part of this right because now glossiness is going to be between 0 and 1 but the specular exponent is going to be something completely different right um so let's uh let's redefine the specular exponents based on gloss right so we're going to pass the specular exponent in here um and now we're going to use gloss to remap this to some reasonable values um so this can be done in many different ways this is just a way that i personally like doing it the way that i usually set up gloss is i do exp2 which is 2 to the power of the value you send it as an input i usually do exp2 gloss at times six somewhere or eight and then plus one um there we go so if we use this as a specular exponent it's basically two to the power of this value and then if we go back to unity we're now going to have a something that feels much more linear than we had before um actually plus sorry plus 1 should be outside of there wait that barely made any difference plus two is better usually you don't want the very low ranges regardless um so just adding something is something you can do um yeah so now it feels a little bit more linear instead of having to deal with like huge values um i think i did times 11 as the default in shader forge back in the day in case you want the really really small highlights um but yeah um kind of a neat little trick however this is done in real time um you might not want to run this math in the in the like fragment shader um it's probably better to do this on the c sharp end and you pass in you do this in c sharp and then you pass in the higher value afterwards so it's not the best thing for like optimizing stuff um but it's really useful it also makes it it also makes it much easier in case this is not uniform so if you have a gloss map for instance this would be a texture and not a uniform value across the entire shader in which case you couldn't even do it on the cpu so um it's just a nice nice little remapping value exp2 ought to be cheap on floats though i imagine so yeah um yeah all right um cool let's move on to compositing um so currently our specular highlight does not care about light color uh which it should do right um because we're sending some color in we want to bounce that same color off of it right so we want our specular light to also be colored by the color of the light source um which i lost somewhere where is it light color there we go uh so multiply that by the light color and then if we go back to unity um then that doesn't matter because we are not making this a we're just using the x component there but this is a vector three so we want to use all three components uh because we're now multiplying a color into this there you go so now specular reflections are now based off of the light source that is a gross yellow um all right uh so we got the specular highlight down there we go wait what is this one doing oh it looked like this one had incorrect colors anyway uh right so now we got the specular lighting set up here and now we need to composite these together as in we have diffuse lighting we have specular lighting and we also want a color of the surface so uh let's do that let's let's put everything together so adding the lights together um you can do simply do diffuse light plus uh specular light uh because we don't want these to like multiply the other we don't want one light to dampen the other um we want uh we want them to be able to like just be added together um if something is specularly reflected off of something um the diffused lighting underneath should still be there right um so we want to add specular light on top of the diffuse light so if we go back to unity we now have the diffuse light as well as the specular highlight from this right so now you can see we have the combination of both and one of them changes with the view direction and the other one does not right um all right and now we also need to add a color of the surface um so let's go back up here um let's call it color it's not a range all right um so here we get into a little bit of like technicalities of how surface reflections work i'm very much over simplifying things but the way that it usually works is that if you have diffused lighting on something the diffuse light is going to be affected by the color of the surface so if we now go back and we give this uh some sort of color multiplying the diffuse by that color generally gives us the result that we want so now we have you know red surface or maybe we want a blue surface and everything still works the way that we would expect it to then specular light however should not be multiplied by the color of the surface unless it's a metal object um because metals tend to reflect light a little bit differently than uh like plastic for instance um so usually if you want things to look a bit more plastic you generally don't multiply the color of the surface by the specular light um but if it's if it's supposed to look metallic then usually you do want to multiply the surface color there this gets way more complicated if you want to start trying to do even more physically accurate model i also i'm heavily simplifying this now um so generally you want to leave the specular light as a pure reflected light and you don't want to actually change the um that light based on the surface color but yeah so that's a very basic very basic implementation of light sources any questions so far is the specular highlight that prominent usually um depends on the the object like some objects have very like intense specular highlights some don't um so it really depends on what it is what type of surface it is it's very smooth um it's not very smooth um yeah it depends like if you have something like shiny cars or uh water um or anything that's polished like polished metal um polished plastic polished wooden tables like all of these things tend to um tend to have this very very sharp specular highlight uh does pbr not use blindfold plus lamborghini no very much not um so pbr well actually you can if you modify it there are ways of making these models a bit more realistic so there are a few concepts that kind of got introduced with physically based shading or physically based rendering or physically based lighting that that you can implement on these things too so here's a bit of an overview of a generalized concept of what we're talking about uh we have some surface right that's great uh we have some sort of light source somewhere and there's a light ray shooting into the surface right um then if you want to visualize what diffuse lighting is as in diffused lighting was what we're talking about here with the lime version uh diffused lighting um is uniform in all directions so if there's an incoming light ray then if we want to try to quantify how much lighting is bounced off in either direction then diffuse lighting means that the amount of light coming out is uniform in all directions there's the there's some amount of light just uniformly spreading in every direction um so sometimes you can visualize that by this little lobe kind of like being a shape for um where the light is going out after a single ray is incoming right specular reflections work a little bit differently specular reflections are very strong when they're reflected but they weaken when they are further away right so usually those look something like this right if it's not very glossy um if it's very glossy uh then usually it tapers off very quickly um and then those uh these are called lobes like specular lobes so usually it looks something like this if you want to draw the outline of you know the vectors coming out of that reflections um and then you can combine both of them so you have diffuse lighting that's going to be spreading like relatively equal all directions but then you're going to get a spike for where the specular lobe is sharp and pointing out at the reflected vector um and then that's going to taper off and then eventually you're going to get something like this so then this is the reflected direction um and so basically what we're describing is oh in this case we would have a camera here right um so basically what we're describing here is called a b r d uh this is a word that means bi-directional reflectance distribution function basically what it means is that we have some function that takes a light source input that then spreads light in various directions and how it spreads these lights is defined by the brdf so we did set up a basic brdf here we have some incoming light we have the lime version for our diffused light and we have the uh blindfold for the for the specular light um all right um yes this is a little bit penis-like but you know what that's okay we're all adults so all right and this function can look different depending on what type of surface it is right so in this case it has both a glossy highlight right has a glossy highlight and it has a diffuse spread here but that's not necessarily the case and it also depends on what kind of surface this is right so the one of the concepts of physically based shading is called energy conservation um oh yeah it does look like a duck that's true so one of the concepts of um in physically based shading is that you have some amount of incoming light right as soon as you get into physical based chaining you usually start getting to use like uh symbols like this where it's really confusing because then the other vectors are also called that by something else i don't know um but basically you have some incoming light and what phys or what energy conservation means is that when this is bounced off it doesn't create additional light this is something that can get kind of complicated because what that means is that you need to make sure that the sum of all of the outgoing rays in both your diffuse spreading and the specular reflections all of that needs to uh not go above one and anything that's less than one um needs to be considered an absorption right as in light comes in and the surface absorbs some amount of that light and then some light bounces off of it right diffuse bounces in all directions specular bounces in this direction but if you sum up all of these vectors the idea of energy conservation is that they should all add up to one in case everything is reflected um but if some amount is absorbed it can be less than that um what we have done here is definitely not energy conservation so for instance if we change the gloss the amount of light that gets reflected is way more when we have low gloss we have high gloss even though theoretically this should only bounce off um you know the same amount of photons we shouldn't add more light just because we have a more rough surface right so one of the concepts is energy conservation um so if we want to make a very very bad estimation of what this looks like um we can take the specular light and multiply it by gloss um this is not the way you're supposed to do this this is just an approximation but if we do that you can see that um as it gets glossier it collects more light in one spot and when you reduce the glossiness the light spreads out so it's much weaker right so that's a way of like approximating uh this behavior of energy conservation right with pbr do you need to calculate bounces uh no pbr so any brdf doesn't really account for bounces uh but some of them are based off of bounces um it's there are many different like methods of calculating this but basically what many of the brdfs do is that they presume that the surface on a microscopic level looks a certain way so for instance you might presume that you have a surface that has these v shapes in it for instance because generally on a microscopic level surfaces are not perfectly flat right and then what you might do when you try to work out a mathematical formula for a brdf you consider um what would happen to light rays coming into this situation um and then you start thinking well okay if it's sitting directly on it might bounce like this right uh so if we consider these perfectly specular then we would get certain behaviors depending on the angle that the light is coming in um and so there are models that presume that these are perfectly specular and there are models that presume that these are perfectly diffused um and that also means you need to take into account occlusion uh where like uh any light coming from this direction wouldn't hit down here it would only be able to hit up here so there are all sorts of effects that you can get out of this um that um yeah that you can do a bunch of math functions on you can read up about them they're called like cook tauren's function auran nayars diffuse reflections there are many many many different physically based shading models out there um and unity's built-in shader and unreals built-in shader all that stuff is physically based pretty much everything is physically based by defaults these days but i think it's useful to talk about how to write these just to get a general idea of how how do you write functions like this what's a starting point what's a brdf and so forth so physically based rendering in and of itself um is generally a broader topic than just the surface lighting like this there you go brdf oh jesus christ it embedded a huge thing please do not yeah so you can read about those there the a bunch of the models are listed there the lambertian uh the blinn fong and cook taurens and all of that stuff so uh yeah but they generally get way more complicated there's one more thing i wanted to talk about related to this just as an aside the way the unity's forward renderer the built-in forward renderer works is that for any additional light sources there's one more shader pass so there's an extra shader pass that's called a light add shader pass so whenever something is rendered with the unity's legacy forward renderer then it runs an additional shader pass for every additional light source so if you have four light sources around a mesh that mesh is going to render four times so in that case basically what it does is that it kind of skips anything that you only want to do once on a shader but it renders additional light sources additively on top of your existing render so you would have pretty much the same code um except you need to handle uh directional versus point lights um and then you would pass that into the second pass but it's a little legacy most people don't use the legacy render pipeline or actually maybe most people do um so i don't know how relevant it is to add support for that now uh but yeah that's pretty much how it works in that case oh someone asked a question sorry i missed that it's possible to use shaders for creating a glowing orb like in this picture with a light and aurora around it if so could you show it yes that so usually that looks like you would just use a sprite for that um like the the general effect of that is uh it's sort of a combination of a bunch of particles if you want to get the smoke effect around that uh there could be a sprite in the center that has a very um like just an additively rendered quad with a flare uh and then the lines going off to the side those are in almost all cases done using textures as well um and if you want to search for resources on this anisotropic lens flare and then you will find it very popular in sci-fi genres um i think mass effects it was like a core thing in the mass effect series they used anisotropic lens flares everywhere um bam so if you want a keyword to search for that would be the one could you take this shader and use that as a mirror or would you need a completely new shader you would need a completely new shader making a mirror is much much much more complicated it depends on how you want to make that mirror more specifically it depends on what you want to be visible in the mirror and whether or not it should be dynamic or non-dynamic the only thing that is visible in this mirror is the point light right now or our directional light right nothing else is visible there are other aspects of lighting where for instance we haven't talked about image-based lighting which if you use the unity's reflection probes uh basically those provide the data for image-based lighting where you kind of bake an a cube map or an environment map where you save data about the way that the environment looks you bake that into a texture and then the shaders read from that cube map both for diffuse lighting and for specular lighting that's almost always used for chrome for instance anything chrome uses that anything that's remotely shiny uses that these days um so yeah when we say legacy pipeline does that mean anything that isn't hdrp or urp i mean the built-in pipeline the one where you just start unity and you don't select any of the render pipeline is that what they do for scope reflections almost certainly yeah um cube maps and environment maps are super super popular for anything that's like supposed to reflect the environment it doesn't do real-time reflections though but it works very well in many many different cases to use forward rendering and budget cuts yes deferred rendering relies on screen space textures deferred rendering means that you need a lot of video memory in order to have that and given that vr is already so high resolution uh we didn't want to use um we didn't want to use um deferred rendering for that reason because we're going to need like a ton of video memory just to cover the amount of pixels in a in a vr headset right so so this is forward rendering this is not screen space based at all um another another difficulty with deferred rendering is that you can't use um msaa like multi-sample anti-aliasing and anti-aliasing in vr is really important um so an msaa is one of the best anti-aliasing algorithms out there um so like in terms of like how good it looks and how much it costs uh whereas if you use deferred rendering you pretty much always use one of those like shitty anti-illusion algorithms that kind of makes all of your textures also look smudgy um generally like you have to like sort of hack around it um but with forward rendering you can use msaa which looks super good and like dynamic lighting was not really a big priority for us the biggest feature of deferred rendering is that additional light sources are almost free but additional light sources in forward rendering is really costly so usually when you're in a forward renderer um you're limited to like four light sources per object because if you go beyond that it tends to get really expensive um or you need to like cut down the way that they are rendered so additional light sources might be per vertex instead of per fragment um so it really depends on depends on the case and what your requirements are for the game but generally that's the trade-off deferred rendering you can have like tons of light sources they're almost free and in forward rendering you get really nice anti-aliasing light so light sources are expensive but everything looks really crisp forward rendering also means that you can have any kind of lighting on any object whereas in deferred rendering you sort of need to unify the way that lighting works that everything needs to be lit the same way and anything on top of that would have to be like exceptions um yeah and transparency also generally gets complicated in deferred rendering not always but usually okay sorry there's a long rant about deferred shenanigans sounds like deferred rendering is always inferior oh definitely not uh deferred rendering like i said deferred allows you to have like however many light sources you want pretty much like that's the advantage of deferred rendering and yeah it's also deferred rendering or usually like deferred pre-pass or uh clustered forward rendering is usually the way that people go now but yeah with no extra performance costs so i don't know if you know of a big o notation but the expensiveness of adding light sources in deferred rendering is linear the expensiveness of adding light sources in forward rendering is non-linear it's exponential because you have meshes or every mesh needs to be rendered in extra time for every extra light source so things grow much faster in forward rendering but in deferred rendering you can just slap a bunch of light sources in there and again i'm simplifying things a lot right now there are many different types of deferred rendering um there's like different pre-pass and whatnot which is a little different but anyway right there was one more thing i wanted to talk about we were looking at some screenshots uh in uh at various games and specifically in overwatch and in dark souls there was some effect that was called a fresnel effect so i think i think we should wrap this up with just making a simple fresnel shader so usually for now in terms of the artistic effect not the physical phenomena don't mix them up so usually if you're reading about like physically based shading there's usually several terms um a fresnel term is usually one of the things that are included in your physical based shading model but both the artistic effect of harnell and the physically based lighting version of fresnel are both related to when your camera has a very low grazing angle towards the normal of a surface so um yeah so if you want to do for now effect that is very straightforward um so let's do a for now the only thing you need to do is the dot product between the view vector and the normal vector and then if we return that and we got the fresnel effects um so if you want to do this for like item highlighting or something um you would generally want to first invert it so you have glowing around the edges so now you got this and this should be a familiar effect at this point if you've seen this type of glowy uh thing um yeah and then if you want this to be like i don't know something that you're going to use in some sort of effect maybe we'll do something like this and then you would add the fresnel on top of that and now it's like some sort of flashing indicator of something that looks horrible uh let's use a cosine instead um oh geez i had negative values ripped but you get the idea um do you recall where the fernell was using dark souls yes uh yeah v is the view vector uh the direction to the view or the direction to the camera um uh but yeah so it's um probably just dig up the fresnel thing here you go so this is you can see that this is the same concept you can see that it's like um glowing around the edges uh wherever the normal starts facing away from you it works really well on very round objects right it doesn't work as well on like hard edges it doesn't actually create an outline but you can sometimes use it as a bit of a hacky substitute for it very commonly used for um like highlighting objects uh this is an example in overwatch also using fernell you can see that roadhog's belly as it like curves away from us um you get a stronger highlight and then it fades toward the center right so it's the same idea another one in overwatch tracer teleporting is all fuller for now to make an outline could you use fresnel and step to some extent but you wouldn't get an outline on hard edged objects which is again the downside of this so if we go back to this code we could of course threshold this so we could do step on r for now um threshold it at 0.9 or something um so you sort of kinda get some sort of outline but usually doesn't look very good um so it works like the only like good case for fresnel is specifically like a sphere uh but other than that you can see they start getting like artifacts uh that you usually don't want for this um so it's a very like naive way of doing this it works well for smooth objects not as well for objects like this one right um i think that's gonna be it we definitely don't have enough time to get into normal maps so we're gonna do that tomorrow do you want an assignment i should just ask would you like something to do before tomorrow how about make a um make this shader have compatibility for multiple light sources how about that the thing i mentioned about having a second pass i think it would be good to learn about how that works um if you're interested um so uh i'm gonna try to not help you too much on that um and this might force you to do some googling and some sloothing and interneting and you might need to look into unity's built-in shaders which is what i usually do for whenever i want to match unity stuff um yeah so how about that as an optional assignment i think that's going to be it oh things might get complicated for light cookies um so you can ignore anything related to light cookies if you want or spotlights in general might be complicated i forget how they work but it might be thank you all for today hope it was useful you
Info
Channel: Freya Holmér
Views: 211,350
Rating: undefined out of 5
Keywords: Acegikmo, Freya Holmér, Freya, Holmér, Twitch, Unity, Unity3d
Id: mL8U8tIiRRg
Channel Id: undefined
Length: 210min 2sec (12602 seconds)
Published: Fri Feb 26 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.