0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Mac用アプリでシェーダーを使った描画を少し試す(VS Code での Swift開発)【その2】

0
Last updated at Posted at 2026-02-19

はじめに

以下の記事の話の続きです。

●Mac用アプリでシェーダーを使った描画を少し試す(VS Code での Swift開発) - Qiita
 https://qiita.com/youtoy/items/8fb6618f6404848c235c

上記記事の手順では、シェーダーに関する処理を、アプリ関連処理を扱うファイル内に文字列として含めて扱っていました。

2026-02-19_21-13-33.jpg

これを別ファイルに切り出すための変更を加えてみます。

お試し: シェーダーに関する処理を別ファイルに分ける

まずは、前回試した内容に関して少しだけ内容を変更したものを試します。具体的には、「シェーダーに関する処理を別ファイルに分ける」というものです。

前回と同じベースのプロジェクトを作る

前回と同じベースのプロジェクトを作って、その後の手順を進めていきます。そのために、以下のコマンドを実行します。

mkdir MetalShaderApp2
cd MetalShaderApp2
swift package init --type executable

その結果、以下のフォルダ・ファイルが作成されました。

2026-02-18_00-13-58.jpg

シェーダー用のファイルを作成

次に、シェーダー用のファイルを作成します。具体的には、 Shaders.metal というファイルを MetalShaderApp2.swift と同じ階層に作成しました。

2026-02-18_00-15-20.jpg

また、 Shaders.metal の内容は以下にしました。

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行追加した部分があります。以下で「この行を追加」とコメントを書いた部分です。

Package.swift
// swift-tools-version: 6.2
import PackageDescription

let package = Package(
    name: "MetalShaderApp2",
    platforms: [.macOS(.v13)],
    targets: [
        .executableTarget(
            name: "MetalShaderApp2",
            resources: [.process("Shaders.metal")]  // この行を追加
        )
    ]
)

.copyShaders.metal をバンドルへコピーします。これにより、「バンドルに加えたソースコードのテキストファイル」を、ランタイムで使う(この後に出てくる device.makeLibrary(source:options:) で扱う)ということを行います。

MetalShaderApp2.swift を書きかえる

あとは 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 で処理を実行すると、以下のようにアプリを起動できました。

2026-02-18_00-25-28.jpg

想定通りにシェーダーによる描画が行われました。

気になること

現在、 Shaders.metal を VS Code で扱っている時に、シンタックスハイライト・整形が使えない状態です。これについて、例えば「Metal Shader Language」をキーワードに VS Code の拡張機能を検索すると、以下などが出てきたりします。

2026-02-19_21-21-46.jpg

また、Metal が C++ ベースの言語なので、C++ のフォーマッターを使うというやり方もあるようでした。

このあたり、もう少し調べてみられればと思っているところです。

その後やっていること

その後、シェーダーでの描画処理で、静止画ではなく動きのあるものを試してみています。

現状、少し動作が怪しい部分がある気がする状態なので、そのあたりを見直した後に、できれば記事化などできればと思っています。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?