MIS.W 公式ブログ

早稲田大学公認、情報系創作サークル「早稲田大学経営情報学会」(MIS.W)の公式ブログです!

プログラミングでお絵かき【新歓ブログリレー2020 9日目】

こんにちは。54代タコスです。皆さんはシェーダーというものをご存じですか?ざっくりいうとシェーダーとは3Dモデルをディスプレイで表示するときの色を計算するプログラムです(多分)。この部分は光が当たってないからベースの色から少し暗い色にするみたいなことを考えるとわかりやすいと思います。

ここでは、本来の使い方である3Dモデル云々は置いておいて、ディスプレイに表示する色を計算する機能を使ってお絵かきしたいと思います! 今回は一番簡単な図形である円を描いてみることにします。たかが円されど円です?
書いたコードをブラウザ上ですぐに実行できるshadertoyというサイトがあるので一緒にやってみましょう!プログラミングわからんという人もコピペで動かせるので大丈夫ですよー。

https://www.shadertoy.com/view/WdfcD7

step1

shadertoyを開くと以下のコードが記述されていると思います。

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec4 col = vec4(0.);   
   
    fragColor = col;
}

画面は真っ黒ですね?それでいいのです。 この関数内にある2つの変数について説明します。

まずはfragColor。この変数はピクセルの色を表す変数で、書かなければプログラムが動きません。ここに代入された色が画面に映ります。最重要ですね。
この変数の型はfragColorの隣には書いていませんがvec4型といって、4つのfloat型(少数のことですね)が集まったものです。 つまり色は4つの数字で表されているという事になります。その4つの数字の正体とは・・・
皆さんご存じの光の三原色であるR(赤)+G(緑)+B(青)にA(アルファ値)を加えたものです。
0~1の範囲で赤・緑・青の強さを指定することで色を表現していたのですね。アルファ値に関しては今回はどうでもいいので忘れましょう。どんな数字を入れても結果は変わらないので大丈夫です。
書き方の例を紹介します。例えば、白を表したいときは、RGBを全て最大値である1にすればよいので、

vec4(1.,1.,1.,0.);  

のように書きます。1.じゃなくて1だけじゃダメなんですか?と思ったあなた。ダメなんです。環境にもよりますがshadertoyは小数点にうるさいです。float型にはとりあえず小数点打っておきましょう。

次にcol。これもvec4型です。colorのcol ですね。fragColorに渡す色を入れておくために作った変数です。上のプログラムではvec4(0.)が代入されていますね。
あれ?vec4型は4つの数字の集まりのはずなのに0が一つだけしか入っていません。なぜでしょうか?
これはrgbaに入れる数字が全て同じ場合は省略して1つだけ書けば良いというルールがあるからです。もちろんvec4(0.,0.,0.,0.)と書いても良いのですが面倒ですよね。

colには赤 = 0,緑 = 0,青 = 0、つまり、黒が入れられたという事になります。そしてこのcolはそのままfragColorに代入されていますね?つまりこのプログラムが返す色は黒となります。このプログラムはディスプレイの各ピクセルそれぞれに対して実行されます。だから真っ黒の画面が映ったというわけです。

しかし、全てのピクセルが同じ色になっても円は描けませんよね。自分が色を付けたい位置を指定できるように座標を設定する必要があります。では次のステップに進みましょう。

step2

次は以下のコードをコピペして実行してみましょう。貼り付けたら左下にある黒い三角形のボタンをクリックするか、Alt + Enterキーで実行できます。

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{   
    vec4 col = vec4(0);  
    
    vec2 uv = (fragCoord.xy * 2. - iResolution.xy)/ min(iResolution.x,iResolution.y);
    col = vec4(uv.x,uv.y,0.,0.);
        
    fragColor = vec4(col);
}

f:id:taco8432:20200407012559p:plain こんな感じになればOKです。脱単色ですね。早速コードの解説をしていきます。

増えたところその1

 vec2 uv = (fragCoord.xy * 2. - iResolution.xy)/ min(iResolution.x,iResolution.y);

