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

}
相关推荐
_oP_i10 小时前
Unity Addressables 系统处理 WebGL 打包本地资源的一种高效方式
unity·游戏引擎·webgl
新中地GIS开发老师20 小时前
WebGIS和WebGL的基本概念介绍和差异对比
学习·arcgis·webgl
_oP_i2 天前
Unity 中使用 WebGL 构建并运行时使用的图片必须使用web服务器上的
前端·unity·webgl
flying robot5 天前
Three.js简化 WebGL 的使用
webgl
小彭努力中5 天前
114. 精灵模型标注场景(贴图)
前端·3d·webgl·贴图
小彭努力中5 天前
109. 工厂光源(环境贴图和环境光)
前端·深度学习·3d·webgl·贴图
小彭努力中6 天前
112. gui辅助调节光源阴影
前端·深度学习·3d·webgl
refineiks7 天前
three.js绘制宽度大于1的线,并动态新增顶点
3d·图形渲染·webgl
小彭努力中8 天前
102. 管道漫游案例
前端·3d·webgl
小彭努力中8 天前
107. 阴影范围.shadow.camera
前端·深度学习·3d·webgl