Files
UShaderLab/README.md
2026-07-05 13:23:08 +08:00

421 lines
14 KiB
Markdown

# UShaderLab Guide
[English](./README.md) | [中文](./Docs/README_CN.md)
![ExampleImage](./Docs/ExampleImage.png)
UShaderLab lets you write Unreal Engine materials in a text format that feels close to HLSL. You write `.usl` files, and the plugin parses them into material graphs with editable parameters exposed through ShaderLab Material Instances.
## Basic Workflow
1. Create a `.usl` file under `<Project>/Shaders` or a plugin's `<Plugin>/Shaders` directory.
2. Add `#include "/Plugin/ShaderLab/ShaderLab.ush"` at the top so the IDE can recognize DSL macros and `UE_` intrinsics.
3. Use `SL_SETTINGS` for material settings, and `SL_PROPERTY` for instance parameters.
4. Write the material body with entries such as `SL_SURFACE`, `SL_UNLIT`, or `SL_POSTPROCESS`.
5. In the Content Browser, create a `ShaderLab Material Instance` and choose the corresponding ShaderLab base material.
6. Adjust parameters on the generated instance, then assign it to meshes, UI, post process volumes, or other supported targets.
It is recommended to run this once in the editor console:
```text
ShaderLab.IDE.Prepare
```
This prepares basic HLSL recognition and path mapping for `.usl` and `.uslfunc`, making IDE completion more useful.
> VSCode users are recommended to install the [shader-validator](https://marketplace.visualstudio.com/items?itemName=antaalt.shader-validator) extension. UShaderLab completion is already supported.
## Minimal Example
Here is a minimal Surface material:
```hlsl
#include "/Plugin/ShaderLab/ShaderLab.ush"
SL_SETTINGS(Domain = Surface, BlendMode = Opaque)
SL_PROPERTY(Category = "Surface")
float3 BaseColor = float3(0.8, 0.2, 0.1);
SL_PROPERTY(Category = "Surface", ClampMin = 0, ClampMax = 1)
float Roughness = 0.5;
SL_SURFACE()
void Surface(inout FShaderLabSurface S)
{
S.DiffuseAlbedo = BaseColor;
S.Roughness = Roughness;
}
```
The file name is used as the ShaderLab material name. Create an instance from **Material > ShaderLab Material Instance** in the Content Browser, choose this material in the picker, then edit `BaseColor` and `Roughness` in the instance details panel.
## File Types
`.usl` is a ShaderLab file that generates a material. You can think of it as a text-authored `UMaterial`. One `.usl` corresponds to one instantiable ShaderLab material.
`.uslfunc` is a function library file. You can think of it as a text-authored `UMaterialFunction`. It does not generate a material by itself. It can only be included by `.usl` or other `.uslfunc` files to share functions and parameters.
```hlsl
#include "/Project/Lib/MyLibrary.uslfunc"
```
Paths usually use virtual shader paths:
```text
/Project/...
/Plugin/<PluginName>/...
/Engine/...
```
## Common Settings
`SL_SETTINGS` is written at the top level and describes the material domain, blend mode, and common material settings.
```hlsl
SL_SETTINGS(Domain = Surface, BlendMode = Masked, TwoSided = true, OpacityMaskClipValue = 0.4)
```
Common `Domain` values:
- `Surface`: regular surface material.
- `PostProcess`: post process material.
- `UI`: UI material.
- `Decal`: decal material.
- `Volume`: volume material.
- `LightFunction`: light function material.
Common `BlendMode` values:
- `Opaque`
- `Masked`
- `Translucent`
- `Additive`
- `Modulate`
- `AlphaComposite`
- `AlphaHoldout`
## Parameter Declarations
There are two main kinds of input parameters: `SL_PROPERTY` declares editable parameters on the material instance, while `SL_COLLECTION` declares global inputs from a Material Parameter Collection.
```hlsl
SL_PROPERTY(Category = "Surface")
float3 Tint = float3(1, 1, 1);
SL_PROPERTY(Category = "Surface", ClampMin = 0, ClampMax = 1)
float Metallic = 0.0;
SL_PROPERTY(Category = "Textures", DefaultTexture = "white")
Texture2D AlbedoTex;
SamplerState AlbedoTexSampler;
SL_PROPERTY(Category = "Switches")
#define UseTexture 1
SL_COLLECTION(Path = "/Game/YourFolder/MPC_Global", Parameter = "Wind")
float3 Wind;
```
Common type mapping:
- `float`: Scalar parameter.
- `float3`: Color parameter.
- `float4`: Vector parameter.
- `Texture2D`, `TextureCube`: texture parameter.
- `#define Name 0/1`: Static Bool parameter, usable in `#if`.
- `SL_COLLECTION(...) floatN Name;`: Material Parameter Collection input, not an instance knob.
Common specifiers:
- `Category`: category in the instance details panel.
- `SortPriority`: sorting priority.
- `ClampMin`, `ClampMax`: Scalar slider range.
- `DefaultTexture`: default texture, such as `white`, `black`, `grey`, `normal`, or an asset path.
- `CustomPrimitiveData`: reads a Scalar or Vector from component Custom Primitive Data.
Common `SL_COLLECTION` usage:
```hlsl
SL_COLLECTION(Path = "/Game/YourFolder/MPC_Global", Parameter = "Wind")
float3 Wind;
SL_SURFACE()
void Surface(inout FShaderLabSurface S)
{
S.EmissiveColor = abs(Wind);
}
```
Static Bool parameters can directly control preprocessor branches:
```hlsl
SL_PROPERTY(Category = "Switches")
#define UseTexture 1
SL_SURFACE()
void Surface(inout FShaderLabSurface S)
{
#if UseTexture
S.DiffuseAlbedo = float3(1, 1, 1);
#else
S.DiffuseAlbedo = float3(0.2, 0.2, 0.2);
#endif
}
```
When a Static Bool is overridden in a material instance, the corresponding permutation is recompiled.
## Surface Materials
Most regular surface materials use `SL_SURFACE`. The function takes `inout FShaderLabSurface`; fill only the fields you care about, and unspecified fields keep their defaults.
```hlsl
SL_SURFACE()
void Surface(inout FShaderLabSurface S)
{
float2 uv = UE_TextureCoordinate(0);
S.DiffuseAlbedo = Texture2DSample(AlbedoTex, AlbedoTexSampler, uv).rgb * Tint;
S.Roughness = Roughness;
S.EmissiveColor = float3(0, 0, 0);
}
```
`FShaderLabSurface` = the 18 SlabBSDF shading pins **plus** the material-level (whole-material) outputs, so a
simple material writes everything in one block. Common fields:
- Shading (SlabBSDF pins): `DiffuseAlbedo`, `F0`, `F90`, `Roughness`, `Anisotropy`, `Normal`, `Tangent`,
`EmissiveColor`, `SSSMFP`, `SecondRoughness`, `Fuzz*`, `Glint*`, …
- Material-level: `Opacity`, `OpacityMask`, `Refraction`, `PixelDepthOffset`, `AmbientOcclusion`, `SurfaceThickness`
The material-level fields are only active for the matching `Domain`/`BlendMode` (validated at build with a
`.usl`-line error, mirroring the engine): `OpacityMask` needs `Masked`; `Refraction` needs a translucent blend +
`RefractionMethod = RM_IndexOfRefraction`; `SurfaceThickness` needs `bIsThinSurface = true`. **`Opacity` is
coverage**: under Substrate the engine's `MP_Opacity` is only active for `AlphaComposite`, so translucent/decal
coverage is applied automatically by wrapping the material in a Substrate Weight node (you just write `S.Opacity`).
If a body needs transform helpers that depend on the material context, declare `Parameters` explicitly:
```hlsl
SL_SURFACE()
void Surface(FMaterialPixelParameters Parameters, inout FShaderLabSurface S)
{
float3 n = UE_TransformTangentVectorToWorld(Parameters, float3(0, 0, 1));
S.Normal = n;
}
```
## Other Material Entries
In addition to `SL_SURFACE`, other entries can express different material types.
```hlsl
SL_UNLIT()
void Unlit(inout FShaderLabUnlit U)
{
U.EmissiveColor = float3(1, 0.5, 0.1);
}
```
Common entries:
- `SL_UNLIT`: unlit material.
- `SL_HAIR`: hair material.
- `SL_EYE`: eye material.
- `SL_WATER`: Single Layer Water.
- `SL_CLEARCOAT`: clear coat material.
- `SL_TOON`: toon material.
- `SL_VOLUME`: volume material, requires `Domain = Volume`.
- `SL_LIGHTFUNCTION`: light function material, requires `Domain = LightFunction`.
- `SL_POSTPROCESS`: post process material, requires `Domain = PostProcess`.
- `SL_UI`: UI material, requires `Domain = UI`.
## Layered Materials
Multiple `SL_SLAB` blocks can be combined into one Substrate material with `SL_FRONTMATERIAL`. A slab is a
pure BSDF layer, so it takes `inout FShaderLabSlab` (the 18 SlabBSDF pins only — no material-level fields like
Opacity). Whole-material outputs go in an `SL_MATERIAL` block (see below).
```hlsl
SL_PROPERTY(Category = "Layer", ClampMin = 0, ClampMax = 1)
float Mix = 0.5;
SL_SLAB()
void Base(inout FShaderLabSlab S)
{
S.DiffuseAlbedo = float3(0.1, 0.1, 0.1);
S.Roughness = 0.8;
}
SL_SLAB()
void Coat(inout FShaderLabSlab S)
{
S.DiffuseAlbedo = float3(0.8, 0.2, 0.1);
S.Roughness = 0.2;
}
SL_FRONTMATERIAL(HorizontalMix(Base, Coat, Mix))
```
Common composition operators:
- `VerticalLayer(Top, Base, Thickness)`: layer one material over another.
- `HorizontalMix(Background, Foreground, Mix)`: horizontal mix.
- `Add(A, B)`: add.
- `Weight(A, Weight)`: weight.
- `Select(A, B, Threshold)`: select.
A mix factor can be a float literal, a Scalar parameter, or an `SL_VALUE`.
```hlsl
SL_VALUE()
float EdgeMask()
{
return saturate(UE_TextureCoordinate(0).x);
}
SL_FRONTMATERIAL(HorizontalMix(Base, Coat, EdgeMask))
```
For a layered material, the whole-material outputs (which a single `SL_SURFACE` would carry as `S.*`) go in an
`SL_MATERIAL` block filling `FShaderLabMaterialOutput` — one Custom node feeds all of them (shared computation).
It is mutually exclusive with `SL_SURFACE`.
```hlsl
SL_MATERIAL()
void Material(inout FShaderLabMaterialOutput O)
{
O.OpacityMask = Texture2DSample(MaskTex, MaskTexSampler, UE_TextureCoordinate(0)).a;
}
```
## Vertex And Interpolator
`SL_VERTEX` outputs vertex-stage data, such as world position offset.
```hlsl
SL_PROPERTY(Category = "Vertex")
float Offset = 10.0;
SL_VERTEX()
void Vertex(inout FShaderLabVertex V)
{
V.WorldPositionOffset = float3(0, 0, Offset);
}
```
`SL_VERTEX` can also compute UVs at vertex frequency into `V.CustomizedUV0..7` (interpolated to the pixel shader,
read back with `UE_TextureCoordinate(i)`). Writing `V.CustomizedUV<i>` **requires** `SL_SETTINGS(NumCustomizedUVs = N)`
with `N > i` — the engine only allocates the first `NumCustomizedUVs` slots (default 0) and otherwise passes the
raw vertex texcoord through. ShaderLab turns a missing/too-small `NumCustomizedUVs` into a build error rather than a
silent no-op. See `Shaders/Examples/CustomizedUV.usl`.
`SL_INTERPOLATOR` computes a value in the vertex stage and reads it in the pixel stage.
```hlsl
SL_INTERPOLATOR()
float3 VertexNormalColor()
{
return abs(UE_VertexNormalWS());
}
SL_SURFACE()
void Surface(inout FShaderLabSurface S)
{
S.DiffuseAlbedo = UE_Interpolator(VertexNormalColor);
}
```
Interpolators are useful for passing vertex-frequency results to the pixel stage. Do not put pixel-only sampling logic inside `SL_INTERPOLATOR`.
## Function Libraries .uslfunc
`.uslfunc` files are used to share material functions. A library can declare `SL_FUNCTION` and its own `SL_PROPERTY`. When a `.usl` includes the library, the library parameters are promoted onto that material instance.
Library file:
```hlsl
#include "/Plugin/ShaderLab/ShaderLab.ush"
SL_PROPERTY(Category = "Detail", ClampMin = 0, ClampMax = 1)
float DetailStrength = 0.5;
SL_FUNCTION()
float3 ApplyDetail(float3 color, float2 uv)
{
float stripes = frac(uv.x * 12.0);
return lerp(color, color * stripes, DetailStrength);
}
```
Material file:
```hlsl
#include "/Plugin/ShaderLab/ShaderLab.ush"
#include "/Project/Lib/Detail.uslfunc"
SL_PROPERTY(Category = "Surface")
float3 BaseColor = float3(0.8, 0.8, 0.8);
SL_SURFACE()
void Surface(inout FShaderLabSurface S)
{
S.DiffuseAlbedo = ApplyDetail(BaseColor, UE_TextureCoordinate(0));
}
```
If multiple libraries contain parameters with the same name, use `SL_IMPORT` to add a namespace prefix to promoted parameters.
```hlsl
SL_IMPORT(Namespace = "Detail")
#include "/Project/Lib/Detail.uslfunc"
```
## Common UE_ Intrinsics
ShaderLab exposes common UE material nodes through the `UE_` prefix. They can be called like regular functions inside a `.usl` body.
Common categories:
- UV: `UE_TextureCoordinate(0)`.
- Time: `UE_Time()`.
- Geometry data: `UE_VertexNormalWS()`, `UE_PixelNormalWS()`, `UE_CameraVectorWS()`.
- Object and instance data: object position, object radius, actor position, Custom Primitive Data.
- Coordinate transforms: `UE_TransformVector...`, `UE_TransformPosition...`.
- Noise and rotation: `UE_Noise(...)`, `UE_RotateAboutAxis(...)`.
- Scene textures: `UE_SceneTexture(...)`, `UE_SceneDepth(...)`, `UE_CustomDepth(...)`, `UE_CustomStencil(...)`.
- Distance fields: `UE_DistanceToNearestSurface(...)`, `UE_DistanceFieldGradient(...)`.
Example:
```hlsl
SL_SURFACE()
void Surface(inout FShaderLabSurface S)
{
float t = UE_Time();
float2 uv = UE_TextureCoordinate(0);
float3 normalColor = abs(UE_VertexNormalWS());
S.EmissiveColor = normalColor * (0.5 + 0.5 * sin(t + uv.x * 6.28318));
}
```
## Material Instances And Usage Notes
ShaderLab materials should be used through `ShaderLab Material Instance`.
Usage is not declared in `.usl`. When assigning an instance to static meshes, skeletal meshes, particles, UI, or other targets, use the normal UE material instance rules to set or record the corresponding usage. Before packaging, make sure the usages you actually need have been determined in the editor.
`.usl` and `.uslfunc` files under project or plugin `Shaders` directories participate in packaging. Textures, MPCs, Profiles, and other assets referenced by material instances should still exist as normal UE content assets.
## Implementation Idea
As a user, you can think of UShaderLab as three layers:
- `.usl` is the material source. It stays close to HLSL, with `SL_` macros for material settings, parameters, and entries.
- In the editor, the plugin parses `.usl` and builds a UE material graph from parameters, Custom HLSL, Substrate BSDFs, and required helper nodes.
- What you use in the scene is a ShaderLab Material Instance. The instance stores parameter overrides and owns the compiled result needed for rendering.
Static switches declared with `#define` are exposed as Static Bool parameters on the material instance. After an instance override, related `#if` branches are evaluated for that permutation, so different branches can be written as real compile-time branches.
The base material is mainly a template. In daily use, you only need to create and save ShaderLab Material Instances.