1. Technical Challenges
After completing the visual analysis of Return of the Obra Dinn, I began developing the surface material in Unreal Engine 5.
Unlike a conventional Toon Shader, this experiment presented two main challenges: creating a more complex tonal structure and allowing the material to respond to lighting within the scene.
Challenge 01: Five-Level Toon Shading
Traditional Toon Shaders typically use two or three distinct tonal levels, separating illuminated and shadowed surfaces with hard boundaries.
For this experiment, however, I wanted to divide the surface shading into five tonal regions:
Black → Dark Grey → Mid Grey → Light Grey → White
Rather than using the same transition between every region, I wanted to combine smooth gradients with sharp boundaries.
Tonal Transition Expected Effect
Black → Dark Grey Smooth gradient
Dark Grey → Mid Grey Sharp boundary
Mid Grey → Light Grey Smooth gradient
Light Grey → White Sharp boundary
This design was intended to support the subsequent Post Process Material.
Since the final black-and-white dithering effect would use different pixel distributions for different tonal regions, the surface material needed to provide a controllable brightness structure.
If all transitions used hard boundaries, the image would appear too flat. However, using only smooth gradients would weaken the visual impact of large black-and-white shapes.
Therefore, I needed to combine both transition methods within a single material.
Challenge 02: Dynamic Lighting
The second challenge was making the Toon Shader respond to changes in light positions within the scene.
In UE5, when using an Unlit Material for custom toon shading, the material does not automatically respond to conventional scene lighting.
This means that moving a Point Light would not automatically change the shading on the object’s surface.
For Rushing Place, the environment contains multiple light sources and complex spatial structures. Without lighting interaction, the relationship between characters and their surroundings would lack visual consistency.
Therefore, I needed to develop a custom lighting calculation system that could read light positions from the scene and calculate brightness based on the orientation of each surface.
2. Technical Development
To create a five-level Toon Shader that could respond to scene lighting, I divided the development into two stages: first, building the five-level shading system, and then implementing custom lighting through Blueprint and a Material Parameter Collection.
Step 1: Five-Level Toon Shading
First, I created an Unlit Material in UE5 and used Emissive Color to output the final colour.
Unlike traditional Toon Shaders, I wanted different transitions between the five tonal regions. To achieve this, I combined Lerp, Step, Divide, and Saturate nodes to create both smooth gradients and sharp boundaries.
To make the different regions easier to identify during testing, I initially used five colours: blue, red, yellow, green, and white. These would later be converted into grayscale.
1. Setting Up Five Colour Regions
I created five Vector Parameters representing different tonal levels, along with five Scalar Parameters to control the brightness thresholds.
Parameter Value
M1 0.2
M2 0.4
M3 0.6
M4 0.8
M5 0.9
These parameters control the distribution and transition points of the different colour regions.
2. Creating Smooth Gradients
For regions requiring smooth transitions, I used Divide and Saturate to generate blending values between 0 and 1, then used Lerp to blend between two colours.
The first gradient was created using the following node connection:
LightValue → Divide (M1) → Saturate → Lerp (Colour 1 / Colour 2)
As the brightness increases, the material gradually transitions from the first colour to the second.
For another gradient, I used Subtract to define a specific brightness interval:
LightValue → Subtract (M4) → Divide (M5 – M4) → Saturate → Lerp
This allows the gradient to remain within a controlled brightness range without affecting other tonal regions.
3. Creating Sharp Tonal Boundaries
For regions requiring clear boundaries, I used Step nodes to compare LightValue against thresholds such as M2 and M3.
The node connection was:
LightValue + Threshold → Step → Lerp (Alpha)
When the brightness crosses a specified threshold, the Step node switches its output between 0 and 1, creating a sharp transition between two colours.
Finally, I combined the five colour regions using multiple Lerp nodes and connected the final result to Emissive Color.
This allowed me to control both smooth gradients and sharp tonal boundaries within a single material.

Step 2: Custom Lighting System
After completing the five-level Toon Shader, the next step was to make the tonal regions respond to light positions within the scene.
Since I was using an Unlit Material, the shader could not directly respond to UE5’s conventional lighting system. Therefore, I needed to manually calculate the directional relationship between the surface and each light source.
1. Creating a Material Parameter Collection
First, I created a Material Parameter Collection (MPC_ToonLight) to share lighting information between Blueprint and the material.
I added three Vector Parameters:
LightPos0 / LightPos1 / LightPos2
These parameters store the world positions of three Point Lights.
2. Passing Light Positions Through Blueprint
Next, I created BP_ToonLights3 and added three Point Light Components.
In the Construction Script, I used Get World Location to retrieve the position of each light, then used Set Vector Parameter Value to write these coordinates into MPC_ToonLight.
The node connection was:
Point Light → Get World Location → Set Vector Parameter Value
Each light was assigned to its corresponding parameter: LightPos0, LightPos1, or LightPos2.
This allowed the material to access the light positions without manually entering their coordinates.
3. Calculating Surface Brightness
Back in the Material Editor, I used Collection Parameter nodes to retrieve the three light positions.
First, I used Absolute World Position to obtain the world coordinates of the current pixel.
Then, I used Subtract to calculate the direction from the surface towards the light source, followed by Normalize to convert it into a unit vector.
The node connection was:
Light Position – Absolute World Position → Normalize
Next, I used PixelNormalWS to obtain the surface normal in world space and Dot Product to calculate the relationship between the surface orientation and the light direction.
PixelNormalWS + Light Direction → Dot Product
When the surface faces the light source, the Dot Product produces a higher value. When the surface faces away from the light, the value becomes lower.
I repeated this calculation for all three light sources.
4. Combining Multiple Light Sources
To allow the material to respond to three light sources, I used two Max nodes to combine the three Dot Product results.
The node connection was:
Dot 0 + Dot 1 → Max → Max (Dot 2)
This method selects the highest brightness value from the three light sources, preventing excessive brightness caused by directly adding their contributions together.
I then used ConstantBiasScale to adjust the brightness distribution and Saturate to clamp the result between 0 and 1.
The resulting LightValue was connected to the five-level Toon Shader developed earlier, controlling the distribution of the different tonal regions.
This allowed the surface shading to change when the light position data was updated.
It is important to note that this system calculates brightness primarily based on light direction. It does not fully simulate physically accurate lighting effects such as distance attenuation, occlusion, or shadows.
Step 3: Final Material Integration
Finally, I connected the custom lighting system to the five-level Toon Shader.
The overall material workflow was:
Point Light Position → Surface Normal Calculation → LightValue → Five-Level Shading → Emissive Color
Through this process, I developed a Toon Shader with five distinct tonal regions, different transition styles, and the ability to adjust its shading based on multiple light positions.
This material provided a controllable brightness structure for the subsequent Post Process Material, allowing me to further convert different grayscale regions into black-and-white dithering patterns.




3. Result
Through this experiment, I developed a five-level Toon Shader capable of calculating surface brightness based on multiple light positions.
Compared with a conventional Toon Shader, this material allows me to control not only the number of tonal regions but also the transition style between them.
By using a Material Parameter Collection and Blueprint, the material can also update its shading distribution according to the light position data provided by the scene.
The main goal of this stage was not to generate the final black-and-white dithering effect, but to establish a stable and controllable tonal structure for the subsequent Post Process Material.