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

13 KiB
Raw Permalink Blame History

UShaderLab 说明

English | 中文

ExampleImage

UShaderLab 用来在 Unreal Engine 里用接近 HLSL 的文本语法编写材质。使用者编写 .usl 文件,插件会把它解析成材质图,并通过创建 ShaderLab Material Instance 暴露可调参数。

基本工作流

  1. <Project>/Shaders 或某个插件的 <Plugin>/Shaders 目录下创建 .usl 文件。
  2. 文件顶部#include "/Plugin/ShaderLab/ShaderLab.ush",便于 IDE 识别 DSL 宏和 UE_ 内建函数。
  3. SL_SETTINGS 描述材质设置,用 SL_PROPERTY 声明实例参数。
  4. SL_SURFACESL_UNLITSL_POSTPROCESS 等入口编写材质主体。
  5. 在 Content Browser 里创建 ShaderLab Material Instance,选择对应的 ShaderLab 基材质。
  6. 在生成的实例上调参数并把实例赋给网格、UI、后处理或其它承载对象。

推荐在编辑器控制台执行一次:

ShaderLab.IDE.Prepare

它会为 .usl.uslfunc 准备基础的 HLSL 识别和路径映射,让 IDE 补全更舒服。

VSCode推荐安装shader-validator插件,已经支持该插件的补全

最小示例

下面是一个最简单的 Surface 材质:

#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;
}

文件名就是这个 ShaderLab 材质的名字来源。在内容浏览器Material ▸ ShaderLab Material Instance创建,选择器里选择它,然后在实例详情面板里调整 BaseColorRoughness

文件类型

.usl 是可生成材质的 ShaderLab 文件理解为UMaterial。一个 .usl 对应一个可被实例化的 ShaderLab 材质。

.uslfunc 是函数库文件理解为UMaterialFunction。它不能直接生成材质只能被 .usl 或其它 .uslfunc 引入,用来复用函数和参数。

#include "/Project/Lib/MyLibrary.uslfunc"

路径通常使用虚拟 shader 路径,例如:

/Project/...
/Plugin/<PluginName>/...
/Engine/...

常用设置

SL_SETTINGS 写在文件顶层,用来描述材质域、混合模式和常见材质设置。

SL_SETTINGS(Domain = Surface, BlendMode = Masked, TwoSided = true, OpacityMaskClipValue = 0.4)

常用 Domain

  • Surface:普通表面材质。
  • PostProcess:后处理材质。
  • UIUI 材质。
  • Decal:贴花材质。
  • Volume:体积材质。
  • LightFunction:灯光函数材质。

常用 BlendMode

  • Opaque
  • Masked
  • Translucent
  • Additive
  • Modulate
  • AlphaComposite
  • AlphaHoldout

参数声明

输入参数主要有两类:SL_PROPERTY 声明材质实例上的可调参数,SL_COLLECTION 声明来自 Material Parameter Collection 的全局输入。

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;

常见类型对应关系:

  • floatScalar 参数。
  • float3Color 参数。
  • float4Vector 参数。
  • Texture2DTextureCube:贴图参数。
  • #define Name 0/1Static Bool 参数,可用于 #if
  • SL_COLLECTION(...) floatN Name;:材质参数集合输入,不是实例旋钮。

常用 specifier

  • Category:实例面板里的分类。
  • SortPriority:排序优先级。
  • ClampMinClampMaxScalar 滑条范围。
  • DefaultTexture:贴图默认值,可用 whiteblackgreynormal 或资产路径。
  • CustomPrimitiveData:让 Scalar 或 Vector 从组件的 Custom Primitive Data 读取。

SL_COLLECTION 常用写法:

SL_COLLECTION(Path = "/Game/YourFolder/MPC_Global", Parameter = "Wind")
float3 Wind;

SL_SURFACE()
void Surface(inout FShaderLabSurface S)
{
    S.EmissiveColor = abs(Wind);
}

Static Bool 可以直接控制预处理分支:

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
}

