Godot 101: Visual Shader

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right hello everybody and welcome back to a short hopefully short goto 101 tutorial um what we're going to be covering on this session is creating a very simple visual shader in godot we're going to go over a couple of things first is simple noise noise is just useful because in shaders we don't really want to go ahead and create a lot of art noise kind of give us free art and they're also kind of cool to look at and they kind of let us see a bit how we can use noise to affect different things in our image the next thing we're going to do is create the virtual visual shader the visual shading language is an analog for the regular shading language if you're a little less familiar with graphics programming it can help especially since it kind of gives you previews of the image but it also acts as a sort of non-linear programming so if you're less familiar with programming it might be easier to conceptualize something as individual blocks that plug into each other but aren't necessarily a sequence of commands we're also going to touch it very briefly on color but i'm not going to go into detail on that because color is a really big topic and i just want to give you the basics of how you can use some noise to affect the way that something displays so first thing i've got up is uh godot i'm just going to create a new project called 2d shader i'm going to put it into a temporary folder as for the renderer it can matter for shading languages which language you use gl 3.0 versus 2.0 but anything that we're going to be using today it doesn't matter so it doesn't really matter which one you pick you can always change it later you just might have to tweak your shaders in a bit we're also going to create this folder and hit edit all right engine is coming up so the first thing we're going to do is create a very basic scene one second let me just get here so we're going to create a 2d scene or we're just going to give it a random node 2d name it whatever you want such as image this node is actually going to be what draws our picture but we're not going to use a sprite we're going to generate everything either through the noise or with our visual our visual shader so since we don't have a picture the first thing we're going to go over is generating a picture with some noise if you're not familiar with noise generation techniques don't worry we're going to keep this as simple as possible but really what we're going to do is something called open simplex noise and using a noise texture to create that noise so that way we can tweak the noise we can generate a noise image from it and open simplex noise as we'll see in a bit kind of just looks like clouds it's not like tv static which would be very harsh noise although you can create something quite close to it but instead open simplex is going to be very smooth no rough transitions and it'll let us create something that looks nice while still being quite random so first thing we're going to do is create a built-in script for more in-depth scripts or larger scripts or on a bigger project i don't recommend using built-in scripts but for this time we're just going to use a built-in script hit create all right so here is our node now the first thing we're going to do is set this to tool mode tool lets it run in the editor so that way we don't have to actually go ahead and run the scene every single time we want to see it change so it kind of lets us see things in real time we're then going to create a couple of variables the first one is simply the width and the height of the picture we want to generate so we're just going to export an integer from 0 to let's say 2000 called width and we'll set it to just a simple 800 and same thing for height so now we have a width and height of 800 and then we also want to actually i'll get back on this in a second next thing we want to do is actually create a noise for us to tweak so we're just going to export once again and open simplex noise that gives us a way for the editor to tweak the open simplex noise and we'll just call it noise and for now we'll start with a default open simplex noise and go from there the next thing we're going to want to do is create some setter methods so because these are export we kind of want to tweak them after they've been set so in the editor say you change it from 800 to 900 we're going to want to make sure that our noise grows to match that 800 um to 900 you know the width and the height so to do that we're going to set get function called set width and same thing with the height and so this says we want to use a function called set width and set height when this width and height changes from outside of the script so next thing we got to do is define these functions so set width and we'll just do widths and simply first thing we do is say width equals the width variable and same function with height to the height variable so now we have ways for us to tweak the width and height here we also have a way for us to mess with the open simplex noise i'm going to save this scene in our project really quick if we go back to the 2d editor of course we're not going to see anything displayed because this is just a regular node td but we'll see that we have our noise we have our width and our height we also have things that we can tweak on the noise now the nice thing about the editor is once we mess with these uh variables we'll have it set up in a way that it draws the new noise based on what we see from the parameters here so let's go back to our script now we have our noise object but we need a way to produce a picture of that noise from this noise so what we're going to create is a new variable called texture and it's going to be a noise texture we'll create a new one now a noise check texture very simple it's a texture that uses an image and the image is just going to be generated from some noise so what we want to do is when we're ready we want to set actually um let's do this let's create a function helps out so we'll create a function called refresh noise and so that says anytime we do something with a noise let's just refresh everything to make it nicer so when we refresh the noise what are some things the noise is going to do well we have our texture we'll set its width to be our new width the texture is height to match the height so we just make sure that it matches the dimensions that we provide next we're going to go texture dot noise equals our noise so that says the texture which is a noise texture to generate the image from noise use the noise that is available in the editor and then finally just for a good measure let's make it seamless and what that means is once it generates the image from the noise it'll make sure that the image itself when you reach an edge of the noise it matches the left side so if you were to for example move around and you reach from one edge and then teleport to the other you won't have a huge jump in value it'll still remain smooth not really important for right now but it can be useful if you want looping textures the next thing we're going to do is when we update the texture with all these it's actually going to have to do some things with the threading to generate our new image so we're going to just do a yield which says it just says let's wait for some event on the texture object called changed and then once that texture has actually changed we're going to save a variable called image to the texture.getdata so once the texture has changed it'll produce a new data so new height new width new noise we'll want to get that new image and so let's just make sure we save that our image equals null so we have a way now to modify our noise and then we can store the picture generated from the noise right here easy enough now when we're ready we want to make sure that we do very few things first we want to well actually ready we're just going to tell ourselves the process let's do something every frame so set process to true now what we're saying is okay we want to do something every frame and what we do every frame in our set or in our process function we're just going to do a very basic thing first we're going to create our texture well we'll just draw our texture what we're going to do instead is um let's take a step back we'll still set process just in case i'm going to call a function called update update says when this function is called it'll actually do a number of things behind the scenes but the one that we're really cared about is this draw function now we don't actually have an image drawn but we want to make sure that when we have our shader run we want something to apply to there might be a better way to do this but the easiest way to apply something is to just a blank white square because any modifications you do to the color white will just be very clearly applied to white if you do the same thing to black you might run into the trouble where when it mixes the two colors together well black mixed with anything you kind of just wind up with black so just to be safe we're going to draw a white square that's the size we're looking for so we'll just call draw rect which draws our rectangle the rect is going to be a new rect 2 and its position we'll just say it's vector two dot zero that's the zero vector that starts at zero zero meaning that we're gonna start at the top left corner of our image its size is then going to be a new vector two with width and height so we're gonna start at the top left point or over here and then draw out the width and then draw down the height that gives us our rectangle the color we're going to want to draw very simple let's just do color.white we'll draw a white square easy so let's go back to our picture we sometimes have to reload our scene uh just to get it refreshed but hey now we have our white square being drawn well white square is not very interesting or useful and it definitely doesn't teach us anything about our noise texture so now is we're going to use the shader to apply our noise texture to the image one pixel at a time so to do that let's go back to our image and we'll say material create a new shader material and once we have a new shader material we're going to create a new visual shader just for sanity's sake we're just going to do we're going to save it save our new shader resource and we go to our shader and we're left with a basic spatial shader now this looks like it's a lot of outputs to worry about and when you're dealing with shading in 3d space a lot of these are important but we're dealing with a canvas item which is sort of how godot represents 2d objects so let's go back and set it to canvas item and you'll see that we have a lot fewer things to worry about in our fragment shader now i'm not going to go into how shaders work in depth um there's a lot of other tutorials you should follow to learn more about shaders themselves but shaders at least in godot this is pretty common are broken down into three segments first is the vertex shader which runs on each vertex so our image has uh well it could have any number of vertices i suppose but suppose it has these four vertices top left top right bottom left and bottom right if you wanted to manipulate those you could simply just write some code in your vertex shader that say oscillates the position of the vertex offsetting it with a value over time so say you can have it kind of move in a circle that's not very useful for us we're just going to be messing with the one square so we're going to go to the fragments shader now the light shader i actually don't know very much about light computation the lighting engine in godot it doesn't matter for us right now so the only thing we're going to worry about is the fragment shader very brief primer on the fragment shader is it basically is when it samples over every single pixel so remember how we had 800 by 800 pixels it's effectively going to call our shader 800 by 800 times which is a lot of times and run some code on each individual pixel to determine what color to draw that pixel at what alpha to draw it with what's the normal normal is useful for things like lighting but we're just really going to come up with if we're looking at a individual picture or pixel let's draw that pixel with our noise and eventually we're going to tweak the noise some more but we're just going to take whatever our noise is draw that noise as our color now the noise is currently living in our image script if we recall we have our texture here that is our noise image we want to take this noise image and then draw that to our shader in our material so what we need to do is create a way for us to input something into the shader now the way that that's handled in shading languages is called a uniform so if i go here and i'm right clicking right click type in uniform and there's all sorts of uniforms i want a texture uniform and a texture uniform is basically just what is a way for us to i want to input a texture into this shader through our material let's call this shader texture uniform called noise so now we have a variable called noise it has some data in it of course it's white by default if we haven't put anything in there that's why when we preview it with this rgb preview we see white and if we take just the color from this uh texture uniform and dump it into the color output we're basically saying take this default white texture and draw it to the uh every single color so nothing useful happening there so we want to now tell our script hey let's input our oops sorry about that let's input our texture uniform into our noise into that texture uniform one second sorry about that um okay so we need to go back to our script now our script whenever we update remember we have our our process function let's just go back to our function process and so every time we draw a frame we want to make sure that whatever new image we got from the texture and that texture being our noise texture we want to make sure that that actually we don't even think we need this image well that's good to have it anyway we want to make sure that the material has our uh our noise texture so the first thing we want to do is grab our material so if we look at our image node we see that it has a material here so to simply access that we can go our mat equals mat short for material if i spell it material it actually overrides the material variable so we'll just do our mat equals material and material can be one of a number of things but we know it's going to be a shader material so we can sort of typecast it as a shader material and then once we have it typecast as a shader material we can then use a function called set shader param so i can do mat dot set shader param and this is a way for me to input something into a uniform so the param was called noise so that is our reference to this texture uniform called noise right here and we're going to input our texture that we've produced and just for safe measure i'm going to do if not image return so remember that when we change the width and height and even the noise parameter itself of our object we want to make sure that the texture is ready which we can do by calling git data or when changed has finished so if the image isn't ready yet we don't want to draw some sort of corrupted you know we might get something really bad we might even stall or crash the system system probably being just godot engine don't worry so what we're going to do is we're going to take we're only going to take the texture and apply it to the material if an image is prepared i forgot one sec get here and we're going to say set noise so if we happen to produce a brand new noise object we just want to make sure that we also refresh the everything there so i can do set noise noise and stored noise equals noise and then finally the last thing i forgot was every time we change one of these parameters we just want to refresh the noise and generate a new image just to make sure we've got the right image so just to go over our script one last time we have some parameters that set up a noise every time the noise is changed we refresh the noise refreshing the noise just changes parameters on the noise texture as well as updating the image that we render from it to make sure that we have an image ready when we are ready to go we simply draw a white square and then we tell ourselves to process every frame once again the draw function white square and then the process every frame we're saying every frame we want to make sure that the texture that is assigned to the shader material is our noise texture this might even be overkill we can give it a shot and see if just sticking this in ready will work because the texture object itself isn't changing but we'll just want to make sure we can always mess with that here in a bit so now if we go back to our our shader and just for good measure let's close the image and relaunch it now we can see that hey we've got this noise looking texture this noise that we're looking at here is the noise that's produced by this open simplex noise so if i change the seed we'll see that the noise changes i can even mess with the parameters such as changing the period or the persistence and hey we're basically feeding all this noise data directly into our shader that's great perfect so our shader is very simple we take our uniform of the noise we take its rgb data and we draw it as the color output now one thing i forgot to note was in this texture uniform there is this uv parameter now the default uv i'll even show it a uv sort of looks like but uv is effectively just uh x y coordinate that we're drawing so x and y were already reserved so u and v were just the next letters to use this uv the green value represents how much y the red value represents how much x so here we've got a y of zero and an x of zero so it just draws as black but a y of one draws us green and an x of one draws us red and so down here we get a yellow color which is red plus green now if we were to take this and draw it into the uv you can see that nothing changed and that's just because the default uv that's passed in was that uv so we didn't have to pass it in it's sort of a gimme for us but we're just going to take advantage of the fact that it's already there in some cases you might actually need to do it this way in fact we could trick it and do the screen uv that'll be fun we can do the screen uv and then as i move the camera around you can kind of see that it's like i'm peering into a distant window and that's because it's using the uv coordinates of my screen as opposed to using the uv coordinates of the texture itself kind of a tricky stuff but basically we're just going to leave it to the default uv so okay so this is probably the world's most basic noise shader in fact there's kind of no point to this because if you just drew a texture with a noise texture godot would handle all of that for you so let's do something a little a little more interesting with it and that's a little bit more interesting of our noise shader so what we're going to do is we're actually going to cycle colors through so instead of just being this grayscale image we're going to build colors and then over time rotate those colors so it'll be red green blue and everything in between so that this noise cloud actually starts fading through colors and that'll look pretty interesting so with this we're going to do a couple of things first let's ignore our uniform for a second so we're going to go back to just the white image and let's take an input that's sort of a default uniform that always exists called time time's a great thing to use to cycle through stuff nothing interesting here but basically the time is going to be our color or as as time goes through we're going to cycle through our color now typically when we mess with colors we're looking in a domain called the rgb domain right gr glue red green blue red group bleed is not three colors so if we look at let's go to paint.net oops um why are you here let's move up let's go to paint and make a new image file new sure size doesn't matter so if we look at our color wheel here we'll notice that it has rgb which is very simple we can dial in a red green blue value and so say we go no red no green we're left with just pure blue and then if i draw around the pencil i get a blue color i can also mix in a combination of the three and go hey this is a neat color i guess let's draw that one as well let me draw it with a larger brush there's our color so once again all red no blue all green gives us that yellow color which we saw in that in our in our uv but there's another way to do color and that's called hue saturation and value hue saturation and value is a bit easier to do stuff with in my opinion it's just not exactly how you know colors are generated but you can think of it as you know these three components first is a hue which is effectively an angle along this circle you can see here the dot moves an angle along this circle from 0 to 360 degrees of your color you can also think of it as a polar coordinate if you're used to that the next is saturation saturation is uh how much that color is from zero to you know from white to the color you can think of that as the distance of our vector so our of our polar coordinates so if you have an angle and a distance q is our angle and this is our our distance for saturation and then value value you can think of as instead of being a circle imagine a cylinder we can even draw this out if we have let me make a new image that's a little bit smaller if we have our circle here if we have the distance from uh let me draw this a different color sorry the distance why aren't you red the distance from the center is hue and then let's go blue the oh sorry the angle is the hue the distance is the saturation but then we also have a third value which you can think of as the depth in a cylinder so imagine this is our cylinder and we'll draw the back side of it here and our depth within this let's go we already read blue so let's do green our depth within here is the value and value just says if the value is zero were a black color and if the value is one then or whatever color is determined by hue and saturation so as you approach a value of zero you basically just turning it into the color black which we can see here so if i change value to zero it doesn't matter what our hue is doesn't matter what our saturation is we're always going to be drawing black but if i change our value all the way up and say i take it to a blue i can either draw a faded blue or a full blue just based on the saturation so with this we're going to take our hue value and then over time we're just going to spin this in a circle around you know around our clock face so we're going to start at the top and then we're just going to draw you know circles around and around and as our hue rotates we're going to be changing this hue value here we'll probably just leave saturation and value at some place just because what's more interesting is to see the colors change we don't need the intensity of those colors to change so i know that was a lot to cover just to worry about hue and saturation but um hue saturation value that's just another vector of three things so what we can do is we can actually just build that vector so i can do something called a vector compose node here and x y and z x is our hue y is our saturation z is our value let's just start with our uh everything other than hue is going to be y and z and then we'll just put time into the x so the hue is going to be changing as time changes and then the saturation value are going to remain constant and then i can even just output this color as our color for our image so if we come back let's go to my shader why is time not changing oh yeah so the last thing i forgot was this is uh this is currently an art the color is expecting rgb uh so we need to convert it to rgb so i can take the hue saturation and value to rgb so remember we just defined that this vector was an hsv vector so i can now feed this through our conversion node and there we go we now say for some time rotate the hue value leave saturation and value the constant and then convert that to an rgb value and then output that as our color so now we've got this sort of rainbow cube or rainbow square to look at so now we are able to produce a color and before we were able to produce some noise so the last thing we're going to do is combine the two so we're going to take our color and then offset it by the noise so that we kind of see these colored clouds as opposed to a single um a single block of color or as you know a grayscale cloud formation so that's going to require a few more nodes so if we let me zoom out a little bit if we think of it as just a few pieces this is our output we just converted back to hue with the our rgb from hue at the very end this is input number one which is time we don't really have control over time unfortunately but we can we can reuse it for our own advantage and then input number two is our texture which is our noise uniform that we fed into it from our script so if we take our texture and we go okay a texture the noise texture we were seeing before in fact let's just oops let's go back to the noise texture if you go back to our noise texture you can see that it uh gets close to the color white and gets close to the color black in some places i'm sure it actually reaches you know the actual color black as well as reaches the actual color white but effectively we've got values from zero to one so if we have a value from zero to one and we want that to offset our hue so here's our hue remember based on time this is just spinning we want each individual pixel based on its grayscale value to slightly adjust the hue for its pixel so if for example our color is black let's do this here's our black color versus our white color which we'll just we'll just draw like this so if it's black we actually want to take the hue value here's our hue value let's say it's straight up if it's black then we want to actually move the u value this way just slightly and whereas if it's white we'll be moving the hue value this way this just slightly so we need a way to take whatever sampled pixel we got from our noise and then adjust the hue value that was produced through time left or right a little bit or positive or negative a little bit so to do that very simple with this rgb output here when we sample it this gives us an rgb value now when something is gray scale the r the g and the b are all the same value that's important because there's no use in us reusing all three of those values we only need one of them because we know that it's going to be representative of the other two so i can do something called a scalar um deconstruct or decompose vector decompose sorry that's what i'm looking for and so i can take that rgb value and then i can decompose it into individual xyz which are actually just the r the g and the b value so if i have an rgb value i don't really care about these two so we'll just ignore them and so we want to use the r value i can do the exact same thing with g or b they're the same the same number so i want to take my rgb value or just the r value and i want to say if we're at the color black that should be minus half of a point whatever the you know half of a unit whatever that unit is going to be and if it's white that should be plus uh half a point so to do that's very simple because it goes from 0 to 1 we want to actually go from negative 0.5 to 0.5 so we'll simply just do a subtraction so a scalar operation will just take our r value we'll subtract it by 0.5 and that gives us you know our if it's white it gives us a gray value if it was black and give us sort of a negative value so now that we have our let me just redraw you now that we have our own sort of offset that we want to do we're just going to do an addition of the hue that we've come up with over time so time comes into our hue we'll do an uh we can actually change it here we'll go over to our scalar operation we'll do an addition and then we'll just say okay we've got a hue but we want to offset it by the output of these the noise that we normalized or kind of denormalized actually so we're going to take this b value and then the a value will be the current time and then whatever the difference between those two that'll get fed into the hue value and now we've sort of got this cyclic psychedelic cloud shape um so what's going on you know we got this cool shader all we did was kind of plug in noise to our hue so we're really looking at two individual components the first is this vector function here that we kind of see just flashing through colors that is just a hsv value that is rotating from a hue of 0 to 360 and then loops back to 0 and 360 i mean it goes around and around in a circle just giving us these assorted colors we then take our noise texture which if we just remember what that noise looked like in fact we can just generate some noise really quick in paint.net let's do another one we'll do effects um where is noise i don't want noise i want to render clouds this is actually well ignore the black and red colors imagine it's black and white but this is sort of the same noise texture we're looking at and so what we do is we take the rotating hsv value we then sample an individual pixel so we say we sample this one we then say how close to black or white is it if it's purely white that counts as 0.5 if it's purely black then that counts as negative 0.5 and if it's a gray point in the middle so perfectly gray then that would count as an offset of zero so then we take whatever color this is adjust it by the closeness to white or black that this pixel is in our noise and then that is the output for that individual pixel in our shader so this is where you can start to get a little artistic with it you can add noise on top of noise you can use noise to adjust how much it affects each other we can also do things like well what if i wanted to go slower i can add a scalar up here and i can say let's multiply whatever time that we get by 0.25 so it goes a quarter speed that it was before and we'll see that it's moving a lot slower excuse the dog additionally we could say okay we were taking that um that value and subtracting it to plus or minus 0.5 we can amplify that value as well so instead of being a negative 0.5 to a plus 0.5 we can multiply it and we can say let's do it from a negative point let's just double it who knows what this is going to look like so we can do this and so that we can use this to up the intensity between the colors so even though we have a very slight amount of change in the cloud each little change causes a very drastic amount of difference in our shader um in fact let's try upping the speed again there we go so it looks like well we can do one and you know whoa there we go so now you can come up with these really cool background shaders you can let's say we want to mess with the alpha so we can just create a scalar constant of 0.5 dump that out into the alpha channel and now it's sort of translucent um you know we can have it fade in and out we can um eventually do things like using these values to fade between images if you want or to hide an image whatever you want but basically what we've created is a very simple uh noise input to our visual shader and we've created a very simple visual shader that's very simple and is sort of put into a few components so let's review this one last time before we're done first let's go back to our our script once again the script's very simple it's just setting up our noise so the noise itself as well as the size of the noise this is all just updating it and then finally the last thing we do is we make sure that the noise texture that we've produced is being assigned to our material through that noise uniform next we have our uh our actual shader i mentioned before that it's non-linear that's sort of what this branch here implies is we've got two different things going on that eventually come up together and create our final image so our final shader is broken into three parts one is the input of time that time is used to adjust our output the next is the input of our shader the shader is used to offset the input from the time and then finally we take our offsetted time hue value create a hue saturation value color and then apply that color to the output of our shader and that's what we're seeing here so very simple handles a few things like input and output and it's very cool to look at you kind of get some very cool immediate results when you're working on it which is always great because that can make it difficult to if you don't get the results back it makes it difficult to see what's going on and understand it let's try one last thing so in our shader we can set some flags here i don't think we care about any of these modes oh yeah we can always mess with the modes there's all this stuff that happens in the engine itself that um we can mess around with but really canvas shaders are quite simple there's not much we can do to them and um that's what makes them fun to work with is it's very clear what's going on once you get into 3d it's a lot more difficult so anyway uh here's a very basic shader uh i'll post all of the code and put a link to it in the comments below or in the description below so that way you can see what i wound up doing and play around with it and hopefully won't have any trouble um recreating it on your own so with that thanks again so much for watching um please leave a comment if you have something that you're interested in or have any questions about what's going on make sure to subscribe if you'd like to see more of this and if you have any ideas of a new thing you'd like to do let me know i'm more than happy to try out little projects like this and help people learn how to do simple things that they might be familiar with in a different engine or have seen before but do it in good deal so with that thank you
Info
Channel: Godot Academy
Views: 1,514
Rating: undefined out of 5
Keywords:
Id: TCHv0clEGX4
Channel Id: undefined
Length: 40min 50sec (2450 seconds)
Published: Mon Aug 10 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.