Grass Fields in Unity URP! Generate Blades with Compute Shaders! ✔️ 2020.3 | Game Dev Tutorial

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi i'm ned and i make games today i'm returning to grass rendering by showcasing another style of grass made using unity's universal render pipeline this technique uses a compute shader to model little blades of grass which bend and wave with the wind i recommend you watch my video on compute shaders to make sure you're up to speed i'll put a link in the corner and the video description this is also the fourth in a series on grass rendering you don't have to watch the previous videos but they might give you some insight into the process i made this project using unity 2020.1.16 f1 and universal render pipeline 8.3.1 if you're using a different version check the comments for updates to get started enable the universal render pipeline by downloading the package creating a settings asset and enabling it in your project then open a 3d modeling program and create a flat plane to generate grass on a blade will sprout from the center of each triangle in the mesh you could use unity's default plane but the grass looks better on a mesh that's more dense once you're done create a new compute shader in unity called grass blades compute then download the nmg grassblade's compute helpers.hlsl file from the video description it contains some math and helper functions we'll be using you'll have to create the hlsl file in your operating system open the compute shader let's start out by drawing a green triangle on top of each triangle in the source mesh define a kernel called main and include the helper function file now let's take a look at the data structures they should be familiar from the compute shader video from the source mesh we only need the object's base position these two buffers contain the vertices and triangles of the source mesh arranged as triplets of indices into the vertex array this draw vertex struct defines the output vertices they contain the position in world space as well as height used to interpolate the blade color the draw triangle will contain three points as well as a normal to use for lighting this normal will actually point parallel to the grass blade since grass is semi-translucent using this normal for lighting gives a more natural look for the final product finally create an append buffer to use as a stack for the output triangles as opposed to the previous grasp renderer this time we'll use the indirect arguments buffer in this shader as a refresher the graphics engine reads the values inside this buffer to know how many vertices are in the computed mesh to draw we'll count vertices manually in the kernel function allowing us to skip the triangle count to vertex count compute shader used in the last video the argument buffer must be read write enabled so we can modify it in the shader next we'll receive some variables from the c-sharp renderer script including the number of source triangles and a local-to-world transformation matrix let's move on to the kernel function set the number of threads to 128 1 1. again we'll use the x component of the thread id to process the source triangles inside the function return of the id is larger than the number of source triangles now we need to compute the center of the triangle and a few other vectors let's define a function to help position our blade in the world using the three triangle corners it will find the center point and the normal of the triangle first convert each vertex position to world space then calculate the center position from that this next function will calculate a triangle's normal as well as something called a tangent space to world space transformation matrix you may be wondering what is tangent space it's a coordinate system based around a surface in tangent space the z coordinate points along the surface's normal then the x coordinate points parallel or tangent to the surface when working with uvs the x component usually points in the same direction as the u coordinate since we're working with a triangle let's have a point from the first to the second triangle corner finally the y coordinate points perpendicular to both the x and z vectors along a direction called the by tangent now that we have defined a tangent basis we can construct a matrix to convert from tangent space to world space all we have to do is multiply a point with a matrix to do that this will be very useful it's simpler to construct our grass blade in tangent space for instance the corners of the grass triangle will be at negative one-half zero zero one-half zero zero and zero zero one so back to the script this function will calculate all of that for us it's in the helper function file if you're curious to see how it works returning to the kernel call this new function passing in the triangle corners then create an array of draw vertex to hold the points of the draw triangle now we can call the setup blade point function passing it the blade's anchor point the tangent to world matrix and the uv of each vertex inside we can easily convert the uv to tangent space and then convert that to world space also set a height again for color blending then construct the triangle from the points array and append it to the buffer don't forget to set the lighting normal now we need to add 3 to the vertex count in the indirect arguments buffer we can't just use the normal addition operator since this kernel function is multi-threaded doing so would introduce some race conditions leading to unpredictable errors this interlocked add function make sure that only one thread at a time adds to the buffer now return to unity and create the graphic shader an unlit shader called grass blades also create a grassblades.hlsl file and an nmg grassblade's graphicshelpers.hlsl file this file is also found in the video description once again you'll have to create the hlsl files in your operating system in the shader file rename the shader and remove the default properties replace them with a base color and a tip color in the sub shader tags add universal pipeline in the pass remove the boilerplate code add in the light mode tag and turn off culling these grass blades will not have any thickness and they'll need to be visible from both sides then add the platform requirement pragmas the light and shadow keyword pragmas the function registry pragmas the hash include grassblades.hlsl file ok open that hlsl file at the top add a hash shift block to stop the file from being loaded twice and then add a couple of include lines to import the helper functions and the urp lighting functions for the data structs you can copy and paste the draw vertex and triangle from the compute shader the append buffer becomes a structured buffer here so we can read it like an array next is a fairly standard vertex output structure with a single uv for blending colors a position normal and clip space position then define variables for the material colors the rest of the shader is also pretty simple the vertex function needs to get the triangle and vertex that correspond to the vertex id passed to it then set the position normal uv and clip space position in the output structure the fragment function is very standard it calculates data for the lighting algorithm figures out the albedo by blending the base and tip colors with the uv and calls the urp lighting function to calculate the final color return to the scene editor and create a material for your shader there's still one more script to write until we can see anything create a c-sharp script called the procedural grass renderer open that up add serialized fields for the source mesh compute buffer and material then create a structure for the source mesh data only containing a position define several instance variables including an initialized state variable the four buffers source vertex source triangle draw and indirect argument the id of the grass kernel the grass kernel dispatch size and the bounds of the mesh and local space next calculate the strides for each compute buffer the draw buffer contains three floats on the triangle for the normal vector plus four floats per vertex for position and height now this array will hold the initialized data for the indirect buffer we have to reset the buffer to this at the beginning of each frame in on enable add some assertions to make sure settings aren't null before the script runs then make sure things are cleaned up if the script hasn't been initialized gather data from the source mesh and compile the data to upload to the vertex and triangle buffers then calculate the number of source triangles initialize the various buffers make sure the draw buffer is an append type so the compute shader can add to it on the fly for each source triangle we'll output one draw triangle so the length of the draw buffer is the source triangle count for now anyway now get the id of the grass kernel set some variables on the compute shader and the material then calculate the dispatch size so it will cover all source triangles and make a rough estimation of the local bounds in the on disable function release all buffers in late update reset the system if it's in edit mode then clear the draw buffer reset the indirect arguments buffer so the compute shader can count vertices starting from zero then transform the bounds to global space upload the local to world matrix dispatch the grass kernel and call draw and direct procedural to trigger the graphic shader finally return to the scene editor and create a game object add the procedural grass renderer component to it and fill in all fields accordingly you might have to enter play mode first before you can see anything but there we go some green triangles there's a lot to do before they look like grass let's first make them more customizable and randomized open the compute shader file and add a few float properties height height variance width width variance and max bend angle the variance fields describe the amount each dimension will vary between blades while the max bin angle describes the amount the grass blades could lean forward as if bending under their weight write this get blade dimensions function to add in height and width variance it generates two random numbers based on world position and then adjusts the width and height with them so all blades don't face the same way i want to rotate each around the tangent z axis by a random angle we can do this by calculating another transformation matrix this angle axis 3 by 3 function generates a rotation matrix that rotates around the given axis by the given angle pass it an up vector and a random angle to create the matrix we need now to handle the bend we want a similar matrix to rotate the blade towards the ground or along the tangent space x-axis the angle should be random too but bounded by the max bend property multiplied by 90 degrees apply these rotations by multiplying them with a tangent to world rotation remember matrix rotations are applied in reverse order of multiplication since the twist and bend rotations are in tangent space they must apply before converting to world space accordingly they must appear last in the multiplication chain pass the new dimensions and transform to set up blade point don't forget to update the argument list and multiply the offsets by the dimensions in the renderer script create a graph settings serializable object and add variables for max bend angle height width and height and width variance add a serialized field for the setting object in on enable pass those settings to the shader then multiply the bounds by the maximum size of a blade return to unity and test it out it's looking better already the next step is to add curvature and detail to the blades by adding more triangles to each blade i'll do this by breaking the blade into multiple segments with an edge between them in the compute shader we need to set a maximum number of supported segments and the corresponding maximum number of points there are two points per segment plus the tip point so this formula calculates the number of points correctly this line also defines the length of the point array in the kernel function next add a couple of variables to set the number of segments to generate as well as a curvature variable to fine-tune the shape in the kernel function bound the number of segments by the maximum supported amount let's generate the points in a loop now creating the bottom edge of each segment since these segments are just subdividing the grass triangle we created before we can calculate the uv of these points linearly the v coordinate climbs the blade as we progress in the loop but it never reaches zero while the u coordinate converges on one half since we want the blade to have a natural looking curve we need to increase the bend angle as we progress up the blade to do that we can modify the transform matrix based on the height write this function to do it pass in the twist and tangent to world matrices since they are constant per blade as well as the bend angle then in the function calculate the bend matrix to have the blade bend more towards the top use a power function with the curvature property multiply the various transformations together just like before now we have everything needed to create the bottom two vertices of this segment pass the position dimensions transformation matrix and uv to the setup function note the u of the second blade is subtracted from one creating two symmetrical points now we need to calculate the tip vertex call the transform function with a height of 1 and then call setup bladepoint adding the result to the list so we have a list of vertices but we need to transform that into triangles let's first count the number of triangles one in the tip segment and then two in each other segment notice how we've laid out the points in the array we can create the triangles from every three consecutive points it's as simple as writing a loop to iterate through the point array creating each triangle structure and appending it to the buffer don't forget to adjust the vertex count to account for more triangles here in the c sharp renderer add grass settings for the max segments and blade curvature in on enable ensure the max segments is always at least one and then calculate how many triangles are in each blade just like we did in the compute shader now update the draw buffer size according to this new number of triangles and then pass the new settings to the shader back in unity test out the curvature and see how it affects your grass it's looking pretty good now let's breathe life into it with wind just like in the other grass renderers i'll simulate wind using a noise texture again we'll need the red and green channels so make sure to generate independent noise in each in the texture importer set srgb to off and turn off compression then open the compute shader thanks to the core.hls file included here we can reference textures just like we would in a graphic shader do that and then add variables for texture scale wind frequency wind position multiplier and wind offset amplitude we'll implement wind using another rotation matrix we'll have the noise texture generate the rotation axis but first we need the texture uvs it should vary by the x and z world position and time scaling each by their respective properties and then adding them together apply the wind texture scale next sample the texture with this uv and then use the red and green channels as the x and z coordinates of a world space vector taking the cross product of that vector with the grass as normal will produce a rotation axis that's consistent across blades but also perpendicular to the blade's growth direction in the kernel function call getwind axis before the loop points closer to the tip should feel the wind more so we'll pass the wind axis to the figure transformation for height function in both instances it appears compute the wind direction matrix using the wind axis and the wind amplitude angle scaled by the height on the blade since the wind axis is in world space we need to apply it when the rotation is also in world space in other words it must be applied after the tangent to world rotation remember rotations are applied in the opposite order of multiplication so the wind matrix should appear first in the chain okay we're done here for now so open the c sharp renderer add settings for each wind variable and in on enable pass them all to the grass compute shader we also need to manually set the underscore time variable since unity does not automatically do so for compute shaders update it and late update to maintain consistency put the time value in the y component return to the scene editor and define the win settings you may need to enter play mode to see any movement alright that's looking good i think we should make a field of grass with this stuff unfortunately if you duplicate the renderer you encounter the problem with game objects sharing a compute shader the second renderer overwrites the compute buffer in the first preventing it from rendering to fix this open the c-sharp renderer script and add fields to hold instantiated copies of the compute shader and material in on enable instantiate each and replace all references with the instantiated versions in on disable destroy the copied shaders in late update update the references to refer to the copies now returning to unity you can see a lot more grass now if you tried to test this with a bunch of different grass renderers you'd quickly kill your fps one way to help with this is with lod or level of detail calculations this technique simplifies blades that are far from the camera outside of the player's notice we'll do this by decreasing the number of blade segments for far away blades open the compute shader and create variables for the lod settings to organize things i'll put them all into a single vector where the x component is the smallest distance from the camera where lod will start to take effect the y component is the distance from the camera where blades will be their most simple and the z component is a curvature factor affecting the transition between these two states create a get num segments function which takes in world space position we'll calculate the number of segments to generate here calculate the distance from the camera then unler that to receive one at the minimum distance and zero at the maximum apply the curvature factor and multiply with the maximum number of segments clamp this number so we get sensible results then return in the kernel function call get num segments here in the c sharp renderer add fields for the lod settings in the graph settings variable and pass them to the compute shader in on enable then in late update pass the shader the position of the camera return to unity and fine tune these lod settings to create a large field turn your grass into a prefab and copy it all over the place if you get the settings right you'll never be able to tell where the lod takes effect and that's it you've created some remarkable looking grass if i say so myself now we've created three different styles of grass using a variety of rendering techniques so you'd think we'd be done with this series but rendering grass is a very deep rabbit hole to dive into and i'm afraid we've only just scratched the surface in a future video i'll detail how to make your grass react to creatures trampling it so please subscribe and turn on notifications so you won't miss it thanks so much for watching i'd really appreciate it if you could like this video it lets youtube know to recommend it and it really helps out the channel of course please leave a comment if you have any questions i have one for you how do you plan to use grass in your project do you have a topic you'd like to see me cover thanks again for watching and make games you
Info
Channel: Ned Makes Games
Views: 28,113
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, compute, compute shader, geometry, geometry shader, grass, grass shader, level of detail, lod, wind, procedural generation, procedurally generated, procedural, grass blades
Id: DeATXF4Szqo
Channel Id: undefined
Length: 22min 9sec (1329 seconds)
Published: Wed Dec 09 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.