LoginSignup
15
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-05-18

久々にiqさんのブログ記事からsmooth minimumの距離関数の記事を書きます。

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

最初は、友人にメタボールって、書けます?

と言われ、この記事をシェアされたことからでした。

メタボールを作る

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

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

球を重ねる

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

と実装するとこんな感じ

// 球
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

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

光沢をつける

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

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

滑らかにしたいよね?

どうやってなめらかにするの?

float smin( float a, float b, float k )
{
    float res = exp( -k*a ) + exp( -k*b );
    return -log( res )/k;
}

を使います。

これ何やっているの?

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

\begin{align}
y &= x \\
y &= -(-x) \\
y &= - \log(\exp(-x)) \\
\end{align}

として、無理やり exp を出して、合成しています。

ちなみに、sinasintanatan でも試しましたが、うまくはいかず…

なんで exp ?

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

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

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

恐らく

感覚的には、Toylor展開した時のオーダーが、細かいからだと思っています。

どうでしょう?

\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

メタボール

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

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);
}
15
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
8