2D SDF推导6: 双圆图形

理解了向量夹角,极坐标,对称性,线段的SDF函数之后,常见多边形基本思路都是一致的,本文开始对一些带曲线的形状进行推导, 这些形状都是有两个不同大小的圆组成。 本节的Shader都在

Vesica

关于vesica形状,网上有做一个一些判断,但是我觉得只需要判断 <math xmlns="http://www.w3.org/1998/Math/MathML"> P P </math>P 点到 <math xmlns="http://www.w3.org/1998/Math/MathML"> B B </math>B 点的距离,与 <math xmlns="http://www.w3.org/1998/Math/MathML"> P P </math>P 点到 <math xmlns="http://www.w3.org/1998/Math/MathML"> G G </math>G 点的距离就行了。 代码如下

glsl 复制代码
float sd_vesica(vec2 P, float r, float d)
{
    P = abs(P);
    vec2 PB = vec2(-d, 0.0);
    float h = sqrt(r*r - d*d);
    vec2 PG = vec2(0.0, h);
    
    return min(
        distance(P, PB) - r,
        distance(P, PG)
    );
}

Uneven Capsule

假定有圆c1, 圆心位于中心,半径为r1, 圆c2, 圆心位于(0, h), 半径为r2. 最关键是找到上图中蓝色的v1向量,v2与v1垂直, v1的角度 <math xmlns="http://www.w3.org/1998/Math/MathML"> θ \theta </math>θ 有 <math xmlns="http://www.w3.org/1998/Math/MathML"> s i n ( θ ) = ( r 1 − r 2 ) h sin(\theta)=\cfrac{(r1-r2)} h </math>sin(θ)=h(r1−r2)

于是有v1向量为 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( s i n ( θ ) , c o s ( θ ) ) (sin(\theta), cos(\theta)) </math>(sin(θ),cos(θ))

有v2向量为 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( − c o s ( θ ) , s i n ( θ ) ) (-cos(\theta),sin(\theta)) </math>(−cos(θ),sin(θ))

根据向量 <math xmlns="http://www.w3.org/1998/Math/MathML"> O P → \overrightarrow{OP} </math>OP 在 v2上的投影k的大小, 可以划分出3个区域。 最后得到代码

glsl 复制代码
float sdf_uneven_capsule(vec2 p, float r1, float r2, float h) {
    p.x = abs(p.x);
    
    float sin_theta = (r1-r2) / h;
    float normalize_y = sin_theta * 1.0;
    float normalize_x = sqrt(1.0 - (normalize_y * normalize_y));
    float cos_theta = normalize_x;
    vec2 v2 = vec2(-normalize_y, normalize_x);
    vec2 v  = vec2(normalize_x, normalize_y);
    float k = dot(p, vec2(v2));
    
    // area1
    if( k < 0.0 ) return length(p) - r1;
    // area3
    if( k > cos_theta*h ) return length(p-vec2(0.0,h)) - r2;
    // area2
    return dot(p, v ) - r1;
}

Egg

如何画一个蛋参考上图,点A的坐标为 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( d , 0 ) (d, 0) </math>(d,0) 半径为 <math xmlns="http://www.w3.org/1998/Math/MathML"> r r </math>r . 求解一个蛋的SDF,主要需要确定上图的三个点 <math xmlns="http://www.w3.org/1998/Math/MathML"> B B </math>B <math xmlns="http://www.w3.org/1998/Math/MathML"> D D </math>D <math xmlns="http://www.w3.org/1998/Math/MathML"> O O </math>O 为圆心做的图。 通过 <math xmlns="http://www.w3.org/1998/Math/MathML"> D F DF </math>DF 左右边判判断 选择 <math xmlns="http://www.w3.org/1998/Math/MathML"> B B </math>B 还是 <math xmlns="http://www.w3.org/1998/Math/MathML"> D D </math>D ,通过 <math xmlns="http://www.w3.org/1998/Math/MathML"> P P </math>P 的 <math xmlns="http://www.w3.org/1998/Math/MathML"> y y </math>y 正负号判断选择 <math xmlns="http://www.w3.org/1998/Math/MathML"> B B </math>B 还是 <math xmlns="http://www.w3.org/1998/Math/MathML"> O O </math>O 点,最后代码有