なにやら難しいですね。今回は何をしているかが大雑把にわかればいいです。では、ずばり何をしているのかというと、座標を画面の中心を原点(0,0)とした座標に変換して、vec2型のuvという変数に代入しています。よく見るxyの座標系と同じですね。 f:id:taco8432:20200407021403p:plain こんな感じです。雑ですみません。 y座標が-1 ~ 1の範囲に収まっているのが肝です。上の計算ではディスプレイの横(x)と縦(y)のうち、短い方の範囲が-1 ~ 1になるようにしています。
vec2型とは、step1で出てきたvec4の中身が2つになったやつです。見た目通りですね。ではuvという変数を構成する2つの数とは何でしょうか?
お察しの通りx座標とy座標です。これで座標を扱えるようになりました。

増えたところその2

  col = vec4(uv.x,uv.y,0.,0.);

colの色を上書きしています。
ここで初めて見るのがuv.xやuv.yという表現。vec2型は変数名の後に.xを付けると1つ目の数、.yを付けると2つ目の数を表すことが出来ます。uv.xはuvの1つ目の数、つまりx座標の値ですね。uv.yはy座標です。
ちなみにvec4型も同様に、x,y,z,wで要素を指定することができます。vec4型は色を表すことが多いので特別にr,g,b,aという文字を使っても指定することが出来ますよ。
ここでは赤の強さをx座標の値、緑の強さをy座標の値、青の強さを0にしています。つまり、x座標が大きくなるほど赤が-1 ~ 1の範囲で大きく、y座標が大きくなるほど緑が-1 ~ 1の範囲で大きくなるということになります。step1で、色の強さは0 ~ 1の範囲で指定するといいましたね?なので0より小さい数を入れた場合は0に、1より大きい数は1として計算されます。

以上の事を考えるとどうして画像のような色になったのかわかりますね。では最後のstep、ついに円を描きます!

step3

以下のコードをコピペで実行すると...

   void mainImage( out vec4 fragColor, in vec2 fragCoord )
{   
    vec4 col = vec4(0);  
    vec2 uv = (fragCoord.xy * 2. - iResolution.xy) / min(iResolution.x,iResolution.y);

    float d = length(uv);
    col = vec4(step(0.5,d));
        
    fragColor = vec4(col);
}

f:id:taco8432:20200407133244p:plain 出やがりましたわね。円です。

コードの解説をしていきましょう。

増えたところ①

   float d = length(uv);

dはfloat型の変数です。座標と原点の距離を入れておくために作りました。distanceのdです。
dに代入されているlength(uv)。length(x)関数はxの長さを返してくれる関数です。今回の場合はlength(uv)なので、原点からuvが表す点までの距離を表すものになっています。原点から遠い点ほどlength(uv)の値は大きくなるということですね。

増えたところ②

   col = vec4(step(0.5,d));

colを上書きしています。rgba全ての値をstep(0.5,d)という値にしているようです。
step関数について説明します。

f:id:taco8432:20200407140744p:plain
step(0.5,x)のグラフ
step(0.5,x)のグラフを見てみると、xが0.5未満のときは0、xが0.5以上の時は1を示していることがわかります。step関数は第2引数が第1引数未満のときは0を、以上の時は1を返す関数なのです。

これでcol = vec4(step(0.5,d))で円を描くことが出来る理由が分かったのではないでしょうか。d、すなわち原点からの距離が0.5未満の時、rgbは0、つまり黒。0.5以上の時、rgbは1、つまり白になるため円が描けるのです。第1引数の値を0.5ではない数字に変えれば円の大きさが変わります。0.5とdの順番を変えれば黒と白を入れ替えることが出来ます。

最後に

いかがでしたか?今回は描いたのは円だけでしたが、世の強い方々はこれを応用してめちゃめちゃカッコいいシェーダーアートを生み出しています。憧れちゃいますね。
シェーダーでは今回描いたような2Dだけではなく3Dの表現もできるんですよ。奥が深い分野だと思います。僕も最近3Dの表現を使ったシェーダーを書いてみたので見てみてください(宣伝)

明日はHarrison Kwagoe君の記事です。JavaAndroidアプリを制作するようですよ。お楽しみに~