シェーダー

バージョン 1.12.0 から、カスタムシェーダを描画する API が Ebitengine に入りました。このドキュメントでは、カスタムシェーダの使い方について説明します。

シェーダとは

シェーダは GPU 上で実行されるプログラムです。カスタムシェーダは Ebitengine ユーザーが記述できるシェーダです。シェーダを使うことで、複雑な描画が GPU 上で効率的にできるようになります。

Ebitengine はシェーダのうちフラグメントシェーダと呼ばれるものが記述できます。フラグメントシェーダはピクセルごとに実行されるシェーダです。大雑把に言うと、ピクセルごとに色を計算する関数です。この色の計算が、 GPU 上で並列に走ります。

シェーダを使うことでライティング、ブラーなどの様々な効果が実現できます。サンプルについては examples/shader を参照してください。

go run github.com/hajimehoshi/ebiten/v2/examples/shader@latest

Ebitengine はシェーディング言語として Kage という独自言語を採用しています。これは Go 互換の文法を持ちますが、細部が Go とは異なります。 Kage は高いポータビリティを持ちます。 Ebitengine は環境によって OpenGL や Metal などをグラフィックスライブラリとして使いますが、 Kage はどの環境でも同様に動くように、動的にコンパイルされます。


Ebitengine API

NewShader

func NewShader(src []byte) (*Shader, error)

NewShader はシェーディング言語 Kage で記述されたシェーダプログラムをコンパイルし、結果を返します。

もしコンパイルエラーが起きたならば、 NewShader はエラーを返します。

(*Image).DrawRectShader

func DrawRectShader(width, height int, shader *Shader, options *DrawRectShaderOptions)

DrawRectShader は指定された幅と高さ、指定されたシェーダを使って矩形を描画します。

DrawRectShaderOptions

DrawRectShaderOptionsDrawRectShader のためのオプションです。

type DrawRectShaderOptions struct {
    // GeoM は描画の幾何行列である。
    // デフォルト (ゼロ) 値は単位行列で、矩形を (0, 0) の位置に描画する。
    GeoM GeoM

    // CompositeMode は描画のコンポジットモードである。
    // デフォルト (ゼロ) 値は通常のアルファブレンディングである。
    CompositeMode CompositeMode

    // Uniforms はシェーダのための Uniform 変数の集合である。
    // キーは Uniform 変数の名前である。
    // 値は float または []float でなければならない。
    // もし Uniform 変数の型が配列、ベクターまたは行列ならば、
    // 線形に展開した値をスライスとして指定しなければならない。
    // たとえば、もし Uniform 変数の型が [4]vec4 ならば、スライスの値の数は 16 になる。
    Uniforms map[string]interface{}

    // Images は描画元画像の集合である。
    // すべての画像の大きさは矩形の大きさと同じでなければならない。
    Images [4]*Image
}

その他

よりプリミティブな描画のために、 (*Image).DrawTrianglesShaderDrawTrianglesShaderOptions もあります。

シェーディング言語 Kage

文法

基本的に Go と同じです。文法レベルでは完全互換です。 gofmt を実行することさえ出来ます。

Kage には現在 Go の以下の機能がありません。

単位モード

バージョン 2.6 から、 Kage に単位モードが加わりました。次のコメントがコンパイラディレクティブとなり、単位モードを指定できます。

ピクセルモードの場合、 Kage シェーダで取り扱う単位が全てピクセルになります。

デフォルトでは後方互換性のためにテクセルモードになっています。新しい Kage プログラムではピクセルモードにすることを強く推奨します。

エントリーポイント

Kage が現在定義できるのはフラグメントシェーダのみです。以下のシグニチャを持つ Fragment 関数がエントリーポイントになります。

func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4
名前説明
dstPosvec4描画先の座標。単位はピクセル。第 3、第 4 要素は常にそれぞれ 0、 1。
srcPosvec2描画元テクスチャの座標。単位はテクセルまたはピクセルで、単位モードによる。
colorvec4頂点から与えられる補助的な色情報。各要素は 0 から 1 の値。 DrawTrianglesShader 関数使用時にのみ意味を持つ。
(戻り値)vec4現在の座標の色。各要素は 0 から 1 の値。

組み込み型

Kage は次の組み込み型を持ちます。

float は浮動小数点型です。 Go の float32float64 とは異なり精度の保証がありません。

vec2vec3vec4 はベクターと呼ばれ、それぞれ 2、 3、 4 つの値の組を表す型です。各要素は float です。 Swizzling と呼ばれる操作を行えます。

