How to get certain lighting information into your shader?

Heyhey, guys! :]

I am currently setting up a fresnel-based iridescent shader and my goal is to make it somewhat physically accurate. For that I need to feed some light information into the shader that’s not easily accessible through the available default nodes. Basically, what I need is the information how much light hits the surface at any point and from which angle.

I am sure that I’ll have to do some blueprinting to do that but since I am still pretty new to that, I definitely need some advice here. Is there a way to access both light intensity hitting the surface and light angle?

I had the idea of getting the light direction of every light source in the scene with every loop as well as getting the intensity of every light source and decreasing that with higher distance between light sources and surface, which approximates light intensity hitting the surface. However, it’s not the most accurate solution for sure (since that doesn’t take shaded areas into account and generally it’s more of an approximation rather than fully correct) and I feel there are better approaches, especially since this doesn’t take GI into account.

I know I could simplify it by just using SkyAthmosphereLightDirection and multiplying the fresnel with that before feeding it into the hue shift I’m using to create the iridescence (which is what I’m currently doing) but that has several downsides compared to what I am trying to achieve, like being dependent only on one single light source (and only the directional light, nothing else) rather than taking all lights or even something like GI into account, it that approach doesn’t consider light intensity and/or shaded areas, either.

If there are technical limitations that restrict me from accessing this info, I’ll use the approach above but I’d really rather like to get something more accurate.

Any advice is welcome! :] Thank you in advance!

Leo

Thank you! :] I’ll take a dive into that right away!

If you want to stick with deferred rendering, you aren’t totally out of luck.

Material Parameter Collections and Custom Primitive Data allow you to store data that can be accessed by a material. It has limits in the number of datapoints it can store. So perhaps your iridescent object should simply check if there are any nearby lights and store only the most relevant values for that specific object as per instance data. This way the material doesn’t need to evaluate 1000 lights most of which do not touch the object.

To mostly replicate a point light, you need to know at least its world position and power, so that neatly fits into a float4 vector. In the material, you can get the direction vector between the surface of the mesh and the light position (stored as RGB) with simple subtraction. Normalize this for a unit vector that points towards the light. This can be compared to the normal vector of the mesh to tell how much that surface aligns with the light vector.

The power can be stored in the Alpha channel. You’ll need to decide how to implement this based on the light’s settings. By default, UE uses a physically accurate “inverse square falloff”. Since you know the power and distance (the length of the light vector pre-normalization) of the light, you should be able to calculate intensity the using the appropriate falloff formula (such as the inverse square law of light).

You can see how it could be problematic to need to repeat this logic in the material for every light, but a few times for a limited number of nearby lights is probably fine.

Spot lights are more complex, since they have a position, direction, power and cone angle at least. So you’d need two vectors to store and replicate them. Other light types can also likely be analytically reproduced in a material in similar ways.

However, all of these techniques will lack cast shadows. A surface will be able to tell if it faces the light and how far it is (and thus the light intensity) but not if that light is obscured by other objects or even part of the same mesh. It isn’t really feasible to solve this in the surface domain.

Another solution could be to apply this as a post process instead of in the material shader itself.
Use a custom stencil bit to flag an object as iridescent and mask it accordingly. You can easily calculate the fresnel in a post process material and extract the final combined light intensity too - including cast shadows, unlike in the surface domain.

You wouldn’t have the angle of lights other than the sun though, unless you also stored the light position data in an MPC or something. For performance, you once again may want to limit this to the nearest x number of lights. I highly doubt the contribution of even the 3rd lights direction would be meaningful.

The sun would be so much brighter than most other contributions would be meaningless so if this is meant to be outdoors I wouldn’t bother with anything else. For indoors, I would probably just evaluate the one light, such as the nearest or perhaps most powerful light within a certain range.

Below I made a rough example of this as a PPM. There’s no masking here so its applying to the whole scene, but that’s pretty simple to add - lots of tutorials. Notice how the Fresnel falls off in the shadow. It doesn’t understand the direction of the light here, but it does understand the intensity.

Also I disabled the skylight for this effect. The skylight gives the underside of the sphere enough light to hide the effect I was trying to demonstrate. That probably can be improved greatly with further adjustment but I’ll leave that to you if you want to pursue it further.