着色器类、混入与继承
Stride Shading Language(SDSL)是 HLSL 的扩展,使其更贴近 C# 的语法和概念。该语言是面向对象的:
- 着色器类(shader class)是代码的基础
- 着色器类包含方法和成员
- 着色器类可以被继承,方法可以被重写
- 成员类型可以是着色器类
SDSL 使用一种独特的方式处理多重继承。继承通过混入(mixin)执行,因此继承顺序至关重要:
- 继承顺序决定了方法的实际实现(最后一个重写)
- 如果某个混入在继承中出现多次,只有第一次出现会被考虑(连同其成员和方法)
- 要调用方法的上一个实现,使用
base.<方法名>(<参数>)
关键字
SDSL 使用与 HLSL 相同的关键字,并新增了以下关键字:
stage:方法和成员关键字。该关键字确保方法或成员只定义一次,并且在各组合中保持一致。stream:成员关键字。该成员在着色器的每个阶段都可访问。更多信息请参阅着色器阶段输入/输出的自动管理。streams:一种全局结构,用于存储着色器多个阶段间需要的变量。更多信息请参阅着色器阶段输入/输出的自动管理。override:方法关键字。缺少该关键字会导致编译错误。abstract:用于方法声明(没有方法体)之前。clone:方法关键字。当一个方法在着色器类的继承树中出现多次时,该关键字会强制在继承的每一层创建该方法的多个实例,而不是一个。更多信息请参阅组合。Input:用于几何着色器和曲面细分着色器。更多信息请参阅着色器阶段。Output:用于几何着色器和曲面细分着色器。更多信息请参阅着色器阶段。Input2:用于曲面细分着色器。更多信息请参阅着色器阶段。Constants:用于曲面细分着色器。更多信息请参阅着色器阶段。
抽象方法
SDSL 支持抽象方法。抽象方法需要加上 abstract 关键字前缀。你可以继承带有抽象方法的着色器类,而不必实现它们;编译器只会产生一个无害的警告。不过,你应该在最终的着色器中实现它,以防止编译错误。
标注
与 HLSL 一样,SDSL 也支持标注(annotation)。其中一些最有用的包括:
- 针对 float4 变量的
[Color]。ParameterKey 的类型将是Color4而不是Vector4。它还会向 Game Studio 指明该变量应被视为颜色,从而可以在 Game Studio 中编辑它。 [Link(...)]指定使用哪个 ParameterKey 来设置该值。不过,仍会创建一个独立的默认键。[Map(...)]指定使用哪个 ParameterKey 来设置该值。不会创建新的 ParameterKey。[RenameLink]阻止创建 ParameterKey。应与[Link()]一起使用。
示例代码:标注
shader BaseShader
{
[Color] float4 myColor;
[Link("ProjectKeys.MyTextureKey")]
[RenameLink]
Texture2D texture;
[Map("Texturing.Texture0")] Texture2D defaultTexture;
};
示例代码:继承
shader BaseInterface
{
abstract float Compute();
};
shader BaseShader : BaseInterface
{
float Compute()
{
return 1.0f;
}
};
shader ShaderA : BaseShader
{
override void Compute()
{
return 2.0f;
}
};
shader ShaderB : BaseShader
{
override void Compute()
{
float prevValue = base.Compute();
return (5.0f + prevValue);
}
};
示例代码:继承顺序的重要性
注意当我们改变 ShaderA 与 ShaderB 之间的继承顺序时会发生什么。
shader MixAB : ShaderA, ShaderB
{
};
shader MixBA : ShaderB, ShaderA
{
};
// 生成的代码(表示形式)
shader MixAB : BaseInterface, BaseShader, ShaderA, ShaderB
{
float Compute()
{
// 来自 BaseShader 的代码
float v0 = 1.0f;
// 来自 ShaderA 的代码
float v1 = 2.0f;
// 来自 ShaderB 的代码
float prevValue = v1;
float v2 = 5.0f + prevValue;
return v2; // = 7.0f
}
};
shader MixBA : BaseInterface, BaseShader, ShaderA, ShaderB
{
float Compute()
{
// 来自 BaseShader 的代码
float v0 = 1.0f;
// 来自 ShaderB 的代码
float prevValue = v0;
float v1 = 5.0f + prevValue;
// 来自 ShaderA 的代码
float v2 = 2.0f;
return v2; // = 2.0f
}
};
静态调用
你也可以在不继承某个着色器的情况下使用它的变量或调用它的方法。为此,请使用 <着色器名>.<变量或方法名>。其行为与静态调用相同。
注意,如果你静态调用了使用着色器类变量的方法,着色器将无法编译。这是一种只使用着色器一部分内容的便捷方式,但这并不是一种优化。着色器编译器已经会自动移除所有不必要的变量。
示例代码:静态调用
shader StaticClass
{
float StaticValue;
float StaticMethod(float a)
{
return 2.0f * a;
}
// 这个方法使用了 a
float NonStaticMethod()
{
return 2.0f * StaticValue;
}
};
// 这个着色器类没问题
shader CorrectStaticCallClass
{
float Compute()
{
return StaticClass.StaticValue * StaticMethod(5.0f);
}
};
// 这个着色器类无法编译,因为该调用不是静态的
shader IncorrectStaticCallClass
{
float Compute()
{
return StaticClass.NonStaticMethod();
}
};
// 修复方法之一
shader IncorrectStaticCallClassFixed : StaticClass
{
float Compute()
{
return NonStaticMethod();
}
};