理解了向量夹角,极坐标,对称性,线段的SDF函数之后,常见多边形基本思路都是一致的,本文开始对一些带曲线的形状进行推导, 这些形状都是有两个不同大小的圆组成。 本节的Shader都在
- shadertoy: www.shadertoy.com/view/4cGGWR
Vesica
- geogebra: www.geogebra.org/classic/xe2…
关于vesica形状,网上有做一个一些判断,但是我觉得只需要判断 点到 点的距离,与 点到 点的距离就行了。 代码如下
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
- geobebra: www.geogebra.org/classic/vcx…
- shadertoy: www.shadertoy.com/view/4cGGWR
假定有圆c1, 圆心位于中心,半径为r1, 圆c2, 圆心位于(0, h), 半径为r2. 最关键是找到上图中蓝色的v1向量,v2与v1垂直, v1的角度 有
于是有v1向量为
有v2向量为
根据向量 在 v2上的投影k的大小, 可以划分出3个区域。 最后得到代码
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
- geogebra: www.geogebra.org/classic/ft8…
如何画一个蛋参考上图,点A的坐标为 半径为 . 求解一个蛋的SDF,主要需要确定上图的三个点 为圆心做的图。 通过 左右边判判断 选择 还是 ,通过 的 正负号判断选择 还是 点,最后代码有
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
- geogebra: www.geogebra.org/classic/mq8…
如何画一个月亮参考上图,点B的坐标为 , 半径为 . 中心圆的半径为 . 上图黑色加粗部分为d,r1,r2. 对于月亮的距离划分为三个区域, 分别是求P到B,C, D 三个点的距离。区域判断主要是通过向量叉积判断方向可得。 最关键是求出 点的坐标 。 这个问题可以转化为两个圆的方程求解 两个圆的方程分别是:
- 圆A,圆心 ,半径 :
- 圆B,圆心 ,半径 :
为解这两个方程,首先可以通过从一个方程中减去另一个来消去 ,得到 的关系式。
从上面两个方程中减得:
展开后得到:
这可以简化为 的一个线性方程:
从而有效的 为:
之后我们可以将 的值代回任一圆的方程中来求解 。将 代入圆A的方程:
最后有代码
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
- geogebra: www.geogebra.org/classic/hep…
如何画一个心参考上图, 心外部的距离可以变成线段 和圆 的SDF。心内部的距离在向量 左侧为圆的距离, 右侧为距离 点或者线段 的最短距离。最后有一下代码
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);
}