在材质实例里覆盖 Static Bool 后,会按对应排列重新编译。

Surface 材质

普通表面材质最常用 SL_SURFACE。函数参数是 inout FShaderLabSurface,只需要填写你关心的字段,没写的字段会使用默认值。

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 = 18 个 SlabBSDF 着色引脚 加上 材质级(整材质)输出字段,所以简单材质可以在一个 body 里全部写完。常用字段:

  • 着色SlabBSDF 引脚):DiffuseAlbedoF0F90RoughnessAnisotropyNormalTangentEmissiveColorSSSMFPSecondRoughnessFuzz*Glint*
  • 材质级:OpacityOpacityMaskRefractionPixelDepthOffsetAmbientOcclusionSurfaceThickness

材质级字段仅在匹配的 Domain/BlendMode 下生效(建图期按引擎 IsPropertyActive 做契约校验,不匹配则映射 .usl 行报错):OpacityMaskMaskedRefraction 需半透明混合 + RefractionMethod = RM_IndexOfRefractionSurfaceThicknessbIsThinSurface = trueOpacity 是覆盖率Substrate 下引擎的 MP_Opacity 仅对 AlphaComposite 激活,因此半透明/贴花的覆盖率由建图器自动在整材质外包一层 Substrate Weight 节点实现(你只需写 S.Opacity)。

如果需要在 body 里使用依赖材质上下文的变换函数,可以显式声明 Parameters

SL_SURFACE()
void Surface(FMaterialPixelParameters Parameters, inout FShaderLabSurface S)
{
    float3 n = UE_TransformTangentVectorToWorld(Parameters, float3(0, 0, 1));
    S.Normal = n;
}

其它材质入口

除了 SL_SURFACE,还可以使用其它入口来表达不同类型的材质。

SL_UNLIT()
void Unlit(inout FShaderLabUnlit U)
{
    U.EmissiveColor = float3(1, 0.5, 0.1);
}

常用入口:

  • SL_UNLIT:无光照材质。
  • SL_HAIR:头发材质。
  • SL_EYE:眼睛材质。
  • SL_WATERSingle Layer Water。
  • SL_CLEARCOAT:清漆材质。
  • SL_TOON:卡通材质。
  • SL_VOLUME:体积材质,需要 Domain = Volume
  • SL_LIGHTFUNCTION:灯光函数材质,需要 Domain = LightFunction
  • SL_POSTPROCESS:后处理材质,需要 Domain = PostProcess
  • SL_UIUI 材质,需要 Domain = UI

多层材质

多个 SL_SLAB 可以通过 SL_FRONTMATERIAL 组合成一个 Substrate 材质。Slab 是纯 BSDF 层,参数为 inout FShaderLabSlab(只有 18 个 SlabBSDF 引脚,没有 Opacity 等材质级字段)。整材质输出写在单独的 SL_MATERIAL 块里(见下)。

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))

常用组合:

  • VerticalLayer(Top, Base, Thickness):上下层叠。
  • HorizontalMix(Background, Foreground, Mix):横向混合。
  • Add(A, B):相加。
  • Weight(A, Weight):权重。
  • Select(A, B, Threshold):选择。

混合因子可以是浮点字面量、Scalar 参数,或一个 SL_VALUE

SL_VALUE()
float EdgeMask()
{
    return saturate(UE_TextureCoordinate(0).x);
}

SL_FRONTMATERIAL(HorizontalMix(Base, Coat, EdgeMask))

多层材质的整材质输出(单入口 SL_SURFACE 里是 S.*)写在 SL_MATERIAL 块里,填 FShaderLabMaterialOutput——一个 Custom 节点输出全部字段(共享计算)。它与 SL_SURFACE 互斥。

SL_MATERIAL()
void Material(inout FShaderLabMaterialOutput O)
{
    O.OpacityMask = Texture2DSample(MaskTex, MaskTexSampler, UE_TextureCoordinate(0)).a;
}

顶点与插值

SL_VERTEX 用于输出顶点阶段数据,比如世界位置偏移。

