DreamShaderLang
快速开始

项目结构

DShader/ 如何组织、六种源文件各存放什么、插件如何贡献自己的源码根、插件拥有哪些目录,以及决定哪些内容会被编译的 Package 规则。

DreamShaderLang 源文件位于项目根目录下的一个目录中——默认是 DShader,由 Project Settings ▸ DreamPlugin ▸ Dream Shader ▸ Paths ▸ Source Directory 设置。相对路径会相对于项目 目录解析。

源目录、其 Packages 子文件夹以及生成的 shader 目录由插件在模块 启动时创建——也就是编辑器首次启动时,无论你是否编写过任何源文件。

(自 1.6.0 起)它不再是唯一的根:每个启用了 DShader 文件夹的插件都会贡献一个自己的源 根——见下方的插件源码根。

六种源文件

文件夹由你自行选择;重要的是扩展名。

扩展名是什么产出
.dss(自 2.0.0 起)DreamShaderLang 2.0 编译单元:一个材质或材质函数(包含 layer 和 blend)每个 export 一个资产
.dsi(自 2.0.0 起)材质实例:父级和覆盖项一个 UMaterialInstanceConstant
.dsp(自 2.1.0 起)Custom Pass 管线(UE 5.8)一个 UDreamPassPipeline
.dsm一个 1.x 材质其 Shader,外加在它旁边声明的函数和 layer 资产
.dsf一个 1.x 材质函数文件它声明的函数资产
.dshheader:辅助项、VirtualFunction / extern 声明、共享常量;两种方言均可容纳不产出任何内容——它只通过导入或包含它的文件进入编译

请将新源文件写为 .dss;.dsm / .dsf 是 1.x 语法,仍然通过同一个编译器构建,因此 项目可以用 dsc migrate 每次迁移一个文件。每种类型可以包含什么 见文件模型。

目录树

MyProject/
├─ DShader/
│  ├─ Materials/
│  │  ├─ M_Sample.dss
│  │  └─ M_Legacy.dsm
│  ├─ Functions/
│  │  ├─ F_Tint.dss
│  │  └─ F_Old.dsf
│  ├─ Instances/
│  │  └─ MI_Sample_Red.dsi
│  ├─ Passes/
│  │  ├─ P_Outline.dsp
│  │  └─ M_OutlineComposite.dss
│  ├─ Shared/
│  │  ├─ Common.dsh
│  │  └─ Color.dsh
│  ├─ VirtualFunctions/
│  │  └─ Engine.dsh
│  ├─ Decompiled/
│  │  ├─ Materials/
│  │  └─ Functions/
│  ├─ Packages/
│  │  └─ @typedreammoon/
│  │     └─ dream-noise/
│  │        ├─ dreamshader.package.json
│  │        └─ Library/
│  │           └─ Noise.dsh
│  ├─ .dreampass/
│  ├─ DreamShader.code-workspace
│  └─ dreamshader.lock.json
├─ Intermediate/
│  └─ DreamShader/
│     └─ GeneratedShaders/
└─ Plugins/
   └─ DreamShader/

插件源码根

(自 1.6.0 起)

插件可以随附构建其材质的源文件,而不必将它们放在项目的目录树中。

Plugins/
└─ MoonToon/
   ├─ DShader/
   │  ├─ Materials/
   │  │  └─ M_Toon.dss
   │  ├─ Shared/
   │  │  └─ Toon.dsh
   │  └─ Packages/
   └─ MoonToon.uplugin
行为规则
发现无需配置。开关是 Project Settings ▸ … ▸ Paths ▸ Scan Plugin Source Directories(默认开启)
默认资产目标插件根下的文件会生成到其自身插件的挂载点——/MoonToon,而不是 /Game。在 1.x 文件中,只有缺失或全为空白字符的 Root 才会被默认化,因此 Root="/" 会重新选择回到 /Game
导入相对路径永不跨越根。 文件只会相对于其自身所在的根以及该根的 Packages 解析;要使用另一个根中的 header,请写入以 / 开头的路径(先找自己的根,再找其他根)、复制它,或将其作为 Package 交付——见导入
可写性只有项目根可写。 VirtualFunction 同步会跳过插件根下的文件,并且对于插件随附的源文件,Adopt 会被禁用:插件按其作者撰写的样子交付其定义
监视器每个根一个监视器,因此插件的源文件也会获得 Auto Compile On Save
工作区DreamShader.code-workspace 每个根有一个 folders 条目——项目为 ".",插件为 Plugin: <Name>
Material Content BrowserRoot 一列标明 Project 或所属插件,搜索框也会匹配根名称——输入插件名称即可筛选出它随附的所有内容
Cook另一个插件自身源文件下的失败(Root 指定了引擎或商城插件,DSH8090)不再中止宿主项目的 cook;它会被记录为 [Cook] Not this project's to build(自 2.1.2 起)。项目自身的源文件仍会中止它

警告:

与现有根重叠的插件 DShader 文件夹会被忽略并给出警告,而不是让一个文件有两个 所有者。当 Source Directory 指向某个插件文件夹,或指向包含某个插件文件夹的位置时,就会发生这种情况。

注意:

根列表会被缓存,仅在 Source Directory 或扫描开关发生变化时,或在 Material Content Browser 中刷新时才会重建——这也是拾取你刚创建的 DShader 文件夹或会话中途挂载的插件的方式。 但监视器只在启动时注册一次,因此对于会话中途出现的根,其 Auto Compile On Save 要等到下次编辑器启动才会 开始工作。

目录角色