ivec2ivec3ivec4 も同様にベクターであり、各要素は int です。

mat2mat3mat4 はそれぞれ 2、 3、 4 次の正方行列を表す型です。各要素は float です。

Kage は他に配列型をサポートします。構造体はまだサポートされていません。

組み込み型の初期化関数

Go と似ていますが、型名を関数のように使うことでその型の値を得ることが出来ます。ベクター型と行列型だけ特殊で、引数を柔軟に取りえます。

v1 := vec4(0)              // Returns a vec4 whose components are all 0.
v2 := vec4(1, 2, 3, 4)     // Returns a vec4 whose components are 1, 2, 3 and 4.
v3 := vec3(5, 6, 7)
v4 := vec4(1, v3)          // Returns a vec4 whose components are 1, 5, 6 and 7.

m1 := mat4(2)              // Returns a mat4 whose diagonal components are 2 and the others are 0.
m2 := mat4(v1, v2, v3, v1) // Returns a mat4 whose columns are v1, v2, v3 and v1.

Swizzling

ベクター型には Swizzling と呼ばれる特殊な操作があります。要素の一部また全部を一度に読み書きできます。

v1 := vec4(1, 2, 3, 4)
v2 := v1.xyz           // Get vec3(1, 2, 3) and initialize v2 with this.
v2.xyz = v2.xxx        // Get vec3(1, 1, 1), and set it to all the components to v2.
                       // Then, v2 is now (1, 1, 1).

各要素は次のように表されます。同じグループ内ならば自由に組み合わせができますが、違うグループのものを混ぜることはできません。例えば .xxyy.abgr は問題ありませんが、 .xxgg などは無効です。

Uniform 変数

Uniform 変数はシェーダの外部から値が与えられるグローバル変数です。この値はピクセルの位置に依らず一定となります。

Kage では Uniform 変数は大文字から始まる (export される) グローバル変数になります。

Uniform 変数は Kage の中では代入することができません。

Kage では Uniform 変数以外のグローバル変数を定義することができません。

組み込み関数 (Go)

関数説明
cap(x T) intT は配列型。配列の長さを返す。 (v2.1.0)
len(x T) intT は配列型。配列の長さを返す。

組み込み関数 (制御)

関数説明
discard()現在のピクセルの出力を中断する (v2.4.0)

組み込み関数 (数学)

多くの組み込み関数はジェネリックです。断りがない場合、 Tfloatvec2vec3vec4 のいずれかを表します。ベクター型の場合、各要素に関数が適用されます。

関数説明
sin(x T) T\sin{x} を返す。
cos(x T) T\cos{x} を返す。
tan(x T) T\tan{x} を返す。
asin(x T) T\arcsin{x} を返す。
acos(x T) T\arccos{x} を返す。
atan(y_over_x T) T\arctan(\mathit{y\_over\_x}) を返す。
atan2(y, x T) T\arctan(y/x) を返す。
pow(x, y T) Tx^y を返す。
exp(x T) Te^{x} を返す。
log(x T) T\log_e{x} を返す。
exp2(x T) T2^{x} を返す。
log2(x T) T\log_2{x} を返す。
sqrt(x T) T\sqrt{x} を返す。
inversesqrt(x T) T1/\sqrt{x} を返す。
abs(x T) Tx \geq 0 ならば x を、それ以外の場合は -x を返す。
sign(x T) Tx \gt 0 ならば 1 を、x = 0 ならば 0 を、それ以外の場合は -1 を返す。
floor(x T) Tx 以下の最も近い整数と同じ値を返す。
ceil(x T) Tx 以上の最も近い整数と同じ値を返す。
fract(x T) Tx - \mathrm{floor}(x) を返す。
mod(x, y T) Tx - y \cdot \mathrm{floor}(x/y) を返す。
min(x, y T) Tx \lt y ならば x を、それ以外の場合は y を返す。
max(x, y T) Tx \lt y ならば y を、それ以外の場合は x を返す。
clamp(x, min_value, max_value T) T\min(\max(x, \mathit{min\_value}), \mathit{max\_value}) を返す。
mix(x, y, a T) Tx \cdot (1 - a) + y \cdot a を返す。
step(edge, x T) Tx \lt \mathit{edge} ならば 0 を、それ以外の場合は 1 を返す。
smoothstep(edge0, edge1, x T) Tx \le \mathit{edge0} ならば 0 を、 x \ge \mathit{edge1} ならば 1 を、それ以外の場合はエルミート補間を 0 から 1 の値で行った値を返す。
length(x T) float\sqrt{x[0]^2 + x[1]^2 + \cdots} を返す。
distance(p0, p1 T) float\mathrm{length}(p0 - p1) を返す。
dot(x, y T) floatx[0] \cdot y[0] + x[1] \cdot y[1] + \cdots を返す。
cross(x, y vec3) vec3x \times y (クロス積) を返す。
normalize(x T) Tx と同じ向きを持つが長さが 1 のベクターを返す。
faceforward(n, i, nref T) T\mathrm{dot}(\mathit{nref}, i) \lt 0 ならば n を、それ以外の場合は -n を返す。
reflect(i, n T) Ti - 2 \cdot \mathrm{dot}(n, i) \cdot n を返す。
refract(i, n T, eta float) T(v2.4.0)
transpose(m T) TT は行列型。x の転置行列を返す。
dfdx(p T) T(注意: この関数の結果は内部の状態に依存し、非決定的です。)
dfdy(p T) T(注意: この関数の結果は内部の状態に依存し、非決定的です。)
fwidth(p T) T(注意: この関数の結果は内部の状態に依存し、非決定的です。)

