2D SDF推导4: 通用正多边形

最开始想要看一看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边形中心为点 O ( 0 , 0 ) O(0, 0) O(0,0) 半径为r,根据N的大小,将圆等分成N块。每块占据的角度为 δ \delta δ 任意点 P ( r 1 , θ ) P(r1, \theta) P(r1,θ) 根据 θ \theta θ 都可以归属到某一块中。如下图,圆角块开始边角度为2PI / N * 0,结束边角度为2PI / N * 1. 假设开始点为 A A A ,结束点为 A ′ A^\prime A′ . 过 O O O 做 A A ′ AA^\prime AA′ 的垂线有交点 D D D . 点 P P P 到边的距离就是 P O PO PO 在 P D PD PD 上的投影长度减去 P D PD 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) ;
}

实际上我们只需要知道两个长度即可, P O PO PO 在 P D PD PD 上的投影长度ProjectLength 和 P D PD PD 的长度。PD的长度可以直接通过 r × c o s ( δ / 2 ) r \times cos(\delta / 2) r×cos(δ/2) 直接计算出。 投影长度可以通过 P O PO PO 与 P D PD 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;
}

最后我们就可以得到以下漂亮的图形了

相关推荐
总要学点什么3 天前
第65篇:地图卷帘控件——点「关闭卷帘」,消失的却是右边那半张地图
信息可视化·gis·webgl·cesium·空间分析
总要学点什么4 天前
第63篇:3DTiles-大雁塔模型——190 块瓦片不是八叉树,抬高的却是球心那条法线
3d·数据挖掘·数据分析·gis·webgl
Jmyd01238 天前
3D数字协同设计实训空间技术架构解析:从三维采集到WebGL虚拟展示的全链路实现
3d·架构·webgl·虚拟实训
总要学点什么8 天前
第57篇:三维场景演示_基础版——同一秒,播过去和拖过去差出一千多米
3d·数据挖掘·数据分析·gis·webgl
总要学点什么9 天前
第55篇:空间分析-剖面分析——红线走大圆,剖面却按经纬直线采样
前端框架·gis·webgl·cesium·空间分析
:mnong9 天前
Web 三维可视化技术对比:ParaView Web vs 本项目 wasm + Three.js
前端·webgl·opengl·opencl·科学可视化
疯狂敲代码的老刘9 天前
my-fingerprint浏览器扩展:1.4K星开源指纹防护神器,Canvas/WebGL/WebGPU伪装一键开启
webgl
总要学点什么10 天前
第51篇:数据分析-热力图——笔刷画的是圆,落到地上却成了椭圆
3d·数据挖掘·数据分析·前端框架·gis·webgl·cesium
子龙_10 天前
来在网页上用threejs画个蛋白质吧
前端·webgl·three.js
总要学点什么12 天前
第47篇:三维特效-雷达波效果——波纹一圈圈往外推,一半却埋在地下
3d·前端框架·gis·webgl·cesium