シェーダー
バージョン 1.12.0 から、カスタムシェーダを描画する API が Ebitengine に入りました。このドキュメントでは、カスタムシェーダの使い方について説明します。
シェーダとは
シェーダは GPU 上で実行されるプログラムです。カスタムシェーダは Ebitengine ユーザーが記述できるシェーダです。シェーダを使うことで、複雑な描画が GPU 上で効率的にできるようになります。
Ebitengine はシェーダのうちフラグメントシェーダと呼ばれるものが記述できます。フラグメントシェーダはピクセルごとに実行されるシェーダです。大雑把に言うと、ピクセルごとに色を計算する関数です。この色の計算が、 GPU 上で並列に走ります。
シェーダを使うことでライティング、ブラーなどの様々な効果が実現できます。サンプルについては examples/shader
を参照してください。
go run -tags=example github.com/hajimehoshi/ebiten/v2/examples/shader@latest
Ebitengine はシェーディング言語として Kage という独自言語を採用しています。これは Go 互換の文法を持ちますが、細部が Go とは異なります。 Kage は高いポータビリティを持ちます。 Ebitengine は環境によって OpenGL や Metal などをグラフィックスライブラリとして使いますが、 Kage はどの環境でも同様に動くように、動的にコンパイルされます。
例
package main
// Uniform variables.
var Time float
var Cursor vec2
var ScreenSize vec2
// Fragment is the entry point of the fragment shader.
// Fragment returns the color value for the current position.
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
// You can define variables with a short variable declaration like Go.
lightpos := vec3(Cursor, 50)
lightdir := normalize(lightpos - position.xyz)
normal := normalize(imageSrc1UnsafeAt(texCoord) - 0.5)
ambient := 0.25
diffuse := 0.75 * max(0.0, dot(normal.xyz, lightdir))
// You can treat multiple source images by
// imageSrc[N]At or imageSrc[N]UnsafeAt.
return imageSrc0UnsafeAt(texCoord) * (ambient + diffuse)
}
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
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).DrawTrianglesShader
と DrawTrianglesShaderOptions
もあります。
シェーディング言語 Kage
文法
基本的に Go と同じです。文法レベルでは完全互換です。 gofmt
を実行することさえ出来ます。
Kage には現在 Go の以下の機能がありません。
- ほとんどの型 (
rune
、string
、int
を除く数値型、interface
、スライス、ポインタ、構造体、関数型、チャネル) len
以外の組み込み関数 (new
、make
、panic
など)type
による新しい型の定義- 構造体
import
switch
goto
for-range
- ゴルーチン
defer
init
関数- メソッド定義
エントリーポイント
Kage が現在定義できるのはフラグメントシェーダのみです。以下のシグニチャを持つ Fragment
関数がエントリーポイントになります。
func Fragment(position vec4, texCoord vec2, color vec4) vec4
名前 | 型 | 説明 |
---|---|---|
position | vec4 | 描画先の座標。単位はピクセル。第 3、第 4 要素は常にそれぞれ 0、 1。 |
texCoord | vec2 | 描画元テクスチャの座標。単位はテクセル。 |
color | vec4 | 頂点から与えられる補助的な色情報。各要素は 0 から 1 の値。 DrawTrianglesShader 関数使用時にのみ意味を持つ。 |
(戻り値) | vec4 | 現在の座標の色。各要素は 0 から 1 の値。 |
組み込み型
Kage は次の組み込み型を持ちます。
bool
int
float
vec2
,vec3
,vec4
(ベクター型)mat2
,mat3
,mat4
(行列型)
float
は浮動小数点型です。 Go の float32
や float64
とは異なり精度の保証がありません。
vec2
、 vec3
、 vec4
はベクターと呼ばれ、それぞれ 2、 3、 4 つの値の組を表す型です。各要素は float
です。 Swizzling と呼ばれる操作を行えます。
mat2
、 mat3
、 mat4
はそれぞれ 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
などは無効です。
x
,y
,z
,w
r
,g
,b
,a
s
,t
,p
,q
Uniform 変数
Uniform 変数はシェーダの外部から値が与えられるグローバル変数です。この値はピクセルの位置に依らず一定となります。
Kage では Uniform 変数は大文字から始まる (export される) グローバル変数になります。
Uniform 変数は Kage の中では代入することができません。
Kage では Uniform 変数以外のグローバル変数を定義することができません。
組み込み関数 (Go)
関数 | 説明 |
---|---|
cap(x T) int | T は配列型。配列の長さを返す。 (v2.1.0) |
len(x T) int | T は配列型。配列の長さを返す。 |
組み込み関数 (制御)
関数 | 説明 |
---|---|
discard() | 現在のピクセルの出力を中断する (v2.4.0) |
組み込み関数 (数学)
多くの組み込み関数はジェネリックです。断りがない場合、 T
は float
、 vec2
、 vec3
、 vec4
のいずれかを表します。ベクター型の場合、各要素に関数が適用されます。
関数 | 説明 |
---|---|
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) T | x^y を返す。 |
exp(x T) T | e^{x} を返す。 |
log(x T) T | \log_e{x} を返す。 |
exp2(x T) T | 2^{x} を返す。 |
log2(x T) T | \log_2{x} を返す。 |
sqrt(x T) T | \sqrt{x} を返す。 |
inversesqrt(x T) T | 1/\sqrt{x} を返す。 |
abs(x T) T | x \geq 0 ならば x を、それ以外の場合は -x を返す。 |
sign(x T) T | x \gt 0 ならば 1 を、x = 0 ならば 0 を、それ以外の場合は -1 を返す。 |
floor(x T) T | x 以下の最も近い整数と同じ値を返す。 |
ceil(x T) T | x 以上の最も近い整数と同じ値を返す。 |
fract(x T) T | x - \mathrm{floor}(x) を返す。 |
mod(x, y T) T | x - y \cdot \mathrm{floor}(x/y) を返す。 |
min(x, y T) T | x \lt y ならば x を、それ以外の場合は y を返す。 |
max(x, y T) T | x \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) T | x \cdot (1 - a) + y \cdot a を返す。 |
step(edge, x T) T | x \lt \mathit{edge} ならば 0 を、それ以外の場合は 1 を返す。 |
smoothstep(edge0, edge1, x T) T | x \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) float | x[0] \cdot y[0] + x[1] \cdot y[1] + \cdots を返す。 |
cross(x, y vec3) vec3 | x \times y (クロス積) を返す。 |
normalize(x T) T | x と同じ向きを持つが長さが 1 のベクターを返す。 |
faceforward(n, i, nref T) T | \mathrm{dot}(\mathit{nref}, i) \lt 0 ならば n を、それ以外の場合は -n を返す。 |
reflect(i, n T) T | i - 2 \cdot \mathrm{dot}(n, i) \cdot n を返す。 |
refract(i, n T, eta float) T | (v2.4.0) |
transpose(m T) T | T は行列型。x の転置行列を返す。 |
dfdx(p T) T | |
dfdy(p T) T | |
fwidth(p T) T |
組み込み関数 (画像)
関数 | 説明 |
---|---|
imageSrcNAt(pos vec2) vec4 | 描画画像 N の、与えられたテクセル単位の位置 pos の色を vec4 で返す。 N は 0 から 3 の値を取る。 |
imageSrcNUnsafeAt(pos vec2) vec4 | 描画画像 N の、与えられたテクセル単位の位置 pos の色を vec4 で返す。 N は 0 から 3 の値を取る。セーフバージョン (imageSrcNAt ) との違いは、画像の境界外の位置を指定したときの戻り値である。セーフバージョンはこの場合 vec4(0) を返すが、アンセーフバージョンは未定義である。アンセーフバージョンは高速に動作する。もし位置が画像の境界内にあることが確実ならば、パフォーマンスのためにアンセーフバージョンを使ってもよい。 |
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 プログラムを編集するための、有志によるエディタプラグインがあります。