はじめに
以下の記事の話の続きです。
●Mac用アプリでシェーダーを使った描画を少し試す(VS Code での Swift開発) - Qiita
https://qiita.com/youtoy/items/8fb6618f6404848c235c
上記記事の手順では、シェーダーに関する処理を、アプリ関連処理を扱うファイル内に文字列として含めて扱っていました。
これを別ファイルに切り出すための変更を加えてみます。
お試し: シェーダーに関する処理を別ファイルに分ける
まずは、前回試した内容に関して少しだけ内容を変更したものを試します。具体的には、「シェーダーに関する処理を別ファイルに分ける」というものです。
前回と同じベースのプロジェクトを作る
前回と同じベースのプロジェクトを作って、その後の手順を進めていきます。そのために、以下のコマンドを実行します。
mkdir MetalShaderApp2
cd MetalShaderApp2
swift package init --type executable
その結果、以下のフォルダ・ファイルが作成されました。
シェーダー用のファイルを作成
次に、シェーダー用のファイルを作成します。具体的には、 Shaders.metal というファイルを MetalShaderApp2.swift と同じ階層に作成しました。
また、 Shaders.metal の内容は以下にしました。
#include <metal_stdlib>
using namespace metal;
struct RasterizerData {
float4 position [[position]];
float2 uv;
};
vertex RasterizerData vertexShader(uint vertexID [[vertex_id]]) {
float2 positions[4] = {
float2(-1.0, -1.0), float2(1.0, -1.0),
float2(-1.0, 1.0), float2(1.0, 1.0)
};
RasterizerData out;
out.position = float4(positions[vertexID], 0.0, 1.0);
out.uv = positions[vertexID] * 0.5 + 0.5;
return out;
}
fragment float4 fragmentShader(RasterizerData in [[stage_in]]) {
return float4(in.uv, 1.0, 1.0);
}
最初に作成されたファイルを書きかえる
次に、最初に作成された 2つのファイルを書きかえます。
Package.swift を書きかえる
最初は Package.swift です。
基本的には前回の記事の内容と同じですが、今回の対応用で 1行追加した部分があります。以下で「この行を追加」とコメントを書いた部分です。
// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "MetalShaderApp2",
platforms: [.macOS(.v13)],
targets: [
.executableTarget(
name: "MetalShaderApp2",
resources: [.process("Shaders.metal")] // この行を追加
)
]
)
.copy で Shaders.metal をバンドルへコピーします。これにより、「バンドルに加えたソースコードのテキストファイル」を、ランタイムで使う(この後に出てくる device.makeLibrary(source:options:) で扱う)ということを行います。
MetalShaderApp2.swift を書きかえる
あとは MetalShaderApp2.swift を書きかえるだけです。これも、ほとんどは前回の記事の内容と同じです。
前回と違う部分は、「★変更箇所: ここから」と「★変更箇所: ここまで」ではさまれた部分です。
import MetalKit
import SwiftUI
class Renderer: NSObject, MTKViewDelegate {
let device: MTLDevice
let commandQueue: MTLCommandQueue
let pipelineState: MTLRenderPipelineState
init?(mtkView: MTKView) {
guard let device = mtkView.device else { return nil }
self.device = device
self.commandQueue = device.makeCommandQueue()!
// ★変更箇所: ここから
guard let metalURL = Bundle.module.url(forResource: "Shaders", withExtension: "metal"),
let source = try? String(contentsOf: metalURL, encoding: .utf8),
let library = try? device.makeLibrary(source: source, options: nil)
else {
return nil
}
// ★変更箇所: ここまで
let desc = MTLRenderPipelineDescriptor()
desc.vertexFunction = library.makeFunction(name: "vertexShader")
desc.fragmentFunction = library.makeFunction(name: "fragmentShader")
desc.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat
guard let state = try? device.makeRenderPipelineState(descriptor: desc) else {
return nil
}
self.pipelineState = state
super.init()
}
func draw(in view: MTKView) {
guard let commandBuffer = commandQueue.makeCommandBuffer(),
let descriptor = view.currentRenderPassDescriptor,
let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: descriptor)
else { return }
encoder.setRenderPipelineState(pipelineState)
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
encoder.endEncoding()
if let drawable = view.currentDrawable {
commandBuffer.present(drawable)
}
commandBuffer.commit()
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
}
struct MetalView: NSViewRepresentable {
func makeNSView(context: Context) -> MTKView {
let mtkView = MTKView()
mtkView.device = MTLCreateSystemDefaultDevice()
context.coordinator.renderer = Renderer(mtkView: mtkView)
mtkView.delegate = context.coordinator.renderer
mtkView.framebufferOnly = false
return mtkView
}
func updateNSView(_ nsView: MTKView, context: Context) {}
func makeCoordinator() -> Coordinator { Coordinator() }
class Coordinator { var renderer: Renderer? }
}
@main
struct ShaderApp: App {
var body: some Scene {
WindowGroup {
MetalView().frame(width: 500, height: 500)
}
.windowResizability(.contentSize)
}
}
動作確認
上記の対応をした後に swift run で処理を実行すると、以下のようにアプリを起動できました。
想定通りにシェーダーによる描画が行われました。
気になること
現在、 Shaders.metal を VS Code で扱っている時に、シンタックスハイライト・整形が使えない状態です。これについて、例えば「Metal Shader Language」をキーワードに VS Code の拡張機能を検索すると、以下などが出てきたりします。
また、Metal が C++ ベースの言語なので、C++ のフォーマッターを使うというやり方もあるようでした。
このあたり、もう少し調べてみられればと思っているところです。
その後やっていること
その後、シェーダーでの描画処理で、静止画ではなく動きのあるものを試してみています。
現状、少し動作が怪しい部分がある気がする状態なので、そのあたりを見直した後に、できれば記事化などできればと思っています。




