Skip to main content

ShaderWorks

uexShaderWorks is a lightweight plugin module that provides a bridge between scene lights and custom material shaders. Every frame it collects visible light data, packs it into a GPU-friendly struct, and uploads it to the Scene Uniform Buffer. Any material can then access this data through Scene.AllLightParams in a Custom expression node, enabling fully custom lighting models without any C++ rendering code.

This module works in tandem with the Screen-Space Shadows (Bend Studio) pass. Because the SS Shadow pass reads its light data from the same Shader Works subsystem, custom-shaded surfaces and screen-space shadows are automatically aligned.


Architecture

The module consists of three pieces:

UuexShaderWorksSubsystem — An Engine Subsystem (auto-instantiated, no manual setup) that detects the active world, iterates lights using TActorIterator, caches their components, and updates a packed parameter struct each frame. It handles world transitions, actor spawns/destroys, and PIE/Game/Editor world detection automatically.

FuexShaderWorksSVE — A Scene View Extension that calls SyncAllLights() in SetupViewFamily (game thread) and uploads the packed params to SceneUniforms in PreRenderViewFamily_RenderThread (render thread). The split ensures light data is always current without race conditions.

FAllLightParams — The GPU-side struct registered as a Scene Uniform Buffer member. Contains sun direction/colour/intensity, 3-point sky ambient, and up to 8 point/spot lights with full spatial and photometric data.


Limitations

Shader Works provides light data only — it does not provide shadow information. Materials using Scene.AllLightParams will compute lighting without shadows from the engine's shadow system. The recommended approach is to use the Screen-Space Shadows pass alongside custom-lit materials. Because both systems read from the same subsystem, the most significant lights are always consistent between custom shading and shadow generation.


Usage in Material Editor

Access light data from any Custom expression node using the Scene.AllLightParams prefix. The plugin's Content directory contains modular and full shader sample materials demonstrating various shading approaches including common BRDFs.


Available Fields

All fields are accessed via Scene.AllLightParams.* in Custom nodes.

Sun / Directional Light

FieldTypeDescription
SunDirectionfloat3Direction the sun ray travels toward the surface. Negate this to get the L vector used in lighting equations.
SunIntensityfloatDirectional light intensity.
SunColorfloat3Directional light linear RGB colour.
SpecularPowerfloatShared specular exponent (default 32).

Sky Ambient

FieldTypeDescription
SkyColorUpfloat3Sky colour for upward-pointing normals.
SkyColorHorizonfloat3Sky colour for horizontal normals.
SkyColorDownfloat3Sky colour for downward-pointing normals (ground bounce).

Point / Spot Lights (array, max 8)

Access using index i (0–7). Check ActiveLightCount before iterating to avoid reading uninitialised entries.

FieldTypeDescription
LightPosRadius[i]float4xyz = world position, w = attenuation radius.
LightColorIntens[i]float4xyz = linear RGB colour, w = intensity.
LightDirType[i]float4xyz = direction (spot lights), w = type (0 = point, 1 = spot).
LightConeAngles[i]float4x = cos(inner angle), y = cos(outer angle).

Global

FieldTypeDescription
ActiveLightCountintNumber of valid entries in the light arrays (max 8).
GlobalExposurefloatFinal output multiplier applied across all custom lighting calculations.

Example: Basic Diffuse + Specular

The following Custom node snippet demonstrates a simple Blinn-Phong surface using ShaderWorks light data. It is provided as a starting reference — the plugin's sample materials contain more complete implementations.

// Access packed light data
float3 N = normalize(Parameters.WorldNormal);
float3 V = normalize(Parameters.CameraVector);

// Sun contribution
float3 L_sun = normalize(-Scene.AllLightParams.SunDirection);
float NdotL_sun = saturate(dot(N, L_sun));
float3 H_sun = normalize(L_sun + V);
float spec_sun = pow(saturate(dot(N, H_sun)), Scene.AllLightParams.SpecularPower);

float3 lighting = NdotL_sun * Scene.AllLightParams.SunColor * Scene.AllLightParams.SunIntensity;
lighting += spec_sun * Scene.AllLightParams.SunColor * Scene.AllLightParams.SunIntensity;

// Sky ambient (hemisphere approximation)
float upness = dot(N, float3(0,0,1)) * 0.5 + 0.5;
float3 ambient = lerp(Scene.AllLightParams.SkyColorDown,
Scene.AllLightParams.SkyColorUp, upness);
lighting += ambient;

return lighting * Scene.AllLightParams.GlobalExposure;

For point/spot lights, iterate up to Scene.AllLightParams.ActiveLightCount and apply standard distance attenuation using LightPosRadius[i].w as the attenuation radius.