はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Shadertoy】ディスタンスフィールド(距離関数)でお絵かきしてみる

はじめに

今回はディスタンスフィールド(距離関数)を学び,以下のようなお絵かきをすることを目的にやっていこうと思います。

f:id:hanaaaaaachiru:20201019190825g:plainf:id:hanaaaaaachiru:20201021201634g:plain
今回の作品

ディスタンスフィールドとは?

ディスタンスフィールド(距離関数)とは2点間の距離を返す関数のことです。

distance(p0, p1) // 点p0と点p1との距離を返す
f:id:hanaaaaaachiru:20201019174831p:plain
距離関数

距離関数を使ってみる

まずはイメージを掴むためにも,距離関数で得た値をそのまま色として出力してみましょう。

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // 左下(0,0) ~ 右上(1,1) になるように正規化
    vec2 uv = fragCoord/iResolution.xy;
    
    // 画面中央からの距離を求める
    float d = distance(vec2(0.5, 0.5), uv);
    
    // 距離を色として出力
    fragColor = vec4(d, d, d, 1);
}
f:id:hanaaaaaachiru:20201019175819p:plain
出力結果

出力画像をみてみると,外側にいくにつれて白色になっています。

これは距離が遠いほどdの値が大きくなる(中心がd=0で,四角がd=\frac{1}{\sqrt{2}})ので,(R, G, B)のそれぞれの値が大きくなり白色になったというわけです。

f:id:hanaaaaaachiru:20201019181131p:plain
座標

楕円を描画してみる

次にstep関数というものを使う事で,簡単に円を描画することができます。

step(edge, x) // xの要素のうち、edgeよりも小さいものは0.0,それ以外を1.0を返す
f:id:hanaaaaaachiru:20201019182150p:plain
例:step(0.3, x)

先ほどの距離関数と組み合わせてみます。

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // 左下(0,0) ~ 右上(1,1) になるように正規化
    vec2 uv = fragCoord/iResolution.xy;
    
    // 画面中央からの距離を求める
    float d = distance(vec2(0.5, 0.5), uv);
    
    // 閾値を0.3としてstep関数を適応
    float edge = 0.3;
    float color = step(edge, d);
    
    // 距離を色として出力
    fragColor = vec4(color, color, color, 1);
}
f:id:hanaaaaaachiru:20201019185534p:plain
出力画像

縦横のピクセル数が異なるので,私の場合は横長の楕円が出力されました。

また色を反転させたいときは、step(edge, 1.0 - d)とすることでできます。

float edge = 0.8;
float color = step(edge, 1.0 - d);
f:id:hanaaaaaachiru:20201019185443p:plain
色の反転

動きを付けてみる

uniform変数の一つであるiTime(シェーダーの再生時間)を使う事で動きをつけることができます。

// 閾値を0 ~ 0.5に時間変化させる
float edge = abs(sin(iTime)) * 0.5;
float col = step(edge, d);
f:id:hanaaaaaachiru:20201019183504g:plain
出力画像

またcol1- colに変更することで色を反転させられます。

f:id:hanaaaaaachiru:20201019184936p:plain
色の反転

線を描画してみる

以下の二つの円を掛け合わせることで,線を描画することができます。

float circleA = step(edge, d);
float circleB = 1.0 - step(edge + 0.01, d);
f:id:hanaaaaaachiru:20201019190536p:plainf:id:hanaaaaaachiru:20201019190538p:plain
←CircleA  →CircleB

この2つの楕円を組み合わせるとこんな感じになります。

float edge = abs(sin(iTime)) * 0.5;
float col = step(edge, d) * (1.0 - step(edge + 0.01 , d));
f:id:hanaaaaaachiru:20201019190825g:plain
出力画像

仕組みとしてはstep(edge, d)1.0 - step(edge + 0.01, d)の両方が1の箇所が1として出力されます。

f:id:hanaaaaaachiru:20201019191533p:plain
仕組み

しましま模様を書いてみる

次にsin関数を上手に使う事で、しましま模様を書いてみようと思います。

float edge = 0.3;
float color = step(edge, sin(d * 100.0 + iTime * 20.0));
f:id:hanaaaaaachiru:20201021180946g:plain
出力画像

仕組みはsin波のedge以上を白,小さいものを黒をして出力しています。

f:id:hanaaaaaachiru:20201021181734p:plain
仕組み

図形をいっぱい描画する

入力された座標を少し加工すると図形を複数描画することができます。

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // 左下(0,0) ~ 右上(1,1) になるように正規化
    vec2 uv = fragCoord /iResolution.xy;
    
    // 座標を加工する
    vec2 st = uv * 10.0;
    st = fract(st);
    
    // 画面中央からの距離を求める
    float d = distance(vec2(0.5, 0.5), st);
    
    float edge = 0.3;
    float col = step(edge, sin(d * 20.0 + iTime * 5.0));
    
    // 距離を色として出力
    fragColor = vec4(col, col, col, 1.0);
}
f:id:hanaaaaaachiru:20201021201634g:plain
出力画像

入力されたuvfract関数を用いて繰り返すことで実現をします

fract(x) // xの小数を返す
f:id:hanaaaaaachiru:20201021220741p:plain
座標の加工

さいごに

今回はディスタンスフィールド(距離関数)を使って色々とお絵かきしてみました。

これからもちょくちょくシェーダー関連をあげるかもしれないので、よければお付き合いください。

ではまた。