GameMakerStudio2(GMS2) シェーダー入門

ぽりたんQ(polytan_Q) (@polytan_Q) | Twitter

です。普段は趣味でゲーム作ってます。

 

GameMakerStudio2(GMS2)の日本語の資料って少ないな、というのも今は昔、とても重用できる有志の方の日本語の記事が今はまあまああります。でもシェーダーについてはあんまりないんじゃないかな?と思うので、とても簡単なシェーダーの入門記事です。

f:id:polytanQ:20190921230506p:plain

 

ちなみに大半はこの海外の記事の受け売りです。

https://developer.amazon.com/de/blogs/appstore/post/acefafad-29ba-4f31-8dae-00805fda3f58/intro-to-shaders-and-surfaces-with-gamemaker-studio-2 

 

GMS2のシェーダーはGMS2内のGMLとは違います。いわゆるGLSL言語です。なのでGMLに慣れてる人でもちょっと手をつけにくいのではないでしょうか?でもそんなに難しくないです。

 

 

そもそもGMS2のシェーダーで何ができるか?について少し説明します。知ってる人は飛ばしてください。

端的に言えば画像(スプライト)の表示をいじる事ができます。GMS2はオブジェクトや背景をサーフェスに投影してゲーム画面を写しています。このオブジェクトやサーフェスにシェーダーを適用すれば見た目をいじることができます。サーフェスについてはこの記事では深く触れません。最悪知らなくても平気です。 

 

どう見た目をいじれるのか。

これは何もシェーダーを適用してないときのゲーム画面です。

f:id:polytanQ:20190921230220p:plain



例えばこのように普通のゲーム画面をグレースケールや白黒の1bitにする事ができます。

f:id:polytanQ:20190921225709p:plain

f:id:polytanQ:20190921230340p:plain

他にはこのようなゲームボーイ風にもできたりします。

f:id:polytanQ:20190921230506p:plain

 

シェーダー自体の書き方より前に、シェーダーの適用方法について説明します。これはオブジェクトやサーフェスに適用するGML言語です。

 

新規プロジェクトを立ち上げます。(GMLで作ります。)新規スプライト
を作成し、そのスクリプトを表示するオブジェクトを作ります。

f:id:polytanQ:20190921231444p:plain

説明用に透過PNG画像のスクリプトを用意しました。

 f:id:polytanQ:20190921231358p:plain

オブジェクトを作成したら、見た目をいじるのでdrawイベントを作成します。

shader_set関数を使用します。()内にシェーダーの名前を入れます。今回はテスト用にshd_testとします。

シェーダーをセットしたらスクリプトを描画します。一番簡単なdraw_selfで構いません。次にshader_reset()関数を使い、シェーダーをリセットします。他のオブジェクトに影響が出ないようにするためです。

とりあえずこれで最低限シェーダーを適用する準備ができました。

f:id:polytanQ:20190921231911p:plain

 

shader_set(shd_test);
draw_self();
shader_reset();

 

 

shaderを新規作成します。先程設定したシェーダー名でokです。シェーダーは新規作成するだけでテンプレみたいなものができます。

二つのシェーダーができます(vertex shaderとfragment shader)、二つというより二つひと組と言った方が正確ですが...

f:id:polytanQ:20190921232828p:plain

 

shaderは先程も触れた通りGMLではないので、セミコロンがあってもなくても大丈夫なGMLとは違い文末のセミコロンは必須です。コンパイルエラー文もあまり間違い部分に触れてくれないので注意が必要です。

シェーダーをいじらないで出力すると当然ですがそのまま表示されます。(roomにオブジェクトをそのまま置いて出力するだけです。)

f:id:polytanQ:20190921233155p:plain

 

 

.fshのほうをいじります。

f:id:polytanQ:20190921233539p:plain

この部分は最後の出力文で、ドットを出力する時のRGB値とアルファ値を指定することでオブジェクトの色を変えます。今回はオブジェクトを真っ赤にしてみます。

f:id:polytanQ:20190921234127p:plain

