テラByteの時代にキロByte

shader又はdemosceneに関係する事

--shader(GLSL)の小技 16-- 湾曲と補正

距離関数に入れる前の座標を湾曲させます。単純にすると歪がでるので、それを補正します。

www.iquilezles.org

ここにも載っていますが、違う方法でやってみます。
まず、プロットに使うような関数を用意します。

float func(float x)
{
    float t = iTime*3.0;
    return 0.2*sin(x*3.0+t)+0.2*sin(x*2.0+t);
}

これで空間を歪めます。

p.y -= func(p.x);

こうです。次に、この関数を微分します。微分は極小の勾配なので、

float e = 0.01;
float g = (func(p.x+e)-func(p.x-e))/(2.0*e);

こうなると思っていたら、添削を受けました。

GLSL Sandbox をsandboxにupしたらforkされて

GLSL Sandbox こうなりました。

float g = (func(p.x+e)-func(p.x-e))/(PI*e);

この方が良さそうです。まあ、見た目の話ですので、どちらでも良いとは思いますが、お好みでどうぞ。
このgを使って補正をかけます。

p.y *= cos(atan(g));

これについては、やはり適当にやって落ち着いた式です。数学的根拠はありません。良さげなので使ってます。 以前 Shader - Shadertoy BETA ここで、ツッコまれました。
これで、空間の湾曲と補正が終わりです。あとは普通に距離関数を使うだけです。
この応用で

vec2 bend(vec2 p,  float d)
{
    p.y -= d*p.x*p.x;
    p.y *= cos(atan(2.0*d*p.x));
    return p;   
}

こんなモノも作りました。ベジェ曲線はコストが高いので、2次関数で湾曲させます。ただし、両端は歪が取れないので誤魔化す様に使ってます。
また、2次元の湾曲もできます。

p.zy -= func(p.x);
vec2 g = (func(p.x+e)-func(p.x-e))/(PI*e);
p.zy *= cos(atan(g));

こんな感じで処理すれば、良いと思います。
このやり方でアーティファクトが出るので距離関数の出力に補正(*0.8)をかけてます。

return deSeg(p,2.0,0.2) *0.8;

アーティファクトの対処法として、1以下の数字を乗算する方法と、もう一つ

return min(0.4, deSeg(p,2.0,0.2));

なんてのもアリです。

今回のshader (shadertoy rule)

mat2 rotate(float a)
{
    return mat2(cos(a),sin(a),-sin(a),cos(a));
}

float func(float x)
{
    float t = iTime*3.0;
    return 0.2*sin(x*3.0+t)+0.2*sin(x*2.0+t);
}

float deSeg(vec3 p, float h, float r)
{
    p.x -= clamp( p.x, -h, h );
    return length( p ) - r;
}
 
#define PI radians(360.0)
float map(vec3 p)
{
    float e = 0.01;
    p.y -= func(p.x);
    // 微分する。
    float g = (func(p.x+e)-func(p.x-e))/(PI*e);
    // 補正
    p.y *= cos(atan(g));
    return deSeg(p,2.0,0.2) *0.8;
}

vec3 doColor(vec3 p)
{
    return vec3(0.3,0.5,0.8);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv=(fragCoord*2.0-iResolution.xy)/iResolution.y;
    vec3 ro=vec3(0,3,5);
    ro.zx*=rotate(iTime*0.1);
    vec3 rd=normalize(vec3(uv,2));
    vec3 w=normalize(-ro);
    vec3 u=normalize(cross(w,vec3(0,1,0)));
    rd=mat3(u,cross(u,w),w)*rd;
    float d,i,t=0.0;
    vec3 p=ro;
    vec3 col=vec3(0);
    for(i=1.0;i>0.0;i-=1./50.0)
    {
        t+=d=map(p);
        if(d<0.001)
        {
            col=doColor(p);
            col*=i*i;
            break;
        }
        p+=rd*d;
    }
   fragColor=vec4(col,1.0);
}