Colored Shadows and Selective Lighting

Colored Shadows

First, I wanted to further explore the possibilities of combining my shader with colored textures. When 
I tried assigning various texture colors to different objects, I noticed something strange:


The style of the shading appeared to depend on the hue of the texture, even when the different-hued textures were similar in brightness and saturation. For "warmer" hues closer to red and yellow, the dark areas of the shading were much brighter.

At first, I thought this was because I isolated the red channel at some point when creating my crosshatch pattern.

However, after further testing, it became clear that this wasn't the issue. The red channel gets converted to greyscale anyway via the remap node.

The actual culprit was here. In the process of combining the normal map with the crosshatch texture, I create a Vector2 between the normal map + texture and the normal map + texture plus a small constant.


When I output the result of this Vector2 directly to the base color, this is the result:


Evidently, this Vector2 destroys the colors of the original map and replaces all of it with this shade of yellow, with the result of the shading depending on how close the original map was to that shade of yellow. The only reason why the original material color was showing up in the final render at all is because of the final pass just before outputting to the base color, where I multiply the color by the original material.

To be honest, I don't really understand how this bit of my shader graph even works. All I know is that it's crucial to integrating my custom crosshatch texture with the shading, so I don't feel confident messing with it. 

So, in order to get around the color consistency issue, I'd have to make sure the color wasn't interacting with that part of the shader graph. The fix was fairly simple: all I did was take the normal map + color texture and convert it to greyscale by splitting the channels, averaging them, then creating a new color with the average being the input to the red, blue, and green channels.


I take this and feed it into the series of nodes that applies the crosshatching, in place of the normal map + color texture. This ensures the shading depends purely on the brightness of the color texture and not the hue.


The color is added back into the mix by multiplying the normal map + color texture with the greyscale crosshatched texture at the end.


Here's what the results look like.


Again, all four objects use the same shader with the same input parameters. The only difference is the color of the texture. The textures all have the same brightness and saturation: they only differ in their hue.


By adjusting the saturation of the color in the texture, I can control how far the color extends beyond the shading.


Here's what it looks like if I adjust the brightness while keeping the saturation constant:


What I've basically done is create a colored version and a black-and-white version of the shading, and I can control the thresholds at which each of them takes effect by modifying the brightness and saturation.

I think this effect looks nice, and the way it's implemented makes it easy to work with. All I have to do to create different styles of shading on different body parts is change the saturation and value of my brush when texture painting. And if I want alternate character colors without affecting the quality of the shading, all I have to do is shift the hue of the texture map, which should be fairly simple to do.

Attempts at Introducing Selective Lighting

First off: my initial idea of using lighting render layers to control which lights affect which objects doesn't seem like it's going to work out. 


It seems that render layers only affect the built-in lighting system: if you've created custom lighting with a shader, that will override the render layers.

Instead, the way to go seems to be to create a custom HLSL function that uses the URP function GetAdditionalLight(). GetAdditionalLight(), as opposed to the GetMainLight() function that I've been using, allows you to get lights other than the main directional light in the scene. Each additional light has an index, and all you need to do is simply supply the index for the light that you want as an input parameter to the function. 


Sounds simple enough, but there's some problems. As far as I can tell, the only way to select specific lights to use in your shader is via the index. There's no way to supply a specific GameObject as an input to your shader graph, and there doesn't seem to be a way to call GameObjects by name either.


That's problematic, because there doesn't seem to be any way to see what index each light has, and it's unclear what determines the index that is assigned to each light.

Problem number two: the indexing system appears to be fundamentally bugged. I set up my shader graph so that I could pass the additional light index into the graph as a parameter, allowing me to select which light to use on a material-by-material basis. For some reason, though, the objects would completely ignore their assigned index, and only ever respond to the additional light that was closest to the camera.


I decided to do some debugging, which is rather difficult in the shader graph as there's no way to output values directly to any sort of console. I had to infer values by converting them to colors and outputting them as the result of the shader.


Anyway, I tested out all the components of my shader graph and my HLSL code, and concluded that the problem was with the GetAdditionalLights() function itself. It simply did not work as advertised: no matter what index was supplied as a parameter, it would select which light it felt like via its own arcane methods.

After combing through some documentation and forum posts, I eventually worked out a likely explanation for the issue. If the project is using the Forward+ rendering path, you need to use a specific set of macros in order to loop properly through additional lights. I found a forum post where a Unity staff member admits that this is "not exactly nice" and "we would like to change [it] at some point."


Problem is, I don't want to iterate through every additional light. I want to pick and choose specific additional lights. It's probably possible to adapt the structure of the loop so that it isolates specific indices based on an input value, but I haven't bothered to spend the brainpower to work out how to do that.


In the meantime, though, I could consider simply not using the Forward+ render path. 

Once I switched my render path from Forward+ to Forward (which was not exactly intuitive: the naming conventions and locations of my URP-related files did not match up with the tutorials I found, which I thought was puzzling given that I'd initialized my project using the default Universal 3D preset and hadn't messed with any of the settings), my objects started properly reacting to individual lights based on the index I set for them. 

\

Of course, switching from Forward+ to Forward has some additional consequences, but from what I understand, it seems like Forward would be more suitable for my project anyway. Forward+ uses a more powerful light culling formula that improves performance when large numbers of lights are used, increases the limit on the number of lights that can affect each object. However, it incurs an overhead performance cost compared to Forward, and I don't imagine I'll care very much about any of the fancy lighting stuff Forward+ unlocks.

Now, there's still the issue of not being able to see the light indices or control how they're assigned. 
I noticed that if all of my additional lights were directional lights (which affect the entire scene regardless of placement), the order at least seemed to be consistent regardless of how they were positioned relative to each other, the objects, and the camera. However, I couldn't detect any rhyme or reason to how the order was selected: it didn't seem to depend on their names, their order in the scene view, or any other parameter I had control over. If I used other light types, such as point lights or box lights, the index order appeared to depend on the proximity of each light to the camera. I don't want the index order to change suddenly during runtime, so unless I find some way of manually setting the index, things might have to get a little messy.

Next Steps

These are the next avenues of investigation I'm considering:

1. implementing shadows in the shader graph
2. controlling the index of lights
3. implementing object outlines, either using the object shader graph, or as a post-processing effect

Comments