DreamShaderLang
HLSL / GLSL 背景

GLSL 简介

写给 DreamShaderLang 作者的 GLSL 背景 —— 1.x 源文件里哪些 GLSL 写法是一等 token、哪些会在 Function 体内被改写、哪些带警告,.dss 为什么只认 HLSL,以及哪些根本没有对应物。

网上大多数着色器示例都是 GLSL 写的。DreamShaderLang 不是 GLSL 编译器,也从不输出 GLSL —— 它的目标是 Unreal 的材质图和 HLSL。1.x 语法对 GLSL 写法是刻意照顾的,照顾方式有三种;.dss 则只认 HLSL。 分清这几种就是本页的全部要点。

机制位置发生了什么
一等 token1.x 的 Properties、Inputs、Outputs、Graph 声明、Graph 构造器vec3 就是一个类型 token,读作 float3;没有诊断
标识符改写1.x Function / GraphFunction 的签名和函数体在输出 HLSL 之前按整标识符替换,于是 vec3 变成 float3、mix 变成 lerp;没有诊断
带警告的 legacy 规则 since 2.0.01.x Graph 里的 mix、fract、mod、inversesqrt读作 lerp、frac、fmod、rsqrt,并给出 warning DSH5277
只认 HLSL.dss没有 GLSL 写法:mix 这类函数名是错误 DSH4250,vec3 不是类型(DSH4201)
// GLSL
vec3 applyTint(vec3 color, vec3 tint)
{
    return color * tint;
}
// DreamShaderLang —— 同一个 helper。vec3 在声明里保留,
// 在生成的 HLSL 里变成 float3。
Function vec3 ApplyTint(in vec3 color, in vec3 tint) {
    return color * tint;
}

GLSL 类型别名

在 1.x 源文件里,这些是真正的 token,凡是接受对应 float / int / uint / bool 写法的地方都接受它们,读作右边那个写法。 .dss 不认它们 —— 写 HLSL 拼写;dsc migrate 迁移时会替你改写。

GLSL 写法等价于
vec2 vec3 vec4float2 float3 float4
ivec2 ivec3 ivec4int2 int3 int4
uvec2 uvec3 uvec4uint2 uint3 uint4
bvec2 bvec3 bvec4bool2 bool3 bool4

没有 vec1、ivec1、uvec1、bvec1,也没有 half 的 GLSL 向量写法。单分量的 GLSL 风格形式 根本不存在 —— 请用 float 或 float1。

由于 int、uint、bool 和 half 最终都折叠到同一套浮点分量数,在 int3、ivec3、bool3 和 float3 之间做选择是文档层面的决定,不是语义层面的。唯一可观察的差别是 Graph 块中整数 构造器调用设置的整数标记,它存在的唯一目的是拒绝整数除法。见 类型与值。

函数体内部的改写

Function / GraphFunction 的签名会把每个类型 token 过一遍规范化器,函数体则把每个标识符 过一遍同一张表的超集。两者都按小写化后的整标识符匹配,所以改写不区分大小写。

被改写的标识符变成签名类型中函数体文本中
vec2 vec3 vec4float2 float3 float4✔✔
ivec2 ivec3 ivec4int2 int3 int4✔✔
uvec2 uvec3 uvec4uint2 uint3 uint4✔✔
bvec2 bvec3 bvec4bool2 bool3 bool4✔✔
mat2float2x2✔✔
mat3float3x3✔✔
mat4float4x4✔✔
mixlerp✘✔
fractfrac✘✔
modfmod✘✔

签名别名 15 条;函数体别名 18 条。改写能识别注释和字符串,并且只作用于 Function 和 GraphFunction 体 —— Graph 块、Shader 体和 ShaderFunction 体都不做规范化。函数体里的改写不给诊断;DSH5277 只针对 Graph 文本。

函数体改写按整标识符匹配、不区分大小写。Function 或 GraphFunction 体里名为 Mix、Mod、 Fract、Vec3、Mat4 … 的 helper、局部变量或结构体成员,都会被静默改名成 lerp、fmod、 frac、float3、float4x4。没有任何诊断;故障表现为 HLSL 编译错误,或者悄悄算错。请改名。

注意 mat2 / mat3 / mat4 改写得很干净,但图里没有矩阵值。声明能解析,失败发生在调用时: Graph 调用把矩阵传进 pin 是 DSH4361。见 HLSL 简介中的矩阵一节。

Graph 块里的 GLSL 函数名

数学内置按精确的 HLSL 名字匹配 since 2.0.0。四个 GLSL 拼写在 1.x Graph 里仍然可用,作为一条把它说出来的 legacy 规则:

GLSL 名字在 1.x Graph 块中在 .dss 中在 1.x Function 体中
mix读作 lerp,warning DSH5277错误 DSH4250 —— 写 lerp改写成 lerp
fract读作 frac,warning DSH5277错误 DSH4250 —— 写 frac改写成 frac
mod读作 fmod,warning DSH5277错误 DSH4250 —— 写 fmod改写成 fmod
inversesqrt读作 rsqrt,warning DSH5277错误 DSH4250 —— 写 rsqrtHLSL 里没有这个名字
clamp、dot、min、max、pow、sqrt、abs、floor、ceil、sin、cos、normalize两种语言拼写相同 —— 都是真正的内置同左HLSL 内建函数
smoothstep、step、length、cross、reflect、refract真正的内置 since 1.6.0 —— 两种语言拼写相同同左HLSL 内建函数
distance、exp、exp2、log、log2真正的内置 since 2.0.0同左HLSL 内建函数

