2D SDF运算1: 变换

旋转移动

坐标系中形状的变换可以由坐标系的逆变换完成. 但是有一个问题没搞清楚,旋转+平移 会出现一个无穷值,不知道是inverse内置函数的问题,还是逻辑上的问题, 体现在图像上是内圆不见了。 这里回顾一下平移,旋转矩阵(线性变换)

glsl 复制代码
  float dx = sin(iTime/3.0);
  float dy = cos(iTime/3.0);
  mat3 translationMatrix = mat3(
      1, 0, dx,
      0, 1, dy,
      0, 0, 0
  );
  
  float theta = PI * (abs(sin(iTime/3.0)) + 0.1);
  mat3 rotationMatrix = mat3(
      cos(theta), -sin(theta), 0,
      sin(theta), cos(theta), 0,
      0, 0, 0
  );

缩放

缩放是通过压缩/扩张空间来做的,所以在最后的distance需要反向扩张/压缩回来。均匀缩放的计算非常简单,通用的非均匀缩放且获得正确的SDF是不可能的。因为维度的信息丢失了。这里直接贴IQ的代码

glsl 复制代码
float opScale( in vec3 p, in float s, in sdf3d primitive )
{
    return primitive(p/s)*s;
}

圆角 Rounded

rounded,就是将角外的圆弧往里面收。要做的操作便是将坐标系沿着角的方向(类似于3D面的法线方向)往外拉扯,在最后取得距离在减去相应拉出去的量。 对应到代码中就是会有一个加法操作,然后有一个减法操作。

Star

在star的sdf中,往外拉的极坐标的半径

对比之前通用角SDF的函数,变化有

glsl 复制代码
float roundR = 0.3;

//+ polar_P = vec2(length(P) + roundR, theta2);
//- polar_P = vec2(length(P)         , theta2);

//+ return sign(cross2(v2, v1)) * length(v1-h*v2) - roundR; 
//- return sign(cross2(v2, v1)) * length(v1-h*v2)         ; 

Polygon

代码是通过不断的调试和观察图像获得,我现在没办法很明确的讲清楚,只能凭感觉能写出来,基本思路也是沿着极坐标下半径进行放大缩小。读者在完成前面的SDF推导之后,也能感觉出来

下列代码中第44行为什么是要减去一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> r o u n d R ∗ a b s ( c o s ( d e l t a / 2. ) ) roundR * abs(cos(delta/2.)) </math>roundR∗abs(cos(delta/2.)) 我猜测是为了让放大缩小弄到边上,这个值也是实验出来的...

glsl 复制代码
float rounded_polygon(vec2 P, float radius, int sides) {
    // get polar angle
    float angle = atan(P.y, P.x);
    // make angle to range [0, 2*PI]
    angle = P.y > 0. ? angle: angle + PI * 2.;

    // get each piece angle
    float delta = 2. * PI / float(sides);
    // how many pieces?
    float areaNumber = floor(angle / delta);
    
    // start angle of current piece
    float theta1 = delta * areaNumber;
    // end angle of current piece
    float theta2 = delta * (areaNumber + 1.0);
   
    float roundR = -abs(sin(iTime / 2.)) * 1.5  ;
    float radius2 = -radius - roundR;
    radius2 = -abs(radius2);

    // start point on current piece
    vec2 POINT_A       = vec2(radius2  * cos(theta1), radius2 * sin(theta1) );
    // end point on current piece
    vec2 POINT_A_Prime = vec2(radius2 * cos(theta2), radius2 * sin(theta2) );
    // the middle of startPoint and endPoint
    vec2 POINT_D       = (POINT_A + POINT_A_Prime)/2.0;
    // corrdinate center
    vec2 POINT_O       = vec2(0.0); 
    // start axis of current piece
    vec2 iAxis1   =  vec2(-POINT_O+POINT_A);
    for (int i=0; i<2; i++) {
        vec2 PP = P;
        if (i==1) {     // symmetrical for area2
            PP = symmetrical_point(P, POINT_D);
        }      
        vec2 vector1  = vec2(PP - POINT_A);
        float a1 = vector_angle(iAxis1, vector1);
        if (a1 <  (delta / 2.0)) {
            return length(vector1)-roundR * abs(cos(delta/2.)) ;
        }
    }
    
    // area 3 
    float theta = mod(angle, delta) - delta / 2.0;
    float l1 =  (length(P) + roundR) * cos(theta) - length(POINT_D) - roundR;
    return l1;
}

镂空 Annular

理解2D图形的SDF是距离边缘的距离,图形内部为负数,外部为正数,Annular其实非常符合直接,函数如下表示