SL_PROPERTY(Category = "Vertex")
float Offset = 10.0;

SL_VERTEX()
void Vertex(inout FShaderLabVertex V)
{
    V.WorldPositionOffset = float3(0, 0, Offset);
}

SL_VERTEX 还能在顶点频率计算 UV写入 V.CustomizedUV0..7(插值到像素,用 UE_TextureCoordinate(i) 读回)。写 V.CustomizedUV<i> 必须SL_SETTINGS(NumCustomizedUVs = N)N > i——引擎只分配前 NumCustomizedUVs 组(默认 0其余槽位直接透传原始顶点 texcoordShaderLab 会把「写了 UV 但 N ≤ i」变成建图错误而非静默失效。示例见 Shaders/Examples/CustomizedUV.usl

SL_INTERPOLATOR 用于在顶点阶段计算一个值,并在像素阶段读取。

SL_INTERPOLATOR()
float3 VertexNormalColor()
{
    return abs(UE_VertexNormalWS());
}

SL_SURFACE()
void Surface(inout FShaderLabSurface S)
{
    S.DiffuseAlbedo = UE_Interpolator(VertexNormalColor);
}

插值器适合把顶点频率的结果带到像素阶段。不要把只适合像素阶段的采样逻辑放进 SL_INTERPOLATOR

函数库 .uslfunc

.uslfunc 用来复用材质函数。库里可以声明 SL_FUNCTION,也可以声明自己的 SL_PROPERTY。当一个 .usl 引入库时,库里的参数会提升到这个材质实例上。

库文件:

#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);
}

材质文件:

#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));
}

如果多个库里有同名参数,可以用 SL_IMPORT 给提升出来的参数加命名空间前缀。

SL_IMPORT(Namespace = "Detail")
#include "/Project/Lib/Detail.uslfunc"

常用 UE_ 内建函数

ShaderLab 通过 UE_ 前缀暴露常见的 UE 材质节点。它们可以在 .usl body 中像普通函数一样调用。

常用类别:

  • UVUE_TextureCoordinate(0)
  • 时间:UE_Time()
  • 几何数据:UE_VertexNormalWS()UE_PixelNormalWS()UE_CameraVectorWS()
  • 对象和实例对象位置、对象半径、Actor 位置、Custom Primitive Data。
  • 坐标变换:UE_TransformVector...UE_TransformPosition...
  • 噪声和旋转:UE_Noise(...)UE_RotateAboutAxis(...)
  • 场景纹理:UE_SceneTexture(...)UE_SceneDepth(...)UE_CustomDepth(...)UE_CustomStencil(...)
  • 距离场:UE_DistanceToNearestSurface(...)UE_DistanceFieldGradient(...)

示例:

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));
}

材质实例与使用注意

ShaderLab 材质应该通过 ShaderLab Material Instance 使用。

Usage 不写在 .usl 里。把实例赋给静态网格、骨骼网格、粒子、UI 等对象时,按 UE 的材质实例规则设置或自动记录对应 usage。打包前需要确保实际使用到的 usage 已经在编辑器阶段确定。

.usl.uslfunc 会从项目或插件的 Shaders 目录参与打包。材质实例引用的贴图、MPC、Profile 等资源仍应按正常 UE 资产规则存在于工程内容中。

实现思路

使用者可以把 UShaderLab 理解成三层:

  • .usl 是材质源码。它保持接近 HLSL 的写法,用 SL_ 宏补充材质设置、参数和入口信息。
  • 插件在编辑器里解析 .usl把参数、Custom HLSL、Substrate BSDF 和必要的辅助节点组成 UE 材质图。
  • 最终给场景使用的是 ShaderLab Material Instance。实例保存参数覆盖并负责拥有可渲染所需的编译结果。

静态开关 #define 会作为材质实例的 Static Bool 参数暴露。实例覆盖后,相关 #if 会按当前排列生效,所以可以把不同分支写成真正的编译期分支。

基材质主要是模板,日常使用时只需要创建和保存 ShaderLab Material Instance。