Rim Light Driven by Light Direction
A character material where the rim light takes its color from whichever spotlight the camera is facing, and an underlight that puts the arena's candles on the legs.
Rift Fighters stages its fights in a candle-lit temple lit by four colored spotlights: green and violet from the sides, blue from above, orange from the floor. A rim light with a fixed color ignores all of it. The character carries the same edge highlight no matter which way the camera swings, and that edge stops telling you anything about where the character is standing.
I wanted the rim color to come from the lights themselves. Swing the camera toward the green light and the character's edge goes green. Swing toward the violet light and it goes violet. Between the two, it reads as the blend.
A material cannot ask a light actor which way it is pointing, so something has to hand it that information. I built a blueprint, BP_Spotlights_DirCol, that holds the four spotlights as actor variables. On a custom event it casts each actor to a spot light, takes the world rotation of its root component, pulls the forward vector out of that rotation, and writes the vector into a material parameter collection called MPC_Light_DirCol. It writes each light's color into the same collection on the same pass.
Eight vector parameters come out of it: four directions and four colors. Everything after this point is material math reading those eight values, so a light can be moved, re-aimed, or recolored in the level and every character picks up the change the next time the blueprint runs, with nobody opening the material.
The base is a Fresnel node at exponent 3, scaled by a thickness parameter and sharpened by a Power node whose exponent is a sharpness parameter, then clamped and used to lerp between black and white. That is a plain white edge on the whole character, and every color that follows is masked by it.
On top of that sit four identical chains, one per light. Each chain takes that light's direction vector out of the collection, normalizes it, multiplies it by -1 so it points back toward the light rather than away from it, and takes the dot product against the camera direction vector. That dot is 1 when the camera is looking straight down the light's axis, 0 when the camera is perpendicular to it, and negative when the light is behind the camera, so a clamp to 0-1 discards the back half. A Power node with the falloff-angle parameter as its exponent sets how wide the arc is before that light starts contributing.
What comes out is a single greyscale value covering the whole character: how much this light counts right now. Multiply it by that light's color, repeat four times, add the four results together, multiply the sum by the Fresnel mask, and scale the whole thing by a dim parameter.
The blend is a consequence of the math, not a case I had to write. Standing between the green light and the violet one, both dot products land partway, both colors contribute in proportion, and the rim crossfades as the camera moves instead of snapping from one color to the next.
The blueprint captures each light's color along with its direction, and driving the rim from those captured colors was the plan. I cut it for scope and left the four rim colors as their own parameters. That turned out to be the better setup. The rim palette is a look decision and the level lighting is a lighting decision, and keeping them apart lets me push the rim green harder than the light that motivates it without touching the light.
The arena floor is covered in candles, and the characters needed to look like the candles were reaching them. A second Fresnel, at exponent 2, is multiplied by the dot product of the world-space vertex normal against straight down, which gives a mask that only exists on downward-facing surfaces. That drives a lerp into the orange color parameter.
What limits it is a vertical gradient: 1 at the feet, 0 at the top of the falloff, with the curve set by a parameter. A second gradient fades the underlight back off right at the feet, standing in for the occlusion where a character's own body blocks the candles nearest to it.
The gradient is currently read from a UV channel on the character, which means it is in character space and travels with the model. On the floor, where the fight happens, it reads correctly. Off the floor it does not: a character who jumps carries the candlelight up with him. The next iteration drives the same gradient from world height instead, so the underlight comes off on the way up and returns on the way down without an animation on it or a line of gameplay code.
I used AI to learn how to do this in Unreal: which nodes exist, what a material parameter collection can and cannot carry, how a blueprint writes into one. I placed and wired every node in these graphs myself, and I tuned every value in them by looking at the result.
Four colored lights drive the rim on every character using the material, and the whole system tunes from six scalars and four colors in one collection. Re-aim a light in the level and the characters pick up the new direction. The underlight puts the candles on the legs, with the height falloff and the foot occlusion each on their own parameter.
Four lights did not need four cases. It is one chain repeated four times and summed, and every in-between angle the camera can occupy is handled by the fact that a dot product returns a number instead of a yes. When the math produces the intermediate states, there are no intermediate states left to author.
I built the underlight falloff in character space and had been calling it world space until I sat down to write this page. The equivalent fix on The Lord of the Rings: War in the North in 2010 was driven by distance to the ground, and it did behave correctly when a character left the floor. Sixteen years and two engines later I reached for the cheaper mask and did not catch the difference until I had to state it plainly.






