ShaderView 是一个 SwiftUI 包,旨在集成和显示 Metal 着色器。它简化了在 SwiftUI 应用程序中使用用 .metal
编写的自定义着色器的过程。
将任何 .metal 文件添加到项目中,可以为空文件。这将确保可以创建一个 metal 库并且可以编译默认着色器。
ShaderView()
允许您显示 .metal
文件中定义的任何着色器。要使用您的自定义着色器,只需在初始化 ShaderView
时指定着色器的名称。
ShaderView()
fragmentShaderName
:可选字符串。 片元着色器的名称。 如果未提供,则默认为标准着色器。vertexShaderName
:可选字符串。 顶点着色器的名称。 如果未提供,则默认为标准着色器。fallbackView
:可选视图。 发生错误时显示的视图。 如果未提供,则默认为 FallbackView()
。placeholderView
:可选视图。 加载着色器时显示的视图。 如果未提供,则默认为 PlaceholderView()
。shaderInput
:可选 ShaderInputable。 着色器的输入。 如果未提供,则默认为 ShaderInput()
的新实例。当仅使用其中一个默认着色器(defaultFragmentShader 或 defaultVertexShader)时,至关重要的是要使顶点着色器的输出与片元着色器的输入相匹配。以下是一些默认布局,可确保您的着色器与该软件包一起使用,尤其是在您选择仅使用其中一个默认着色器时。
我的软件包使用这些结构体将信息传递给着色器。即使在使用自定义着色器时,也必须将它们包含在 .metal 文件中,因为软件包的缓冲区需要它们。
VertexOutput
:默认顶点着色器输出和默认片元着色器预期输入。struct VertexOutput {
float4 position [[position]];
float2 screenCoord;
};
ViewPort:
用于大小的默认视口结构,两个默认着色器在缓冲区位置 0 处所需的输入。struct Viewport {
float2 size;
};
这是一个确保与软件包兼容的模板。自定义着色器时,除了 shaderInput 之外,保持参数相同。ShaderInput 可以替换为自定义参数,有关更多信息,请参见下面的“自定义着色器输入”部分。
fragment float4 customFragmentShader(VertexOutput in [[stage_in]],
constant Viewport& viewport [[buffer(0)]],
constant ShaderInput& shaderInput [[buffer(1)]]){
// Shader logic...
return float4(red, green, blue, alpha); //Standard return format
}
这是一个确保与软件包兼容的模板。自定义时,请确保具有相同的输入和输出类型。
vertex VertexOutput customVertexShader(uint vertexID [[vertex_id]],
constant Viewport& viewport [[buffer(0)]]) {
// Shader logic...
return VertexOutput; //Standard return format compatable with the default fragment shader
}
提供给 ShaderView
的任何着色器输入都必须符合此协议。它需要软件包用于将数据转换为 Metal 友好格式并管理其更改的方法。
public protocol ShaderInputable: AnyObject, ObservableObject{
init(time: Float)
var time: Float {get set}
var onChange: (() -> Void)? { get set }
func updateProperties(from input: any ShaderInputable)
func metalData() -> Data
}
init(time: Float)
:一个构造函数,用于使用给定的时间值初始化着色器输入。var time: Float
:一个表示时间的属性,该时间由软件包管理以每帧计算时间。var onChange: (() -> Void)?
:一个可选的闭包,可以在着色器的属性更改时调用,以便在运行时更改着色器输入。func updateProperties(from input: any ShaderInputable)
:一种从符合 ShaderInputable
的另一个实例更新着色器输入属性的方法。 应针对相同的类成员进行操作,而无需创建新实例,以获得软件包的最佳性能。func metalData() -> Data
:一种将着色器输入属性转换为 Metal 兼容的 Data
格式的方法。该格式将被软件包用来调整缓冲区的正确大小。class ShaderInputableExample: ShaderInputable {
var time: Float = 0.0
var onChange: (() -> Void)?
//New variable example. Changing this value triggers the 'onChange' closure, allowing the shader to respond to changes in its active state.
var isActive: Bool = true{
didSet{
onChange?()
}
}
required init(time: Float){
self.time = time
self.isActive = true
}
func updateProperties(from input: any ShaderInputable){
guard let input = input as? ShaderInputableExample else {
return
}
self.isActive = input.isActive;
}
func metalData() -> Data {
var metalInput = CustomMetalShaderInput(time: self.time, isActive: self.isActive)
return Data(bytes: &metalInput, count: MemoryLayout<CustomMetalShaderInput>.size)
}
}
class SubclassedShaderInput: ShaderInput {
//New variable example. Changing this value triggers the 'onChange' closure, allowing the shader to respond to changes in its active state.
var isActive: Bool {
didSet {
onChange?()
}
}
required init(time: Float) {
self.isActive = true
super.init(time: time)
}
override func updateProperties(from input: any ShaderInputable){
guard let input = input as? SubclassedShaderInput else {
return
}
self.isActive = input.isActive;
}
override func metalData() -> Data {
var metalInput = CustomMetalShaderInput(time: self.time, isActive: self.isActive)
return Data(bytes: &metalInput, count: MemoryLayout<CustomMetalShaderInput>.size)
}
}
为了使 Metal 能够正确读取着色器输入数据,缓冲区大小和期望值需要匹配。建议定义自定义着色器输入结构,该结构也将在 Metal 中以相同的方式定义。
此示例与上述的 SubclassedShaderInput 和 ShaderInputableExample 兼容。
struct CustomMetalShaderInput {
var time: Float
var isActive: Bool //new variable example
var padding: [UInt8] = Array(repeating: 0, count: 3) //needed for difference in size of boolean in swift vs metal
}
要更改日志级别,请使用 ShaderViewLogger.setLogLevel(level: newLevel)
,其中 newLevel 是以下任何级别:
public enum ShaderViewLogLevel: Int {
case none = 0
case error = 1
case debug = 2
}