Inspiration
What it does
How we built it
Challenges we ran into
Accomplishments that we're proud of
What we learned
What's next for Add PBR Lithing Module
Project Story
About the Project
This project started from a simple question:
«How can a toon shader respond more accurately to real-time lighting without losing the visual stability and artistic control expected from character rendering?»
NonToon already provides a modular foundation for combining toon shading with physically inspired rendering techniques. However, while experimenting with lighting in Unity's Built-In Render Pipeline, I noticed that the most important limitation was not necessarily the number of available visual effects.
The deeper problem was how incoming lighting information was interpreted.
In a typical real-time environment, especially one containing multiple directional, point, and spot lights, the physically correct light direction is not always the most visually useful direction for toon shading. At the same time, aggressively stabilizing the lighting direction with character-relative or world-space biases can cause strong directional lights to lose their actual directionality.
This project explores a middle ground: a modular lighting system that preserves accurate local-light behavior when reliable lighting information is available, while gracefully falling back to stable character-oriented lighting when it is not.
The project is designed specifically for:
- Unity Built-In Render Pipeline
- Forward Rendering
- NonToon
- ShaderCore's modular shader architecture
- Character and avatar rendering
The main idea is to introduce a dedicated lighting interpretation layer between Unity's raw lighting data and the final shading modules.
Unity Built-In Lighting ↓ Lighting Analysis ↓ Lighting Interpretation ↓ NonToon Shading Modules
Instead of forcing every shading module to independently interpret the current light environment, the system can provide a more consistent set of lighting information such as:
- resolved main light direction,
- direct-light confidence,
- approximate environmental light direction,
- local point and spot light direction,
- form-shadow information,
- cast-shadow information.
Inspiration
The project was inspired by a recurring problem in toon and avatar shaders.
A shader may look excellent under a simple directional light but become unstable or visually incorrect when placed in a more complex environment.
For example:
- a strong spotlight may illuminate the character from the side, but the toon shadow may still appear biased toward the top of the character;
- two opposing lights may produce an unstable averaged light direction;
- ambient lighting may brighten the character without providing a meaningful direction for the toon shadow;
- additional point or spot lights may behave differently from the main toon-shading model;
- physically accurate lighting may create undesirable facial shadows;
- aggressively stabilized lighting may ignore the actual scene lighting.
These are not simply artistic problems or physical-lighting problems. They are lighting interpretation problems.
This led to the idea of separating two concepts:
- The physical light direction
- The light direction used for stylized shading
Rather than choosing one permanently, the shader can transition between them depending on how trustworthy the available lighting information is.
Conceptually:
[ \mathbf{L}_{final}
\operatorname{normalize} \left( w_d\mathbf{L}{direct} + w_e\mathbf{L}{environment} + w_h\mathbf{L}{head} + w_u\mathbf{L}{up} \right) ]
where:
- \mathbf{L}_{direct} is the actual direct-light direction,
- \mathbf{L}_{environment} is an approximate environmental lighting direction,
- \mathbf{L}_{head} is a character-relative fallback direction,
- \mathbf{L}_{up} is the world-up fallback direction,
- w_d, w_e, w_h, w_u are adaptive weights.
The important part is that these weights do not need to remain constant.
When direct lighting is strong and reliable:
[ w_d \rightarrow 1 ]
and the fallback weights can decrease.
When direct lighting becomes weak or ambiguous:
[ w_d \rightarrow 0 ]
and environmental or character-relative lighting can gradually take over.
What I Built
The project is structured as a set of Built-In Render Pipeline lighting modules rather than one monolithic visual effect.
Adaptive Light Direction Resolver
The first component resolves the direction used by toon shading.
Instead of always adding a fixed head-direction or world-up bias to the physical light direction, the amount of stabilization is controlled by the strength and reliability of direct lighting.
A simplified model is:
[ c_d = \operatorname{saturate}(Y_{direct} \cdot s) ]
where:
- Y_{direct} is the luminance of the direct light,
- s is a configurable confidence scale,
- c_d is the direct-light confidence.
Fallback weights can then be derived from:
[ w_{fallback}=1-c_d ]
This produces behavior such as:
Strong directional light → Follow the physical light direction
Weak direct light → Blend toward environmental lighting
Ambiguous or nearly directionless lighting → Use stable character-relative fallback lighting
This prevents strong lights from being unnecessarily overridden while still allowing stable rendering in poor lighting environments.
Built-In ForwardAdd Toon Lighting
Unity's Built-In Forward Rendering processes the main lighting contribution and additional per-pixel lights differently.
The project therefore treats them differently as well.
ForwardBase ↓ Main toon shading ↓ Stable base appearance
ForwardAdd ↓ Per-light toon response ↓ Point / Spot / Additional Directional lighting
Instead of treating additional lights as generic additive illumination, each additional light can receive its own toon response.
For a light direction \mathbf{L} and surface normal \mathbf{N}:
[ N_L = \mathbf{N}\cdot\mathbf{L} ]
A stylized light response can then be generated using:
[ T = \operatorname{smoothstep} \left( t-s,, t+s,, N_L \right) ]
where:
- t is the toon threshold,
- s is the transition softness.
This allows point and spot lights to illuminate the model while still following the same visual language as the base toon shading.
For example:
Red point light from the left → Red toon-shaped illumination on the left side
Blue point light from the right → Blue toon-shaped illumination on the right side
The result is more expressive than collapsing all lights into one averaged direction.
Local Light Precision
Another part of the project focuses on preserving the actual spatial behavior of local lights.
For a point light, the light direction should be calculated from the current surface position:
[ \mathbf{L}
\operatorname{normalize} \left( \mathbf{P}_{light}
\mathbf{P}_{surface} \right) ]
This matters because the light direction changes across the surface of a large object.
Spot lights require additional information:
- light position,
- light forward direction,
- cone attenuation,
- distance attenuation,
- optional cookie information.
The project therefore treats directional, point, and spot lighting as distinct lighting cases rather than assuming that a single direction model is sufficient for all of them.
SH-Based Environmental Lighting Analysis
One of the more experimental parts of the project is using spherical harmonics not only as ambient color, but also as a source of approximate directional information.
A common use of spherical harmonics is:
Surface normal → Evaluate SH → Ambient color
The project explores evaluating environmental lighting in several directions:
[ +X,,-X,,+Y,,-Y,,+Z,,-Z ]
For example:
[ D_x = Y(+X)-Y(-X) ]
[ D_y = Y(+Y)-Y(-Y) ]
[ D_z = Y(+Z)-Y(-Z) ]
An approximate environmental lighting direction can then be constructed:
[ \mathbf{L}_{env}
\operatorname{normalize} \left( D_x,D_y,D_z \right) ]
This does not attempt to reconstruct the complete lighting environment.
Instead, it provides a useful approximation for questions such as:
«Which side of the character is receiving more environmental light?»
That information can then act as a fallback when a strong direct-light direction is unavailable.
Form Shadow and Cast Shadow Separation
Another important design decision was separating:
- form shadow, caused by the orientation of the surface,
- cast shadow, caused by light occlusion.
These are visually different phenomena.
Form shadow can be derived from:
[ \mathbf{N}\cdot\mathbf{L} ]
while cast shadow comes from the shadow attenuation provided by the rendering pipeline.
Instead of permanently multiplying both together, the project allows them to be controlled independently.
Conceptually:
[ S_{final}
S_{form} \cdot \operatorname{lerp} \left( 1,, S_{cast},, k_{cast} \right) ]
This makes it possible to create styles such as:
Strong toon form shadow + Soft or reduced cast shadow
or:
Soft form shading + Strong environmental cast shadows
This separation gives artists considerably more control over the final appearance.
What I Learned
The most important lesson was that lighting accuracy and visual stability are not the same thing.
A physically accurate light vector may still produce an undesirable toon result.
At the same time, an aggressively stabilized light direction may produce a visually consistent character but disconnect the character from the surrounding scene.
The better solution is not necessarily to choose one approach.
It is to determine when each approach should dominate.
I also learned that the Built-In Render Pipeline still exposes a surprisingly useful set of lighting information when its rendering structure is treated as part of the shader design.
The distinction between:
- ForwardBase,
- ForwardAdd,
- direct lighting,
- spherical harmonics,
- Light Probes,
- Reflection Probes,
- shadow attenuation,
is not merely an implementation detail.
It can become part of the artistic lighting model itself.
Another important lesson was that modular shader architecture changes how a feature should be designed.
A lighting correction system should not require every module to independently modify the light direction.
That would create conflicting assumptions between:
- shade,
- face shading,
- specular,
- hair highlights,
- rim lighting,
- future material modules.
A better architecture is to calculate shared lighting information once and allow individual modules to consume it according to their own needs.
For example:
Direct Specular → Mostly physical light direction
Body Toon Shade → Resolved stable light direction
Face SDF → Character-relative resolved direction
Hair Highlight → Physical direction with hair-specific modification
This makes the system more flexible without forcing all shading effects to use exactly the same interpretation of the light.
Challenges
Balancing Physical Accuracy and Artistic Stability
The hardest problem is deciding when the real light direction should be trusted.
A simple fixed blend is not sufficient.
For example:
70% physical light 30% head-relative light
may work in one environment but fail in another.
A strong spotlight should probably be followed almost completely.
A weak ambient environment may require significantly more stabilization.
The challenge is therefore designing a confidence model that behaves predictably across many lighting conditions without becoming overly complex.
Multiple Lights
Multiple lights create another fundamental problem.
A simple weighted average of multiple light directions can become unstable.
For two opposing lights:
[ \mathbf{L}_1 \approx -\mathbf{L}_2 ]
their sum becomes:
[ \mathbf{L}_1+\mathbf{L}_2 \approx \mathbf{0} ]
Normalizing this result does not produce a meaningful dominant direction.
This is one reason the project avoids treating every light as if it must be compressed into a single final vector.
Additional lights can instead be processed independently through the ForwardAdd path.
Maintaining Compatibility with Built-In RP
The project intentionally avoids depending on features specific to Scriptable Render Pipelines.
That means the design must work within the information and pass structure available in the Built-In Render Pipeline.
This constraint removes some modern rendering techniques, but it also keeps the project focused.
The goal is not to reproduce HDRP inside a toon shader.
The goal is to make better use of the lighting information that Built-In RP already provides.
Avoiding Module Conflicts
ShaderCore's modular architecture is powerful, but it means shared state must be handled carefully.
If several modules independently modify the same lighting direction, the final result can become dependent on module order.
The project therefore treats lighting interpretation as infrastructure.
The ideal architecture is:
Raw Built-In Lighting Data ↓ Lighting Resolver ↓ Shared Lighting State ↓ Shade / Face / Hair / Specular / Other Modules
This reduces duplicated logic and makes future lighting modules easier to develop.
Performance
Every additional lighting calculation has a cost.
Potentially expensive operations include:
- repeated normalization,
- multiple SH evaluations,
- additional texture sampling,
- complex per-light ForwardAdd logic.
The challenge is therefore to distinguish between information that should be calculated once and information that genuinely needs per-pixel or per-light evaluation.
The project prioritizes techniques that reuse existing Built-In RP lighting data rather than introducing new rendering passes or external scene systems.
Future Direction
The lighting foundation can support several additional modules in the future.
Possible extensions include:
- Reflection Probe based environment reflection,
- thin-material transmission,
- dual-lobe specular,
- generic anisotropic reflection,
- clear-coat layering,
- fabric sheen,
- more advanced shadow-edge shaping.
However, the priority is to first improve the quality of the lighting information consumed by the shader.
A complex material model cannot fully compensate for incorrect or unstable lighting interpretation.
The long-term goal is therefore not simply to add more shader features.
It is to create a modular system where physically meaningful lighting information and stylized rendering can coexist.
The project can be summarized as:
«Use the real light when it is useful, stabilize it when it is not, and allow each shading module to choose the interpretation that best matches its visual purpose.»
That principle became the foundation of the project.
Log in or sign up for Devpost to join the conversation.