Kagari
キーワードを入力してドキュメントを検索します。

ガイド

シェーダーの書き方

シェーダーは、アセットディレクトリに置く WGSL ファイルで、レンダラーが GPU で実行します。このガイドでは、std::screen::shader の 6 つのロールそれぞれについて、ファイルが定義するもの、読む引数、受け取る時刻を説明します。std::shaders の既製のシェーダーも同じ規則に従っているので、そのソースはよい例になります。

最初のシェーダー

覆うものの彩度を下げ、その強さを引数に取る色フィルターです。Kagari 側では引数の型とアセット定数を宣言します。

kis
use std::screen::shader::{ColorShader, filter};
/// The arguments of `MEMORY`.
#[shader_arg]
struct Memory {
strength: Decimal,
}
const MEMORY: ColorShader<Memory> = asset("memory.wgsl");
const fn flashback(mut screen: Screen) -> Screen {
screen.filter().change(Some(filter(MEMORY, Memory { strength: 0.8 }))).apply_animation(tween(ms(800)));
screen
}

WGSL ファイルでは、同じフィールドを持つ Args を宣言し、effect を定義します。

wgsl
// The flashback filter: desaturates the stage by `strength`.
struct Args {
strength: f32,
}
fn effect(uv: vec2<f32>) -> vec4<f32> {
let c = kg_source(uv);
let gray = dot(c.rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
return vec4<f32>(mix(c.rgb, vec3<f32>(gray), clamp(args.strength, 0.0, 1.0)), c.a);
}

ファイルに書くのは Args、effect、自分のヘルパーだけです。Kagari はファイルの前にロールごとのプレリュードを置きます。プレリュードはバインディング(args など)、エントリーポイント、kg_ ヘルパーを宣言します。バインディングや @vertex、@fragment、@compute の関数を自分で宣言しないでください。

ロール

アセット型がロールを決め、ロールがファイルの定義する effect 関数を決めます。

アセット型effect入力
ColorShader<A>fn effect(uv: vec2<f32>) -> vec4<f32>uv は画面上の位置で、デザイン解像度全体で 0..1。kg_source(uv) はフィルターが覆うものを読む
MixShader<A>fn effect(uv: vec2<f32>) -> vec4<f32>uv は色と同じ。kg_source(uv) はエンティティ(不透明度適用済み)、kg_backdrop(uv) はその下にあるものを読む。最終的な画素を返すので、合成はシェーダーが行う
TextureShader<A>fn effect(uv: vec2<f32>) -> vec4<f32>uv はペイント全体で 0..1。kg_size() はペイントのサイズ(ピクセル)
MaskShader<A>fn effect(uv: vec2<f32>) -> f32uv はマスクされる矩形全体で 0..1。kg_size() はそのサイズ(ピクセル)。0.0 から 1.0 を返す。範囲外の値は切り詰められる
LocalMotionShader<A>fn effect(state: KgState, t: f32) -> KgStatestate はこのフレームでのエンティティのポーズ、t は変換後の時刻
TransitionalMotionShader<A>fn effect(start: KgState, end: KgState, progress: f32, t: f32) -> KgState開始ポーズと終了ポーズ、イージング適用後の進行度 0..1、時刻

WGSL は、from など Kagari では名前に使える語の一部を予約しています。WGSL ファイルの中では、引数や Args のフィールドにそれらの名前は付けられません。effect の引数名は自由です。

ミックスシェーダーは通常の合成を置き換えます。ミックスのプレリュードには、std のブレンドモードが使う W3C の合成式のヘルパーがあります。kg_straight(c)(切り詰めたストレートの色)、kg_composite(s, b, m)(ソース s を背景 b の上に、混ぜた色 m で合成)、ブレンド関数 kg_hard_light、kg_soft_light、kg_dodge、kg_burn、kg_lum、kg_clip_color、kg_set_lum、kg_sat、kg_set_sat です。乗算モードは次のとおりです。

wgsl
alias Args = KgNoArgs;
fn effect(uv: vec2<f32>) -> vec4<f32> {
let s = kg_source(uv);
let b = kg_backdrop(uv);
return kg_composite(s, b, kg_straight(b) * kg_straight(s));
}

エンティティを上下に揺らすローカルモーションです。

wgsl
struct Args {
height: f32,
period: f32,
}
fn effect(state: KgState, t: f32) -> KgState {
var s = state;
if args.period <= 0.0 {
return s;
}
s.position.y -= args.height * (0.5 - 0.5 * cos(2.0 * KG_PI * t / args.period));
return s;
}

約束事

  • 色はどこでも乗算済みで、リニア光です。kg_source と kg_backdrop が返す値、Color の引数、effect が返す値がすべてそうです。CSS と同じように sRGB の値で計算するには、乗算を戻して変換し、計算後に変換し直します:kg_srgb_to_linear、kg_linear_to_srgb、kg_unpremultiply、kg_premultiply。
  • 位置とサイズはデザイン解像度のピクセルで、y は下向きです。
  • KgState は { position: vec2<f32>, scale: f32, rotation: f32 } です。position はエンティティのアンカーが置かれる位置、scale は拡大率、rotation はラジアン単位の時計回りの回転です。不透明度はモーションに含まれません。
  • 時刻は秒単位です:t と kg_time()。
  • プレリュードの名前は kg_、Kg、KG_ で始まります。自分の名前にこれらの接頭辞を使わないでください。

プレリュードがロールごとに提供するもの:

ヘルパーロール値
KG_PIすべてπ
kg_time()すべて効果の時刻(秒)
kg_srgb_to_linear(c)、kg_linear_to_srgb(c)すべてvec3<f32> の伝達関数
kg_unpremultiply(c)、kg_premultiply(c)すべて乗算済みとストレートの vec4<f32> の変換。完全に透明な色の乗算を戻すと 0
kg_size()テクスチャー、マスクペイントまたはマスクされる矩形のサイズ(ピクセル)
kg_target_size()、kg_texel()色、ミックス、テクスチャー、マスクデザイン解像度(ピクセル)と、uv 単位での 1 ピクセル
kg_source(uv)色、ミックス効果の下にある内容
kg_backdrop(uv)、kg_opacity()ミックスエンティティの下にあるものと、エンティティの不透明度

引数

シェーダー型の A がシェーダーの引数型です。() か、#[shader_arg] を付けた構造体です。#[shader_arg] 構造体は名前付きフィールドを持ち、型パラメータを持ちません(const パラメータは使えます)。各フィールドの型は次のいずれかです。WGSL ファイルは、対応するメンバーを同じ順序で並べた struct Args を宣言します。引数型が () なら alias Args = KgNoArgs; と書きます。メンバーは位置で対応するので、WGSL が Kagari のフィールド名を予約している場合などには、WGSL のメンバーに別の名前を付けられます。プレリュードは値を args としてバインドします。

Kagari のフィールド型WGSL の型値
boolu320 または 1
i32、u32i32、u32そのまま
Decimal、Ratf32最も近い f32
Colorvec4<f32>乗算済みのリニア光
Point、Offset、Sizevec2<f32>ピクセル
Anchorvec2<f32>割合
Anglef32ラジアン、時計回り
Durationf32秒
フィールドのない列挙型u32宣言の中でのバリアントの番号(0 から)
#[shader_arg] 構造体同じメンバーを同じ順序で持つ構造体フィールドごと
[T; N](N > 0)array<T', N>要素ごと

値は WGSL のストレージの規則(アラインメントとパディングを含む)で配置されるので、対応する WGSL の宣言で書いたとおりに読めます。配列や入れ子の構造体も使えます。

kis
use std::screen::{Color, Point};
#[shader_arg]
pub struct Spot {
pub position: Point,
pub color: Color,
pub radius: Decimal,
}
#[shader_arg]
pub struct Spots {
pub count: u32,
pub spots: [Spot; 4],
}
wgsl
struct Spot {
position: vec2<f32>,
color: vec4<f32>,
radius: f32,
}
struct Args {
count: u32,
spots: array<Spot, 4>,
}
  • Kagari 側はコンパイラが検査します。構造体以外への #[shader_arg] は E3018、名前付きフィールドを持たない構造体や型パラメータを持つ構造体は E3025、ほかの型のフィールドや長さ 0 の配列は E3026 です。
  • WGSL の Args が Kagari の型と合っているかは、まだ何も検査しません。手で揃えてください。予定: 両者を比べるビルド時の検査。
  • 配置後の値は 16384 バイト以下でなければなりません。それより大きい値では、スロットを効果なしで描き、警告をログに出します。

効果の時刻

効果はどれも kg_time()(モーションでは t も)で秒単位の時刻を受け取ります。

  • フィルター、ミックス、マスク、ローカルモーション、ペイントは、そのスロットの時計で進みます。スロットとは、画面フィルター、エンティティのフィルター・ミックス・マスク・ローカルモーション、画像レイヤーのペイントです。時計は、スロットに新しいシェーダーが設定されたとき、それを設定したトゥイーンやトランジションの開始時に 0 から始まります。その後の変更が同じシェーダーを保つ間は、引数がどう変わっても、ステップをまたいでも進み続けます。ペイントでは同じテクスチャーシェーダーと同じマスクシェーダー、ローカルモーションでは同じシェーダーと同じ振る舞いが「同じシェーダー」です。
  • ゲームが次のステップへ進むと、変わらなかったスロットの時計は引き継がれます。ロードの後など、引き継ぐ時計がないときは、ステップのエントリー時にすでに効果を持っているスロットの時計はステップのエントリーから始まります。
  • トランジションの中のマスクは、トランジションの開始から数えます。
  • トラベルは開始から数えます。progress はトラベルのイージング適用後の進行度で、0 から 1 まで進みます。
  • animations 設定がオフのとき、効果の時刻はすべて 0 で、トラベルは時間がかかりません。

ローカルモーションの LocalMotionBehavior は、シェーダーに渡す前に時計を変換します。Periodic { after, period } は 0..after を 1 回再生した後 after..after + period を繰り返し、Stall { after } は after で止まります。そのためループするシェーダーは t が小さいままであることを当てにでき、落ち着くモーションは式が終わるちょうどその場所で止まれます。

シェーダーはフレーム間の記憶を持ちません。各フレームは時刻と引数だけから効果を計算します。微分方程式に従うモーションは、sine_integral のように、毎フレームシェーダーの中で 0 から t まで解きます。

引数のトゥイーン

効果のスロットは Prop なので、change(…).apply_animation(tween(…)) でアニメーションします。古い効果と新しい効果が同じシェーダー(ローカルモーションでは同じ振る舞いも)を使うとき、トゥイーンは引数を動かします。f32 の値とベクトル(小数、色、点、サイズ、角度、長さ)はトゥイーンのイージングに従い、u32/i32 の値(整数、bool、列挙型)は開始時に切り替わります。それ以外の変更はトゥイーンの開始時に切り替わります。引数のトゥイーン中も効果の時計は進み続けます。

kis
use std::shaders::color::blur;
screen.filter().change(Some(blur(0.0))).apply_animation(instant());
screen.wait(ms(500));
screen.filter().change(Some(blur(6.0))).apply_animation(tween(ms(1500)));

シェーダーの検査

kagari build は、ゲームが使うすべてのシェーダーを、使われているロールごとに検査します。ロールのプレリュードをファイルの前に置き、その結果を検証します。問題があると、アセットのパス、ロール、ファイル内の行(Args がないときなど、ファイルの行にない問題では行なし)を示す行を出力し、ビルドは終了ステータス 1 で失敗します。

text
error: shader `memory.wgsl` (color): line 9: …

2 つのロールで使うファイルは、ロールごとに 1 回ずつ検査されます。実行時に、あるロールを実行できない GPU(たとえばストレージバッファやコンピュートシェーダーのないもの)では、スロットを None と同じように描き、警告をログに出します。シェーダー効果 を参照してください。

シェーダーファイルは普通のアセットです。asset("…") のパスはアセットディレクトリからの相対パスで、ビルドはファイルをゲームのアーカイブにパックします。std のシェーダーは std.kiarchive にパックされます。アセットディレクトリ直下の std ディレクトリはそのために予約されています。

最終更新日 2026年10月29日