目录归属内容
DShader/你源根。按你喜欢的任何方式组织;此处的布局是约定,而非规则。
DShader/Materials/你材质源文件,.dss 或 .dsm,通常一个文件一个材质。
DShader/Functions/你材质函数:带导出函数的 .dss,或 1.x 的 .dsf。
DShader/Instances/你.dsi 材质实例。
DShader/Shared/你项目内部的 .dsh header。
DShader/VirtualFunctions/编辑器CreateVirtualFunction 为现有 UMaterialFunction 写入声明的位置。
DShader/Decompiled/编辑器Export .dss / Export .dsi / Export Legacy .dsm / .dsf 的输出,分为 Materials、Functions、Layers、LayerBlends。
DShader/Packages/扩展已安装的共享库。始终是源根下那个字面量子文件夹 Packages;不能单独配置。
DShader/.dreampass/(自 2.1.0 起)插件Custom Pass HLSL 的快照;只有通过预检查的才会进入全局 shader 槽位。随源文件一起提交。
DShader/DreamShader.code-workspace插件每次 Open Dream Shader Workspace 时整体重写。
DShader/dreamshader.lock.json扩展记录已安装的 Package 版本。插件既不读取也不写入它。
Intermediate/DreamShader/GeneratedShaders/插件映射到 /DreamShaderGenerated。(自 2.0.0 起)Function 的 HLSL 会写入其 Custom 节点,因此编译不会写入此处;在此发现的 .ush 是 1.x 构建遗留的,Clean Generated Shaders 会将其删除。

警告:

DreamShader.code-workspace 在每次调用时都从固定对象序列化而来——从不读取、合并或保留。 手动添加的 launch、tasks、extensions 或额外的 settings 都会被销毁。请将个人配置保存在 DShader/.vscode/settings.json 中,该命令永远不会触碰它。

命名约定

类型约定
材质M_*.dss / M_*.dsm
材质函数F_*.dss / F_*.dsf;layer 为 L_*,blend 为 LB_*
材质实例MI_*.dsi
Custom Pass 管线.dsp,放在其 pass 材质旁边
共享 header按领域命名——Texture.dsh、Color.dsh
Package 入口点Library/<Name>.dsh

.dss 资产以其 export 函数命名,因此保持文件名和 export 名称一致可以避免 意外。扩展名匹配不区分大小写,因此 M_Water.DSM 也是材质文件。标识符则是另一回事: 插件在将命名空间和函数名净化成 HLSL 和资产 标识符时会替换非 ASCII 字符,因此请让这些名称保持 ASCII。

导入与包含

让每个材质的导入保持在少量稳定的入口点:

// 1.x
import "Shared/Common.dsh";
import "@typedreammoon/dream-noise/Library/Noise.dsh";
// .dss
#include "Shared/Common.dsh"
#include "@typedreammoon/dream-noise/Library/Noise.dsh"

一个 specifier 会依次尝试三个候选:导入文件所在目录、所属根的源目录,以及该根的 Packages 目录。第一个存在且仍位于其包含根内的候选胜出 —— 包中的文件不能用 ../ 爬出去。没有扩展名的 specifier 会被追加 .dsh;只有 header 才能被引入(导入 .dsf / .dsm 是 DSH2252)。解析不到任何位置的则是 DSH8292。完整规则见 导入与命名空间。

浅层导入还有另一项好处:构建键覆盖文件引入的每个 header,所以编辑一个 header 会让所有包含它的文件失效。

什么会被编译,什么不会

每条构建路径都采用相同的文件 (自 2.1.2 起) —— 启动时的扫描、一次保存及其排队的依赖项、Recompile All、commandlet 的 compile -All、cook 和 Material Content Browser:

位置构建什么
源码根,在其 Packages 之外每个 .dss、.dsi、.dsp、.dsm 和 .dsf;.dsh 仅通过包含它的源文件构建
包的 Assets/ 文件夹其 .dss、.dsf 和 .dsi,构建到包的资产根(/Game/DreamShaderPackages/<scope>/<name>)
请求材质函数的包头 —— /// @generate、#pragma generate、1.x Generate =那些材质函数
包中的其他任何内容 —— Examples/、Assets 之外的 .dss 或 .dsf什么都不会构建,在任何路径上

要以示例为例,把它从 DShader/Packages 复制到 DShader/ 下的某处。直到 2.1.2,包中 Assets 之外的 .dsf、.dss、.dsi 或 .dsp 会被文件监视器和 Recompile DSM 编译,但不会被 compile -All、cook 和 Material Content Browser 编译;请把这类源文件移到 Assets 下。见 包 ▸ 包资产。

注意:

标记为 NoGeneration 的源文件(#pragma nogeneration、/// @nogeneration、1.x NoGeneration = true)会像其他文件一样被枚举,但只有点名它的编译才会构建其资产 —— 见 NoGeneration。

包的 .dsh header 可以被导入,但它从不参与 VirtualFunction 声明扫描 —— 包中随附的 VirtualFunction 永远不会与它的资产进行校验。

版本控制

内容是否提交?
每个源文件 —— .dss、.dsi、.dsp、.dsh、.dsm、.dsf提交 —— 这就是材质逻辑
DShader/.dreampass/提交,当你使用 Custom Pass 时 —— HLSL pass 的快照
dreamshader.lock.json提交,如果团队会安装包
Config/DefaultEngine.ini提交 —— 项目设置存在这里并共享
DShader/Packages/团队自定:提交它,或按锁定文件重新安装
生成的 .uasset两种做法都行 —— 只提交源文件并在 CI 中重新生成,或者把生成资产也提交;见版本控制
Intermediate/DreamShader/不提交

团队内统一一个 Source Directory,这样导入在每台机器上的解析结果才一致。构建键按项目相对路径记录源文件(自 2.1.1 起),所以另一份检出不会把每个资产都重建一遍。插件的 DShader 文件夹随插件一起提交 —— 它和插件的 Content/ 属于同一份交付物。

下一步

本页目录