GLSLレイマーチング研究_距離関数について勉強してみた24(smooth minimum functionでメタボールを作ってみた。)

久々にiqさんのブログ記事から[smooth minimum](http://iquilezles.org/www/articles/smin/smin.htm)の距離関数の記事を書きます。

今回は、距離関数というか、Blend関数について、書きます。  

最初は、友人にメタボールって、書けます？  
と言われ、この記事をシェアされたことからでした。  

[メタボールを作る](http://www.syuhitu.org/other/meta/meta.html)

これ、レイマーチじゃ辛くね?

と思い、別方法で実装を考えることに…  

# 球を重ねる

単純に、球を4つ並べて、一点に時間差で向かうようにしたらいいんじゃない？  

と実装するとこんな感じ

```glsl
// 球
float dSphere(vec3 p, float r){
    // ここに図形の距離関数を書く
    return sqrt(p.x*p.x+p.y*p.y+p.z*p.z) - r;
}
// 球を4つ重ねる
float instanceSpherer(vec3 p){
    p = mat3(cos(time),-sin(time),0, sin(time),cos(time),0 ,0,0,1)*p;
    p = mat3(1,0,0, 0,cos(time),-sin(time), 0,sin(time),cos(time))*p;
    vec3 p1 = vec3(p.x+1.0*sin(time), p.y, p.z);
    vec3 p2 = vec3(p.x-1.0*sin(time), p.y, p.z);
    vec3 p3 = vec3(p.x+2.0*sin(time), p.y, p.z);
    vec3 p4 = vec3(p.x-2.0*sin(time), p.y, p.z);
    float spherer01 = dSphere(p1, 1.3+0.3*sin(time));
    float spherer02 = dSphere(p2, 1.3+0.3*cos(time));
    float spherer03 = dSphere(p3, 1.3+0.3*cos(time));
    float spherer04 = dSphere(p4, 1.3+0.3*cos(time));
    return min(min(min(spherer01, spherer02), spherer03), spherer04);
}
```

![motion01.gif](https://qiita-image-store.s3.amazonaws.com/0/102634/715813f5-4544-26fa-9aa0-748d22641286.gif)

光沢付けたらそれっぽくなるんじゃね？

# 光沢をつける

これは、ライティングにスペキュラー光を使えばいけます。

```glsl
vec3 doColor(vec3 p){
    float e = 0.001;
    if (instanceSpherer(p)<e){
        vec3 normal  = genNormal(p);
        vec3 light   = normalize(vec3(1.0, 1.0, 1.0));
        float diff   = max(dot(normal, light), 0.1);
        float spec = pow(diff*diff, 15.0);
        return vec3(diff*0.3+spec, diff+spec, diff+spec);
    }
    return vec3(0.0);
}
```

![motion02.gif](https://qiita-image-store.s3.amazonaws.com/0/102634/3ee4fedf-3d6a-ca79-b89e-3c1928a4591d.gif)

# 滑らかにしたいよね？
どうやってなめらかにするの？  

```glsl
float smin( float a, float b, float k )
{
    float res = exp( -k*a ) + exp( -k*b );
    return -log( res )/k;
}
```
を使います。  
これ何やっているの?  

アイデアとしては、`exp` で重ねる。

```math
\begin{align}
y &= x \\
y &= -(-x) \\
y &= - \log(\exp(-x)) \\
\end{align}
```
として、無理やり `exp` を出して、合成しています。  
ちなみに、`sin` と `asin` や `tan` と `atan` でも試しましたが、うまくはいかず…  

## なんで `exp` ?
数理物理だとよくある手法ですが、`exp` で滑らかにするという事をします。

### 例

フーリエ変換とかもよくある例ですね。

* [フーリエ変換](https://ja.wikipedia.org/wiki/%E3%83%95%E3%83%BC%E3%83%AA%E3%82%A8%E5%A4%89%E6%8F%9B)
* [軟化子](https://ja.wikipedia.org/wiki/%E8%BB%9F%E5%8C%96%E5%AD%90)


```math
{\hat  {f}}(\xi ):=\int _{{-\infty }}^{{\infty }}f(x)\ e^{{-2\pi ix\xi }}\,dx
```

### 恐らく
感覚的には、Toylor展開した時のオーダーが、細かいからだと思っています。  
どうでしょう？

```math
\exp(x) = 1 + \frac{1}{1!} \cdot x^1 + \frac{1}{2!} \cdot x^2 + \cdots + \frac{1}{n!} \cdot x^n + \cdots + \cdots
```

# 完成品

![motion03.gif](https://qiita-image-store.s3.amazonaws.com/0/102634/0d5e0cd8-3b31-c8aa-cedf-21fd7feb9500.gif)


[メタボール](https://muripolife.github.io/raymarching_base/RayMarchingDistanceFunction/metaball/metaball.html)

```glsl
// ============================================================================
// メタボールの距離関数
// ============================================================================

precision mediump float;
uniform vec2  resolution;
uniform vec2  mouse;
uniform float time;
uniform sampler2D prevScene;

float dSphere(vec3 p, float r){
    return sqrt(p.x*p.x+p.y*p.y+p.z*p.z) - r;
}

// 接合部分を滑らかにする
float smin( float a, float b, float k )
{
    float res = exp( -k*a ) +exp( -k*b );
    return -log( res )/k;
}

float instanceSpherer(vec3 p){
    p = mat3(cos(time),-sin(time),0, sin(time),cos(time),0 ,0,0,1)*p;
    p = mat3(1,0,0, 0,cos(time),-sin(time), 0,sin(time),cos(time))*p;
    vec3 p1 = vec3(p.x+1.0*sin(time), p.y, p.z);
    vec3 p2 = vec3(p.x-1.0*sin(time), p.y, p.z);
    vec3 p3 = vec3(p.x+2.0*sin(time), p.y, p.z);
    vec3 p4 = vec3(p.x-2.0*sin(time), p.y, p.z);
    float spherer01 = dSphere(p1, 1.3+0.3*sin(time));
    float spherer02 = dSphere(p2, 1.3+0.3*cos(time));
    float spherer03 = dSphere(p3, 1.3+0.3*cos(time));
    float spherer04 = dSphere(p4, 1.3+0.3*cos(time));
    // return smin(spherer01, spherer02, 32.0);
    // return min(min(min(spherer01, spherer02), spherer03), spherer04);
    return smin(smin(smin(spherer01, spherer02, 32.0), spherer03, 32.0), spherer03, 32.0);
}

float distanceHub(vec3 p){
    return instanceSpherer(p);
}

vec3 genNormal(vec3 p){
    float d = 0.001;
    return normalize(vec3(
        distanceHub(p + vec3(  d, 0.0, 0.0)) - distanceHub(p + vec3( -d, 0.0, 0.0)),
        distanceHub(p + vec3(0.0,   d, 0.0)) - distanceHub(p + vec3(0.0,  -d, 0.0)),
        distanceHub(p + vec3(0.0, 0.0,   d)) - distanceHub(p + vec3(0.0, 0.0,  -d))
    ));
}

vec3 doColor(vec3 p){
    float e = 0.001;
    if (instanceSpherer(p)<e){
        vec3 normal  = genNormal(p);
        vec3 light   = normalize(vec3(1.0, 1.0, 1.0));
        float diff   = max(dot(normal, light), 0.1);
        float spec = pow(diff*diff, 15.0);
        return vec3(diff*0.3+spec, diff+spec, diff+spec);
    }
    return vec3(0.0);
}

void main(){
    vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
    vec3 cPos         = vec3(0.0,  0.0,  3.0);
    vec3 cDir         = vec3(0.0,  0.0, -1.0);
    vec3 cUp          = vec3(0.0,  1.0,  0.0);
    vec3 cSide        = cross(cDir, cUp);
    float targetDepth = 1.0;

    vec3 ray = normalize(cSide * p.x + cUp * p.y + cDir * targetDepth);

    float dist = 0.0;
    float rLen = 0.0;
    vec3  rPos = cPos;

    for(int i = 0; i < 32; ++i){
        dist = distanceHub(rPos);
        rLen += dist;
        rPos = cPos + ray * rLen;
    }

    vec3 color = doColor(rPos);
    gl_FragColor = vec4(color, 1.0);
}
```