組み込み関数 (画像)

関数説明
imageSrcNAt(pos vec2) vec4描画元画像 N の、与えられたテクセルまたはピクセル単位の位置 pos の色を vec4 で返す。単位は単位モードによる。 N は 0 から 3 の値を取る。 pos は、 N の値に関わらず、常に 0 番目のテクスチャの位置である。 N ≧ 1 については、 Kage が自動的に N 番目のテクスチャの適切な位置に変換する。
imageSrcNUnsafeAt(pos vec2) vec4描画元画像 N の、与えられたテクセルまたはピクセル単位の位置 pos の色を vec4 で返す。単位は単位モードによる。 N は 0 から 3 の値を取る。 pos は、 N の値に関わらず、常に 0 番目のテクスチャの位置である。 N ≧ 1 については、 Kage が自動的に N 番目のテクスチャの適切な位置に変換する。
セーフバージョン (imageSrcNAt) との違いは、画像の境界外の位置を指定したときの戻り値である。セーフバージョンはこの場合 vec4(0) を返すが、アンセーフバージョンは未定義である。アンセーフバージョンは高速に動作する。もし位置が画像の境界内にあることが確実ならば、パフォーマンスのためにアンセーフバージョンを使ってもよい。
imageSrcNOrigin() vec2描画元画像 N のテクスチャ上における左上位置を、テクセルまたはピクセルで返す。単位は単位モードによる。
imageSrcNSize() vec2描画元画像 N の大きさを、テクセルまたはピクセルで返す。単位は単位モードによる。
imageDstOrigin() vec2描画先画像 N のテクスチャ上における左上位置を、テクセルまたはピクセルで返す。単位は単位モードによる。
imageDstSize() vec2描画先画像 N の大きさを、テクセルまたはピクセルで返す。単位は単位モードによる。

バージョン 2.6 から、次の関数は非推奨になりました。

imageSrcTextureSize() vec2描画元画像のテクスチャの大きさをピクセル単位で返す。
imageDstTextureSize() vec2描画先画像のテクスチャの大きさをピクセル単位で返す。
imageSrcRegionOnTexture() (vec2, vec2)描画元画像の、テクスチャ上の左上位置と大きさをテクセルまたはピクセル単位で返す。単位は単位モードによる。
imageDstRegionOnTexture() (vec2, vec2)描画先画像の、テクスチャ上の左上位置と大きさをテクセルまたはピクセル単位で返す。単位は単位モードによる。 (v2.1.0)

テクスチャと画像

Ebitengine の画像 (ebiten.Image) は実際には内部のテクスチャ上の一部です。

ピクセルモードの場合、すべての単位はピクセルになるので、あまり留意することはありません。一方テクセルモードの場合、シェーダ上での座標計算が少々複雑になります。以下の説明は、主にテクセルモードを使う場合の説明です。

ピクセルは 1 画素を 1 とする単位です。一方テクセルは 0 から 1 の範囲が全体を表す単位です。テクセルの意味はテクスチャに依存するため、異なるテクスチャのテクセルを混ぜることは出来ません。

ピクセルとテクセルを相互に変換するには次の式を使います。

\begin{aligned} (\text{texels}) &= \frac{(\text{pixels})}{(\text{the texture's size in pixels})} \\ (\text{pixels}) &= (\text{texels}) \cdot (\text{the texture's size in pixels}) \\ \end{aligned}

チュートリアル

エディタのプラグイン

Kage プログラムを編集するための、有志によるエディタプラグインがあります。