DreamShaderLang
快速开始

日常工作流

保存、防抖、生成这个循环 —— 什么会触发重建、什么会被跳过,以及改一个 .dsh 会让什么失效。

日常循环是:改源文件、保存、让插件编译、看结果。编辑器扩展负责改善"编辑"这一半;生成由 Unreal 插件 负责,而且只发生在编辑器进程里。

这个循环

  1. 修改 DShader/ 下的 .dsm.dsf.dsh
  2. 先处理编辑器扩展本地报出来的问题 —— 语法、未知标识符、错误的 import。
  3. 保存。 源目录监视器发现变化。
  4. 防抖计时器等过 Save Debounce Seconds —— 默认 0.25,钳制到 [0.05, 10.0] —— 于是连续多次保存 只触发一次编译。
  5. DreamShader 编译该文件,并把结果输出到 Output Log。
  6. 通过 Tools ▸ DreamShader ▸ Material Content Browser 或编辑器扩展的预览查看材质。

自动编译由两项项目设置控制:Auto Compile On Save(默认开启)和 Save Debounce Seconds。关闭 自动编译后,监视器完全忽略文件变化,需要手动触发编译。

一次编译做了什么

每个阶段都是一道闸门:第一个失败的阶段会中止编译,它的消息就是本次编译结果。

#阶段说明
1文件种类闸门.dsh 从不生成资产 —— 按设计就在这里失败
2加载 prepared source把每个 import 递归内联成一整段文本
3内容闸门按扩展名逐文件做子串扫描,检查它可以声明什么
4解析整段 prepared 文本作为一个解析单元
5计算 hashprepared 文本做 CRC32,格式化为八位十六进制
6写 helper include仅当该单元至少声明了一个 Function
7材质函数资产每个 ShaderFunction / ShaderLayer / ShaderLayerBlend 一个,按声明顺序
8材质本体仅当该单元声明了顶层 Shader

函数资产在材质之前生成,所以同一文件里的 Shader 可以调用旁边声明的 ShaderFunction。解析器警告 从不导致编译失败,它们会以 Warnings: 开头附加到结果消息后面。

成功时的输出形如:

Generated DreamShader thin-custom material /Game/Materials/M_Emissive from .../M_Emissive.dsm.
Generated ShaderFunction /Game/Functions/F_Tint from .../M_Emissive.dsm.
Generated DreamShader helper include '...' from .../Common.dsm.
Skipped /Game/Materials/M_Emissive from .../M_Emissive.dsm; source hash is unchanged.

一个文件也可能编译成功但什么可放置的产物都没有:只包含 FunctionGraphFunctionVirtualFunction 声明的单元会成功,并给出对应说明。而完全没有任何顶层块的单元是失败

三种文件在实际使用中的区别

种类你改它是为了保存它会
.dsm改一个材质编译该材质,以及同文件里声明的函数资产
.dsf改一个可复用函数资产编译它声明的函数资产
.dsh改共享 helper什么都不生成 —— 见下文

什么会触发编译

触发方式是否强制目标范围
保存时自动编译(监视器 + 防抖)内存被保存的文件 —— 最常见的路径
Generate all in-memory materials —— 编辑器启动,以及每次修改 Default Compiler Backend内存所有项目源文件
Material Content Browser 的 Compile / Compile all / 刷新缩略图内存单个源文件,或全部列出的源文件
实时预览渲染内存单个源文件
Materialize,以及为内存材质创建子实例磁盘单个源文件,持久化
Commandlet -run=DreamShader由调用方决定磁盘持久化
Cook,仅在 cook director 进程上磁盘所有项目源文件,持久化

交互式编辑器从不为单个材质写出 .uasset。生成的资产一直留在内存里,直到 cook、commandlet 或显式 Materialize 把它们写到磁盘。见内存材质

什么时候会跳过重建

每个生成的资产会在 package 元数据里存两个键:

DreamShader.SourceFile相对项目目录的源文件路径,使用正斜杠
DreamShader.SourceHashprepared 源文本的 CRC32,八位十六进制

非强制编译时,如果资产存在、存储的源路径匹配(忽略大小写)、存储的 hash 匹配(区分大小写),这次工作就 会被跳过,结果是 Skipped {AssetPath} from {File}; source hash is unchanged.

由于 hash 是在 import 内联之后计算、并逐字节比较的:

改动效果
M_Foo.dsmM_Foo.dsm 失效
改被两个材质 import 的 Common.dsh两个材质都失效
调整空白、改注释也会失效 —— 比较的是文本,不是语义
把项目挪到别的目录什么都不失效 —— 存的是项目相对路径
重命名源文件存储的路径不再匹配,于是不会跳过任何东西

语言层面没有清除已存 hash 的办法。要通过非强制入口触发重建,只能改源文本(任意改动),或者删掉生成的 资产。

.dsh 之后

保存 header 什么都不会生成。它会失败并输出 DreamShader header '{File}' does not generate assets directly. Recompile dependent .dsm or .dsf files instead.。 改 .dsh 会让所有 import 它的 .dsm.dsf 的 hash 失效,但这些文件只有在它们自己被编译时才会 重建。

能让依赖方重建的路径:

  • 分别保存每个依赖的 .dsm / .dsf
  • Tools ▸ DreamShader ▸ Recompile DSM,它把所有项目 .dsm.dsf 压入待编译队列;
  • Generate all in-memory materials —— 编辑器启动,以及每次修改 Default Compiler Backend
  • Material Content Browser 的 Compile / Compile all
  • commandlet 或 cook。

Gen 页把 header 显示为 ◆ function / header,副标题是 used by {N} material(s),改之前就能看到影响 范围。

一次重建会毁掉什么

生成的资产是源文件的产物,不是文档。重新生成会清掉生成的注释框、断开每个材质属性输入、删除所有表达式、 把材质渲染状态重置为引擎默认值,然后重新应用 Settings 并重建。资产内部一切手动改动都会消失,只有一个 例外:文本不以字面量 DreamShader: 开头的注释框会保留。

在默认 backend 下,重新生成还会清空手动设置在生成材质实例上的所有参数覆盖 —— 标量、向量、纹理、 静态开关一视同仁 —— 而且没有任何诊断。把值写进源文件的 Properties 默认值里,或者在一个子级 UMaterialInstanceConstant 上覆盖,重新生成永远不会碰它。见 重新生成

DreamShader 也拒绝覆盖不是自己生成的资产:磁盘上没有 DreamShader.SourceFile 元数据的资产会以 Asset '…' already exists and was not generated by DreamShader. 失败。这道守卫不覆盖 ThinCustom 实例路径,所以不要手工创建 UDreamShaderMaterialInstance 资产。

出错时看哪里

解析和生成错误会进 Output Log 的 LogDreamShader 类别、Material Content Browser 的源文件列表,以及 Saved/DreamShader/Bridge/diagnostics.json —— 后者是编辑器扩展读取的文件。

位置显示什么
Output Log全部消息,按顺序
Gen 页每个文件只显示一条 —— 第一条被接受的诊断
编辑器扩展按文件的完整列表,定位到行列

行列号指向你实际编辑的那个文件:import 行会被替换成空行,每个被内联的文件用来源标记包起来,所以位置 不会偏移。到错误速查里查具体消息。

迁移已有材质

在 Content Browser 里右键 UMaterialUMaterialFunction,使用 DreamShader ▸ Export DSM / Export DSF。输出落在 DShader/Decompiled/ 下并会自动打开。把它当作初稿 —— 在正式作为源文件之前, 先整理命名、helper 分层和 import。见反编译导出

下一步

本页目录