Godot 4 Post Processing: 3D Pixel Art Shader

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
the first gdo video I made was about 3D Pixel Art since then I've been trying to improve the effect by adding Pixel Perfect outlines as a post-processing effect here's how I managed to achieve it let's get started with the pixelization since my previous video I've been using a simpler and easier way to pixelate the screen add a viewport container to the scene and set the anchor to full wck then add a sub viewport node to The Container we'll make our 3D models in camera a Char of the viewport now in the container set stretch to on and we can use a stretch shrink option to add pixelization this option will lower the resolution of the sub view port and stretch it out to fit our container size but you may notice that the pixels looking a little bit blurry in the containers inspectre go to texture and set the filter to nearest and the pixels will become nice and crisp the benefit of this method is we can have things like the UI not be affected by the pixelization if we choose the next step to figure out was postprocessing the main way of doing custom postprocessing effects in Gau 4.0 feel feels a little bit hacky but it works well and it's covered in the official documentation to create a custom post processing effect we create a new mesh renderer and set the mesh type to quad set its width and height to two and flip faces to on now add a new shade of material to the mesh finally in our Shader set render mode to unshaded and in the vertex function set position to the vertex and 1.0 in the W Channel this will make our quad Encompass the screen at all times it's worth noting the gdau documentation also recommends adding the quad as a child to your camera and setting the extra cool margin of the Quad to as high as it will go we can now write our postprocessing effects in the fragment function of this Shader but how currently we just have a quad Block in the view of our scene while gdau provides us with some screen textures of our rendered scene which we can sample to create our effects these textures are the screen texture the normal roughness texture and the depth texture the easiest to use is the screen texture gdau provides us with a built-in screen UV which provides us with the UV relative to the screen of the current fragment if we sample the screen texture with a screen UV and plug the results into the albo output of the Shader we can now see the scene again we can also manipulate the value to change how it looks the second easiest to use is a normal roughness texture this texture stores the few space normals of the object objects and the scene in The X Y and Zed channels and the roughness data in the W Channel we can sample this texture just like we did for the screen texture but the values are mapped between 0 and one to make them usable we want to change it so that it's between minus one and 1 instead which we can easily do by multiplying the sampled value by two and subtracting one finally we have the depth texture which requires a little more effort to use as before we can get the depth by sampling the depth texture and storing the value from the r Channel unfortunately this value isn't usable by us yet as it isn't linear meaning everything in the texture is Almost White unless it's very close to the camera to make the value linear we need to use the inverse projection Matrix I'm using the Vulcan back end so first I need to turn my screen UV into normalized device coordinates like so we set the Zed value of the NDC to the depth value we sampled from the texture then we can apply the inverse projection Matrix like so to get our value finally because negative Zed is forward in gdau our depth value will be the negative of the calculated view. Z we can also plug it into our albo to see what's happening here I'm dividing the result by 20 just to make it easier to see we're going to be sampling depth a lot though so I moved it into its own function before we move on it's important to understand when gdau generates these textures gdau generates these textures during rendering after the opaque object pass but before the transparent object pass this means that any transparent objects in your scene will not show up in these textures also when we use them in a Shader gdau will treat the object as transparent so the object itself will not be visible this is why our full screen quad isn't blocking The View when we use these textures now we can use a combination of these textures to create our post-processing effects my goal for this effect was to create a darker highlight around the outside of objects and then a lighter highlight for the sharp edges within the object two but switched to a dark highlight for unlit faces here's how I achieved it at first I tried a common Edge detection algorithm called Robert's cross this algorithm involves getting four UVS diagonally adjacent to the current fragment and sampling the depth and normal textures at these UVS we then do some calculations on these values to detect edges we can control the thickness of the Outline by applying an offset to the 4 UVS the bigger the offset the thicker the line This outline works very well in general for 3D scenes but although it's subtle it didn't look great in our pixelated scene particularly on complicated objects like the big leaves I think this is likely due to the sampling being in a diagonal Direction which doesn't play well with Pixel Perfect outlines after some more searching I found the algorithm that I settled on let's start with a highlight around the outside of our objects we can get this entirely from our depth texture first let's sample the depth at our current fragment then we need to calculate the UVS of the pixels directly above below to the left and to the right of our current fragment we can do this by calculating the texal size which is 1.0 / by the viewport size and using that as an offset for our screen UV I store these in an array so I can iterate over them in a for Loop in the loop I calculate a depth Difference by summing the difference of our fragments depth compared to the adjacent pixels plugging this into our albo we can see some nice outlines we can clean this up a touch by using a step to apply a threshold to our difference we can apply this over the original scene by sampling the screen texture and plugging it into a mix function we can put a highlight color in and use a depth Edge to switch between the two colors but I want the outline to be the original color but darker so to do this we can create a Darkness amount multiplier and multiply the sampled screen texture value by it to darken the edge outlines this looks good at first but the Eide amongst you might notice that there is an issue because the of the outline is outside of the object the color of the outline is the color from the object behind my fix for this is While We're looping over the UVS to calculate the depth difference we can also figure out which of our five UVS is the closest to the camera then sample the screen texture from that nearest UV and use that color for our depth outline much better now let's tackle the inner edges like before sample the normal of the current fragment and then Loop over our UV array to calculate a difference between the fragments normal and the normal of the offset UV we need to do a few more calculations though to get our final normal outline first to get a Pixel Perfect outline we have to select a single direction from which the normal is chosen this is called our normal Edge bias it can be any arbitrary value so after playing around a little bit I settled on 111 being the best we can then get the dotproduct of our normal diff and our Edge bias to calculate an indicator which we can then use to limit the outline to just the closest one to our Edge bias next we sum the dot product of our normal diff with itself doing this will give us the square of its magnitude we then multiply that with our indicator finally we got a square root of the sum and apply a step threshold like we did with the depth Edge to get our final normal Edge now let's update our mix to also include the normal Edge first if the depth Edge is greater than zero The Edge color is equal to the nearest screen sample multiplied by the darken amount else The Edge color is equal to the original screen color multiplied by the lighten amount the reason why I use the original color for the normal Edge is because I find it tends to produce better results and also the same reason for why I prioritize the depth Edge over the normal Edge it just seems to create better looking outlines now we're almost there the last thing I wanted to add to my effect though was if the fragment of a normal Edge is UNL darken the outline instead of lightening it this should be pretty easy to calculate using the dotproduct the dot product of two directions will give us a value between minus one and 1 if the directions are facing the same way the value will be greater than zero and if they're facing opposite directions the value will be less than zero in summary if the dot product of our fragments normal and the light direction is positive the fragment should be unlit as you can see though the normals are in view space so we need to construct a matrix that will convert a view space Direction into a world space Direction so that the normals will not change when the camera does we can use that to then calculate our DOT product then last but not least when setting the normal Edge color switch between the darken and lighten amount based on if our DOT product value is positive or negative and there's the final effect so that's how I created this 3D Pixel Art Shader I hope you find this useful as always there's a GitHub repo if you want to check out and play around if you want please subscribe and let me know in the comments if you have any topics you'd like covered in future videos anyway see you in the next one cheers
Info
Channel: Crigz Vs Game Dev
Views: 28,591
Rating: undefined out of 5
Keywords: Godot, Godot 4, Shaders, Post Processing, Game dev, Indie, Maths
Id: WBoApONC7bM
Channel Id: undefined
Length: 9min 48sec (588 seconds)
Published: Sun Jan 07 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.