GLSLSandboxでレイマーチング

#経緯
シェーダーを勉強しようと思い、色々調べていたらレイマーチングというものを知った。
下記のサイトでレイマーチングについて学んだ後、
取り敢えず何か作りたかったので、簡単そうな鉄火巻きを作ることにした。

#使用したもの
[GLSL Sandbox](http://glslsandbox.com/e)　

#目標
１．複数オブジェクトの表示
２．回転や平行移動の使用
３．陰影の表示（ソフトシャドウ）
を実現することを目標とした。

#コード
```
precision mediump float;
uniform float time;
uniform vec2  mouse;
uniform vec2  resolution;

//カメラ
const float PI = 3.14159265;
const float angle = 60.0;
const float fov = angle * 0.5 * PI / 180.0;
const vec3 cPos = vec3(0.0, 2.0, 5.2);
const vec3 cDir = vec3(0.0, -1.0, -2.0);

//光
const vec3 lightPos = vec3(0.0, 5.0, 5.0);
const vec3 lightDir = vec3(0.0, 1.0, 1.0);

//色
vec3 noriColor = vec3(0.2, 0.2, 0.2);
vec3 riceColor = vec3(1.0, 1.0, 1.0);
vec3 tunaColor = vec3(1.0, 0.0, 0.0);
vec3 wPlainColor = vec3(0.7, 0.7, 0.7);
vec3 bPlainColor = vec3(0.3, 0.3, 0.3);

//平行移動
vec3 translate(vec3 p, vec3 t) {
    mat4 m = mat4(vec4(1.0, 0.0, 0.0, 0.0),
                  vec4(0.0, 1.0, 0.0, 0.0),
                  vec4(0.0, 0.0, 1.0, 0.0),
                  vec4(-t.x, -t.y, -t.z, 1.0));

    return (m * vec4(p, 1.0)).xyz;
}

//任意軸回転
vec3 rotate(vec3 p, float angle, vec3 axis){
    vec3 a = normalize(axis);
    float s = sin(angle);
    float c = cos(angle);
    float r = 1.0 - c;
    mat3 m = mat3(
        a.x * a.x * r + c,
        a.y * a.x * r + a.z * s,
        a.z * a.x * r - a.y * s,
        a.x * a.y * r - a.z * s,
        a.y * a.y * r + c,
        a.z * a.y * r + a.x * s,
        a.x * a.z * r + a.y * s,
        a.y * a.z * r - a.x * s,
        a.z * a.z * r + c
    );
    return m * p;
}

//立方体の距離関数
float sdBox( vec3 p, vec3 b )
{
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}

//円柱の距離関数
float sdCappedCylinder( vec3 p, float h, float r )
{
  vec2 d = abs(vec2(length(p.xz),p.y)) - vec2(h,r);
  return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}

//平面の距離関数
float sdPlane( vec3 p, vec4 n )
{
  return dot(p,n.xyz) + n.w;
}

//描画関数
float distanceFunc(vec3 p) {
    //平行移動と回転
    vec3 q = translate(p, vec3(0.0, 0.5, 0.0));
    vec3 r = rotate(q, radians(20.0 * time), vec3(0.0, 1.0, 0.0));
    vec3 s = rotate(r, radians(70.0), vec3(1.5, 0.5, 0.5));

    //オブジェクトの描画
    float tuna = sdBox(s, vec3(0.05, 0.53, 0.05));
    float rice = sdCappedCylinder(s, 0.15, 0.50);
    float nori = sdCappedCylinder(s, 0.17, 0.50);

    //床の描画
    float plain = sdPlane(p, vec4(0.0, 1.0, 0.0, 1.0));

    //結合
    return min(min(min(nori, rice), tuna), plain);
}

//色の計算
vec3 calcColor(vec3 p) {
    //上と同様
    vec3 q = translate(p, vec3(0.0, 0.5, 0.0));
    vec3 r = rotate(q, radians(20.0 * time), vec3(0.0, 1.0, 0.0));
    vec3 s = rotate(r, radians(70.0), vec3(1.5, 0.5, 0.5));

    float tuna = sdBox(s, vec3(0.05, 0.55, 0.05));
    float rice = sdCappedCylinder(s, 0.15, 0.50);
    float nori = sdCappedCylinder(s, 0.17, 0.50);

    float plain = sdPlane(p, vec4(0.0, 1.0, 0.0, 1.0));

    //距離関数が最小のものの色を描画
    float minDist = tuna;
    vec3 color = tunaColor;

    if(rice < minDist) {
        minDist = rice;
    	color = riceColor;
    }
    if(nori < minDist) {
    	minDist = nori;
    	color = noriColor;
    }

    if(plain < minDist) {
        minDist = plain;
        color = bPlainColor;
	    
        //チェック柄にする
        float u = 1.0 - floor(mod(p.x, 2.0));
        float v = 1.0 - floor(mod(p.z, 2.0));
        if((u == 1.0 && v < 1.0) || (u < 1.0 && v == 1.0)){
            color = wPlainColor;
        }
    }

    return color;
}

//法線
vec3 getNormal(vec3 p){
    float d = 0.0001;
    return normalize(vec3(
        distanceFunc(p + vec3(  d, 0.0, 0.0)) - distanceFunc(p + vec3( -d, 0.0, 0.0)),
        distanceFunc(p + vec3(0.0,   d, 0.0)) - distanceFunc(p + vec3(0.0,  -d, 0.0)),
        distanceFunc(p + vec3(0.0, 0.0,   d)) - distanceFunc(p + vec3(0.0, 0.0,  -d))
    ));
}

//影
float genShadow(vec3 ro, vec3 rd){
    float h = 0.0;
    float c = 0.001;
    float r = 1.0;
    float shadowCoef = 0.5;
    for(float t = 0.0; t < 50.0; t++){
        h = distanceFunc(ro + rd * c);
        if(h < 0.001){
            return shadowCoef;
        }
        r = min(r, h * 16.0 / c);
        c += h;
    }
    return 1.0 - shadowCoef + r * shadowCoef;
}

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

    //レイの方向
    float targetDepth = 1.0;
    vec3 ray = normalize(vec3(sin(fov) * p.x, sin(fov) * p.y, -cos(fov)) + cDir * targetDepth);   
	
    //レイマーチング
    float distance = 0.0; // レイとオブジェクト間の最短距離
    float rLen = 0.0;     // レイに継ぎ足す長さ
    vec3  rPos = cPos;    // レイの先端位置
    for(int i = 0; i < 64; i++){
        distance = distanceFunc(rPos);
        if(distance < 0.001) break;
        rLen += distance;
        rPos = cPos + ray * rLen;
    }

    //光
    vec3 light = normalize(lightDir);
    //影
    float shadow = 1.0;
    //出力色
    vec3 outColor;

    //レイの当たり判定
    if(abs(distance) < 0.001){
        //法線
        vec3 normal = getNormal(rPos);

        //光
        vec3 halfLE = normalize(light - ray);
        float diff = clamp(dot(light, normal), 0.1, 1.0);
        float spec = pow(clamp(dot(halfLE, normal), 0.0, 1.0), 30.0);

        //影
        shadow = genShadow(rPos + normal * 0.001, light);

        //色
        vec3 objColor = calcColor(rPos);
	    
        outColor = objColor * diff + vec3(spec);
    }else{
        outColor = vec3(0.0);
    }

    //最終的な出力
    gl_FragColor = vec4(outColor * max(0.5, shadow), 1.0);
}
```

#出力結果
<img width="441" alt="鉄火巻き.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/530813/db24e79e-0768-c6ce-27a2-f85e1da2336c.png">
オブジェクトも正常に表示され、影も映っている。

#感想
作ったものはシンプルだがこれだけでも結構悩まされた。
距離関数を理解するのが難しい。
もっと勉強して色々できるようになったら楽しそう。

#参考にしたサイト
・[2019年度GLSLお役立ちシート]
(https://qiita.com/7CIT/items/6cfac5849dce678f86d3)
・[wgld.org]
(https://wgld.org/d/glsl/)
・[WebGL と GLSL で始める、はじめてのシェーダコーディング（１）](https://qiita.com/doxas/items/b8221e92a2bfdc6fc211)
・[The Book of Shaders]
(https://thebookofshaders.com/?lan=jp)