glsl 复制代码
float op_annular_circle( in vec2 p, in float r1 , float r2)
{
  return abs(length(p) - r1 ) - r2;
}

拉长 Elongation

如果需要将一个圆沿着X轴拉长, 假定A1点坐标为(d, 0), 从坐标轴的视角出发,是将 <math xmlns="http://www.w3.org/1998/Math/MathML"> C D CD </math>CD <math xmlns="http://www.w3.org/1998/Math/MathML"> C 1 D 1 C_1D_1 </math>C1D1 这两条线里内的所有点都映射到 <math xmlns="http://www.w3.org/1998/Math/MathML"> C D CD </math>CD 这条线上的投影点。也就是 <math xmlns="http://www.w3.org/1998/Math/MathML"> C D CD </math>CD <math xmlns="http://www.w3.org/1998/Math/MathML"> C 1 D 1 C_1D_1 </math>C1D1 内所有点的x值变成0. 同时 <math xmlns="http://www.w3.org/1998/Math/MathML"> C 1 D 1 C_1D_1 </math>C1D1 外所有点的x值减去 <math xmlns="http://www.w3.org/1998/Math/MathML"> d d </math>d , 于是有代码

glsl 复制代码
float sdf_elongate_circle1(vec2 p,  float r, float xv ) {
    p.x = p.x > 0.0 ? max(p.x - xv, 0.0 ) : p.x;
    return length(p) - r;
}

如果要往x轴负数方向也拉长

glsl 复制代码
float sdf_elongate_circle1(vec2 p,  float r, float xv ) {
    p.x = p.x > 0.0 ? max(p.x - xv, 0.0 ) : p.x;
    p.x = p.x < 0.0 ? min(p.x + xv, 0.0 ) : p.x;
    return length(p) - r;
}

y轴的处理与x轴没有区别,于是有

glsl 复制代码
float sdf_elongate_circle1(vec2 p,  float r, float xv, float yv ) {
    p.x = p.x > 0.0 ? max(p.x - xv, 0.0 ) : p.x;
    p.x = p.x < 0.0 ? min(p.x + xv, 0.0 ) : p.x;
    p.y = p.y > 0.0 ? max(p.y - yv, 0.0 ) : p.y;
    p.y = p.y < 0.0 ? min(p.y + yv, 0.0 ) : p.y;
    return length(p) - r;
}

四个判断条件可以合并为clamp函数

glsl 复制代码
float sdf_elongate_circle3(vec2 p,  float r, vec2 h ) {
    p = p - clamp(p, -h, h);
    return length(p) - r;
}

最后有以下效果

维度变换 Change of Metric

距离场最核心的函数便是Length. 这个距离是欧氏距离。距离的计算方法为 <math xmlns="http://www.w3.org/1998/Math/MathML"> l e n g t h = x 2 + y 2 length=\sqrt{x^2+y^2} </math>length=x2+y2

如果这个距离的计算函数变成 <math xmlns="http://www.w3.org/1998/Math/MathML"> l e n g t h n = x n + y n n length_n = \sqrt[n]{x^n + y^n} </math>lengthn=nxn+yn

n的不同取值会为图形带来一些奇妙的变化,视觉效果会更加圆一点.. 但是IQ大神是不推荐这种变换,因为会影响到raymarching的结果

相关推荐
小彭努力中13 小时前
141. Sprite标签(Canvas作为贴图)
前端·深度学习·3d·webgl·three.js
Ian10252 天前
WebGL进阶(十一)层次模型
webgl
mirrornan3 天前
什么是Web3D?有何优势?有哪些应用场景?
3d·web3·webgl·3d模型
哈市雪花4 天前
图像处理 之 凸包和最小外围轮廓生成
图像处理·人工智能·图形学·最小外围轮廓·最小外包
小彭努力中4 天前
138. CSS3DRenderer渲染HTML标签
前端·深度学习·3d·webgl·three.js
优雅永不过时·4 天前
three.js实现地球 外部扫描的着色器
前端·javascript·webgl·three.js·着色器
汪洪墩5 天前
【Mars3d】实现这个地图能靠左,不居中的样式效果
前端·javascript·vue.js·3d·webgl·cesium
allenjiao5 天前
webgl threejs 云渲染(服务器渲染、后端渲染)解决方案
webgl·云渲染·threejs·服务器渲染·后端渲染·云流化·三维云渲染
踏实探索5 天前
OpenLayers教程12_WebGL自定义着色器:实现高级渲染效果
前端·arcgis·vue·webgl·着色器
EasyNTS7 天前
H.265流媒体播放器EasyPlayer.js网页直播/点播播放器WebGL: CONTEXT_LOST_WEBGL错误引发的原因
javascript·webgl·h.265