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);

}
相关推荐
GISer_Jing1 天前
WebGL跨端兼容实战:移动端适配全攻略
前端·aigc·webgl
Aurora@Hui4 天前
WebGL & Three.js
webgl
CC码码6 天前
基于WebGPU实现canvas高级滤镜
前端·javascript·webgl·fabric
ct9786 天前
WebGL 图像处理核心API
图像处理·webgl
ct9788 天前
Cesium 矩阵系统详解
前端·线性代数·矩阵·gis·webgl
ct97811 天前
WebGL Shader性能优化
性能优化·webgl
棋鬼王11 天前
Cesium(一) 动态立体墙电子围栏,Wall墙体瀑布滚动高亮动效,基于Vue3
3d·信息可视化·智慧城市·webgl
Longyugxq14 天前
Untiy的Webgl端网页端视频播放,又不想直接mp4格式等格式的。
unity·音视频·webgl
avi911114 天前
Unity毛玻璃渲染模糊渲染Shader数学入门
unity·aigc·图形学·shader·hlsl
花姐夫Jun14 天前
cesium基础学习-坐标系统相互转换及相应的场景
学习·webgl