DreamShaderLang
DreamShaderLang 2.0

DreamShaderLang 2.0

DreamShader 2.0 的 HLSL 形状语言 —— .dss 源文件、uniform 与文档指令、每个 export 一个资产,以及它和 1.x 语言怎么并存。

DreamShader 2.0 在 1.x 语言旁边加了第二门语言。材质被写成它骨子里本来的样子 —— 带声明的 HLSL: uniform 就是参数,/// 文档注释带着参数的元数据,#pragma material(...) 放材质设置,一个导出函数把材质填满。

项目值
文件.dss —— 编译单元 · .dsi —— 材质实例 · .dsh —— 共享头文件,可以同时装两种语言
产物每个文件至多一个材质;其余每个 export 各出一个材质函数、层或层混合
编译器和编 .dsm / .dsf 的是同一个:两个前端、一份图 IR、一个发射器
起始版本since 2.0.0

没有任何东西逼你迁移。 .dsm 由 legacy 前端读成和 .dss 一样的语法树,再由同一份代码去编 —— 拿四个真实源码根的 1.x 冻结图做过对拍,每个源文件都逐节点重建出同一张图。想要只有 2.0 才有的东西时再搬: 真正的表达式与控制流、#include、和材质放在一起的材质实例、经得起工具折腾的注释。 改写交给 dsc migrate,它先证明、后落盘。

一个材质

// M_TeleportGlow.dss
#pragma material(ShadingModel = Unlit, BlendMode = Additive)

/// @group Glow|Look   @desc 乘在粒子颜色上
uniform float4 Tint = float4(1, 1, 1, 1);

/// @group Glow|Look   @desc 整体自发光增益
/// @slider 0 4
uniform float Intensity = 0.7;

float GlowMask(float2 UV)                       // helper:内联进调用它的图
{
    float2 P = UV * 2.0 - 1.0;
    return pow(saturate(1.0 - dot(P, P)), 2.4);
}

export void M_TeleportGlow(inout material m)    // 入口:正是这个签名让它成为材质
{
    float2 UV = UE.TexCoord(Index = 0);
    m.EmissiveColor = Tint.rgb * GlowMask(UV) * Intensity;
}

资产以函数名命名,落在文件所在的位置 —— DShader/FX/M_TeleportGlow.dss 生成 /Game/FX/M_TeleportGlow。 /// @name /Game/Somewhere/Else 可以改写这个去处。

一个材质函数

// MF_ToonUV.dss
/// @asset /MoonToon/MaterialFunctions/Utils/MF_UVChannelSwitch
extern float2 MF_UVChannelSwitch(float UVChannelIndex);      // 一个已经存在的资产

/// @library MoonToon|Shared
/// @param UVChannel    读哪个 UV 通道。
export float2 MF_ToonUV(float UVChannel = 0.0, float4 ScaleOffset = float4(1, 1, 0, 0))
{
    float2 Selected = MF_UVChannelSwitch(UVChannel);
    return Selected * ScaleOffset.xy + ScaleOffset.zw;
}

形参就是输入,带默认值的形参是可选输入,返回值是第一个输出,out 形参是其余输出。

函数是什么

声明变成
export void M(inout material m)本文件的材质 —— 至多一个
export T F(...)一个材质函数资产
/// @layer + export void L(inout material m)材质层
/// @layerblend + export void B(material Bottom, material Top, inout material Result)材质层混合
T Helper(...) —— 不带链接关键字自己不产出任何东西:内联进每个调用者的图
/// @custom + 函数一个 Custom 节点;函数体是 HLSL,原样交给 shader 编译器
/// @asset <path> + extern T F(...);对一个已存在的材质函数资产的调用

参数

uniform 是材质参数,它上面的 /// 块就是参数的元数据。

指令用在含义
@group A|Buniform实例编辑器里的分组;| 表示嵌套
@desc 文本任意tooltip / 描述
@sort 10 · @slider 0 1uniform排序优先级 · 滑条范围
@staticuniform bool静态开关:对它的 if 是编译期裁掉,不是运行时选择
@default /Game/T_X · @sampler Normal纹理 uniform默认资产 · 采样器类型
@nameuniform · 产物显示名 · 资产路径
@library A|B · @param 名字 文本export函数库分类 · 某个输入引脚的描述

static const T Name = ...; 是编译期常量,不产生参数。

函数体

真正的 HLSL 表达式与语句:完整的运算符优先级、?:、强制转换、构造器、swizzle、if / else, 以及编译器能证明循环次数的 for / while(会被展开)。引擎节点是对两个命名空间的调用,实参按名字给:

float3 N = UE.VertexNormalWS();
float4 Tex = Albedo.Sample(UE.TexCoord(Index = 0));
m.Opacity = UE.Fresnel(ExponentIn = 4.0, BaseReflectFractionIn = 0.04);
m.FrontMaterial = Substrate.Slab(BaseColor = Tex.rgb, Metallic = 1.0, Roughness = 0.35);

引擎和已加载插件里的每一个 UMaterialExpression 类都能写成 UE.<Name>;Substrate 节点在 Substrate. 之下, 并且有自己的语法糖。两次相同的调用是同一个节点,所以在用到值的地方直接写调用没有任何代价。

条件是 /// @static uniform 的 if 是 StaticSwitch;其他 if 按变量逐个走运行时选择。 material 是一个带字段的值 —— m.BaseColor = ... —— 没人赋值的字段就是没人连的引脚。

Region 与布局

#pragma region "Rim light"
float Rim = pow(1.0 - saturate(dot(N, V)), RimPower);
m.EmissiveColor = RimColor * Rim;
#pragma endregion

region 是围住其中语句所生成节点的注释框。#pragma layout(...) 是反编译器写下来、用来保住手摆节点位置的行。 其余节点怎么排是一个工程设置 —— Graph Layout Style:Blocks(默认), 按 region 和语句分成一个个框、框之间用具名 reroute 相连;1.x 的布局 Classic;SourceBands,按源码语句一条一带;Layered,整图分层。 dsc dump-layout 可以不构建任何东西就把三种读源码的布局画出来。

它没有的

没有替代
运行时才知道次数的循环、分支里的 discard写进 /// @custom 函数
图里的矩阵在 /// @custom 函数体里算
switch、逗号运算符、let / autoif 链;把类型写出来
材质层堆栈@layer 与 @layerblend 产出两种函数;堆栈在编辑器里组装

工具

工具2.0
dsc check编到 IR 校验为止、不写任何资产 —— CI 的闸门;-Shaders 还会编 shader,并把 HLSL 错误映射回源码行
dsc fmt语言自己的打印器当格式化器,担保不了的文件一律不写
dsc decompile把编译器倒着跑一遍,写出 .dss(实例则是 .dsi)
VS Code高亮与代码片段;跳转、悬停、重命名与节点补全由编译器自己的索引回答
编辑器节点 ↔ 源码行双向跳转,手改过的资产可以 Adopt Into Source

本页目录