- Shadertoy: www.shadertoy.com/view/Mc3GWs
- Geogebra: www.geogebra.org/classic/mcd...
最开始想要看一看SDF,是因为我在网上找到下面一段代码,我发现我根本看不懂,但是挺好用的
glsl
float sdf_polygon(vec2 pt, float radius, int N) {
float a = atan(pt.x, pt.y);
float r = PI * 2.0 / float(N);
if (a < 0.) {
a += PI * 2.0;
}
float d = length(pt) * (1. + f4 - radius );
float a2 = floor(f1 * .5 + a /r)*r;
return cos(a2 - a) *d - f2 / 2.0;
}
到现在我还是看不懂这段代码....但是通过推导,我写出了上面代码更优雅,更好理解的Shader :)
实现通用正多边形的SDF的秘诀,就在于坐标系的转换,通过极坐标可以很好的处理正多边形的每一条边。极坐标有以下性质
- 基于一个固定点(极点)和从这个点发出的射线来定义点的位置。
- 任何平面上的点都通过一对坐标(r, θ)来表示:r是点到极点的距离(半径),θ是从参考方向(通常是x轴正方向)到点的连线与参考方向之间的角度。
设正N边形中心为点 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 0 , 0 ) O(0, 0) </math>O(0,0) 半径为r
,根据N的大小,将圆等分成N块。每块占据的角度为 <math xmlns="http://www.w3.org/1998/Math/MathML"> δ \delta </math>δ 任意点 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( r 1 , θ ) P(r1, \theta) </math>P(r1,θ) 根据 <math xmlns="http://www.w3.org/1998/Math/MathML"> θ \theta </math>θ 都可以归属到某一块中。如下图,圆角块开始边角度为2PI / N * 0
,结束边角度为2PI / N * 1
. 假设开始点为 <math xmlns="http://www.w3.org/1998/Math/MathML"> A A </math>A ,结束点为 <math xmlns="http://www.w3.org/1998/Math/MathML"> A ′ A^\prime </math>A′ . 过 <math xmlns="http://www.w3.org/1998/Math/MathML"> O O </math>O 做 <math xmlns="http://www.w3.org/1998/Math/MathML"> A A ′ AA^\prime </math>AA′ 的垂线有交点 <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"> P O PO </math>PO 在 <math xmlns="http://www.w3.org/1998/Math/MathML"> P D PD </math>PD 上的投影长度减去 <math xmlns="http://www.w3.org/1998/Math/MathML"> P D PD </math>PD 的长度。于是我们可以写出以下的shader
glsl
float sdf_polygon1(vec2 P, float radius, int sides) {
float angle = atan(P.y, P.x);
// Translate the value of angle into the range (0, 2 * PI).
angle = P.y > 0. ? angle: angle + PI * 2.;
float delta = 2. * PI / float(sides);
float areaNumber = ceil(angle / delta);
float theta1 = delta * areaNumber;
float theta2 = delta * (areaNumber + 1.0);
vec2 PA = vec2(radius * cos(theta1), radius * sin(theta1) );
vec2 PA_Prime = vec2(radius * cos(theta2), radius * sin(theta2) );
vec2 PD = (PA - PA_Prime)/2.0;
float projectLength = abs(dot(PD, P)) / length(PD);
return projectLength - length(PD) ;
}
实际上我们只需要知道两个长度即可, <math xmlns="http://www.w3.org/1998/Math/MathML"> P O PO </math>PO 在 <math xmlns="http://www.w3.org/1998/Math/MathML"> P D PD </math>PD 上的投影长度ProjectLength 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> P D PD </math>PD 的长度。PD的长度可以直接通过 <math xmlns="http://www.w3.org/1998/Math/MathML"> r × c o s ( δ / 2 ) r \times cos(\delta / 2) </math>r×cos(δ/2) 直接计算出。 投影长度可以通过 <math xmlns="http://www.w3.org/1998/Math/MathML"> P O PO </math>PO 与 <math xmlns="http://www.w3.org/1998/Math/MathML"> P D PD </math>PD 的夹角求得。 整体代码可以简化为
glsl
float sdf_polygon3(vec2 P, float radius, int sides) {
float angle = atan(P.y, P.x);
float delta = 2. * PI / float(sides);
float theta = mod(angle, delta) - delta / 2.0;
float lengthPD = radius * cos(delta / 2.0);
float lengthProjection = length(P) * cos(theta)
return lengthProjection - lengthPD;
}
最后我们就可以得到以下漂亮的图形了