glsl 复制代码
float sdf_mossegg(vec2 P, float r, float d ) 
{
    P.x = abs(P.x);
    
    vec2 B = vec2(-d, 0.);
    float hh = r-d;
    vec2 D = vec2(0, hh);
    
    
    if (P.y < 0.) {
        return length(P) - hh;
    } else {
    
        float t = cross2(P-B, D-B);
        if (t < 0.0) {
            float tt = sqrt(hh *hh + d * d);
            float rr =r - tt;
            return distance(P, D)  - rr ;
        } else {
            return distance(P, B) - d - hh;
        } 
    
    }
    
}

Moon

如何画一个月亮参考上图,点B的坐标为 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( d , 0 ) (d, 0) </math>(d,0) , 半径为 <math xmlns="http://www.w3.org/1998/Math/MathML"> r 2 r2 </math>r2 . 中心圆的半径为 <math xmlns="http://www.w3.org/1998/Math/MathML"> r 1 r1 </math>r1 . 上图黑色加粗部分为d,r1,r2. 对于月亮的距离划分为三个区域, 分别是求P到B,C, D 三个点的距离。区域判断主要是通过向量叉积判断方向可得。 最关键是求出 <math xmlns="http://www.w3.org/1998/Math/MathML"> C C </math>C 点的坐标 。 这个问题可以转化为两个圆的方程求解 两个圆的方程分别是:

  1. 圆A,圆心 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( 0 , 0 ) (0, 0) </math>(0,0) ,半径 <math xmlns="http://www.w3.org/1998/Math/MathML"> r 1 r_1 </math>r1 : <math xmlns="http://www.w3.org/1998/Math/MathML"> x 2 + y 2 = r 1 2 x^2 + y^2 = r_1^2 </math>x2+y2=r12
  2. 圆B,圆心 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( d , 0 ) (d, 0) </math>(d,0) ,半径 <math xmlns="http://www.w3.org/1998/Math/MathML"> r 2 r_2 </math>r2 : <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x − d ) 2 + y 2 = r 2 2 (x - d)^2 + y^2 = r_2^2 </math>(x−d)2+y2=r22

为解这两个方程,首先可以通过从一个方程中减去另一个来消去 <math xmlns="http://www.w3.org/1998/Math/MathML"> y 2 y^2 </math>y2 ,得到 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x 的关系式。 从上面两个方程中减得: <math xmlns="http://www.w3.org/1998/Math/MathML"> x 2 − ( x − d ) 2 = r 1 2 − r 2 2 x^2 - (x-d)^2 = r_1^2 - r_2^2 </math>x2−(x−d)2=r12−r22

展开后得到: <math xmlns="http://www.w3.org/1998/Math/MathML"> x 2 − ( x 2 − 2 x d + d 2 ) = r 1 2 − r 2 2 x^2 - (x^2 - 2xd + d^2) = r_1^2 - r_2^2 </math>x2−(x2−2xd+d2)=r12−r22

这可以简化为 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x 的一个线性方程: <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 x d − d 2 = r 1 2 − r 2 2 2xd - d^2 = r_1^2 - r_2^2 </math>2xd−d2=r12−r22

从而有效的 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x 为: <math xmlns="http://www.w3.org/1998/Math/MathML"> x = r 1 2 − r 2 2 + d 2 2 d x = \frac{r_1^2 - r_2^2 + d^2}{2d} </math>x=2dr12−r22+d2

之后我们可以将 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x 的值代回任一圆的方程中来求解 <math xmlns="http://www.w3.org/1998/Math/MathML"> y y </math>y 。将 <math xmlns="http://www.w3.org/1998/Math/MathML"> x x </math>x 代入圆A的方程: <math xmlns="http://www.w3.org/1998/Math/MathML"> y 2 = r 1 2 − x 2 y^2 = r_1^2 - x^2 </math>y2=r12−x2

