Custom Lighting in Unity URP Shader Graph! Ready for Toony Lights! ✔️ 2021.1 | Game Dev Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi i'm ned and i make games in this unity tutorial i'd like to show you how to implement custom lighting techniques in unity's shader graph for the universal render pipeline as of now there are no nodes to retrieve light data or modify the result of the lit pbr graph but we can get around that with these techniques you can handle materials that don't look quite right using the lit graph like foliage or skin or create stylized lighting like a toon shader real quick i want to thank david crew all my patrons and my great community for your support helping make this video possible thank you all so much it really means a lot to me and if any of you would prefer to read i've also made a written version of this tutorial check it out in the video description this tutorial was tested in unity 2020.3 and unity 2021.1 using the universal rendering pipeline if you're using a future unity version check the video description for any important updates in this video i'll cover how to set up a simple custom lighting algorithm in the urp shader graph it will feature diffuse and specular lighting it will support the main light additional point and spotlights baked lighting light probes shadows for all light types and fog point light shadows are only available in unity 2020.1 and later i will assume that you know the basics of unity urp and the shader graph you should also understand simple hlsl the shader programming language and what control keywords are and although i'll give a general overview of various lighting terms and techniques i'll leave other tutorials to dive deep into them check out the video description for further reading let's get started make sure your project is set up to use the universal rendering pipeline for testing create a simple scene with a ground plane and a floating sphere just above it create an unlit urp shader graph called test lighting and make a material for it then apply that material to both of your meshes now so that we can reuse the custom lighting in mini graphs create a subgraph called custom lighting and open it inside change the output value to a vector3 called out and add a vector3 albedo property route albedo directly into the output node switch to the test lighting shader graph and add an albedo or base color texture property sample it and route the rgba output into the albedo field of the custom lighting node hook the output into the base color of the master stack the bulk of the custom lighting algorithm must happen in a custom function node which works best reading from a shader code hlsl file you cannot create an hlsl file directly through unity so open your asset folder in your operating system create a text file called custom lighting and change the file extension to hlsl open it in your script editor first off set up a guard keyword to prevent the shader graph from compiling this file twice write this calculate custom lining function with a float3 return value the color and a custom lighting data structure argument write custom lining data above with a single float3 field for the surface albedo or unlit color we'll add many more fields to this structure as we progress for now have calculate custom lighting return the albedo shader graph custom function nodes require a specific syntax so we can't call this function directly to make it easy to reuse this function in written shaders as well as decouple it from the shader graph syntax create a wrapper function it needs the underscore float suffix to define the precision mode remember that return values must be in the form of an out keyword in the function body construct the data structure and add calculate custom lighting setting the result to color back in unity open the custom lighting sub graph create a custom function node and set the inputs and outputs to match calculate custom lighting underscore float set the source as customlighting.hlsl and fill the function name appropriately remembering that you don't need to include the precision suffix here if the preview window is colored magenta then there's an error somewhere double check all fields for typos and remember that capitalization is important finally reroute the graph to flow through the custom function looking at your scene you should see flat textures on all objects that will change shortly diffuse lighting is a soft light that illuminates the side of an object facing a light source it's calculated by taking the dot product of a fragment's normal vector and the light direction let's add shape to our scene by calculating diffuse lighting for the main light open up customlighting.hlsl to calculate diffuse we need a normal vector in world space so add that to the light data structure in calculate custom lighting call get main light from the urp shader library to get us struct with data about the main light including its direction and color initialize a color variable to 0 and return it in between call the new custom light handling function passing the data structure and the main light this function computes the color resulting from this light this radiance value encodes data about the light's strength right now set it to the light's color calculate the diffuse dot product normal.light direction then multiply the result with a surface albedo and radiance to get the final color return it in the custom function wrapper add a normal argument and pass it to the struct back in the custom lining graph you'll get an error about no function existing just add a float3 normal input to the custom function so that it matches the wrapper function once again however now you'll get a bunch more errors about unknown functions and light structures this occurs because the shader graph doesn't include the urp lighting library when rendering the node preview windows to fix this we need to exclude sections of code from the shader graph previews we can use this shader graph preview keyword which is defined only in said previews add an if not defined block around the custom light handling function then in calculate custom lighting write this estimation of diffuse lighting for the preview windows placing the old logic in the else block that way we'll still have a pretty preview but no errors in your sub graph the error should disappear make sure your custom function inputs are in the right order and then add a vector3 normal attribute and route it into the custom function in the test lighting graph you can route the normal vector into the subgraph or if you want to use a normal map sample it and then transform it into world space either way check out your shaded scene specular lighting models the small highlight you see on a shiny surface it's calculated by taking the average of the view direction and the light direction then take the dot product of this half vector and the normal vector using that value directly sometimes leads to highlights in dark areas so it's a good idea to also multiply it with a diffuse lighting value let's program that open customlighting.hlsl to calculate specular lighting we'll need the view direction so add that to the data structure and in the wrapper function too and custom light handling calculate the specular lighting dot product then multiply it with a diffuse result finally to add the specular component of lighting add the diffuse and specular values together in the final calculation in the custom lighting subgraph update the custom function create a view direction node and set it to world space in certain unity versions view direction is not normalized so i always write it through a normalized node then hook that into the custom function looking at your scene now it will be difficult to see any highlights that's because we've neglected smoothness which controls how focused the specular highlight is open the customlighting.hlsl file add a float field for smoothness in the data structure and update the custom function wrapper in custom light handling tighten the specular dot by taking it to a power related to smoothness defined by this function it uses some math to map a value ranging from 0 and 1 to an exponential curve but it's ultimately arbitrary you can edit it as you need this implementation results in a higher smoothness value shrinking the specular highlight before moving on we might as well add a specular approximation for the node preview windows back in the custom lining subgraph add a float smoothness property to the custom function and our corresponding graph property then in test lighting graph add another smoothness property for ease of editing set it to slider mode ranging from 0 to 1. you could of course also use a smoothness texture if you'd rather either way check out your handiwork shadows are an important part of lighting and take a bit to set up if you move an object using your custom lighting over something with a default lit material you'll see that it already casts a shadow however our custom lit shader does not receive shadows there are a few things we need to do to fix that first the programming open the hlsl file shadows at least for the main light are held in textures called shadow maps the system needs something called a shadow cord to accurately read them as well as this fragment's world position add both to the data structure notice that the shadow cord is a float four in the custom function wrapper only add a position argument and set the position in the structure we can calculate the shadow chord from world position right here create another if-def preview block and inside set the coordinate to zero in an else block first calculate the fragment's clip space position which is related to the pixel it displays in on screen depending on your shadow settings unity might store shadow maps and different layouts needing different coordinates this keyword allows you to calculate the correct value when calling git main light pass the shadow coordinate and position the third argument is something called a shadow mask which we'll come back to later this fills a field in the light data structure called shadow attenuation which is a multiplier to the light's radiance due to shadows in custom light handling multiply it with the light color when calculating gradients in the sub graph add a position argument in the custom function and route a position node in world space into it in the scene you'll notice that your materials still receive no shadows this occurs because to save resources unity does not sample shadow maps unless you explicitly tell it to by using various shader keywords return to the custom lighting subgraph at a boolean keyword the option is under the new property menu the name of this keyword isn't important but the reference must be this string exactly underscores and capitals matter set the definition to multi-compile the scope to global and the default to checked now in the scene you'll get an error about shadow cords not existing to fix this open up custom lighting.hlsl and add this little code snippet it removes some unnecessary code from shaders generated by the shader graph which avoids the error as a quick aside this was found out by another shader tutorial writer cyanolex be sure to check out their stuff from the link in the video description it's all great at this point if you don't see any shadows in your scene make sure that shadows are enabled in your urp pipeline settings and on your camera shadows are a bit ugly right now let's fix that by adding two more boolean keywords enabling shadow cascades and soft shadows keep the settings the same for each keyword but set the references to these strings now enable cascades and soft shadows in your settings asset that's much better okay there's only so much you can do with a single light so let's add support for a point and spotlights we've got the framework already so it won't be too difficult open customlighting.hlsl unity stores additional light data separately from the main light in a buffer which we need to loop through this is the primary reason why i prefer implementing custom lighting all in one custom function since loops are impossible in the shader graph anyway there's a keyword enabling multiple lights so surround all this code in an if-def block inside call this function to get the additional light count then loop through each this next function gets a light data structure for each light it works just like git main light except not requiring a shadow cord since that's only needed for main light shadows send the light data structure to custom light handling and add the return value to the final color unlike the main light point and spotlight strengths vary based on position this is encapsulated by the distance attenuation field in the light data structure in custom light handling multiply it into the radiance calculation for reference distance attenuation is always one for the main light back in the custom lighting subgraph add two more boolean keywords with these references they enable multiple lights and their shadows respectively again set the definition to multi-compile the scope to global and the default to on then select your urp settings asset and set additional lights to per pixel and enable cast shadows create some lights to test things out make sure that all lights are in real time mode and that the shadow type is not no shadows turn up the intensity so things are really plain to see do note that point light shadows are only available in unity 2020.1 and later illumination is a broad concept including baked light maps light probes global reflections shadow masks and spherical harmonics we can implement them all in one fell swoop i won't spend too much time explaining how each of these various techniques work or how to use them each topic really deserves its own video just know that each is a way to add a lot of inexpensive lighting to a scene in customlighting.hlsl add two new fields into the custom lighting data a float ambient occlusion which controls how much global lighting this fragment should receive and a float3 baked gi which is the baked lighting color at this fragment moving down to the wrapper function add an argument for ambient occlusion and then pass it to the struct as for big gi we'll calculate it using a few special functions they require the lightmap uv which we'll receive as a float2 argument in the not preview block add these lines this one calculates the final lightmap uv using the lightmap scale unity provides this calculates the spherical harmonics for this fragment which encode the color of the nearest light probes finally resolve everything into a single baked global illumination value with sample gi it samples the light map texture if available to add global light sources into our algorithm write a custom global illumination function inside the preview not defined block we use urp to do the hard work already so just multiply the albedo big gi and occlusion values together to calculate the indirect diffuse value and return it in calculate custom lighting initialize the color variable with custom global illumination but before that we need to correct the global gi value under some circumstances the main light shading contribution is also baked into global gi we don't want to include it twice so call this urp function mix real time and bake gi to remove the main light from the global gi value if needed open your sub graph and create arguments for ambient global occlusion and light map uv add a new float property for the occlusion and hook it into the custom function the lightmap uv is usually stored by unity in most models second set of uvs which is called uv1 route a uv node in uv1 mode into the lightmap uv field finally add one more boolean keyword with this reference which enables mixed lighting in the test lighting graph create an ambient occlusion property it can be a slider ranging from 0 to 1 or an occlusion map if you'd prefer now test things out create a few new lights and set them to baked mode set your ground plane game object to static so it will receive light maps create a light probe group around your floating sphere and make sure differently colored baked lights appear on opposite sides once ready save your project and navigate to the lighting panel select generate lighting and wait for the light mapper to finish its work now check that baked lighting appears on your plane if you disable real-time lights you should still see some lighting where the baked lights are then test the light probes by moving your sphere around again it might be difficult to notice at first so turn off real-time lights if you're having trouble if all's well let's move on to baked reflections unity creates a reflection cube map based on the skybox and it's really easy to get a hold of in customlighting.hlsl add these lines to custom global illumination we first need to sample the cubemap by calculating the sample normal visualize yourself standing at the center of this cube and looking at the pixel that you want to sample that direction is the sample normal in this situation modeling a mirror we calculate it by reflecting the view direction around the normal vector i found that reflections look nice around the edges of an object which is an effect known as rim or fresnel lighting it's calculated by finding the dot product of the normal and view direction subtracting it from one and tightening it with an exponent four in this case then call glossy environment reflections to sample the cube map passing the sample normal a roughness value and the occlusion roughness is 1 minus smoothness and affects how blurry the reflections are i'm repurposing this function from urp's pbr rendering code which expects something called perceptual roughness luckily we can convert to that using roughness to perceptual roughness finally multiply by the fresnel value and add the indirect specular result to the final calculation and that's it if the effect is too strong you can reduce its strength in the lighting window finally let's add baked shadows these are lightweight pre-calculated shadows for the main light baked into a texture kind of like light maps they're always used for far away objects but you can also turn them on for all objects in the quality settings to get started open customlining.hlsl it's time to tackle the shadow mask value that i spoke about earlier add a float4 field in the lighting data structure in the wrapper function set the shadow mask to 0 in the preview block and call this urp function in the not preview block then in calculate custom lighting pass the new shadow mask value to get main light and get additional light in your custom lighting sub graph add one more boolean keyword with this reference and the same settings as always to test shadow masks add a large static object above your plane set your main light mode to mixed and bake lighting again go to your quality settings and change shadow mask mode to shadow mask you should see shadows on all static objects like the ground plane note that light probes received bake shadows so that will affect lighting on dynamic objects like the test sphere before moving on change the shadow mask mode back to distant shadow mask now bake shadows are only used for distant objects fog is the last feature to add and it's pretty simple it's a method of fading distant objects to a flat color helping avoid pop in in your hlsl file add a float fog factor field to the data structure this is another value to calculate in the function wrapper set it to 0 in the preview block otherwise call this function which calculates how much fog this fragment receives based on its depth from the camera the clip space z position then in calculate custom lighting call this function mix fog it applies fog to the final color taking care of each fog mode for us return to the scene view and test things out by turning on fog in the lighting window try each mode and adjust the settings mix fog can handle them all in version 2021.2 unity is poised to release many new features to urp and the shader graph which will all affect this project the biggest change is the addition of custom vertex interpolators which will allow us to support vertex lights and more efficient global illumination they're also adding light cookies reflection probe blending light layers screen space shadows and occlusion and deferred rendering needless to say expect a follow-up video about 2021.2 when it releases thank you all so much for watching it can be difficult to set up custom lighting but it's definitely worth it i think now you have the convenience of the shader graph with complete control over all the lighting in your project use these techniques well and your game will really stand out in the coming weeks i'll release a tutorial building on this framework to create a grass leaf and general foliage shader please subscribe here so you don't miss it if you enjoyed the scenes in this video illustrating how light math works they're filmed using an interactive diagram i made you can play with it yourself in your browser from the link in the video description again i want to take another moment to thank all my patrons for helping make this video possible and give a big shout out to david crew my next gen patron thank you all so much if you'd like to download the project files from this video and all other files from my other tutorials consider joining my patreon if not it's no problem either way you can really help me out with youtube's algorithm by liking this video and be sure to subscribe for more game development tutorials thanks so much for watching and make games you
Info
Channel: Ned Makes Games
Views: 47,689
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, toon, cel, shading, graph, shader graph, custom, lighting, lights, diffuse, specular, baked lights, lightmap, baked shadows, shadowmask, shadow mask, reflection cube, main light, point light, spot light, 2020.3, 2021.1
Id: GQyCPaThQnA
Channel Id: undefined
Length: 26min 12sec (1572 seconds)
Published: Tue Aug 31 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.