Additional Light Shadows
Alright, first off, I'm realizing that I mentioned at the end of one of my posts that I was going to look into alternate Unity versions to see if the shadow attenuation issue was fixed there. Well, I did in fact look into that shortly after making the post, I just forgot to include it in the next one.
To summarize: the shadow attenuation bug is not fixed in beta 6.2, or any other versions I tested: any and all attempts to take the shadow attenuation of an additional light resulted in a uniform value of 1 at all positions.
I was, however, able to implement shadows for additional lights by using a function dedicated to the purpose, called "AdditionalLightRealtimeShadow."
The function is analogous to the MainLightRealtimeShadow function, except instead of a shadow map, all you need is the index of the additional light and the world position map. I was a little suspicious of this at first: why are all the extra steps required to calculate the shadow map for the main light not needed for additional lights? Everything seemed to work fine, though.
I remember doing quite a bit of testing on additional light shadows, but I don't remember a lot of the specifics. I implemented this bit before I started working on the outlines, and it seems like I forgot to take good notes or screenshots at the time. From what I recall, the function seemed to have some annoying quirks (for example, it only worked for spot lights, and not for point lights), but overall, it seemed to work well enough.
Integrating Shadows into the Main Shader
Anyway, I decided I was ready to put all of it together and see what the shading effect, combined with the shadows and outlines, would look like on a more complex model. All I needed was a model to quickly test with, so I generated one using Meshy AI.
I took the shadow calculation code from my shadow shader graph and added it into my custom function that calculated the dot product between the light and surface normals, and output the shadows as an additional result of that function:
Before integrating the shadow effect into the main shader, I wanted to test it in isolation, so I output only the shadow value to the shader's base color to observe the results.
As I changed the camera angle, the light being used to calculate the shadow also changed. This is reminiscent of the indexing issues I was having earlier, where the index order of the lights depended on camera position. This was puzzling, though, since this issue should have been solved by my target color trick.
I did a bunch of testing, and I'm not going to relay all the results of that testing here. The thing I eventually figured out that clued me in to the true nature of the problem was this: if x number of lights shining on the object had a red value matching the target red value for the shadow calculation, then x of the additional lights would be used to calculate shadows, but they wouldn't necessarily be the ones that matched the target color.
Recall that one of the inputs to the AdditionalLightRealtimeShadow() function is the index of the additional light. Also recall that my custom function involves a for loop where I use the GetAdditionalLight() function to obtain the light corresponding to each index, from which I extract the color and direction:
The problem, I concluded, was that the AdditionalLightRealtimeShadow() function had a different method of ordering the indices of the additional lights compared to the GetAdditionalLight() function.
This was a devilishly tricky problem to solve. It meant that my method of light selection control was fundamentally incompatible with my method of calculating shadows. I tried restructuring my code in a few different ways to see if I could bring the indices into alignment, and went searching through forums (and even directly through the .hlsl includes from the URP package itself) for some alternative functions for calculating the shadows, but nothing seemed to work.
Eventually, I found myself back on this page, which describes a method for looping through lights in Forward+. You may recall that I found this page earlier when trying to solve an issue with GetAdditionalLight() not working, but instead of implementing this methodology, I chose to simply switch from Forward+ to Forward.
I noticed that the code here used a slightly different version of GetAdditionalLight() with an additional parameter: a vector4 where all the values are set to 1. At this point, I vaguely remembered some mention of a function being deprecated and a recommendation to use a different version of the function with another parameter, but I couldn't recall exactly where I'd seen it and what the specifics were.
What I think I was remembering was this bit of the shadows.hlsl file included in URP, where it mentioned the AdditionalLightRealtimeShadow() function being deprecated and to use a different version with another parameters instead. I think I saw a forum post mentioning this too.
Notably, this is a different function from GetAdditionalLight(): my wires were getting crossed between AdditionalLightRealtimeShadow() and GetAdditionalLight(). But as a result of misremembering what function this deprecation note was talking about, it gave me the idea that perhaps implementing this loop structure as shown in the documentation would fix the issue.
As it turns out, it didn't fix the issue: the indices still didn't line up between the two functions. However, given that I'd just made a change to how my GetAdditionalLight() function, I wondered if that had somehow fixed the issue where I couldn't just take the shadow attenuation value of the additional lights directly (which the forum posts I saw earlier mentioned had been fixed in Beta 6.2, even though it didn't seem to be fixed on my end earlier). So, I changed my code so that it no longer used AdditionalLightRealtimeShadow() and just sampled the attenuation directly. Now it looked like this:
Basically, this just modifies the dot product map so that it becomes way darker in all the areas where you'd expect to find shadows.
Here's what the combined result of all of this looks like:
Because all of this is just based on the position and rotation of the lights, I imagine there's a lot of potential for keyframing the lights in order to create different moods during certain animations.
By the way, I now have two separate versions of the fullscreen outline shader with different parameters: one for the editor, and one for the in-game view. This makes it a bit easier to work in the editor and get a good sense of what the in-game version is going to look like without having to swap a bunch of parameters whenever I want to check the in-game camera.
Final thing to note for this post: I added a section to the fullscreen shader that multiplies the depth edge detection value by an amount that depends on the dot product between the camera view direction and the normal map. This helps to alleviate some of the erroneous pseudo-shadows that showed up on surfaces that were close to parallel with the view direction. It's pretty rudimentary, but it works quite well, especially in the in-game view.
Comments
Post a Comment