Transparent and Crystal Clear | Writing Unity URP Code Shaders Tutorial [3/9] ✔️ 2021.3

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi I'm Ned and I make games have you ever wondered how shaders work in unity or do you want to write your own shaders for the universal render pipeline but without using the Shader graph either because you need some special feature or just because you prefer writing code by hand this tutorial has you covered this is the third video in a series on hlsl shaders for urp if you talk to Shader devs few words strike more fear into their hearts than transparency well in this video I want to demystify transparent shaders a bit in the process we'll learn about render queues custom inspectors blend modes Alpha clipping winding culling and double-sided normals if you prefer written tutorials check out the article version of this tutorial in the video description as well as links to all the other tutorials in this series if you're starting here on part 3 I would recommend at least downloading the starting scripts from the video description so you have something to work from before I move on I want to thank all my patrons for helping make this series possible and give a big shout out to my next-gen Patron Gooby Dooby Doo thank you all so much let's delay no further and get to programming foreign so far our shaders have ignored the alpha channel of the main texture staying completely opaque I say that it's time we change that transparency is a complicated topic for many reasons but it's pretty easy to get started with it just add one line to your pass block this blend command determines how the rasterizer combines fragment function outputs with colors already present on the screen the color returned by the fragment function is called The Source color while the color stored on the render Target is called the destination color the rasterizer multiplies each color by some number and adds the products together storing the result in the render Target and overriding what was already there you can specify these multipliers using the blend command first The Source color multiplier and then the destination color multiplier blend 1 0 results in a fully opaque material the default for transparency we need to linearly interpolate between the source and destination colors based on the source colors Alpha thankfully shaderlab has a source Alpha and one minus Source Alpha multiplier that's perfect for this goal add this line to your forward lit pass block in the scene editor test out blending by lowering the alpha of your materials color tint or sliding in a texture with an alpha Channel unfortunately it doesn't take long to notice some issues first transparent objects don't always blend with the objects behind them remember the depth buffer from our discussion on Shadow mapping currently the rasterizer stores transparent objects positions in the depth buffer preventing fragments behind them from ever running we can't blend colors with a material that was never drawn we need a way to prevent transparent surfaces from being stored in the depth buffer thankfully this is also easy to do add a z write off command to your forward lit pass block this prevents the rasterizer from storing any of the passes data in the depth buffer now all surfaces behind this Shader will always draw but there's still some weirdness the Skybox completely overrides all transparent objects due to render order and sometimes objects even flicker depending on the camera position the blend operation depends on the order objects draw to the render Target objects behind a transparent object must draw first in order to blend colors with them thankfully we can control the draw order using render cues when preparing to render a scene the urp takes a look at all renderable objects and sorts them by a render queue there are a few cues that you can place shaders into using a q tag set in the subshader tags block the default setting is geometry which is used for opaque materials the transparent cue comes after geometry by placing mylet into the transparent queue we can ensure that opaque objects draw first there's also a Skybox cue which runs in between geometry and transparent previously the Skybox rendered after mylet and since my lit had Z right off the rasterizer allowed the Skybox Shader to override it using the transparent cue we can avoid this issue for debugging and a few other more advanced systems Unity also has a render type tag this should be set to either opaque or transparent reader type doesn't affect render order but go ahead and set it for now now both transparent spheres should show up in front of the Skybox notice that the Spheres even draw over each other correctly knowing what we do about render order you might guess correctly that urp sorts objects within the same queue by the distance from the camera back to front or farthest to nearest this is crucial for transparent shaders unfortunately this sorting does not extend to triangles within a single mesh if you have a mesh with many pieces that overlap they might overwrite one another the only reliable way to fix this is to break your mesh into many pieces just one of the many headaches that you must endure when working with transparent materials unfortunately before moving on take a look at the frame debugger you can see render order and verify that each object is in its correct queue look under draw transparent objects for your transparent materials if for some reason you need to tweak the draw order on a material by material basis every material has a q field at the bottom of its inspector you can change this materials q and even give priority to certain materials for instance the geometry plus 1q runs after all other objects in Geometry but still before Skybox there's one more bug that you might have noticed transparent objects cast fully opaque Shadows transparent also known as partial Shadows are very complicated and I will not be covering them in this series for better or worse the lit Shader does not even support them the best that we can do is disable Shadows for transparent objects but in order to do that we need to set up some c-sharp infrastructure if you wanted you could use this Shader for opaque materials like before set the tint to full Alpha and then use a fully opaque texture however this is hardly an optimal solution since we turn zwriting off there would be no protection from overdraw in addition the Q and render type are also incorrect is there a way to change the z-tests mode and tags using material properties well yes but we'd have to remember to set them all correctly in unison thankfully there's an easier way let's create a custom material inspector script to easily switch between opaque and transparent modes using a single drop down menu editor scripts are placed in a folder called editor so create that first then inside create a c-sharp script called mylet custom inspector open it and delete the start and update functions change the mylet custom inspector to inherit from Shader GUI which is located in the unity editor namespace there's a lot to know about editor scripts but we only need to use a couple key features here first override the on GUI method which Unity calls whenever it needs to draw a material inspector we can get the currently viewed material using the Target Field of material editor before going any further set up some properties in the Shader file properties don't only store values used in hlsl code they can also store metadata about a material for our inspector to use create a float surface type property to remember if this material is opaque or transparent add a hidden inspector attribute to ensure that this property doesn't show up in the user-facing inspector next create three float properties for the source blend destination blend and zerite modes to direct Shader lab to use a property value in the blend and xerite commands surround the name with brackets do this in the forward let pass the shadow Caster pass can use the default values of blend 1 0 and Z right on now we need to change the queue and render type tags unfortunately pure Shader lab doesn't support variable tags we'll take care of this in the c-sharp inspector for now reset the render type to opaque and remove the Q tag it will default to Geometry return to the custom inspector class create an enumeration containing All Surface types that we'd like to support opaque and transparent then to add a drop down containing all values in this enumeration call edit a GUI layout enum pop-up in on GUI the first argument is the UI label and the second argument is the current value the material stores the current value in its surface type property however it's not a good idea to read it directly from the material in order to support serialization undo and various other editor functions Unity provides a material property class it passes a list containing a material property instance for each property in this Shader use the find property method to pick the one corresponding to surface type material properties stores values as floats but it's really easy to cast them as a surface type enum pass that as a second argument to Enon pop-up enum pop-up Returns the value displayed in the pop-up either what was passed in or a new value that the user selects either way cast that back to a float and reset the value in material property now to listen for user input and set material properties appropriately surround in and pop-up with this begin and end change check functions and the change check returns true if the user picks a new value from the drop down next create an update surface type function taking the material as an argument call it inside the and change check block update surface type will refresh the z-right mode blend modes tags and Shadow Caster based on the surface type property since the function runs post input it's safe to read properties directly from the material use a switch statement to refresh the material based on the surface type enum set the render queue using material.render queue override the render type tag using a set override tag method set the Z write and blend properties using set int there's a handy enumeration from Unity for blend modes include the unity.rendering namespace to access it as for zright one corresponds to on and 0 to off lastly to turn off Shadows we can just disable the shadowcaster pass the material class has a set Shader pass enabled method to handle this finally back in on GUI ensure a base dot on GUI call comes at the end of the method this draws the default inspector which includes all properties of your material without the hide and inspector attribute all that's left to do is register the inspector class to our Shader in the dot Shader file use the custom inspector command inside the Shader block to do this okay check it out in the scene you can now easily switch between opaque and transparent mode you should verify that everything looks good in the frame debugger too you might notice one issue if you switch shaders the material might not initialize correctly until you mess with the surface type drop down the Shader GUI class has a callback when a new Shader is assigned to a material assign new Shader to material override it and leave the base call at the top check if the new Shader is the mylet Shader using its name field if true call update surface type another bug squashed is there any way to combine the flexibility of a transparent material with the performance of an opaque one well sort of there's another common transparency strategy called Alpha Testing Alpha clipping or Alpha cutouts in this mode we use a texture like a cookie cutter to render only certain parts of a mesh it's really a hybrid mode each fragment is either fully opaque or fully transparent no blending is required making it safe to write to the depth buffer and to cast Shadows supporting cutouts require small adjustments to just about all code that we've written so far let's start with a custom inspector add a transparent cutout member to the surface type enumeration while we're there rename the transparent mode to transparent blend just to be more clear an update surface type we need to set everything up for cutouts they have the same blend and zerite modes as opaque surfaces but a different cue and render type tag they also can cast Shadows we organize the switch statements to take all of this into account notably cutouts should run in a new queue called Alpha test this queue runs after geometry but before Skybox but since cutouts don't blend why not use the geometry queue it's a question of optimization Beyond enabling transparency draw order is a powerful optimization tool imagine this situation two objects have opaque shaders but one is much more resource intensive we'd like to minimize overdraw to prevent shading the expensive material when it's not visible Unity tries to achieve this with depth sorting but it's not always completely reliable by placing the expensive Shader into a later cue we can ensure that it's drawn when more of the depth buffer is filled Alpha cutouts are more expensive than opaque shaders so Unity orders them later to save time but back to the code in mylet forward letpass.hlsl we can utilize the clip hlsl function to do the work if you pass it a number less than or equal to zero it will discard the currently running fragment uh what does that mean this card is a command to the rasterizer causing it to pretend like it never invoked a particular fragment function it will short-circuit the fragment function returning immediately after clip and throwing out all data pertaining to the fragment it will neither write to the depth buffer or the render Target it's as if the fragment function was never called we want to clip this fragment if the alpha value is less than a threshold let's use one half for now subtract the alpha component by one-half and pass that to clip to apply the color tense Alpha multiply it with a texture sample as well go ahead and try all of this out grab a texture with an alpha Channel and change your material to transparent cutout mode it's looking pretty cool but there are several things to fix let's start with the easiest right now we always clip pixels below 50 Alpha but that might not be appropriate let's add a property to make this adjustable to find an underscore cutoff float property in the Shader file it should always range between 0 and 1 so we can use this special range property type to create a slider in the inspector by the way this property has a magic name meaning Unity always expects the alpha cutout threshold in a property named cutoff this will be important later on like when we look at baked lighting but for now just make sure that your property is named cut off then in mylet forward let pass.hlsl Define the cutoff variable near the other properties and subtract cutoff from the alpha in the clip function instead of 0.5 now you can edit your material to better match the alpha values and the texture you're using on to the next problem we're clipping even and Blended and opaque materials this is not only incorrect Simply Having a clip function in your Shader can drastically decrease performance we should use a keyword to remove the clip line when an opaque or Blended modes this will be the first time that we've actually used a keyword to change our own code these keywords don't have a value not even true or false so a hash shift block cannot parse them instead test if the keyword is defined using the defined function a keyword is defined when it is enabled and it is not defined if it's disabled there is a shortcut for this hash if def keep in mind that there's not a hash L if def for whatever reason so sometimes I still use the entire defined Syntax for clarity anyway we'll use the alpha cutout keyword to include or omit the clip function wrap the clip call in a high shift def block and we're good to go it's possible to enable and disable keywords from c-sharp so we can take care of this in the custom inspector in update surface type enable or disable Alpha cutout based on the surface type now we need Shader variants based on Alpha cutout moving to the dot Shader file add a hash pragma to generate them instead of multi-compile use a Shader feature local command Shader features are just like multi-compiles in that they generate Shader variants based on a list of keywords the difference comes in game builds when you build your game or create a distributable Unity must determine which compiled Shader variants to include in your build it gathers all shaders used by The Game and starts filtering Shader variants Unity includes all variants generated by multi-compile commands but before including a Shader feature variant it checks to make sure that some material in your game has the required keywords enabled for instance Unity only includes mylet variants with Alpha cutout enabled if a material has a cutout surface type and thus we enable the alpha cutout keyword since this check happens at build time keywords that change dynamically at runtime like the urp lighting keywords should use multi-compile otherwise use Shader feature why bother with all this well Shader variants are not cheap to compile Shader features help optimize build time the bigger your game gets the more important this is to think of also Shader features always have an implicit underscore in their keywords list in other words they always trigger a variant with none of the listed keywords enabled of course that variant may or may not be used by your game but Unity is ready for the possibility finally the local suffix indicates that the keyword is unique to this Shader and will not be set globally we set Alpha cutout on a material by material basis so it can use local variants keyword set globally by urp like main Light Shadows cannot be local Unity has a hard limit on the number of global keywords it can support so it's a good practice to use local variants whenever possible with all that done your Shader should look the same as before but your FPS will thank you for this optimization the next bug to tackle is Shadows objects no longer cast Shadows that match their cutout shape to fix that clip fragments in the shadow Caster pass first let's take the alpha clipping logic and mylet forward let pass.hlsl and move it to a function called test Alpha clip pass the color texture sample as an argument to make this available in my lit shadowcaster pass.hlsl we should add it to a separate file and hash include it in both of our pass files create a new milit common.hlsl file remove test Alpha clip from my lit forward lit pass.hlsl and paste it here test Alpha clip requires a couple of properties tint and cut off properties are defined Shader wide so it makes sense to move property definitions to the common file as well finally include the urp library to have access to the texture 2D macros in my lit forward lit pass trim duplicated code and add the include line to bring in our common code file before moving on let's take a moment to think about this hlsl is very C plus plus like so hash include causes the compiler to literally copy and paste the contents of the common file onto this line what happens if we accidentally include a file twice Unity will throw an error saying that a variable has already been declared okay but duplicating include lines is not that common what if my lit common and my lit forward lit paths also include a file called mymath.hlsl then my math would be duplicated to simplify things and prevent errors it's customary to wrap all code in an hlsl file inside something called a guard keyword block first check if a keyword is not defined that's what hash if indef does it's shorthand for hash if not defined on the very next line Define the guard keyword don't forget a hash and if at the end of the file now the first time my math is included the guard keyword is defined the second time the guard keyword is defined so it skips the code inside go ahead and add guard keywords to mylet Common I usually add them to all hlsl files just to be safe after all of this cleanup your Shader should still look the same let's finally add clipping to the shadow caster first in the Shader file add the alpha cutout Shader feature to the shadow Caster pass in my lit shadowcasterpass.hlsl add hash include mylet common.hlsl at the moment the shadow Caster has no support for UVS to sample the main texture we'll need to pass them all the way to the fragment stage to do that add a UV field to interpolators each field here is another that the rasterizer has to interpolate and it's best to keep this struct as small as possible surround the UV field with a half shift block ensuring that it's only interpolated when needed it's not as important to do this in the attribute struct but it can't hurt in the vertex function transfer the UV to the output struct again only if Alpha cutout is defined then in the fragment function sample the color texture and call test Alpha clip we can wrap all of this in a if block 2. remember clip discards a fragment if passed a value below zero causing the rasterizer to throw it out and not write to the depth buffer since the death buffer becomes the shadow map clipped fragments won't show up there either with that your shadows correctly match the clipped shape make sure you test out all modes on your material to make sure that the if blocks are set up correctly there is one last problem I want to fix but it requires a little more explanation often games use Alpha clipping on flat planes in which case everything looks okay but if you turn on Alpha clipping on a sphere you might notice that the inside becomes invisible this is another optimization technique called face culling winding culling or often simply just culling the particulars aren't that important for us but basically the rasterizer determines whether it's rendering the front or the back face of a triangle the back face is usually on the inside of a model so the rasterizer calls it or decides not to render it if you've ever had weird issues importing from blender and had to reverse faces culling is likely the culprit in our case we'd like to render the inside of the sphere luckily it's easy to turn off culling in mylet.shader add a new float property called cull we'll use this to set another Shader lab command also called call can take three different values off front and back off completely turns off culling while the other two call one side of a triangle Unity has a c-sharp enumeration for these options and we can instruct the default material inspector to create a drop down using it in the enumeration back has an INT value of two so set that as the default then add a call command to the forward lit pass and the shadow Caster pass with the call property in Brackets okay but there's a new bug the lighting is incorrect notice that both sides of a triangle receive the same amount of light as if it was made of paper this is because both sides have the same normal Vector it is not automatically flipped for the back face we'll have to do that ourselves and mylet forward let pass.hlsl we can flip the normal Vector given to input data if we're rendering the back side of a triangle to flip a vector simply multiply it by negative one but how can we know which side of a triangle is currently rendering the rasterizer has this information and makes it available through a special fragment stage only semantic to gain access to it simply add another argument to the fragment function both the exact semantic and the argument type depend on the current platform Unity provides macros to sidestep this compatibility issue regardless front faces type is essentially a Boolean true of the triangle front face is visible Unity also provides another macro to choose a value based on the front face like the ternary operator in C sharp use that to either multiply the normal vector by one or negative one before setting it in lighting input so that fixes lighting but the triangle back face now have Shadow acne since Shadow biases also depend on normals we need to flip them too unfortunately the rasterizer front face data is not available in the vertex stage which runs before the rasterizer luckily there's another way if we assume that the normal Vector should always Point roughly towards the camera we can flip it if it does not let's try to keep the angle between the normal vector and the view direction to less than 90 degrees if the angle is greater flip the normal Vector this will bring it back within an acceptable range there's a very simple function to find the angle between two vectors the dot product it Returns the cosine of an angle cosine of 90 degrees is zero so if the dot product is less than zero the angle between the vectors is greater than 90 degrees let's implement this in my lit shadowcasterpass.hlsl using a new flip normal based on view Direction function pass it the position and normal calculate the view direction using this built-in urp function which we also use in the forward lit pass fragment function then only if the dot product of the normal and view direction is less than zero multiply the normal by negative one then return the normal modify the clip space calculation to call Flip normal based on view dear back in the scene editor it looks like there's no more Shadow acne so mission accomplished this technique isn't perfect sometimes it creates Shadows that kind of flicker as you rotate around an object but that's usually preferable to Shadow acne double-sided normals are not free both the flipping operations and the front face semantic have a bit of overhead it's a good idea to use a keyword to turn it on and off thinking about this there's also no reason to ever flip normals if culling is on this leaves only three useful configurations back culling no culling and no culling with normal flipping let's address these concerns by updating the custom inspector create a new a num face rendering mode to encapsulate the aforementioned modes similarly to the surface mode we can use another hidden property to keep track of this setting and on GUI add another enum drop down controlling this new property face rendering mode in update surface type we need to update the call property and either enable or disable a keyword double-sided normals if the face rendering mode is front only set call to back use the unity's call mode enum otherwise turn calling off then enable or disable the keyword appropriately moving on to mylet.shader First hide the call property in the inspector and remove the enum attribute it's handled by code now second add another hidden property for face rendering mode third add a Shader feature for double-sided normals to both of our passes [Music] and my lit forward lit pass wrap all the normal flipping code and if blocks there's a bit of trickery to hide the front face semantic in the argument list it's ugly but it does work in my lit shadowcaster pass similarly wrap the call to flip normal based on View deer within a hash shift block in the scene editor try out different face rendering modes to make sure everything works as expected in the future think carefully whether an object actually needs any of these options turning off culling can dramatically increase performance cost and double-sided renders aren't exactly cheap either these options are very useful for things like foliage just don't blindly enable them for all materials and with that I would consider this a fully functional Shader it supports all the basic needs textures lighting Shadows transparency and even goes above and beyond the lit Shader with double-sided normals but of course we're far from done in the next tutorial I'll focus on more surface options we'll Implement a new lighting model called PBR or physically based rendering rough metallic glassy smooth shiny and glowing materials are in our future for reference here are the final versions of all the Shader files after completing this tutorial you can also view them on GitHub from a link in the video description please stay tuned subscribe and press the Bell to be notified when my next tutorial video goes live if you enjoyed this video consider liking it as well it really helps out with the YouTube algorithm are you having any issues or questions feel free to leave a comment or contact me on any social media I do read all comments and I'll reply as soon as I can I want to take another moment to thank all of my patrons for helping make this video possible and once again to give a big shout out to my next gen Patron Kirby Dooby Doo thank you all for your support it really does mean a lot to me and if you would like to download the example shaders from this tutorial and all of my others consider joining patreon too thank you all so much for watching and make games
Info
Channel: Ned Makes Games
Views: 6,457
Rating: undefined out of 5
Keywords: gamedev, game development, development, unity, unity3d, madewithunity, programming, game design, csharp, nedmakesgames, nedmakesgames dev log, indiedev, indie game, dev log, shaders, 3d modeling, blender, tutorial, walkthrough, shader, universal render pipeline, urp, unitytip, unitytips, shaderlab, hlsl, beginner, starting, shader graph, hdrp, graphics, graphics programming, tech, tech art, lighting, shadows, texture, textures, interpolation, struct, swizzling, macros, material, materials, model, modeling, uvs, unreal
Id: 4zw6Vq5CzLY
Channel Id: undefined
Length: 32min 38sec (1958 seconds)
Published: Wed Oct 12 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.