Creating a Foliage Shader in Unity URP! Grass, Trees, Flowers, Hedges and More | Game Dev Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi i'm ned and i make games in this video i'll show you how to create a cool foliage shader for unity's universal render pipeline using only the shader graph it's great for low poly trees complex trees fields of grass flowers and more these types of things don't look quite right using the default lit shader since it lacks important features like two-sided normals translucency and wind deformation so we'll fix that but first i'd like to introduce you to core who have very kindly sponsored this video core is a free pc game creation platform where anyone can create share and play games no coding is required to start making games on core and i was impressed by the thousands of free music sound and art assets which you can just drag and drop into your game if you do code you can even use the beginner-friendly programming language lua to create your own game logic core has built in multiplayer so that you can publish your game instantly for others to play you know i had to check out the foliage assets since i've been working on lots of it look at all the pre-made models there's a lot of customizability and i was able to get the hang of the editor pretty quickly so i think your unity skills will serve you well here these games were able to leverage these tools to create beautiful gardens forest jungles and more since core is powered by the unreal engine your game will have high quality aaa graphics you can even make money with your games on core core shares 50 of the revenue with its creators through the core perks program i've learned that many core creators have been able to pay their bills buy their dream cars and even quit their day jobs with the help of the perks program all in all i was impressed during my time with core and i hope that you jump in and give it a try use the link in the video description to download core for free and start creating your own game now thank you core for taking a chance on small youtube creators and sponsoring this video real quick i want to thank david crew and all my patrons and my great community for your support helping make this video possible thank you all so much if you'd prefer to read this tutorial i've also made a written version as well as a version about a text-only shader you can find links to both 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 version of unity check the video description for any important updates this tutorial covers creating a foliage shader with the urp shader graph it is complete with diffuse specular and translucency lighting it supports all light types their shadows global illumination and fog we'll also look at wind deformation and two-sided rendering with normal mapping for use with grass billboards this tutorial will not cover how to build any foliage models or textures it will not show how to place grass blades or any other type of foliage around the game world or support character interactions through physics or otherwise i have tutorials planned for all of these topics in the near future though i'll also assume that you know the basics of unity the universal render pipeline and the shader graph you'll need to understand simple hlsl which is the shader programming language and what control keywords are since we'll use both in custom functions and although i'll give you a general overview of lighting techniques normal mapping and tangent space i'll leave other tutorials to dive deep into them check out the video description for further reading get started by setting up your unity project you'll want to go through my custom lighting in the shader graph video which provides the base for our shader it covers adding lights shadows and global illumination support as well as diffuse and specular lighting once you're finished with it either duplicate or rename the completed shader files to foliagelighting.hlsl foliagelighting.subgraph and testfoliage.shadowgraph in foliagelighting.hlsl rename mentions of custom to foliage then in the foliage lighting subgraph we name the custom function node's name field to match all right let's set up a simple test scene to test the shader out on various types of common foliage meshes create a flat plane for the ground to test shadows then create a sphere game object to simulate a simple low poly tree for a grass or bush billboard create a quad game object and position it attached to the ground now to test for complex trees with mini leaf cards or double-sided quads craft something in blender or another 3d modeling program of your choice create three quads and rotate them so there's one in each plane create edges to split each into quarters and then merge vertices to weld the planes together import this funny cube-like shape back into unity and add it to your scene create three materials using the foliage shader one for each model and then assign it to better prepare for grass and leaf cards and alpha clipping support to the shader in the test foliage graph click alpha clip in the graph inspector add a float alpha clip threshold property and route it into the alpha threshold field of the master stack then route the alpha output of the albedo texture sample into the alpha field of the master stack in or another photo editor of choice create a simple texture with an alpha channel for the cutout texture in unity set alpha is transparency to true in the texture importer set up all your materials making sure to add the alpha channel texture to the grass material to make sure everything's working let's test out a few lights also to test global illumination set all the foliage objects to static and make sure double-sided global illumination is enabled on the grass and tree materials then bake a light map that all looks good you might notice the grass and tree meshes are invisible on one side this is due to something called backface culling for meshes like the sphere it allows unity to ignore triangles on the inside of the model but for double-sided geometry like the foliage cards we need to make some adjustments in test foliage turn on the two-sided option in the graph inspector now both sides are visible however the lighting is still incorrect the mesh's normals point outwards from the front face causing both sides to receive the same amount of light i modified the shader to display normals here and we can see that it's pointing in the z direction on both sides to fix this we need to flip the mesh's normal vector when drawing on the back face to support normal mapping we have to do this before we transform the tangent space normal to world space create a subgraph called calculate double sided normal this will take in a tangent space normal and output a world space normal this is front face node returns true if rendering on the front face use a branch node to switch between 1 and negative 1 depending on the value the transform node cannot be modified to use a different normal vector so we must recreate it construct a tangent to world matrix using the modified normal and multiply it with the tangent space normal and normalize again for more information on tanted space normal mapping and the math behind it check out the links in the video description in the main graph route your normal map sample through this subgraph instead of the transform node in the scene lighting should look correct on both sides but you might have to turn off shadows to tell unfortunately this shadow problem is difficult to fix within the shader graph since we cannot modify the shadow caster pass try adjusting the shadow bias settings in the light or urp settings asset if this issue turns out to be a deal breaker for you there is a solution in the tech shader version of this video one of the tricky things about foliage is that it should be translucent light filters through leaves changing the way lighting and shadows affect them short of ray tracing we can't have real translucency but we can add several techniques to approximate it the first is a method using what i call shape normals shape normals are separate normal vectors used exclusively for diffuse lighting as opposed to regular mesh normals that we know and love which describe a face's orientation on the mesh shape normals follow the overall shape of a plant for instance grass's shape normals should follow the terrain while a trees should point outwards from a central location following the shape of a sphere or a cone using shape normals for diffuse lighting hides the fact that a lot of foliage is made up of a bunch of flat planes you might be tempted to use shape normals for all lighting calculations regular normals which i'll refer to as mesh normals from now on are still useful for specular lighting and another technique that we'll get to later the best thing to do is keep track of both i'll cover strategies to do this soon but first let's prepare the lighting algorithm open foliagelighting.hlsl in the data structure rename the current normal world space to mesh normal world space this will hold the regular mesh normals add a new float3 field shape normal world space go through the file renaming the normal world space to the correct variable use the mesh normal in the reflection and fresnel calculations shape normal in the diffuse formula mesh normal in the specular dot product shape and mesh normals in the diffuse and specular node previews and shape normal to mix global illumination in the shader graph wrapper function calculate foliage lighting underscore float split the normal argument into mesh normal and shape normal set them in a data structure then pass shape normal to the output sh and sample gi macros in the foliage lighting subgraph update the custom function to match the new wrapper function add a new shape normal property to the graph and route it into the custom function in test foliage let's temporarily route the double-sided normal into the shape normal return to your scene things should be clear of errors but everything will look the same now to calculate the shape normals for each of our example meshes for the sphere which models a low poly tree or like a hedge the shape normal is equal to the mesh normal which is how things are at the moment for the grass billboard we want the shape normal to point upwards from the terrain so we need to find a vector which points upwards along the grass card this corresponds to the meshes by tangent vector here i've modified the shader to output by tangents as color and we can see that it's green here pointing straight up to support both shape normal options in the same shader we can use an enumeration keyword these allow us to change code depending on an option in the material in the blackboard add a new enum keyword called shape normal the option is under the new property menu set the definition to shader feature the scope to local and the exposed setting to checked add an entry for mesh normal and by tangent we'll now be able to selective active entry in the material inspector if you drag this keyword property onto the graph unity will create a branch node for you it passes through the value connected to the currently selected keyword entry route a normal vector node and a by tangent vector node into the correct fields and then into the shape normal field of the lighting node in the scene editor select the by tangent option for your grass material that looks better already moving on to the tree we'd like to approximate a spherical shape there's not really a corresponding mesh value for this so the best and most flexible option is to pre-calculate shape normals in a c-sharp script and store them in the measures vertex color data create a c-sharp script called foliage shader support dot cs inside add a list of transformed called normal foci this script finds the closest focus to each vertex and calculates a vector pointing from the focus to the vertex this will be the shape normal store the array of vertex positions from the mesh data and create a vertex color array convert all positions to world space this is safe to do since the tree mesh is static loop through each vertex normalizing the vector pointing from the nearest focus to the vertex store it in the color array and then store the color array in the mesh finally call the run function in the awake method back in the scene editor add this script to your mesh game object create another game object for the normal focus and position it near the center of your tree then set it in a script to test things out create a vertex color node in your test foliage graph and route it directly into the base color field press play and you should see colors on the tree mesh looking at it through the scene view the color should roughly correspond to the little xyz compass in the corner back in test foliage remove the vertex color node and route the lighting back into the base color using another keyword entry add the option to use vertex colors as shape normals back in the scene editor change the tree material to use vertex colors and then press play and check it out if things look a little strange you can try adjusting the position of the normal focus adding more foci or even just more vertices to the mesh shape normals create smooth more realistic lighting but the meshes still look opaque if you view a light through a foliage card it should glow a little we can simulate this translucency effect using a simple subsurface scattering lighting algorithm in my lighting formula explorer program which you can check out from the video description i created a diagram detailing the algorithm the effect is strongest when the view direction and light direction are opposite which we can calculate mathematically by taking the dot product of the view direction and the negative light direction this works well but can make materials look a little flat in reality when light exits a transparent material it changes direction slightly towards the surface's normal vector by adding this normal scaled by a scattering coefficient to the negative light direction we can simulate this effect let's ignore scattering for now and add a simple translucency to the lighting algorithm in foliage lighting.hlsl add a float3 subsurface color variable a tint applied to any light filtering through the mesh and a float thinness variable the translucent effects strength in the wrapper function add arguments for these new fields and then set them in the data structure in foliage light handling set the translucency light radiance which is just the regular radiance tinted by the subsurface color calculate the translucency dot product from the negative light direction in the view direction similarly to specular highlights tighten the dot product with a smoothness power and multiply by thinness finally multiply the albedo translucency radiance and translucency strength together to add it to the final color in the foliage lighting subgraph add a new subsurface color and a thinness custom function argument [Music] and a graph property for each in test foliage add subsurface color and thin this property as well in the scene editor set your main light to point straight in the z direction and then take a look at it through any of your meshes you may or may not see a glow depending on how your shadows are set up the problem is that the object's cast shadows will attenuate translucency lighting as well the best solution is just to remove shadow attenuation from the translucency radiance formula this can cause translucency to shine in some places that it shouldn't but in my experience it's not very noticeable the final call is yours let's return to scattering and add that to our algorithm in foliagelining.hlsl add a new float field to the data structure and our corresponding argument in the wrapper function then in foliage light handling calculate the scattered light direction by adding the scaled mesh normal to the negative light direction normalize it and then use it in the dot product formula update your custom function and add properties in both graphs then in the scene editor check out your materials i find that it really adds a lot of depth so that takes care of most translucent effects but it would be nice to approximate translucency from indirect lights too since we can't change unity's lightmapper and again we don't have access to ray tracing the best way to do this is to add ambient subsurface lighting strength which gives foliage a constant glow in foliagelighting.hlsl add a new float field to the data structure and a corresponding argument in the wrapper function in foliage global illumination add a subsurface term to the indirect diffuse formula multiplying the albedo subsurface color thinness and ambient strength together then update the foliage lighting custom function and add a property for ambient subsurface strength to both graphs take a look in the scene editor i found that a little bit goes a long way so far all of these various lighting properties are constant over the mesh it would be better to be able to read them from a texture similarly to a smoothness or an occlusion map however there are so many properties that putting each and individual textures would slow down the shader quite a bit we can combine four of them into one data texture storing each in a color channel you can choose to vary any four properties and organize them in any channel order however for this tutorial i'll use the following layout red contains smoothness blue contains specular highlight strength green contains translucency thinness and alpha contains the ambient occlusion the lighting algorithm is almost ready to handle this except that there is no specular highlight strength i feel that this is pretty useful and prevents highlights in foliage creases so let's add it really quick while we're in there let's also add a multiplier to fade the ambient reflection rim effect which might not be very prominent on foliage in foliage lighting.hlsl in the data structure add a specular strength field and an environment reflection strength field then add variables for both in the custom function wrapper in foliage global illumination multiply the indirect specular result by the environmental reflection strength as well as the specular strength then in foliage light handling multiply the specular value by the specular strength update the foliage lighting subgraph with the new custom function arguments and add the corresponding properties add properties to test foliage as well [Music] and then see how it affects all your materials with that we're ready for the data map in the test foliage graph add a new data map texture2d field and then sample it i like to keep around all the individual properties to use as multipliers for each channel but you can delete them for more minimal materials route and arrange everything and then be sure to double check that everything is hooked up correctly head into your texture creation program of choice and construct a simple test data texture draw a different pattern into each channel preferably with soft gradients so you can test a variety of values back in unity turn off srgb and alpha is transparency in the texture importer and then place this new texture in your materials now it's really easy to see how each value affects the lighting and we're all set to create intricate plant textures with that we're done with the lighting for the foliage shader however it's still too stiff to feel like real plant matter besides translucency plant's tendency to move with the wind is another big challenge we can animate meshes in the shader very efficiently by passing new vertex positions to the master stack this effect is perfect for wind create a subgraph called wind deformation this will take in a vertex position and output a new deformed vertex position add a vector3 wind direction property as well as a float strength property multiply the strength by the direction and then add it to the position outputting the result we can vary the wind strength over time and space using a noise texture add a texture 2d noise texture property and sample it using a sample lod node do note that this must be a sample lod node since we're going to use this in the vertex shader stage next we need to calculate a uv world position should influence it so use a swizzle node to select the x and z coordinates out of the vertex position and then multiply it with a new noise scale property similarly multiply time by a noise frequency property the output of the texture sample ranges from 0 to 1 but that would weight the deformation on the one side of the model use a remap node to change it to range from negative 1 to 1 and then use a split node to grab the red channel multiply it with the wind strength [Music] plants won't sway in a straight line due to wind turbulence to simulate that effect add a float turbulent strength property and a cross wind direction property this cross direction will be perpendicular to the main direction multiply the turbulent strength by the wind strength so it's all proportional and then by the noise g channel and finally the cross wind direction [Music] add the offsets together and then add in the position in the test foliage graph add a new wind deformation node create properties for wind direction strength turbulence noise texture noise scale and noise frequency route all these into the wind deformation node [Music] as well as vertex position in world space for the cross direction we need something perpendicular to the wind direction but it still needs to look natural meaning it shouldn't point into the ground for example the cross product of wind direction and the shape normal will do the trick finally route the deformed position through a world to object transform node and into the position field of the vertex stage master stack open your image processor of choice and create a noise texture make sure that it has independent noise in the red and green channels you can use any shape that you want but i like the look of simple cloud or purlin noise in unity make sure that the texture importer has srgb turned off you can also safely turn off mipmaps as well as compression if the wind motion ever looks jittery to you set up all your materials and now we have wind but i can see several problems first off wind causes the grass to detach from the ground we need a way to scale wind strength over the mesh so that it's weaker near the bottom of the grass quad luckily the uv's y coordinate follow exactly the pattern that we need first in the wind deformation sub graph add a float dampening property and multiply it with the final offset before adding it to the vertex position in the main test foliage graph we need to route the uv y coordinate into this new field but only for grass otherwise the damping should always equal one another keyword will do the trick here add any num keyword called wind strength with all the same settings as before create two entries constant and uv-y drag it out onto the graph to create a choice node and connect a float node with a value of 1 and a constant in a uv node with a split node to grab the y coordinate into uv y brought the output into the dampening field in the scene editor change wind strengthy num on your grass material to uv.y that looks much better okay so let's take a look at the tree here i can see two problems first imagine if a leaf texture was assigned to this material the wind kind of causes it to stretch and distort in unnatural ways to fix this we should use the same position for all vertices when sampling the wind noise this way they will all move uniformly second parts of the mesh attached to the trunk shouldn't move as much imagine the center vertex is anchored in this way we can fix both of these problems by storing custom wind noise positions and dampening values in the mesh vertex data meshes can store a bunch of independent uv coordinates and the shader graph can access four of them the last set referred to as uv3 or text core 3 is usually unused so we can use our c-sharp script to store this wind data inside of it open foliage shader support.cs i'll go over this code quickly since it's kind of tangential to the foliage shader feel free to just download the file from the video description our goal here is to set the wind noise positions to a constant position the center of the mesh's bounds will work just fine and also set the wind dampening values to a value proportional to the distance from the closest focus noise anchors are stored in the xyz channel of the texcord3 vector while the dampling will be in the w channel set the array in the mesh's uv stream and it's ready to be read by a new shader in the wind deformation sub graph add a new vector3 noise position property use that instead of the vertex position when calculating the noise uv in test foliage add another enum keyword called wind anchor add two entries position and textcord3 drag the keyword onto the graph and route a position node in world space and a uv node in uv three channel mode into the fields connect that into the noise position field of the wind deformation node to support pre-calculated wind dampening values add a third entry to the win strength enumeration keyword called tech score 3. use a split node to grab the w component from another uv node and uv 3 channel mode into this new option in the scene editor verify that your tree model has a script attached and that the tech score 3 index is 3. switch the tree material keyword modes to text core 3 and then enter play mode so that the script runs once that looks a lot better to me feel free to modify the c-sharp script as you need the data is highly dependent on each individual model but for now it's time to address the last problem if you look closely on any model but it's especially apparent on the sphere you'll notice that wind deformations don't affect specular lighting they should since wind deformations change the apparent normal of the mesh the normal vector doesn't automatically update to match the new vector positions so that we have to recalculate them as well as tangents it's difficult to do perfectly since a shader only knows the position of one vertex at a time but we can try to estimate it the strategy here is to run three points through a wind deformation algorithm the original position and the original position slightly offset in the tangent and by tangent directions we'll track how the definition algorithm affects them and then construct new tangent and by tangents by comparing them to the original value and thus a normal vector from their cross product we'll implement this formula in a new subgraph called wind deformation with orientation inside output a position normal and tangent vector and add properties for wind direction cross direction strength turbulence noise texture noise position noise scale and noise frequency as well as dampening create a wind deformation node and route all of the properties into it then duplicate it twice leaving everything attached route a world space position into the top wind deformation node the middle node will take the position nudged a small distance in the tangent direction [Music] the bottom node needs the position nudged along the by tangent direction but due to some quirks with the way the shader graph by tangent node works it's a lot safer to calculate the by tangent yourself route the normal and tangent through a cross product node and then normalize the result subtract the top node's output from both the other outputs and take their cross product the order of all these nodes is important to get the correct normal connect up the deformed position and normal outputs for the deform tangent normalize the first vector used in the cross product is guaranteed to be perpendicular to the normal in test foliage we place the wind deformation node with a wind deformation with orientation node route the normal and tangent vectors through a worlds to object transformation node in direction mode before connecting them to the master stack now in the scene editor you won't really notice any changes this is because in the last step we separated vertex position from the noise uv and this partially determines wind offsets in the wind deformation with orientation subgraph we need to add the tangent and by tangent offsets to these noise positions so that looks a lot better on the sphere but not so much on the tree and grass the specular shine seems almost random which is an artifact of the distance between the vertices skipping over and not reflecting changes in the wind noise adding more vertices would certainly help but perhaps the right solution is just to turn off normal recalculation when using extremely low poly meshes in the main graph add a boolean keyword called when deforms normals underscore on the on suffix is important to make sure that this keyword is editable in the material drag the property onto the graph twice connecting the deformed normal and tangents into the on fields route a normal and tangent node both in object space into the off fields and then connect these to the master stack in the scene editor turn off normal deformations for the grass and tree materials as i mentioned it's difficult to get normal deformation perfect without employing some very costly techniques if this is really important to your game you can continue refining the algorithm to match your model's needs perhaps have the algorithm depend solely on position or make the noise a function so that you can calculate exact normals and i think that about wraps it up for foliage in the shader graph you can endlessly tweak things to fit your specific model and speaking of that i'd love to see your trees grassy plains and video game gardens please feel free to send me any screenshots that you have for inspiration here are all the material settings that i used to create the scenes in this video this tree has a custom support script to set wind data which finds connected pieces of the mesh making sure that each chunk has the same wind noise position some effects currently not possible in the shader graph like screen space ambient occlusion really improve the final look of foliage i think you can add support pretty easily when 2021.2 releases or you can take a look at the text shader version of this tutorial which already supports it thanks so much for watching foliage is really fun to create i think and i've only really scratched the surface of foliage rendering here i have several more videos planned including one specifically on covering terrain with fields of grass please subscribe so you don't miss it and don't forget to check out core from the link in the video description to get started making your own game for free thanks again core for sponsoring this video and again i want 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 again so much i really do appreciate it if you'd like to download the project files from this video including any other tutorial i've created consider joining my patreon but if not that'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: 484
Rating: 5 out of 5
Keywords: gamedev, game development, development, unity, unity3d, madewithunity, programming, game design, csharp, nedmakesgames, indiedev, indie game, dev log, shaders, 3d modeling, blender, tutorial, walkthrough, shader, universal render pipeline, urp, unitytip, unitytips, shading, graph, shader graph, custom, lighting, lights, diffuse, specular, baked lights, lightmap, main light, point light, spot light, 2020.3, 2021.1, foliage, grass, tree, flower, leaf, leaves, vegetation, plant, translucent, translucency, wind, windy
Id: x4ufs1OzPIw
Channel Id: undefined
Length: 42min 39sec (2559 seconds)
Published: Fri Oct 15 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.