Continue(s)

Twitter:@dn0t_ GitHub:@ogrew

「デザインのデザイン」を読んだ。

f:id:taiga006:20191106182252j:plain

原研哉著「デザインのデザイン」を読んだ。

GLSLスクールの時間までの空き時間に偶然立ち寄った日本橋丸善で買った。前から名前だけは聞いたことのあった本で、表紙のシンプルさに目を惹かれパラパラ読み飛ばしているうちにレジに並んでいた。最近は専らエッセイ集ばかりを読みふけっていたので新鮮な気持ちで読めた。

最初のデザインの歴史を読み解く部分は門外漢の自分にはやや難解であったが、適宜、線を引いたり調べたりしながら読むことでなんとか理解することができた。そういえば本に線を引いたのも久しぶりのことだった。

語り尽くされたであろう「アート」と「デザイン」の違いについてもしっくりいく説明がなされていた。前段の歴史の部分はこの説明のためにまとめられたと言っていいかもしれない。

無印良品のコンセプトを改めて説明するくだりが印象深い。

(「これがいい」ではなく「これでいい」と言う満足感を目指すという説明の後。)消費社会も個別文化も「が」で走ってきて世界の壁に突き当たっている。そう言う意味で、僕らは今日「で」の中に働いている「抑制」や「譲歩」、そして「一歩引いた理性」を評価すべきである。「で」は「が」よりも一歩高度な自由の携帯ではないだろうか。(中略)明瞭で自信に満ちた「これでいい」を実現すること、それが無印良品のヴィジョンである。

この本はタイトルからいかにも意匠を志す者らに向けられた書籍のように見受けられるが、実際はあとがきにもある通りデザインに疎い自分のような人間がデザインのその繊細さ・掴みどころの無さに触れ、実際にその言葉に耳をすます人たちとの交感のきっかけになることを目指した本であった。

久しぶりに無地良品のカレーが食べたくなったのでオンラインショップを見てみるとオンラインショップサイトのweb designはあまり洗練されていないように感じた。靴下とグリーンカレーをまとめ買いした。

デザインのデザイン

デザインのデザイン

【TouchDesigner】hsv2rgbを”完全に理解した”話。

最近にしてはTwitterの伸びが良かった投稿。 glslfan,glslsandbox等を見たものにglslスクールで学んだテクを織り込んだだけのものです。

その中でフラグメントシェーダーを扱うとき色の扱いが難しいと感じました。 というのも、一番最初に学んだ色の表現方法が vec3(r,g,b) の形式でこれだと求めていた色合いを表現するのに非常にあくせくします。

※以下のshaderはTouchDesigner上のGLSL TOPを利用しています。

uniform vec2 resolution;
uniform vec3 color;
uniform float time;

out vec4 fragColor;

void main() {
    vec2 st = (gl_FragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y);

    float len = length(st);    
    float freq = 20.;
    float speed =2.;    
    float pattern = abs( sin(len * freq - time * speed) );

    float light = .5;
    pattern = light / pattern;
    
    vec3 color = vec3(color.x, color.y, color.z);
        
    vec4 colorOut = vec4(color * pattern, 1.0);
    fragColor = TDOutputSwizzle(colorOut);
}

f:id:taiga006:20191029235047g:plain

※resolution, color, timeはそれぞれGLSL TOPの設定でよしななものを入れています。

上ではred, blueをそれぞれ動かしていますが、色の変化がスムーズには感じられません。 そこでProcessing Community Day Tokyo 2019での一幕を思い出すのです。 そうです、HSV(HSB)を使います。

uniform vec2 resolution;
uniform vec2 params;
uniform float time;

out vec4 fragColor;

vec3 hsv(float h, float s, float v){
    vec4 t = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(vec3(h) + t.xyz) * 6.0 - vec3(t.w));
    return v * mix(vec3(t.x), clamp(p - vec3(t.x), 0.0, 1.0), s);
}

void main() {
    vec2 st = (gl_FragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y);

    float len = length(st);    
    float freq = 20.;
    float speed =2.;    
    float pattern = abs( sin(len * freq - time * speed) );

    float light = .5;
    pattern = light / pattern;
    
    float h = abs(sin(time)); 
    float s = params.x; // saturation
    float v = params.y; // value
    vec3 color = hsv(h, s, v);        
        
    vec4 colorOut = vec4(color * pattern, 1.0);
    fragColor = TDOutputSwizzle(colorOut);
}

