Unreal 5.3 - Making a volumetric ray marching shader from scratch (part 2)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
H Amigos welcome to this new video today we're going to continue working on our volumetric Ray marching material right where we left last time starting with implementing self shadowing from directional lights we are going to also link some of these material properties to seene parameters such as light Direction color and Sky luminance so we don't have to tweak each material instance separately if we want all of our cloths to be lit by the same lights in addition to directional illumination we're going to also incorporate a little bit of simulated ambient lighting coming from the sky dome and finally support for distance filled soft Shadows so our meshes will cast Shadows on our volumes as you can see today we have a lot of ground to cover so let's get started with a bit of an explanation of how we're going to achieve all of this here we have a slice of our volume which for this example is going to be this pretty little Cloud as a quick reminder we are sampling points along the camera race accumulating the density that we find in the volume now to illustrate the lighting or the way that we will simulate the lighting let's picture a single ray of light coming right from this direction when this Ray hits the volume it will lose some of its energy due to scattering and the deeper that the ray goes into the volume the fainter it will get so we can imagine it at something like this and now if we look at this line we have something that looks like our density accumulation gradient but in Reverse when the density that we see from the camera is very high only a bit of light a little bit of light will make it through and vice versa so for rays uh Ray that went like this most of the light's energy would make it through so instead of density we're going to use the opposite value and call it transmittance for how much energy is transmitted through and for Simplicity we're going to simulate this as if the light that is lost just bounced back and was never seen again the more complex reality is that in clouds a lot of that light is scattered throughout the volume as the light bounces against the particles inside and now let's say that instead of a single Ray we have a directional light source like the sun with parallel rays each one of these positions where we were taking samples from is going to receive a different amount of Light which can look a little bit maybe like this and what we're going to do is for each one of these samples take a certain number of steps in the direction of the light or Ray marging in the direction of the light and check how much density each one of these Rays has to go through and that is a bit of a Brute Force approach but there are a couple of optimizations that we can use to reduce the number of Shadow samples to take first since we are taking a fixed number of steps to cover the potential maximum distance throughout the volume we would end up sampling in many cases points outside of it like if we started here after just a couple steps we end up outside of this pink box this means that we can do a bounding box intersection and just stop the shadow sampling if we get outside of it the second one is also pretty straightforward we don't have to take any Shadow steps if the density at a point is at zero or below a very low threshold because there won't be anything to shade in our example that means all of these sample points and finally if the transmittance Falls below another thres hole also very low none of the light will make it through which means that we can also stop taking Shadow steps in our case could be a point maybe around here and after we take let's say this number of steps or transmitters is zero well at that point we know that there's no light making it through this point which means that we can also stop and that covers directional lights now we won't do it for this tutorial so I won't go into all of the details but the steps for a point light are conceptually the same we're still going to trace from each one of our sample points in the direction of the light however Point lights introduce two new complexities to our algorithm the first one is that since the rays are not parallel but they come from The Source in all directions we need to recalculate our light Vector every time that we start the new a new Shadow step Loop so once we start Shadow stepping from this point we need this vector and once we start from this point we need this Vector the other difference is that point lights obviously have an attenuation radius and its intensity Fades naturally over that distance that means that again when we start tracing steps in the direction of the light not only we need to take into account the energy lost through the volume but also the energy that is naturally lost because of this attenuation radius and now without further Ado let's go back to Unreal and start making this happen let's open our material and this is how we left it at the end of the last video the core of this material is this Ray marching custom function which we'll modify in a moment but before we make any changes to this code I want to update the output and add a few inputs so for output currently is set to float three but the truth is we've been outputting a single float this accumulated density anyway we're going to change this to a float for and we'll see what y when I update the code next we'll need five new inputs so 1 2 3 4 five let's give this some names and some default values and that's it the first one obviously is going to be our light vector or the direction of this the directional light so let's call this light vector next we need to specify the amount of Shadow steps let's do that shadow steps and let's also add just as we did with the number of steps and the step size let's add the second parameter for that so Shadow step size next I want to control to have some control over the density of this Shadow so let's call this Shadow density and finally I want to have a parameter for the density and if you remember we have this density scale here that connects to a beer law a be law with a one minus at the end to Output as opacity but we are going to do this calculation inside of our function so we can remove this and change our parameter name to just density perfect now we can give this some default values so for density let's we can reconnect this one that we had here now for sadow density let's create a new scalar parameter and we can call this Shadow density maybe eight for now conect H here and then for shadow steps another scalar Shadow steps let's start with I know 16 and then do a divide so one divided by the shadow steps will give us our shadow step size so we can go Shadow step size here and then Shadow steps there now for light Vector for now let's create a new just new Vector parameter call this light vector and just connect it here we can give it some random value and see what happens and now we have our function is ready to be updated so let me create the code or write the code in a different notepad session and I'll be right back and here is the updated code as you can see adding shadow in adds quite a bit of complexity so let me go step by step and explain what all the new changes do we still have our accumulated density a float that starts at zero but now we have two new variables transmittance which starts at one and is a float and a float three that is called light energy which starts at zero or it will be 0 0 0 the next three lines are a small optimization and it's just moving these multiplications outside of the for Loop you see every time that we use our density we would do something like density time current sample time step size however this two values are constants so we don't have there's no reason to do that inside the for Loop this number of times or this number of times and the same applies to light vector and Shadow density except that in these two cases we are scaling times Shadow step size finally we need a threshold to compare and to know if the shadowing is so intense that there's no more light coming through that optimization I mentioned earlier and that's equal to minus the logarithm of Shadow thres hole divided by Shadow density and now you might be wondering what is this Shadow threshold and that's a variable or an input to the function that I forgot to add and we'll create once we go back to Unreal next we have our local camera Vector in this one line same as before and here we have again same as earlier our Ray marging steps now after taking our current sample in our regular marging we are going to compare that sample with a threshold very small 0.001 and that if ends all the way down here that means that we don't have to do any of these if we sample a position that is almost empty and doesn't have any density on it if it does so if this value is bigger than 01 first we copy the current position to this new float or float three called L post and this will be the position from we which we start our Ray marching towards the light next we have this float called Shadow disc which is just a shadow version of our accumulated density also starts at zero and here we have our four loop with our Ray Marching for Shadows so we're doing this for this number of times Shadow steps and the first thing that we do is update our position in the direction of the light next we are taking a sample at that new position and next we are checking or we are creating this value called exit shadow box which is basically a remapped version of this um position so here we are remapping the position to a value that will go from 0 to five 0.5 to 1 both positive and if we add the three components of this vector well if that value is bigger than one then we are outside our bounding box the other part of this comparison is if our shadow distance or accumulated Shadow distance is bigger than this Shadow threshold that we defined here at the beginning then we can also exit the loop so if this happens or this happens then just break the loop after that so if we don't have to break the loop then we just increase our accumulated Shadow Distance by the sample that we just took so this is our for Loop or our Ray margin for shadows and after that we just scale our current sample so basically this is a way of applying the bare LW and then we update our accumulated light energy G and we also update our transmittance finally After exiting this Shadow Loop and and updating all these values we just need to update our current position just the same as before now I for now I these were the lines where we were taking our final half step or a smaller step or fractional step and for now I just commented the whole thing the reason is that we will have to basically copy all this stuff with this new fractional step but I'm going to wait until we finish with the rest of the code just so I don't have to update this over and over after that I should have highlighted this in a different color we return a new float 4 the which uh first three components is our light energy and the fourth component is our transmittance so hopefully that explained what everything does let's go back to unre and let me first copy all this code and we'll paste it into our custom node oh one more thing before we go back to unreal I forgot to mention mention that we are also scaling the local camera vector by the step size outside of the for Loop so I guess it should be highlighted like these ones so here when we move our camera Vector we don't have to do this multiplication every time and now I can copy everything and go back to Unreal and first update the code and before we add add any connections I need to create this Shadow threshold input so let's copy the name from here add a new input and rename it to Shadow threshold now it also needs a value so let's create another scalar parameter call it shadow shadow threshold and give it maybe 0.01 and connect it there and now remember we are outputting a float 4 so we need a couple of component masks the first one for RGB and the second one for the alpha and we also need a one minus because we're going to to use the transmittance as opacity so let's connect that there and that to missive color oh and I didn't select the alpha there we go and now we have our shadowed Cloud so let's apply and save and let's see how we can improve this the first thing that I want to do is bring back the light color so we can remove this multiply and rename this parameter light color now instead of doing this outside of the function I'm going to pass this parameter as a new input on our function here the reason is that later on we'll add ambient lighting and that light shouldn't be color by this light this other one so let's scroll down to our inputs and add one new input and call this one light color we copy this and here on the line where we update our light energy so light energy plus the equal the exponential of distance Time shadow density um times Shadow sample or current sample times transmittance so at the end of this line add times light color and now we can connect this new variable to the new input and our cloud is also colored by this [Music] light now we can play with this vector and see how the light changes direction or the Shadows change direction but it's a bit of annoying to edit this Vector manually so let's remove this one and search for sky and we have this uh mod um Noe here called a sky atmosphere light Direction so add one of those and connect it to our light Vector now if we apply and save it will almost work so we have our directional light and if we move it yeah the Shadows move but they don't really match the direction of the light and that's because we need to transform this Vector so add a transform node and select from World space to local space and now we can replace this light Vector hit apply save and now it works now we can control the direction of the Shadows by moving the position of the sun and that's pretty cool but we could also now that we are using the sun we could also use the sun color to color this light so let's remove this light color and search again for a sky atmosphere oh atmosphere light [Music] illuminance so we can select that and reconnect it whoop to the light color and here in the preview it will turn black because there's no sky lights but hit apply and save and now our color our Cloud matches the sky or M is like is wide illuminated by the color of the Sun so the sun goes down our Cloud turns more orange and when the sun is overhead it's appear white okay so let's save everything and let's go back to our material and see what else can we do the next one is a bit of an artistic choice but right now our shadow density basically is reducing the amount of energy of the light in the same amount for every wavelength or every color of the light but we can replace this Shadow density for a vector so let's add a vector parameter rename this to also Shadow density and just replace this one with this one and then we can remove this the old parameter now if we set this to I think it was eight eight on RGB and we hit apply and save our cloud looks the same as before but let's say what happens if we do something like 8 16 32 now more light will be absorbed in the green wavelength or more green light will be absorbed and then even more blue light will be absorbed and just a little bit of the red one so let's see what happens if we hit apply and save and go back to the viewport now as the Shadows get more intense they go towards this reddish orange color and that still works with the color of the light so we can create a nice variety of of effects [Music] now let's add a bit of ambient lighting so even in the case where we don't have any directional lights to sample like in this preview we don't get a flat black cloud and we are going to do that after our sadow sampling or our shadow Ray margin we're going to take three extra samples vertically like in this direction then we're going to scale the or get the light energy that accumulates on those and then escale that by a new parameter that we can call ambient density and color that with the color of the sky so hopefully that wasn't very confusing let's go go back to warad and see the updated code here we have it so after updating the light energy and the transmittance outside of the Shadow steps here we first reset the shadow distance because this is just for the ambient lighting and then the ambient lighting is just going to take three samples so these three lines are basically repeated here and here except that we're using different offsets so for this one is 0 0 0.25 so a little bit up then 0.05 a little bit more and then 0.15 a little bit more now the same what we did on our shadow array margin we are accumulating that shadow distance and finally we are updating the light energy the same way way that we did here except that instead of um using our ambient density and or sorry instead of using our shadow density and I forgot to add it here our light color we are using our ambient density and our Sky color so let's copy all this and paste it back in our code so here after this oh sorry no after this group of lines let's paste this and we're going to get a couple errors because we haven't defined ambient density and Sky color so let's copy the name go back to our inputs and add those to so first we need ambient density and next we need our Sky [Music] color and for ambient density oh I already have let's create a new scalar parameter ambient density and connect it here and let's set one and for Sky color actually just for now let's see what happens if we create a new constant vector vector parameter and we call it who connect it here to Sky color and give this a little bit of a blueish tint now we have a little bit of ambient lighting excellent we can increase the ambient density so two and it doesn't have a lot of samples it's just three vertical samples so I would increase that too much values between one and or 0.5 and one or two are probably as high as you want to go with this now we were already using the light Direction and the light illuminance let's take the color of the actual sky on the viewboard so search again for SK sky and here we'll have the SK Sky atmosphere distant light scatter luminance so get one of these and replace our Sky color for this new node and again in the viewport it will won't do [Music] anything and now we have our color our color of the sky also affecting our [Music] material cool and we're all almost [Music] done another cool thing that we can do is query the global distance field to project Shadows on our volumes and as an extra bonus of using this method we can get soft edges uh just basically for free so as you can see this sphere that is really close to our volume is projecting this nice crisp Shadow and the one that is over there is is proing this much more soft Shadow now because we're using the global distance field and not quering the properties of any specific mesh we can also scale and transform all this and the effect updates properly this is a bit expensive but if your game really needs this uh objects in interacting with Clos meses I think it's worth the extra cost and now let's go back to the material and see what changes we have to make to make this happen and here I have the updated graph as you can see it looks a little bit different but it's mostly comments and reorganization to make things easier to read so we have our basic setup parameters are the same as earlier next are directional lighting where I made a little change and that was to normalize the vector for the light Direction after we transform it to local space otherwise the intensity of the light will will be slightly off and will be a bit more intense than it should next we have our ambient or skylighting and finally here's the new section distance fi Shadows so I added four inputs to the function and they are light tangent light Vector WS for warless space camera pause position in warless space and finally the fs steps so starting from the top the light tangent will affect how much penumbra or how soft the edge of our shadows are next for the light Vector in wall space I'm just taking the same light direction that we were using for our shadows and not transforming it but it's still normalizing next the camera position in World space which we can just get as node and finally the number of steps for our new Ray March which I call uh is a scalar parameter called distance field Shadow steps that defaults at 16 and now let's see what changes I made to the code this is the updated code with all the changes highlighted red so after we finished taking our shadow steps here in this section we'll do this new part it starts with refactoring the current position or remapping the current position which at this point goes from 0 to one in the direction that we're sampling into a minus one to one range by subtracting 0.5 and multiplying by two then we multiplying that by the local objects found Max property to support scaling then we need to convert this position to World space and for that we're using this transform local position to world and because this will return a large World coordinate system we need to use then this function large World coordinate to float which will convert this format into just a float three now you might have seen this minus camera position World space and that's because currently and that might be an error I'm not entirely sure uh this large wall coordinate to float function won't take into account the translations of the the translation of the camera so if you don't add this minus camera position at the end the shadows will move when you move the camera and they will be only correct at the origin next we are creating this four variables DF Trace disc DF Shadow curve disc and distance along Trace we'll see what they do here in the for Loop and next we have the for Loop and this is just another ray margin operation so this one for this one we're taking DF steps DFS steps number of steps and on each one we are first increasing the distance along Trace variable by whatever distance we sample in the previous step so so in the first step of this four it will increase that by current Distance by zero next we update this current Distance by just sampling the global field or the this the global distance field with this function get distance to nearest surface Global and this will require passing a position in World space next we are creating this float called sphere size and this is basically what I explained earlier about having soft edges so imagine as that we place a sphere on each point of our trace and that sphere gets bigger the farther we go next we are updating this variable the F Shadow with the minimum of whatever distance we are sampling right now divided by the size of this imaginary sphere and the current value of the variable so it will be the minimum of itself and whatever we are simpling divided by the sphere size and finally we just move the current position in this case we are calling it DF post by adding the light vector and then the current distance that we trace and then this variable called DF Trace distance and the last step is to increase the value of this DF TR Distance by a really small factor and that basically increases the distance of our traces on every step just a tiny bit so after all of this what the only thing that we care about is this term the F shadow so a couple lines later when we are updating our light energy we are adding yet a new term here so we are just multiplying it by this DF Shadow so just copy this part and paste it after the shadow steps and don't forget to multiply our light energy by the DF Shadow term at the end and after updating the code your volume should be shadowed by other objects now you can change the number of steps but I wouldn't go too low or lower than 16 maybe or something like that and the light tangent so you can experiment with different values but if you get it too high or below zero it starts to show some really weird behaviors so maybe something like that doesn't look too bad another advantage of using this type of um distance field Shadows is that the objects will project Shadows even when they are within the volume so as you can see we can move this sphere all the way here and still projects a very nice shadow and now the last thing we we need to do is bring back our fractional step to solve this low quality intersection and these two lines are the important part of our fractional step first we update the current position the same thing that we did in previous video using the final step size and second we take a current sample at the new position now as for the rest let me zoom out I'm going to copy everything inside this if sentence including the if so from here all the way down to here and I don't want to copy this current position update so copy this and paste it after these two [Music] lines so just to summarize we are still doing our regular rearching inside of that we are doing our shadow Ray marging then we're doing our or lighting Ray Mar Ray marching and then our shadowing distance fi Ray marching and once we do all that including our ambient lighting we take one final step where we do all of those things again main Reay marching lighting and shadowing and ambient lighting so let's copy all this and once we paste it into un real things will look a little bit better and you can still see a little bit of the stepping in the direction of the camera but I think it's a pretty good [Music] result and I don't want to end the video without answering at least a few of the questions that some of you posted in the previous one the first one was about non-uniform scaling as we saw scaling this volume in a non-uniform way creates some intersection problems with other meshes in the scene now the source of those problems is in the volume box intersect function and I spent a few hours and trying to solve it and sadly all the solutions I came up with were very similar to the original one that we commented out of the code in the previous video and also didn't work with the same problems based on camera position and whatnot now I think the those problems happen because of the large War coordinate system and I will spend a few more time hours trying to solve it before the next video but sadly I cannot promise anything now the second question was about animating these volumes and of course you can always animate the the cubes themselves where we are painting the materials move them around or scaling and rotate them and all that but I think the question was more about animating the textures themselves and there are a few ways to do that we can Pan the textures in different ways but we need to take into account that they are sprite sheets so we need to pan kind of every frame on the Sprite sheet we can also animate the entry position for our our camera race and finally we can do something like introduce some noise into the texture either volume noise or a 3D noise or to the noise that we remap to the Sprite sheet now we'll see all of those in a bit more detail in the next video where we are going to focus in making a pseudo volume texture editor that we can use within unreal to make our own volumes for from scratch but that's going to be on the next video for for today I I think that's that's probably good enough I hope you guys enjoyed this video and if you did please consider giving a like and subscribing to the channel see you next time
Info
Channel: Enrique Ventura
Views: 3,952
Rating: undefined out of 5
Keywords: material, shader, tutorial, unreal, unity, lesson, course, graph, shadergraph, shader graph, nodes, node, texture, 2d, 3d, high quality, 4k, volume, rendering, raymarch, raymarching, 5.3, ryan brucks, lighting, directional light, shadows, scene lights
Id: SIlKTPnV4R8
Channel Id: undefined
Length: 42min 9sec (2529 seconds)
Published: Sun Oct 15 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.