DreamShaderLang
快速开始

项目结构

DShader/ 怎么组织、插件如何贡献自己的源码根、哪些目录归插件所有,以及决定什么会被编译的 Package 规则。

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

源目录、它的 Packages 子目录和生成 shader 目录都由插件在模块启动时创建,也就是编辑器第一次启动时, 无论你有没有写过任何源文件。

since 1.6.0 起,这不再是唯一的根:每个带 DShader 目录的已启用插件都贡献一个自己的源码根 —— 见下面的插件源码根

目录树

M_Sample.dsm
F_Tint.dsf
Common.dsh
Color.dsh
dreamshader.package.json
Noise.dsh
DreamShader.code-workspace
dreamshader.lock.json

插件源码根

since 1.6.0

一个插件可以自带那些构建它材质的 .dsm / .dsf / .dsh,而不必把它们停在工程的树里。 Root="Plugin.<Name>"1.2.0 起就能把资产进插件;缺的一直是源码这一半。

M_Toon.dsm
Toon.dsh
MoonToon.uplugin
行为规则
发现无需配置。开关是 Project Settings ▸ … ▸ Paths ▸ Scan Plugin Source Directories(默认开)
默认资产目标插件根下没写 Root= 的文件生成到它自己插件的挂载点 —— /MoonToon,不是 /Game。只有缺失或全空白的 Root 才被默认化,所以 Root="/" 可以显式选回 /Game
import永不跨根。一个文件只针对自己所在的根及该根的 Packages 解析 import;刻意跨根要写 import "Plugin.MoonToon:Shared/Toon.dsh"; —— 见 import
可写性只有工程根是可写的。VirtualFunction 同步跳过插件根下的文件:插件按作者写的样子交付定义,编辑器不再改写它们
监视器每个根注册一个监视,所以插件的源文件同样享受 Auto Compile On Save
workspaceDreamShader.code-workspace 每个根一个 folders 条目 —— 工程是 ".",插件是 Plugin: <Name>
Gen 页插件根下的文件在行副标题里标出根名,搜索框也匹配根名 —— 输入插件名就能筛出它带的全部文件

和某个既有根重叠的插件 DShader 目录会被忽略并给出警告,而不是把同一个文件交给两个所有者。 把 Source Directory 指向一个插件目录、或指向一个包含插件目录的位置时,就会出现这种情况。

根列表是缓存的,只在 Source Directory 或那个扫描开关变化、或按下 DreamShader Gen ▸ Refresh 时重建 —— 后者正是用来拾取「你刚建的 DShader 文件夹」或「会话中途挂载的插件」的。但 watcher 只在启动时注册一次, 所以会话中途才出现的根,它的 Auto Compile On Save 要到下次编辑器启动才开始工作。

各目录职责

目录归属内容
DShader/源文件根目录。下面怎么分都行,这里给的是约定而不是规则。
DShader/Materials/材质 .dsm,通常一个文件一个 Shader
DShader/Functions/可复用的 .dsf 函数文件。
DShader/Shared/项目内部的 .dsh header。
DShader/VirtualFunctions/编辑器Create Virtual Function 为已有 UMaterialFunction 写声明的位置。
DShader/Decompiled/编辑器Export DSM / Export DSF 的输出目录,分为 MaterialsFunctionsLayersLayerBlends
DShader/Packages/扩展已安装的共享库。永远是源根下那个字面量子目录 Packages,不能单独配置。
DShader/DreamShader.code-workspace插件每次执行 Open Dream Shader Workspace 都会整体重写。
DShader/dreamshader.lock.json扩展记录已安装 Package 的版本。插件既不读也不写。
Intermediate/DreamShader/GeneratedShaders/插件生成的 .ush helper include,挂载在 /DreamShaderGenerated

DreamShader.code-workspace 每次调用都从一个固定对象序列化而来 —— 从不读取、合并或保留原文件。手动 加的 launchtasksextensions 或额外 settings 都会被销毁。把个人配置放在 DShader/.vscode/settings.json,这个命令不会碰它。

命名约定

类型约定
材质文件M_*.dsm
材质函数文件F_*.dsf
共享 header按领域命名 —— Texture.dshColor.dsh
Package 入口Library/<Name>.dsh

扩展名比较是大小写不敏感的,所以 M_Water.DSM 也是材质文件。标识符则是另一回事:插件把命名空间和 函数名净化成 HLSL 与资产标识符时会替换掉非 ASCII 字符,所以这些名字请用 ASCII。

import

每个材质尽量只 import 少量稳定入口:

import "Shared/Common.dsh";
import "@typedreammoon/dream-noise/Library/Noise.dsh";

一个 specifier 会依次尝试三个候选:导入方文件所在目录、所属根的源目录、该根的 Packages 目录。 第一个存在且没有跑出自己包含根的候选胜出 —— Package 内部的文件不能用 ../ 爬出去。没有扩展名的 specifier 会被补上 .dsh,所以 import "…/Noise" 永远不可能解析到 .dsf

裸 specifier since 1.6.0 不会离开自己所在的根:装一个插件因此不可能改变某条既有 import 的含义。 要刻意跨根,写 import "Plugin.MoonToon:Shared/Toon.dsh";。完整规则见 import 与命名空间

import 保持浅层还有一个好处:源 hash 覆盖的是整段内联后的 import 闭包,改一个 header 会让所有引入它的 文件都失效。

什么会被编译,什么不会

插件有两个枚举器,它们排除的东西不一样。

枚举器扩展名排除使用者
全量源枚举.dsm.dsh.dsf任意根的 Packages 下的全部文件启动时的内存生成、commandlet 的 compile -All、cook、Gen 页列表、VirtualFunction 同步
材质源枚举.dsm.dsfDShader/Packages 下的 .dsmRecompile DSM、自动编译队列、依赖图

Packages 排除对 .dsm 是完整的,对 .dsf 只是部分。Package 里的 .dsf 在交互式编辑器会话中 被编译 —— 文件监视器和 Recompile DSM 都会拾取它 —— 但 compile -All、cook 和 Gen 页 不会。也就是说,一个附带 .dsf 函数资产的 Package 在本地能用,在 headless 构建里会静默地什么都 不生成。库代码请以 .dsh header 形式分发,或者把 .dsfPackages 复制到自己的源码树里。

Package 内的 Examples/**/*.dsm 在任何路径下都不会被编译,也不会出现在 Gen 页。要用示例,把它从 DShader/Packages 复制到 DShader/ 下。

Package 的 .dsh header 完全可以被 import,但从不参与 VirtualFunction 声明扫描 —— Package 里带的 VirtualFunction 永远不会与它的资产做校验。

版本管理

内容是否提交
.dsm / .dsf / .dsh提交 —— 这就是材质逻辑本身
dreamshader.lock.json如果团队会安装 Package,提交
Config/DefaultEngine.ini提交 —— 项目设置存在这里,是共享的
DShader/Packages/团队自定:直接入库,或按 lock 文件重新安装
生成的 .uasset一般不提交 —— 默认 backend 下编辑器本来也不会写出它们
Intermediate/DreamShader/不提交

团队内统一 Source Directory,这样 import 在每台机器上的解析结果才一致。插件带的 DShader 目录随插件 一起提交 —— 它和插件的 Content/ 属于同一份交付物。

下一步

本页目录