すだちキャンパス

すだちキャンパス

やってみたこと、学んだことなどのメモ。

GLSLでかっこいい表現がしたい!~基礎編~

こんにちは。GLSL熱が再燃しかかっています。

GLSL Sandbox や Shadertoy を見ていると、めちゃくちゃかっこいいシェーダーが沢山ありますよね。しかし、多少参考にしてみても、いつまで経っても自分でかっこいいシェーダーを書くことができない・・・
というわけで、自分の勉強のために色々な表現をまとめてみることにしました。とりあえずまとめて、適宜アップデートしていくという形をとる予定です。
また流れとしては、とりあえず(最近知った事も含め)基礎的なところからまとめていって、よく見るけど解説はあまり見ないようなかっこいい表現をまとめるつもりです。余裕があったらthree.js関連にも手を出していきたいところです。
カメの歩みですが頑張っていきたいです!

ここからは全て下記の「~~~~」の部分について書いていきます。

vec2 uv = (gl_FragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
vec3 col;
    
   ~~~~
    
gl_FragColor = vec4(col, 1.);

if文の代替手段(条件分岐)

三項演算子

三項演算子を使うと、次のようにマウスの位置によって色を変えるような分岐をさせる事ができます。

vec3 red = vec3(1., 0., 0.);
vec3 blue = vec3(0., 1., 0.);

color = (mouse.x < 0.5) ? red : blue;

「#ifdef」とは?

Shadertoyでよく見ますが、if文とどう違うのかがよく分かっていなかったので。恥ずかしながら知らなかったのですが、C言語でも使われるコマンドのようです。
#ifdef の後に識別子(変数や関数の名前など)を書いて、それが存在しているかによって処理を分岐させるものだそうです。

backbufferについて

backbufferを使うと、直前のピクセルの色にアクセスすることができます。頭で定義するtimeやresolutionと同様に、

uniform sampler2D backbuffer;

としておけばOKです。
これを使うと、ブラーのような効果を作る事ができます。具体的には、

vec4 shadow = texture2D(backbuffer, vec2(gl_FragCoord.xy/resolution))*0.7;

のようなベクトルを、出力する色に加えるといい感じになるようです。

塗りつぶしではなく線の表現にしたい

主に2つの方法があります。
私はネオンぽくなる方がお気に入りです。

float radius = .7;
//普通
float circle1 = step(abs(radius - length(uv)),.005);
//ネオン
float circle2 = .005 / abs(radius - length(uv));
    
float c = (mouse.x < 0.5) ? circle1 : circle2;
col = c * vec3(.5);

f:id:sweetgohan:20200722222801p:plain

レイマーチングで書く基本図形(立体)

立体は基本的にレイマーチングという手法を使って、距離関数(distance function)によって表していくようです。
以下では、それぞれの立体の距離関数をまとめたいと思います。

float sphereFunc(vec3 p){
float radius = 1.;
return length(p) - radius;
}
直方体

sizeベクトルに、vec3(x, y, z)として各辺の長さを入れると好きな大きさの直方体にできます。

float cuboidFunc(vec3 p, vec3 size){
return length(max(abs(p) - size, 0.));
}
立方体

こちらは立方体なので一辺の長さのみを指定します。

float cubeFunc(vec3 p, float size){
return max(max(abs(p.x),abs(p.y)),abs(p.z))-size;
平面
float planeFunc(vec3 p){
    return dot(p, vec3(0.0, 1.0, 0.0)) + 1.0;
}
円柱
float cylinderFunc(vec3 p){
float radius = .5;
float height = 1.;
vec2 h = vec2(radius, height);
vec2 d = abs(vec2(length(p.xz),p.y)) - h;
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
トーラス
float torusFunc(vec3 p){
    vec2 t = vec2(0.75, 0.25);
    vec2 r = vec2(length(p.xy) - t.x, p.z);
    return length(r) - t.y;
}

複数図形の表現

同じ図形の複製ならmod関数
uv = mod(p, 4.0) - 2.0;

このように書くと、図形が複製されます。

異なる図形ならmin関数

異なる図形を並べるような場合は、距離関数とmin関数を組み合わせると良いようです。

図形を組み合わせる場合
float distanceFunc(vec3 p){
    float d1 = cubeFunc(p);
    float d2 = cylinderFunc(p);
    //組み合わせる
    //return min(d1, d2);
    //切り取る
    return max(d1, -d2);
}

画像のような感じになります。特に、立体から立体を切り取る方は色々応用がききそうですよね。
f:id:sweetgohan:20200712181800p:plainf:id:sweetgohan:20200712181813p:plain

ちなみに、理由はわからないのですが、同じ方法で直方体(cuboidFunc)から直方体を切り抜こうとしても出来ませんでした・・・。直方体から立方体(cubeFunc)は切り取ることができたので、そちらを使おうと思います。
cuboidFuncの方は中は空洞みたいになってるという事なんでしょうか・・・。

図形によって色を分けたい場合

構造体を作り、それに距離関数の返り値と色を格納するとできる様です。

struct Intersect{
    float dist;
    vec3  color;
};

Intersect merge(vec3 p){
    float sphere = sphereFunc(p);
    float torus = torusFunc(p);
    vec3 sphereCol = vec3(.7, .9, .7);
    vec3 torusCol = vec3(.5);
    Intersect i;
    i.dist = min(sphere, torus);
    i.color = sphere < torus ? sphereCol: torusCol;
    return i;
}

これを、レイマーチングの際に次のように呼び出して使います。

    Intersect intersect;
    
    for(int i = 0; i < 64; i++){
        intersect = merge(rayPos);
        distance = intersect.dist;
        rayLen += distance;
        rayPos = cameraPos + ray * rayLen;
    }    
    if(abs(distance) < 0.01){
        vec3 normal = normalize(rayPos);
        float diff = clamp(dot(lightDir, normal), 0.15, 1.0);
        col += intersect.color*diff*.7;
    }