GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (5) 〜テクスチャと色 前編〜
はしがき
SwiftUIでSpriteKitのSKShaderを使って遊んでみようというテーマの記事の5回目です。
前回の記事 では、SKShaderの経過時間の変数とマウス座標の渡しかたについて紹介しました。
今回は テクスチャ と 色 をとりあげます。
まず SKSpriteNode にテクスチャを貼る方法と色のプロパティについて説明してから、 SKShader でノードのテクスチャと色を扱う方法を説明していきます。
GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit シリーズの記事一覧
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (1)
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (2) 〜座標〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (3) 〜変数〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (4) 〜経過時間とマウス座標〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (5) 〜テクスチャと色 前編〜(本記事)
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (6) 〜テクスチャと色 後編〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (7) 〜ノード〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (8) 〜3D〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (9) 〜落穂拾い〜
必要な前提知識
この記事では、以下に該当するようなプログラマーを想定読者としています。
- Swiftの基本文法をおおむね理解している
- SwiftUIの基本的な書きかたを知っている
- フラグメントシェーダー(GLSL, HLSL, etc.)のコードを多少でも書いて動かしてみた経験がある
SwiftUIを使っていてシェーダーにも興味があるけれど、SpriteKitやSKShaderにはなじみがない、ぐらいのレベル感です。
シェーダーの入門知識の説明は省きますので、たとえばGLSLの変数の種類や型名(uniform, float, vec4, ...)、関数名(length, mix, ...)、スウィズル演算子や座標の正規化といった用語が前置きなしに出てきます。
環境
以下の環境で動作確認を行っています。
- Xcode 15.2 (15C500b)
- iOS Deployment Target 15.0
- Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
- Swift Playgrounds 4.5 (*)
- macOS Venture 13.6.6
- MacBook Air M2 2022
* XcodeのPlayground(PlaygroundSupport)では動作しません
貼付しているスクリーンショットはシミュレータの iPhone 13(iOS 15.5)で撮影したものをベースにしています。
SKSpriteNode のテクスチャと色
SKSpriteNode にテクスチャを貼る(texture プロパティ)
SKSpriteNode にテクスチャを貼るには、 SKTexture オブジェクトを使います。
- テクスチャの画像データを用意する
- 1の画像データで
SKTexture
オブジェクトを作成する - 2の
SKTexture
オブジェクトをSKSpriteNode
の texture プロパティに格納する
この手順でノードにテクスチャを表示させることができます。
実際に試してみましょう。今回はテクスチャ用の画像として、シンプルなSVGファイルを用意しました。
<svg xmlns="http://www.w3.org/2000/svg"
width="200" height="200"
viewBox="0 0 200 200">
<polygon fill="#f4a581" points="4 124, 100 24, 100 124" />
<polygon fill="#fdda83" points="104 124, 104 54, 170 124" />
<polygon fill="#84cccc" points="0 128, 200 128, 150 176, 50 176" />
</svg>
このファイルをXcodeプロジェクトのアセットに追加します。
画像データを用意したら、それを使って SKTexture
オブジェクトを作成してノードに適用します。
// 〜略〜
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
// 画像ファイルからテクスチャオブジェクトを作成します
// ファイル名の拡張子は省略できます
let texture = SKTexture(imageNamed: "yacht")
// テクスチャオブジェクトをノードに適用します
node.texture = texture
self.addChild(node)
}
}
サンプルコード全体
import SwiftUI
import SpriteKit
struct ContentView: View {
var currentScene: SKScene {
let scene = MySKScene()
scene.scaleMode = .resizeFill
return scene
}
var body: some View {
ZStack {
Color(white: 0.8)
SpriteView(scene: self.currentScene)
.frame(width: 300, height: 250)
}
}
}
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
let texture = SKTexture(imageNamed: "yacht")
node.texture = texture
self.addChild(node)
}
}
別の書きかたとして、 SKSpriteNode
オブジェクトを作成する際に SKTexture
オブジェクトを引数で渡す方法や、画像ファイルを直接引数で渡す方法もあります。
// ノードオブジェクト作成時にテクスチャオブジェクトを引数で渡すことができます
let node = SKSpriteNode(
texture: SKTexture(imageNamed: "yacht"),
size: CGSize(width: 200, height: 200)
)
// または、画像ファイルを直接指定することもできます
// 指定した画像が自動的にSKTextureに変換されて適用されます
let node = SKSpriteNode(
imageNamed: "yacht"
)
node.size = CGSize(width: 200, height: 200)
SKSpriteNode に色を塗る(color プロパティ)
前回までのサンプルコード中にも何度も出てきていますが、 SKSpriteNode
に色を塗るには、 color プロパティを使います。
// 〜略〜
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
// ノード用の色を用意します
let orange = UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
// ノードをオレンジ色に塗ります
node.color = orange
self.addChild(node)
}
}
サンプルコード全体
import SwiftUI
import SpriteKit
struct ContentView: View {
var currentScene: SKScene {
let scene = MySKScene()
scene.scaleMode = .resizeFill
return scene
}
var body: some View {
ZStack {
Color(white: 0.8)
SpriteView(scene: self.currentScene)
.frame(width: 300, height: 250)
}
}
}
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
let orange = UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
node.color = orange
self.addChild(node)
}
}
色とサイズを指定して SKSpriteNode
オブジェクトを作成する書きかたもあります。
let node = SKSpriteNode(
color: UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0),
size: CGSize(width: 200, height: 200)
)
color
プロパティのデフォルト値は UIColor.clear
なので、色の指定がない場合にはノードは無色透明で表示されます。
SKSpriteNode のテクスチャに色をブレンドする(colorBlendFactor プロパティ)
テクスチャと色を組み合わせてみましょう。
colorBlendFactor プロパティを使うと、ノードに貼ったテクスチャの色と、 color
プロパティで指定した色をブレンドすることができます。
// 〜略〜
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
// ノード用の色を用意します
let orange = UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
// 画像ファイルからテクスチャオブジェクトを作成します
let texture = SKTexture(imageNamed: "yacht")
// テクスチャオブジェクトをノードに適用します
node.texture = texture
// ノードの色をオレンジ色に指定します
node.color = orange
// ノードの色をテクスチャにブレンドする比率を指定します
node.colorBlendFactor = 0.5
self.addChild(node)
}
}
サンプルコード全体
import SwiftUI
import SpriteKit
struct ContentView: View {
var currentScene: SKScene {
let scene = MySKScene()
scene.scaleMode = .resizeFill
return scene
}
var body: some View {
ZStack {
Color(white: 0.8)
SpriteView(scene: self.currentScene)
.frame(width: 300, height: 250)
}
}
}
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
let orange = UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
let texture = SKTexture(imageNamed: "yacht")
node.texture = texture
node.color = orange
node.colorBlendFactor = 0.5
self.addChild(node)
}
}
SKSpriteNode
オブジェクトを作成する際に texture
、 color
、 size
の各プロパティの値を引数でまとめて渡す書きかたもあります。
let node = SKSpriteNode(
texture: SKTexture(imageNamed: "yacht"),
color: UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0),
size: CGSize(width: 200, height: 200)
)
node.colorBlendFactor = 0.5
colorBlendFactor
で指定できる値は 0.0 〜 1.0 で、値を変えると色の混ざり具合が変わります。
並べて比べてみるとこんな感じです。
colorBlendFactor
プロパティのデフォルト値は 0.0 で、テクスチャの画像の色がそのまま表示されます(上段左のNo.1)。値が大きくなるにつれて color
プロパティで指定したオレンジ色の混ざり具合が大きくなっていきます。
ノードにテクスチャが貼られていない場合は、 colorBlendFactor
プロパティの値は無視されて、 color
プロパティで指定した色がそのまま表示されます。
SKShader のテクスチャと色
さて、ここからが本題になります。 SKSpriteNode
に適用したテクスチャと色を SKShader
で利用する方法です。
SpriteKitが用意しているSKShaderで使える関数と変数 のうち、 SKSpriteNode
のテクスチャと色に関係があるものを表にまとめてみます。
変数名 / 関数名 | 型 | 種類 | 概要 |
---|---|---|---|
u_texture | sampler2D | uniform変数 | ノードのtextureプロパティに格納されたテクスチャに紐づくサンプラーです。 |
v_tex_coord | vec2 | varying変数 | テクスチャにアクセスするための座標です。詳細は 第2回の記事 をご参照ください。 |
v_color_mix | vec4 | varying変数 | ノードのcolorプロパティの色にcolorBlendFactorプロパティの比率が反映された色の値です。 |
SKDefaultShading() | vec4 | 関数 | ノードがデフォルトで表示する色が得られる関数です。 |
それぞれサンプルコードとスクリーンショットの画像と一緒に紹介していきます。
u_texture 変数と v_tex_coord 変数
u_texture
変数と v_tex_coord
変数を使って、ノードに貼られたテクスチャの色を取得することができます。OpenGL ES 2.0のGLSLと同じく、組み込み関数の texture2D
が使えるので、これを利用して texture2D(u_texture, v_tex_coord)
と書いて、テクスチャの各座標の色を取得できます。
この関数と変数を使って得られるテクスチャの色の値には、ノードの color
プロパティと colorBlendFactor
プロパティで指定した色のブレンドは反映されていません。
// 〜略〜
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
let orange = UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
let texture = SKTexture(imageNamed: "yacht")
node.texture = texture
node.color = orange
node.colorBlendFactor = 0.5
+ // シェーダーを作成します
+ let shader = SKShader(
+ source: """
+ void main() {
+ // ノードに適用されたテクスチャの色を出力します
+ gl_FragColor = texture2D(u_texture, v_tex_coord);
+ }
+ """
+ )
+ // 作成したシェーダーをノードに適用します
+ node.shader = shader
self.addChild(node)
}
}
テクスチャを貼っただけのとき と同じ表示になりました。
v_color_mix 変数
v_color_mix
変数では、ノードの color
プロパティで指定した色に、 colorBlendFactor
プロパティで指定したブレンド比率を反映させた色を取得できます。
テクスチャの色は反映されません。
// 〜略〜
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
let orange = UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
let texture = SKTexture(imageNamed: "yacht")
node.texture = texture
node.color = orange
node.colorBlendFactor = 0.5
// シェーダーを作成します
let shader = SKShader(
source: """
void main() {
- // ノードに適用されたテクスチャの色を出力します
- gl_FragColor = texture2D(u_texture, v_tex_coord);
+ // ノードのcolorプロパティの色に
+ // colorBlendFactorプロパティの比率を反映させた色を出力します
+ gl_FragColor = v_color_mix;
}
"""
)
// 作成したシェーダーをノードに適用します
node.shader = shader
self.addChild(node)
}
}
colorBlendFactor
プロパティの値が 0.5 になっているぶん、 オレンジ色をそのまま出力したとき よりも、色味が薄くなっていることがわかるでしょうか。この色とテクスチャの色が掛けあわされて ノードがデフォルトで表示する色 になっています。
colorBlendFactor
の項目でも書きましたが、ノードにテクスチャが貼られていない場合は、 colorBlendFactor
プロパティの値は無視されて、 color
プロパティで指定した色がそのまま出力されます。
SKDefaultShading 関数
SKDefaultShading
関数では、ノードがデフォルトで表示する色を取得できます。
つまり、ノードの texture
、 color
、 colorBlendFactor
の 各プロパティの値が反映された色 が戻り値になります。
// 〜略〜
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
let orange = UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
let texture = SKTexture(imageNamed: "yacht")
node.texture = texture
node.color = orange
node.colorBlendFactor = 0.5
// シェーダーを作成します
let shader = SKShader(
source: """
void main() {
- // ノードのcolorプロパティの色に
- // colorBlendFactorプロパティの比率を反映させた色を出力します
- gl_FragColor = v_color_mix;
+ // ノードがデフォルトで表示する色を出力します
+ gl_FragColor = SKDefaultShading();
}
"""
)
// 作成したシェーダーをノードに適用します
node.shader = shader
self.addChild(node)
}
}
シェーダーを適用しないとき と同じ表示になりましたね。
今回のサンプルコード全体
import SwiftUI
import SpriteKit
struct ContentView: View {
var currentScene: SKScene {
let scene = MySKScene()
scene.scaleMode = .resizeFill
return scene
}
var body: some View {
ZStack {
Color(white: 0.8)
SpriteView(scene: self.currentScene)
.frame(width: 300, height: 250)
}
}
}
class MySKScene: SKScene {
override func didMove(to view: SKView) {
let white = UIColor(red: 0.99, green: 0.97, blue: 0.93, alpha: 1.0)
let orange = UIColor(red: 0.93, green: 0.73, blue: 0.38, alpha: 1.0)
self.backgroundColor = white
self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SKSpriteNode()
node.size = CGSize(width: 200, height: 200)
let texture = SKTexture(imageNamed: "yacht")
node.texture = texture
node.color = orange
node.colorBlendFactor = 0.5
let shader = SKShader(
source: """
void main() {
// ノードに適用されたテクスチャの色を出力したい場合
gl_FragColor = texture2D(u_texture, v_tex_coord);
// ノードのcolorプロパティの色に
// colorBlendFactorプロパティの比率を反映させた色を出力したい場合
gl_FragColor = v_color_mix;
// ノードがデフォルトで表示する色を出力したい場合
gl_FragColor = SKDefaultShading();
}
"""
)
node.shader = shader
self.addChild(node)
}
}
まとめ
今回は、SKSpriteNodeとSKShaderでテクスチャと色を扱う方法について説明しました。 [1]
この第5回までの内容で、SKShaderでフラグメントシェーダーを書くにあたっての基本事項はだいぶ把握してもらえたのではないでしょうか。
次回は テクスチャ と 色 の後編として、SKShaderを使ったマルチテクスチャの表示など、少し応用的な例をお見せできればと思っています。よろしければ引き続きご覧ください。
次記事 → GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (6) 〜テクスチャと色 後編〜
GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit シリーズの記事一覧
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (1)
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (2) 〜座標〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (3) 〜変数〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (4) 〜経過時間とマウス座標〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (5) 〜テクスチャと色 前編〜(本記事)
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (6) 〜テクスチャと色 後編〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (7) 〜ノード〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (8) 〜3D〜
- GLSLシェーダーで遊ぼう with SwiftUI × SpriteKit (9) 〜落穂拾い〜
参考リンク
- Drawing SpriteKit Content in a View | Apple Developer Documentation
- Applying Shaders to a Sprite | Apple Developer Documentation
- Creating a Custom Fragment Shader | Apple Developer Documentation
- Loading and Using Textures | Apple Developer Documentation
- Tinting a Sprite | Apple Developer Documentation
- The Book of Shaders
- OpenGL ES 2.0 API Quick Reference Card (PDF)
Discussion