void main()
{
    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

(1.0, 0.0, 0.0, 1.0);はそれぞれ(R,G,B,アルファ値)を表しています。

1が最大値になります。

 

これでオブジェクトを表示してみます。

こんな感じになるかと思います。

f:id:polytanQ:20190921233711p:plain

確かに真っ赤になったけど...思ってたのと違う!と思ったんじゃないでしょうか?

透過部分も全て真っ赤にしているからですね。(アルファ値を常に1にしている)これを回避するには次のように記述します。スプライトのアルファ値を取得してそのままアルファ値に代入しています。

f:id:polytanQ:20190921234040p:plain

varying vec2 v_vTexcoord;

void main()
{
    vec4 texColor = texture2D(gm_BaseTexture, v_vTexcoord);
    gl_FragColor = vec4(1.0, 0.0, 0.0, texColor.a);
}

 

これで透明な部分はそのままに真っ赤にできたと思います。

f:id:polytanQ:20190921234013p:plain

次はグレースケールにしてみたいと思います。簡単な方法でグレースケールにしてみたいと思います。RGB値を全て足して3で割った値をRGB全てに入れるやり方です。これは簡易的に輝度を出しているわけですが、人の目から感じる輝度とは少し違うので、厳密にやりたい方はRGB YUV 変換あたりで調べて変換式を適用すればいいと思います。

 

f:id:polytanQ:20190921234337p:plain

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    vec4 texColor = texture2D(gm_BaseTexture, v_vTexcoord);
    float gray = (texColor.r + texColor.g + texColor.b) / 3.0;
    gl_FragColor = v_vColour * vec4(gray, gray, gray, texColor.a);
}

結果です。

f:id:polytanQ:20190921234357p:plain

次はこの輝度値を利用して白黒の1bitシェーダーを作ってみます。仕組みとしては、輝度値がある閾値を超えたら白、超えてなかったら黒を出力する仕組みです。

GLSL言語はif文が使えます。あまり複雑な処理をシェーダー内でやると重くなる可能性がありますが、この程度なら大丈夫でしょう。

以下のように記述してみます。

 

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    vec4 texColor = texture2D(gm_BaseTexture, v_vTexcoord);
    float gray = (texColor.r + texColor.g + texColor.b) / 3.0;
    if (gray >= 0.8) {
    gl_FragColor = v_vColour * vec4( 1, 1, 1, texColor.a);
    } else {
    gl_FragColor = v_vColour * vec4( 0, 0, 0, texColor.a);
    }
}

その結果がこちらです。(黒背景だと見づらいため背景の色を変えています。)

f:id:polytanQ:20190921235025p:plain

白黒になっていますね。更に閾値を細かくしてGB風の2bit(4色)シェーダーを作ってみます。以下のように記述します。閾値はいじってちょうどいいものを探すといいでしょう。

 

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main()
{
    vec4 texColor = texture2D(gm_BaseTexture, v_vTexcoord);
    float gray = (texColor.r + texColor.g + texColor.b) / 3.0;

    if (gray >= 0.9) {
    gl_FragColor = v_vColour * vec4(0.933, 0.996, 0.870, texColor.a);
    } else if(gray >= 0.750){
    gl_FragColor = v_vColour * vec4(0.674, 0.843, 0.580, texColor.a);
    } else if(gray >= 0.50){
    gl_FragColor = v_vColour * vec4(0.321, 0.568, 0.447, texColor.a);
    } else {
    gl_FragColor = v_vColour * vec4(0.098, 0.203, 0.258, texColor.a);
    }
}

その結果がこちらです。(少し結果がわかりづらくてすみません)

f:id:polytanQ:20190921235737p:plain

 

ここで、シェーダーの簡単な仕組みは分かったけど、ゲーム画面全体にシェーダーを適用する場合、オブジェクト1つ1つにこれを適用するのは面倒だし非効率だと思うはずです。

ここで、サーフェスが登場します。ゲーム画面は最終的にアプリケーションサーフェスに描画されて出力されます。このアプリケーションサーフェスにシェーダーを適用すれば一つの命令でゲーム画面全体にシェーダーを適用できます。今回はアプリケーションサーフェス用にオブジェクトを用意するやり方を紹介します。

アプリケーションサーフェス用にオブジェクトを新規作成します。先ほどまでのオブジェクトのdrawイベントは削除します。

 

drawイベントではなく、draw_GUIイベントでシェーダーを適用します。シェーダーを適用する範囲をアプリケーションサーフェスに設定します。以下のように記述します。

 

shader_set(shd_test);

if (surface_exists(application_surface)){
    shader_set_uniform_f(surface_get_width(application_surface), surface_get_height(application_surface));
}

draw_surface(application_surface, 0, 0);

shader_reset();; 

 

追加でcreateイベントに次のように記述します。

 

application_surface_draw_enable(false); 

 

f:id:polytanQ:20190922000608p:plain

これでゲーム画面全体にシェーダーを適用できました。オブジェクトをroomに配置するのを忘れずに。

(背景も適用されています。)

f:id:polytanQ:20190922000239p:plain

 

シェーダー入門編は以上です。お役に立てれば幸いです。