Getting Started with Compute Shaders in Unity

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] there's a number of times during development i found myself working on some code and getting stuck in the weeds trying to improve performance with a loop that's been taking just a tad bit too long if you're working in the simulation or procedural generation genre you're probably familiar with the long waiting times or your algorithm runs and the results get processed the funny thing is what if i told you that you've accidentally been inflicting that pain on yourself and rather than having to deal with stripping back your complex pathfinding ai or your terrain generation algorithm that takes minutes on end to calculate you could actually speed it up significantly in just a few lines of code by offloading that complex algorithm of yours from that old dusty cpu you're so familiar with and partnering it up with that shiny new clever little gpu everyone's been talking about hi there i'm matt and welcome to gamedev guide in this video we're going to explore how to get started with compute shaders and compute buffers in unity two phrases that i'm sure are very intimidating to many of you watching my own perception of them certainly changed rather rapidly a few weeks ago as i began learning about them and i've realized they're actually a lot less daunting than you may initially think hopefully by the end of this video you'll learn a few things about them and they'll be a little bit more approachable to you so first let's break things down and explain the concept of a compute shader essentially the purpose of a compute shader is to offload some of the processing from your cpu onto your gpu and so they're written in high level shader language or hrsl in unity hence the name compute shader due to being able to run chunks of code in parallel they can be incredibly useful for processing larger algorithms and in many cases handle specific rendering tasks much faster than c-sharp can while not exclusive to compute shaders compute buffers are essentially chunks of data that the compute shader can process they're not actually required for compute shaders to work at all but they're how you'll read and write data from the shader if you want to use a compute shader for computation rather than just rendering now before we get stuck in it's worth mentioning that prior knowledge of writing complex hlsl shaders isn't entirely necessary for you to understand this video but a grasp on the basics of programming in hlsl versus c-sharp is definitely going to help you understand this a lot better if you're brand new to shaders entirely i've posted some resources down in the description that you might find useful in getting started if you're anything like me one of the biggest grievances you have is the lack of support for intellisense and formatting when writing shader code in visual studio i made the switch to vs code when i discovered a plugin that makes writing hlsl code much much easier it's not perfect but seeing as the alternative is just walls of white text and a recurring loop of typos in visual studio i cannot recommend it enough with all that out the way let's finally dive into creating our first compute shader so as i've already mentioned compute shaders can be used for a variety of things and we'll get into a couple of examples of those in a few minutes for now though let's just start with something simple to get to grips with how they work let's create a new compute shader and let's just call it compute shader example or something similar if we open this up we can see that unity generates a basic template for us and you may notice that it's already very different to a standard fragment vertex shader the way i like to think about this is that a compute shader is structured more like an extension of a c-sharp script just written in shader language instead the cs main method here is the main function for our compute shader and it's the code that we will run when we dispatch it from c sharp the line at the top here dictates the kernel or program to compile so that it can be called we can have multiple different kernels or methods in our compute shader and define them at the top here to be dispatched by our c sharp code for now though we'll just stick to this cs main function unity has automatically generated a 2d texture for us as our render textures target the rw specifies that the shader will both read and write to it as a variable the num threads attribute indicates to the gpu the dimensions of the thread groups used by our compute shader keep in mind that compute shaders work by running code in parallel on the gpu so this attribute indicates how the processing should be spread i haven't quite got my head around figuring these out yet or if any combination is better than the other essentially i guess it just depends on the target hardware to how many threads can be used at once and what their maximum size can be but by default unity assigns a group of 64 threads in an 8x8 size for some reason so let's just stick with that for now as we can see the code itself is assigning a color based on the current thread index of each group we'll come back to this in a bit because compute shaders don't have a specific target like a mesh or anything we need to tell them how when and what to run so let's head back into unity and actually get our compute shader well computing let's create a new c-sharp class called compute shader test here let's create a public reference to our compute shader and assign it in the inspector in our script let's also create a dynamic render texture for our compute shader to use as its target we need to enable the random write property so that it can be used by the shader then let's assign our render texture to our shader using the set texture method the first parameter here is our kernel index each kernel in the shader is assigned an index seeing as we only have this single method we know it's index 0 but if you're using multiple kernels you can use the find kernel method on the shader to get its index by name with our texture set we can now execute the shader and get it to draw to our texture by calling the dispatch method here we tell the shader which kernel to dispatch and how many thread groups to use we calculate this by dividing our image size by our thread size of each group now if we press play in unity it will create a render texture and our compute shader will write to it if we hold ctrl and click on the property here we can see a preview of our generated texture pretty neat let's move the script onto our camera and use the on render image function to get the render texture drawing on our screen so that we can play with it a bit okay so now we have our compute shader rendering directly to the camera let's mess with the output a bit and replace the default image let's get our shader to set the current pixel color based on its position across our screen now we know that our current resolution is 256 by 256 but let's suppose we're working with a dynamic resolution i'm going to be using a square texture so at the top of our shader let's create a new float called resolution and then in our script let's assign this based on our current render textures resolution in our shader let's create two floats called x and y and assign them based on the current thread id for each dimension divided by the resolution of our texture when we play our scene we now have an image generated by our compute shader coloring the screen based on the current pixel position calculated in parallel on our gpu obviously this isn't that impressive on its own while it might be somewhat faster this is pretty easily achievable in a standard vertex fragment shader but you can hopefully see how compute shaders work and how we get them to execute the true power of compute shaders come from the use of compute buffers to help with large computations that may be slow to process on the cpu let's explore an example of something that's slow to do on the cpu but can be sped up by offloading to the gpu since there's a little bit of overhead passing data from the cpu to gpu compute shaders tend to work best when dealing with large algorithms so they're especially great for processing things you need to repeat at scale such as simulation data or procedurally generating images in this example i have a bunch of blank cubes i've written a simple simulation algorithm so that when i hit the randomize button here the cubes randomly move and change their color now when we run this once this noise generation process is a breeze but let's imagine we need this algorithm to run multiple times as i increase the repetitions here it takes longer for our code to run and our updated frame to render once we get up to 1000 repetition mark we start to see our poor cpu really struggle let's see how much faster we can perform this if we upload it to our gpu instead with the help of a compute buffer let's create a new compute shader called random shader then we'll remove some of this template stuff from unity and add a new struct at the top called cube in here we'll create a float three called position and then we'll also create a float4 for the color of our cube then let's add an rw structured buffer for this struct called cubes this will be the data that will pass to and from our compute shader in our main function let's set the number of threads as a single dimension to keep things simple for now let's just retrieve the cube from our buffer and return a color of red next let's set up our data in unity to pass to our compute buffer let's start by creating the data for our buffer to use the neat thing about compute buffers is that you can pass matching structs in as data so let's create a struct for our cube and create an array in our class for our cube data then we'll make sure to populate our data array when we first generate our cubes now we're ready to do some computation next let's create a new random compute method to act as an alternative to our current cpu randomization method here we'll create a new compute buffer called cube's buffer the compute buffer needs to know the size of the data and the size in bytes of the struct this can be calculated using the size of method in c sharp and then multiplying out the size of our vector3 and color then we just set our array into the buffer using the set data method the final thing to do is pass this buffer into our compute shader and dispatch it [Music] let's also add a button to our gui to trigger this method now when we press play nothing happens and that's because we need to read the data back from our compute buffer after dispatching our shader let's also dispose the buffer after we're done with it and now we should have a lovely red wall of cubes whenever we press our button so now we're ready to perform our randomization we need to translate the random color generation and random z position functionality from c sharp into our shader so we're going to need to write our own method for generating a random number in shader language fortunately this is a solved problem thanks to lovely strangers on the internet here is a pseudorandom number method that takes an xy input and generates a float feel free to play around with the values in here to get the desired results armed with that let's generate a random z position for our cube by passing our x position and the cube's current z position into our random function and then apply this back to our cube then we'll do the same thing with the color of our cube by passing in different dimensions from its existing color into the random function and now when we press our randomize button our compute shader should generate in the same way our cpu does the final thing we need to do is add in our repetition factor so let's add this to our compute shader and pass it in from c-sharp [Music] as you can see there is significant difference in speed between the cpu version and the gpu version of this code and as we increase the repetitions even further you can see just how great the delta is between the two methods [Music] while this is a somewhat trivial example this should still give you an idea of how useful compute shaders can be for performing complex operations more quickly for instance i'm sure you can imagine how useful this can be to speed up ai calculations in a simulation game with lots of agents or for generating 3d noise textures once you start dealing with computations in three dimensions the cpu really starts to struggle and compute shaders can make this kind of thing much more pleasurable to work with and achieve real-time results hopefully you can now see some of the potential of and benefits from using a compute shader in your project depending on your use case you may be able to significantly improve your rendering performance and optimize some of your complex processes by offloading the work to the gpu now i have absolutely just been scratching the surface of compute shaders in this video and i'm sure many of you watching have been using them for a while but for those of you that are new to the topic hopefully this has acted as a nice overview and helps you get to grips with understanding them a little better if however you want to learn more i've posted a number of links below that should help study them further this comprehensive three-part guide from david curie shows how we can use compute shaders to build a path tracing renderer inside of unity while the results don't render in real time on my gpu it's still pretty impressive after just a few seconds of processing the content of that tutorial is a bit too lengthy to cover in a specific video but i highly recommend giving it a go yourself if you want to get more familiar with things you can do in compute shaders and that's about it for the video just before i go though i want to say a huge thank you to all of you who have been watching the channel sharing their thoughts in the comments and hanging out with us in the discord server i'm sure many of you will agree that this past year has been a wild one for most of us but for me the growth of this channel and its continuing success has been one of the most reassuring parts of an otherwise turbulent and isolating year the channel broke 50 000 subscribers a few months ago and some videos from this year are already close to reaching 200 000 views not only do i struggle to think that there's that many people out in the world who find my ramblings helpful but i frankly couldn't even begin to comprehend that this channel would grow so quickly when i first set it up last year so whether you're new to the channel or a returning subscriber i sincerely mean this to every single one of you when i say thank you [Music] you
Info
Channel: Game Dev Guide
Views: 77,176
Rating: undefined out of 5
Keywords: unity, learn, games, gamedev, how to, making games in unity, tutorial, unity tutorial, learning unity, unity3d tutorial, programming, making games
Id: BrZ4pWwkpto
Channel Id: undefined
Length: 14min 2sec (842 seconds)
Published: Thu Dec 31 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.