How I Created 2D Pixel Art Water - Unity Shader Graph

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everyone today I'd like to show you how I created this water for my indie game to give you a bit of context about my game before we start all you need to know is that it's built using unity and is inspired by games like stardew Valley and Minecraft one of the main features I've been working on is an infinite procedurally generated world for the player to explore still a long way to go before it's complete but I think it's about time I start documenting its progress somewhere so enjoy the video first let me take you back to the beginning creating the water was not an easy process and it's gone through quite a few iterations over time before I discovered the existence of shaders I first tried to draw and animate some Waves by hand but this was extremely time consuming and it didn't even look that good after a bit of time passed I tried implementing a simple Shader that moved the edges of the water back and forth I was feeling a bit better about this approach but unfortunately some areas looked quite problematic also instead of using tiles like I did before I had this idea to generate a separate texture for each chunk and I definitely don't recommend doing this as I remember encountering so many issues trying to make the chunks look seamless anyway although none of those approaches ended up working out they did help me learn a lot and I became much more comfortable working with shaders so I eventually decided that the Final Approach would be to animate the edges of the Water by hand and use a Shader to handle the rest of the texturing in the final version there's basically only four layers a gradient a wavy texture some specular highlights and some seafoam so let's go through each layer one by one before starting on the Shader I needed to create some tiles to use as a base to get the tiles to connect to each other like so I use something called a rural tile you can also choose to add a frame by frame animation to the rule tiles which is how I made the edges move in and out [Music] when I was hand drawing all these Sprites I also made sure to color the edge pixels differently in order to create an outline for the water adding the gradient was a little tricky but I think the effort was worth it originally when I started without it the water just looked a little too flat and out of place and even though the ground is completely flat and two-dimensional I still wanted it to feel like the ocean transitions from shallow water to deep water so the first step to achieving this was to generate some kind of height map because my game is procedurally generated a height map actually already exists when a chunk loads an array of height values is generated using Pearl and Noise these values are then used to determine whether each tile is on grass sand or water which is what gives us these coastlines although it's easy to access these height values on the CPU it's not so simple for a fragment Shader that runs on the GPU the main problem is that the data is split across each of the chunks and I definitely don't want to be passing hundreds of objects into the Shader to fix this problem we just need to combine them together on the CPU first ideally into one big texture [Music] because the world is almost infinitely big we obviously can't get the data from every single chunk at once but if we just focus on the area that is loaded around the player then it's much more doable I won't go through the actual code line by line but here's a simplified version first we check if the player has moved across chunks then for each Chunk in our render distance after we have converted the height values to Colors we can update the corresponding part of the texture using set pixels another nice part about this approach is that because only one height value per tile is needed the resulting height map texture isn't actually that big since the render distance for my game is currently five chunks in each Direction it only ends up being 176 by 176 pixels in size [Music] normally you might think that scaling up such a pixelated image would result in a blocky appearance but due to bilinear sampling of the texture which is actually on by default the transitions between tiles are smooth the other step to implement the height map is to make it follow the player around so how do we achieve that it would be nice if we could simply just move the image around normally but you can see that because the Shader projects the image using World space it then becomes all misaligned so in order to fix this I created some extra properties in the Shader to control the height Maps position and scale the position value will contain the world coordinates of where we want the bottom left quarter of the texture to appear in order to continuously update it as the player moves I also added this segment of code into that same script from earlier now in the actual Shader we can simply subtract the current world texture position from the world position of each pixel and multiply a bioscale property to scale it down since I wanted the texture to span 176 by 176 World units I used a value of 1 divided by 176 which is this small number here you might be wondering why exactly do we need to subtract and scale the world coordinates down well the world coordinates are usually quite far away from the origin because the chunks can be loaded anywhere but texture coordinates are only in range zero to one meaning if we use the world coordinates to sample the texture we would most likely end up sampling outside of it to fix this we need to translate and scale the world coordinates like so and that's precisely what this part of the Shader does in other words all it does is convert World coordinates to texture coordinates alright so now that we have the correct height let's convert it into a color I did this by plugging the height directly into a sample gradient node and then added a gradient from dark blue to light blue let's see what it looks like so far I reckon it looks good but we're going to have to fix up these edges thankfully there's a really simple fix we just need to multiply the Alpha from the gradient and the main texture together okay so this is the first layer all finished now let's move on to the next layer this layer begins with the texture itself I based it off of caustic light patterns so I'll be referring to it as caustic texture a lot after drawing it up in Photoshop I imported it and made sure to set the wrap mode to repeat then I created a new subgraph to contain all the logic for this layer I started by adding the texture into the Shader and to override the color I simply multiplied it with a new color property since the original texture is white multiplying that with blue will just give blue then I wired up the subgraph to the first layer the loop node here is what Blends the layers together once everything was wired up I tried to see what it looked like so far but Unity decided to freeze once it was back I realized the texture wasn't visible properly because it was super small so to fix that I added a property to a control at scale [Music] now that looks much better scale and color can also be modified from the editor [Music] now it's time for the fun part adding movement to the water we're going to do this by using scrolling Purl and noise to distort the texture to make the noise scroll I just added the current time to its input I then Blended the noise into the world coordinates just before the texture is sampled to increase or decrease the intensity of the Distortion I then added this parameter to adjust how strongly the noise is Blended we also have to set the blend mode to subtract which I almost forgot and there we go that's pretty much it for the texture Distortion for now so this is what it looks like so far there is another problem though if we zoom in we can see the pixels from the image are distorted so much that they are no longer Square shaped this can be resolved by pixelating the world coordinates before using them for anything else one way to pick select coordinates is to multiply the UVS by a large number floor them and then divide them by that same number in my case I use 16 since I want that to be 16 by 16 pixels per tile so this layer is almost finished but I ended up adding a few optional extra enhancements firstly I added a parameter to scale down the y-axis of the caustic texture this was because I wanted to create the feeling that we are viewing the water from a slight angle now I can tweak it to see how much squashing looks best it's probably best to keep the value somewhere around one [Music] the other enhancement I did was to create these highlights by overlaying another texture on top [Music] I was able to create this texture by shrinking the edges of the original texture in Photoshop back in the Shader I overlaid them together using a loop node the textures probably could be combined into one but I did it like this for now because it's nice being able to change the color of each of them separately the Lost enhancement I did was to add a slight Fade to random parts of the texture it's a really subtle effect but the Hope was to add a bit of natural looking variation to help break up the repetitiveness here in the Shader I added some Pearl and noise along with some properties to control the scale and intensity of it then to fluctuate the opacity I subtracted the Purl and noise from the alpha Channel that's used for the output color since Alpha value should be in range 0 to 1 I also added a clamp node here just to be safe this is what the result looks like in game [Music] and with that done layer 2 is complete the next layer is where I feel like it all starts coming together though so let's get right to it after I made a new sub graph I copied and pasted the pixelation and time groups from before [Music] the specular highlights we will Begin by creating some scrolling Pearl and noise again but this time the plan is to blend noise that Scrolls in opposite directions to create this effect here [Music] and in order to test what it looks like so far I added the subgraph into the main Shader using a lerp node to blend it with the rest now obviously it doesn't look like specular highlights just yet but it's a good starting point here I added some nodes to override the color then I also added this step node which is really the key thing to create those hard edges it will cut off all the pixels that are lower than a certain threshold which can be adjusted it still looks like blobs at this point though so I decided to add some small lapel and noise to break them up a bit [Music] once all the noise was Blended together this is what it looked like quite the effect I was going for so I realized I needed to change the blend mode to subtract okay it looks much more like sparkles now but there's one more thing left to do I wanted the specular highlights to only be visible over the caustic texture which would reduce the amount of them and should also create a more cohesive look so back in the water Shader I used the alpha values from the caustic subgraph to help determine the alpha values of the specular subgraph and with that done Here's the final look of the specular highlights the only thing left to complete the water now is to add the seafoam along the edges I began the new foam subgraph by copy pasting the same pixelation nodes from before and then added some nodes to scale sample and override the color of a texture and for the texture itself I just reused the same one from the caustic layer as long as it's scaled down enough it kind of looks like foam this here is just adding the subgraph to the main layer using a lope node to blend just like I did with all the other layers [Music] look like this so I needed to figure out a way to only display the foam when it's closer to the edges initially I tried to use the depth value to determine where the foam should appear which is a technique I've seen before in some 3D games however in my case this approach wasn't going to cut it because it caused too much foam in shallower parts of the beach so I came up with an alternative solution if we blur the main texture of the Sprite and use the white color to control the transparency of the foam this should give us a nice fade around the edges however Unity doesn't provide a blur node so I imported this custom gaussian blow sub graph which I found online and it worked really nicely [Music] then all I'm doing here is splitting the color so the alpha value can be multiplied with the blurry texture and then it's combined back together again for the output [Music] now the only thing left to add is the outline there's nothing too special about it since it comes directly from the original texture so here's a quick time lapse and that's all the layers complete here's what the final Shader graph files look like [Music] they're quite fun to play around with some different colors [Music] so that wraps up everything I wanted to share with you today hopefully you found it interesting or helpful please let me know if you have any questions or suggestions and thanks for watching
Info
Channel: jess::codes
Views: 83,944
Rating: undefined out of 5
Keywords: Unity, Shader Graph, Indie Game, Game Dev, Devlog, Pixel art, Water shader, Shader
Id: pGOLstWBCDA
Channel Id: undefined
Length: 14min 10sec (850 seconds)
Published: Sun Aug 20 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.