f:id:taiga006:20191029235155g:plain

※resolution, params, timeはそれぞれGLSL TOPの設定でよしななものを入れています。
※params.xy はそれぞれsaturation, value(brightness)に対応しています。

ここで注目すべきはhsv関数の部分です。

vec3 hsv(float h, float s, float v){
    vec4 t = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
    vec3 p = abs(fract(vec3(h) + t.xyz) * 6.0 - vec3(t.w));
    return v * mix(vec3(t.x), clamp(p - vec3(t.x), 0.0, 1.0), s);
}

もちろん、この関数は自分で思いついたものではありません。(自力でこれは無理やー!)

以下の記事などに乗っている実装を参考に(というかコピペ)しています。

www.laurivan.com https://nogson2.hatenablog.com/entry/2017/10/29/000539nogson2.hatenablog.com qiita.com

ではこの実質3行の関数の中では何が行われているのでしょうか?

マジックナンバーが多すぎて何がなんだかです。

検索するとwikipediaなどに詳しく書いてありました。

とりあえず読んでみます。

ja.wikipedia.org

hsvはよく円錐、円柱のモデルでわかりやすく説明されます。(それは知ってた。)

f:id:taiga006:20191029221239j:plainWikiより)

見て分かる通り、3D空間上の単位円の円周上をH(Hue)、円の半径をS(Saturation)、そして円柱の高さ(深さ?)をV(Value)としてそれぞれ見立てることができます。 なるほど?

ここでS=0であればr=g=b=Vとなることがわかります。(つまりグレースケールです。)

その後、急に場合分けの式が出てきてわけがわからないので他のサイトを見ていきます。

これらのサイトを読みました。

www13.plala.or.jp ofo.jp www.peko-step.com

だんだんわかってきました。 つまるところ、こんな感じです。

f:id:taiga006:20191029224802p:plainRGB←→HSB相互変換【Windowsプログラミング研究所】さんより)

上の記事にもある通り、注目すべきはそのHSVの最大・最小値です。 その最大・最小値はHueの値によって計算方法が変わります。それは上の円を見てもらえればわかると思います。円をぐるっと回っていく中で増減しているRGBの対象がかわっていくのがわかります。それが具体的には60度ずつで変わっていきます。これに対応する形で最大・最小値も変わっていくことになります。このパターン分けがあるせいで計算が難しくなっています。

さてこのへんで座学は飽きてきました。というか7割位理解できた気がします。この「60度ごと」鍵となっていて最初の関数の t.xyz が出ていることがわかってきたのであとは愚直なコードを書いてみて比較することにします。

できました。

vec3 hsv(float h, float s, float v){
    float r = v, g = v, b = v;

    if (s == 0) {
        return vec3(r,g,b);
    }

    h *= 6.; // 0.0 ~ 1.0スケールのものを360を6分割した60度ごとに場合分けする

    float i = floor(h); // 60度刻みのどこに属するか
    float f = h - i; // その範囲での割合

    if (i == 0) { // 0 ~ 60度
     // r = v; (MAX)
        g *= 1 - s * (1 - f);
        b *= 1 - s;
    }
    else if (i == 1) { // 60 ~ 120度
        r *= 1 - s * f;
     // g = v; (MAX)
        b *= 1 - s;
    }
    else if (i == 2) { // 120 ~ 180度
        r *= 1 - s;
     // g = v; (MAX)
        b *= 1 - s * (1 - f);
    }
    else if (i == 3) { // 180 ~ 240度
        r *= 1 - s;
        g *= 1 - s * f;
     // b = v; (MAX)
    }
    else if (i == 4) { // 210 ~ 300度
        r *= 1 - s * (1- f);
        g *= 1 - s;
     // b = v; (MAX)
    }
    else if (i == 5) { // 300 ~ 360度
     // r = v; (MAX)
        g *= 1 - s;
        b *= 1 - s * f;
    }
    return vec3(r,g,b);
}

これを使った結果と先程の便利関数を使った結果を比較してみます。

f:id:taiga006:20191029235918g:plain

(左:便利関数、右:愚直関数)

同じ結果と言って差し支えないと思います。

完全に理解したわけではないですが「完全に理解した」レベルにはなったので今回はこれで良しとします。