[UE5] Anime/Toon/Cel Shading Model (WORKS WITH LAUNCHER ENGINE VERSIONS)

Share some interesting things :grinning_face:

really like your shader, and I’m trying to integrate it with a regular scene. The goal is to make them visually consistent within the same lighting environment (previously I set up lighting channels to make them work together) and under the condition that Lumen is enabled.

Increase the direct light for other shading models in DeferredLightPixelShaders.usf.

#endif // USE_ADAPTIVE_VOLUMETRIC_SHADOW_MAP
float4 Radiance = GetDynamicLighting(DerivedParams.TranslatedWorldPosition, DerivedParams.CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, LightData, LightAttenuation, Dither, uint2(InputParams.PixelPos), SurfaceShadow);
if (ScreenSpaceData.GBuffer.ShadingModelID != SHADINGMODELID_CLEAR_COAT)
{
    Radiance*=4;//Multiplying directly seems a bit reckless?
}
OutColor += Radiance;
#endif // SUBTRATE_GBUFFER_FORMAT==1

Exclude the Lumen ambient light received by SelShader in DiffuseIndirectComposite.usf.

    // Accumulate lighting into the final buffers
#if SUBTRATE_GBUFFER_FORMAT==0
    if (Material.GBufferData.ShadingModelID != SHADINGMODELID_CLEAR_COAT)
    {
       IndirectLighting.Specular *= GetSSSCheckerboadSpecularScale(PixelPos, Material.bNeedsSeparateLightAccumulation);
       FLightAccumulator LightAccumulator = (FLightAccumulator)0;
       LightAccumulator_Add(
          LightAccumulator,
          IndirectLighting.Diffuse + IndirectLighting.Specular,
          IndirectLighting.Diffuse,
          1.0f,
          Material.bNeedsSeparateLightAccumulation);
       OutAddColor = LightAccumulator_GetResult(LightAccumulator);
    }

#endif // SUBTRATE_GBUFFER_FORMAT==0

Under the same directional light source, they no longer appear visually separated:

Now, with exposure turned off and light intensity set to 1, they no longer appear to be from two different worlds.

This is what I got:


Ordinary objects can receive Lumen ambient light from the SelShader

Use FaceMap in unreal engine :upside_down_face:

Just look at the changes in facial shadows

Facemap looks like this:

// ==== Inputs ====
// Input(0): LightDir (float3)      - 世界空间下的太阳方向(指向光线方向)
// Input(2): UV (float2)            - 原始纹理UV
// Input(3): OffsetAngle (float)    - 角度偏移值(弧度)
// ==== Output ====
// float3(x: 新UV.x, y: 新UV.y, z: 输出值)

// 预定义常量
static const float EPSILON = 1e-6f;
static const float HALF_PI = 1.5708f;
static const float TWO_PI = 6.283185307f;

// --- 1. 物体旋转计算 ---
float3x3 WtoL = LWCToFloat(GetPrimitiveData(Parameters.PrimitiveId).WorldToLocal);

// 使用更快的长度计算并避免分支
float sy_sq = WtoL[0][0] * WtoL[0][0] + WtoL[1][0] * WtoL[1][0];
float sy = sqrt(sy_sq);

// 直接计算偏航角,避免完整欧拉角转换
float Yaw = (sy_sq < EPSILON * EPSILON) ? 0.0f : atan2(WtoL[1][0], WtoL[0][0]);

// 角度规范化
Yaw = fmod(fmod(Yaw, TWO_PI) + TWO_PI, TWO_PI);

// --- 2. 太阳方向计算 ---
// 假设LightDir已归一化,直接使用xy分量
float3 SunDir = float3(-LightDir.x, -LightDir.y, 0.0f);

// --- 3. 角度计算---
float cosYaw, sinYaw;
sincos(Yaw, sinYaw, cosYaw);

// 直接计算点积和叉积
float dotVal = cosYaw * SunDir.x + sinYaw * SunDir.y;
float crossVal = cosYaw * SunDir.y - sinYaw * SunDir.x;

// 计算角度并应用偏移
float angle = atan2(crossVal, dotVal) + OffsetAngle + HALF_PI;

// --- 4. 输出值计算 ---
float sinAngle, cosAngle;
sincos(angle, sinAngle, cosAngle);

float value = mad(-0.5f, cosAngle, 0.5f);

// --- 5. UV翻转 ---
float2 NewUV = float2(
    lerp(UV.x, 1.0f - UV.x, sinAngle > 0.0f),
    UV.y
);

// --- 6. 输出结果 ---
return float3(NewUV, value);

@Envieous Is it possible to show a transition area at the boundary between light and dark areas?

1 Like

No. Unreal does not produce a meaningful gradient between the two. This may be possible with raytraced lighting but I haven’t investigated that at all.

5.7 Version released.

Is there a way to change the number of bands and the distance of them in this shader. And can it be done in the editor or is this done in the source?

Unreal does not produce a meaningful gradient with dynamic shadows. The lighting information to produce multiple bands does not exist. What you want may be possible with raytracing, but I haven’t explored that.