注意 sin / cos 建出的 Sine / Cosine 节点按周期计(Period = 1),不按弧度 —— 函数体里的 HLSL sin 才按弧度。 完整的内置目录见数学内置。

构造器与 swizzle

在 1.x 源文件里两者都能原样带过来;.dss 里把类型写成 HLSL 拼写(float3(…)),swizzle 规则相同。

Graph = {
    vec3  color = vec3(1.0, 0.2, 0.1);
    float r     = color.r;
    vec2  rg    = color.rg;
    vec3  bgr   = color.bgr;
}
  • 构造器名跟随类型 token,所以 vec2(…)、vec3(…)、vec4(…)、ivec3(…) 等在 Graph 块里都能用。
  • swizzle 接受一到四个通道字符,来自 xyzw 或 rgba 两个集合。重排和重复读取都允许;一个 swizzle 里混用两个集合 (.xg)是 DSH4230 since 2.0.0。没有 stpq 集合,也没有 uv 集合 —— .uv 是 DSH4230。
  • swizzle 只能收窄或重排。对标量写 Roughness.yz 是 DSH4230,不是广播。请重复通道 0(.xx)或改用构造器。

细节见运算符与转换。

完全没有对应物的东西

GLSL 习惯在 DreamShaderLang 中
void main() { … }.dss 的入口 export void M(inout material m);1.x 用带 Outputs 绑定的 Shader 块
uniform float x;.dss 里就是 uniform float x;,加 /// 标签;1.x 写成 Properties 声明。两者都变成真正的材质参数
文件作用域的 in / out / varying / attribute没有 —— 输入来自 UE.* 节点和参数
sampler2D + texture(s, uv).dss 里 uniform Texture2D Tex; 加 Tex.Sample(uv);1.x 用纹理 property 加 SampleTexture2D(tex, uv),或者用 TextureSampleParameter2D 调用 Tex(Coordinates = uv)
layout(…)、lowp / mediump / highp没有
mat3 m; m * v图里没有矩阵值(DSH4361)—— 用 UE.TransformVector / UE.TransformPosition,或者在 /// @custom / Function 体里算
#version、#extension没有;预处理器只有 #if / #define 那一组
for、while.dss 有(次数在运行时才知道的循环要写进 /// @custom 函数);1.x Graph 没有(DSH2208)
discard只在 /// @custom / Function 的 HLSL 体内部有效

移植一段片段

一段典型的 GLSL 片段,以及落地的几种方式。

float ring(vec2 uv, float t)
{
    vec2  p = uv - 0.5;
    float d = length(p);
    return fract(sin(t) * 0.5 + d * 4.0);
}

写成 .dss 代码,改成 HLSL 拼写,每个调用都是真实的节点:

float Ring(float2 uv, float t)
{
    float2 p = uv - 0.5;
    float  d = length(p);
    return frac(sin(t) * 0.5 + d * 4.0);    // 注意:图里的 sin 按周期计
}

函数前面加一行 /// @custom,它的函数体就原样成为一个 Custom 节点里的 HLSL(sin 按弧度)。

写成 1.x Function,函数体是 HLSL,别名由语言替你改写:

Function float Ring(in vec2 uv, in float t)
{
    vec2  p = uv - 0.5;          // 输出时 vec2 -> float2
    float d = length(p);         // HLSL 内建函数,在函数体里没问题
    return fract(sin(t) * 0.5 + d * 4.0);   // fract -> frac
}

写成 1.x Graph 语句,length 和 frac 都是内置;写 fract 也能编译,但会得到 warning DSH5277:

Graph = {
    vec2  uv = UE.TexCoord(Index = 0);
    vec2  p  = uv - 0.5;
    float d  = length(p);                          // 内置,自 1.6.0
    float r  = frac(sin(UE.Time()) * 0.5 + d * 4.0);
}

1.6.0 之前 length 不是内置,这一段得手动展开成 sqrt(dot(p, p))。旧源码这样写仍然完全有效 —— 只是不再必要。

/// @custom / Function 版本是一个 Custom 节点;图代码版本产出可以在材质图里查看、也可以接参数的真实节点。按场景选择 —— 见函数和 DreamShaderLang 2.0。

GLSL 常见关键字

仅作参考。除上文明确说明的以外,这张表里的东西都不属于 DreamShaderLang 语法(.dss 里的 if、for、while、return、 in / out / inout、const、uniform 是 HLSL 的同名关键字)。

分类关键字
类型void、bool、int、uint、float、double、vec2、vec3、vec4、mat2、mat3、mat4
控制流if、else、for、while、do、switch、case、default
跳转return、break、continue、discard
参数in、out、inout
修饰const、uniform、layout、centroid、flat、smooth、attribute、varying
精度lowp、mediump、highp

接下来

本页目录