最后有代码

glsl 复制代码
float sdf_moon(vec2 P, float r1, float r2, float d ) 
{
    P.y = abs(P.y);
    float a = (r1*r1 - r2*r2 + d*d)/(2.0*d);
    float b = sqrt(max(r1*r1-a*a,0.0));

    vec2 PointC = vec2(a, b);
    vec2 PointB = vec2(d, 0.);
    vec2 PointD = vec2(0., 0.);
    vec2 VectorCB = PointB - PointC;
    vec2 VectorDC = PointC - PointD;
    vec2 VectorCP = P - PointC;
    vec2 VectorDP = P - PointD;

    float t1 = cross2(VectorCB, VectorCP);
    float t2 = cross2(VectorDC, VectorDP);

    if (t1  > 0.0 && t2 < 0.0) {
       return distance(P,  PointC);
    }
    
    return max(
        r2 - distance(P, PointB),
        length(P) - r1
    );
   
}

Heart

如何画一个心参考上图, 心外部的距离可以变成线段 <math xmlns="http://www.w3.org/1998/Math/MathML"> E C EC </math>EC 和圆 <math xmlns="http://www.w3.org/1998/Math/MathML"> D D </math>D 的SDF。心内部的距离在向量 <math xmlns="http://www.w3.org/1998/Math/MathML"> B C BC </math>BC 左侧为圆的距离, 右侧为距离 <math xmlns="http://www.w3.org/1998/Math/MathML"> B B </math>B 点或者线段 <math xmlns="http://www.w3.org/1998/Math/MathML"> E C EC </math>EC 的最短距离。最后有一下代码

glsl 复制代码
float sd_heart( in vec2 P, float r )
{
    P.x = abs(P.x);
    vec2 PointB = vec2(0., r);
    vec2 PointA = vec2(r, 0.0);
    vec2 PointC = vec2(0.5*r, 0.5*r);
    vec2 PointD = vec2(0.25 * r, 0.75 *r );
    vec2 VectorBA = PointA - PointB;
    vec2 VectorBP = P - PointB;

    float t = cross2(VectorBA, VectorBP);
   if (t > 0.0) {
       return distance(P, PointD) -  0.25 * r * sqrt(2.);
    }

    float hh = clamp(dot(P, PointC) / dot(PointC, PointC), 0., 1.);
    return min(
      distance(P, PointB),
      length(P-hh*PointC)
    ) * sign(P.x-P.y);

}
相关推荐
爱看书的小沐2 天前
【小沐学Web3D】three.js 加载三维模型(Svelte.js)
javascript·vue.js·webgl·three.js·opengl·web3d·svelte.js
林枫依依2 天前
Unity Webgl在编辑器中报错:Cannot connect to destination host
unity·编辑器·webgl
烛阴2 天前
UV Coordinates & Uniforms -- OpenGL UV坐标和Uniform变量
前端·webgl
陈小峰_iefreer3 天前
stone 3d v3.3.0版本发布,含时间线和连接器等新功能
3d·webgl·metaverse·cadcg
supermapsupport4 天前
SuperMap GIS基础产品FAQ集锦(20250421)
服务器·webgl·supermap·idesktop
zhu_zhu_xia4 天前
JS通过GetCapabilities获取wms服务元数据信息并在SuperMap iClient3D for WebGL进行叠加显示
javascript·3d·webgl
伶俜monster4 天前
光影编程师:Threejs Shader 基础全攻略
前端·webgl·three.js
烛阴4 天前
Swizzling--OpenGL的向量的灵活组合
前端·webgl
前端小崔4 天前
从零开始学习three.js(14):一文详解three.js中的场景Scene
webgl·three.js·数据可视化
烛阴5 天前
Vec--OpenGL的顶点基础
前端·webgl