【リソパシェーダー紹介】第4回 GLSLとは?
リソパシェーダー紹介第4回となる今回の記事ではそもそもシェーダーとはなにか、という事に関して詳しく紹介していこうと思います
始めに前回までの記事の中で頻繁に登場したvshとfshについて詳しく掘り下げていきます
・vsh
vshは一般にはバーテックスシェーダーと呼ばれており、英名のVertexShaderを略してvshと表記されています(頂点シェーダーとも呼ばれます)
このシェーダーでは名前の通り各頂点の描画を担当しており、具体的には表示の"位置"を変えることが可能となっています。
△それぞれの頂点に対しての処理を行っている
・fsh
fshは一般にはフラグメントシェーダーと呼ばれており、英名のFragmentShaderを略してfshと表記されています(ピクセルシェーダーとも呼ばれます)
このシェーダーでは画面上にある1つ1つのピクセルに対しての描画を担当しており、具体的には表示の"色"を変えることが可能となっています。
△それぞれのピクセルに対しての処理を行っている
そして記事のタイトルで明かしてしまいましたが、今まで見てきたこれらのvsh, fshファイルやシェーダーに使われているコードは全てGLSLという言語で書かれています
・GLSL
第2回のcoreシェーダーの記事にて紹介した、処理の補助として使われている拡張子.glslのファイルも同様にGLSLにて書かれています
△vsh、fshファイルの頭に#moj_import <~.glsl>という記述がある際にincludeフォルダから呼び出されている
まずはGLSLについての簡単な紹介をしていこうと思います。
GLSLとは
>GLSL (OpenGL Shading Language) はGLslangとしても知られ、C言語の構文をベースとした高レベルシェーディング言語である。
https://ja.wikipedia.org/wiki/GLSL
Wikipediaには以上のように記載がされています
端的に言うと"グラフィック関連の記述がしやすいプログラミング言語"といった感じです
大まかな特徴としては、C言語と似た記述である事と、ベクトル演算に特化した機能がある点などが挙げられます。
GLSLにおける全体の処理としては
in等で入力→mainで処理→out等から出力
といった流れになっています
△in等の入力を元にmainで計算後、out等へ出力される
リソパシェーダーでは頂点上(vsh)とピクセル上(fsh)の処理がそれぞれGLSLで記述されています
vsh上では各頂点に対して処理が行われ
fsh上では各ピクセルに対して処理が行われます
流れとしてはvsh→fshの順番で処理が続いているので
vshの出力であるoutは、fshの入力であるinで読み込まれます
△vshのoutの結果がfshのinから読み込まれる
またinやout以外にも、ビルトイン変数という頭にgl_の付いた変数が存在し
こちらはinやoutから宣言を行わなくても、入力、出力の変数として使用することができます
リソパシェーダーでは主にvshの出力にてgl_Positionが使われています(主題から逸れてしまうので今回はここまでで割愛します)
ビルトイン変数の種類や詳しい説明については以下のリンクへどうぞ
https://www.khronos.org/opengl/wiki/Built-in_Variable_(GLSL)
リソパシェーダー上での最終的な出力としては、vshのgl_Positionで位置を、fshのfragColorで色を決定しています。
次にベクトル演算に関してですが、ベクトルを使用した変数は以下のようなコードで設定することができます
//x,yの2つの要素のあるベクトルを設定
vec2 test;
//x,y,z,wの4つの要素のあるベクトルを設定
vec4 test4;
また、それぞれの要素については以下のように呼び出すことが可能です
//x要素を設定
test.x = 1.0;
//y要素を設定
test.y = 2.0;
同時に呼び出すことも可能です
//x,y要素を同時に設定
test.xy = vec2(1.0, 2.0);
この表記をSwizzle演算子と言い、GLSL上では最大4つの要素を持つベクトルまで対応しています
記述としては"."(ピリオド)の後にxyzw、またはrgbaを付ける事で要素を指定することができます
//要素を設定
test4.xyzw = vec4(1.0);
test4.rgb = vec3(0.0);
//順番や重複も自由(ただし要素数は合わせること)
test4.yx = test4.rg;
test4.yzw = test4.rrr;
好みやシチュエーションによって使い分けるのが良さそうです
演算に関しては以下のように記述することで、それぞれ計算されます。
test = vec2(0.0, 1.0);
//単数で加算
test += 1.0; //結果 vec2(1.0, 2.0)
//ベクトルでそれぞれ減算
test -= vec2(2.0, 1.0); //結果 vec2(-1.0, 1.0)
//単数で乗算
test *= 2.0; //結果 vec2(-2.0, 2.0)
//ベクトルでそれぞれ除算
test /= vec2(2.0, 4.0); //結果 vec2(-1.0, 0.5)
シェーダーを書くには
実際にシェーダーを書く環境について紹介していきます
初めにVSCodeを導入し、拡張機能としてShader Toyを入れます
△Shadertoyというシェーダー投稿サイト向けの拡張機能、ローカルでシェーダーを再生するのに便利
次にtest.glslというファイルを作成し、以下のコードをコピペします
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 nCoord;
//座標正規化(0~1)
// nCoord = fragCoord/iResolution.xy;
//座標正規化(-1~1)
// nCoord = fragCoord * 2.0 - iResolution.xy;
// nCoord /= iResolution.xy;
// nCoord /= min( iResolution.x, iResolution.y );
vec3 color;
//色設定(単色)
color = vec3( 0.0, 0.0, 0.0 );
//色設定(座標)
// color.r = nCoord.x;
// color.g = nCoord.y;
//色設定(円形)
// color = vec3( length(nCoord) );
// color = vec3( 1.0 < length(nCoord) );
fragColor = vec4( color, 1.0 );
}
コードを貼り付け後、右クリックからShader Toy: Show GLSL Previewを選択すると、現在のコードからGLSLの内容が描画されます
△描画タブを右クリックし、下に分割を選択しておくと表示の画面比を調整しやすい
確認してみると、最初は真っ黒な画面が表示されるはずです
今回は頂点を使わず、画面上のピクセルで処理が行われるフラグメントシェーダー(またの名をピクセルシェーダー、リソパシェーダー上ではfshに相当)を作っていこうと思います。
色
まず14行目を見てみましょう
//色設定(単色)
color = vec3( 0.0, 0.0, 0.0 );
ここではベクトルとしてcolorに3つの"数"が入っていることが分かります
これらは順番に Red, Green, Blue (以下R,G,B)として0.0〜1.0の間の値で格納され最終的に色として出力されます
まずは試しに一番前の要素であるRの値を1.0に変更してみましょう
color = vec3( 1.0, 0.0, 0.0 );
画面が真っ赤になったのが分かると思います
先ほどまで画面が真っ黒であったのは、RGBの全ての値が0.0になっていた為なのです
次に23行目を見るとcolorがfragColorへ設定されているのが分かると思います
fragColor = vec4( color, 1.0 );
このfragColorは1行目でout vec4 fragColorと指定されている事からも分かる通り、最終的な色を設定するための変数です
この末尾にある1.0というのはAlpha(以下A)つまり不透明度の事を指しています
まとめると以下のようになります
fragColor = vec4( R:赤成分, G:緑成分, B:青成分, A:不透明度 )
このように最終的な色合いがfragColorへRGBAにて0.0〜1.0の範囲で設定されています
また基本的に不透明度Aに関しては常に1.0(完全不透明)を指定しておくのがいいでしょう。
一面同じ色だけの出力では味気ないので次は座標で色が変わるようにしてみましょう
1行目を見るとin vec2 fragCoordとありますが、ここでは座標を取得しています
ここで言う座標とはピクセル上の位置の事を言い、
左下から0,1,2…という風に右上まで数字がカウントされたx座標、y座標の2つの値がvec2としてfragCoordに格納されています
△x幅が1280、y幅が720ピクセルだった場合のfragCoordの座標値
これを、"画面表示の横幅縦幅"が格納されたiResolutionという変数で割ると、座標が正規化され、左下は0.0、右上は1.0となるような0.0~1.0の座標を作ることができます
△fragCoordをx幅1280、y幅720で割り、0.0~1.0に正規化された座標値
試しに5行目のコメントアウトを解除し(Ctrl+/)、x座標とy座標を正規化してみます
そして16行目もコメントアウトの解除で有効化し、正規化したx座標がRに設定されるようにしてみると、左から右にグラデーションが掛かった滑らかな色の表示になると思います
//色設定(座標)
color.r = nCoord.x;
17行目を有効化する事でさらにy座標がGに設定されるようになり、下から上へもグラデーションが掛かった表示にできます
color.g = nCoord.y;
このように座標に対応させて表示を変えていくことが可能です。
図形
今度は色だけでなく形も変更してみましょう
目標としては円が描かれることを目指します
まずは正規化する座標を-1.0~1.0の範囲になるよう計算し直すことで、中心が0.0となるような座標系にしたいと思います
△左下が-1.0、中央が0.0、右上が1.0に正規化された座標値
8,9行目を有効化すると座標が左下-1.0から中心0.0、右上1.0の範囲となり、色の分布も変化します
//座標正規化(-1~1)
nCoord = fragCoord * 2.0 - iResolution.xy;
nCoord /= iResolution.xy;
この状態で、"現在のx,y座標から中心0.0までの距離"を計算するためにlength関数を使用します
これにより、中心は0.0、画面端に近づくにつれ1.0となるような値を円状に取れるようになりました
この値をRGBに入れることで0.0は黒、1.0は白となるような表示にしてみます
20行目を有効化することでぼやけた円が表示されると思います
//色設定(円形)
color = vec3( length(nCoord) );
しかし、この方法だと画面サイズに合わせて円が伸びてしまうため、表示の上では楕円状になってしまいます
これを解決するため、座標を正規化する際に使用する"画面表示の横幅、縦幅"を、"横幅縦幅でどちらか小さい方"の値に置き換えます
この横縦幅で小さい方の値を取るためにmin関数を使用します
これにより0.0を中心に、横縦の短い方の幅に合わせて座標が正規化されるため、縦横比を1:1で揃えることができます
9行目を無効化する代わりに10行目を有効化してみると、表示される円が真円になるのが分かると思います
//座標正規化(-1~1)
nCoord = fragCoord * 2.0 - iResolution.xy;
//
nCoord /= min( iResolution.x, iResolution.y );
最後に21行目を有効にして、1.0未満は値が0.0となるようにすると、境界がクッキリとした円が浮かび上がるようになります。
color = vec3( 1.0 < length(nCoord) );
次回の記事では、今回の情報を踏まえた上でのリソパシェーダーの詳しい解説と
コマンドからリソパシェーダーを使用する方法について紹介していこうと思います。
つづく
リソパシェーダーに関する質問など以下のDiscordサーバーにて受付けています