IQ的这篇文章 iquilezles.org/articles/di... 有这个世界上关于3D SDF已知的一切。 本篇将尝试走一遍并理解其所有的operator,理解无法
拉长Elongation
拉长的基本逻辑是将某个空间直接吃掉,例如在平面坐标中如果将y axis 往左到-1,往右到1,假设左边界和右边界都是0 开始。 等于中间[-1, 1]的空间被创造出来了
glsl
float opElongate( in sdf3d primitive, in vec3 p, in vec3 h )
{
vec3 q = p - clamp( p, -h, h );
return primitive( q );
}
上面这段函数在对于一维拉伸,第一个函数完美运行,并给出精确的外部和内部距离。然而,对于二维和三维拉伸,第一个实现会在体积内部产生一个小的零距离核心。于是IQ为了解决这个问题创造了第二个函数
glsl
float opElongate( in sdf3d primitive, in vec3 p, in vec3 h )
{
vec3 q = abs(p) - h;
return primitive( max(q, 0.0) ) + min(max(q.x, max(q.y, q.z)), 0.0);
}
这里通过min(max(q.x, max(q.y, q.z)), 0.0)
获取到了SDF内部正确的负值
圆角Rounding
Rounding的基本逻辑是将体积扩充到外部世界,由于外部世界与边界的距离是一个圆,自然就形成了rounded的效果。当然在2D SDF中可以先扩充在回收
glsl
float rounding( in float d, in float h )
{
return d - h;
}
镂空Onion
Onion是让SDF变得像洋葱一样,其实就是将物体内部挖空,在数学上就是abs。 同时可以引入一个粗细参数 thinkness
arduino
float opOnion( in float sdf, in float thickness )
{
return abs(sdf)-thickness;
}
距离公式变换
一般来说计算的距离都是欧几里得距离,但是如果将距离函数变化下有时候会得到出其不意的效果。 但是距离函数变化会带来非常多的影响,阴影和环境光灯效果都依赖于正确的欧式距离。
css
float length2( vec3 p ) { p=p*p; return sqrt( p.x+p.y+p.z); }
float length6( vec3 p ) { p=p*p*p; p=p*p; return pow(p.x+p.y+p.z,1.0/6.0); }
float length8( vec3 p ) { p=p*p; p=p*p; p=p*p; return pow(p.x+p.y+p.z,1.0/8.0); }
并集 Union
j这个操作返回两个几何体中任一点到它们的最近点的距离。在两个几何体的合并中,任意一点到合并体的距离等于该点到两个几何体的距离中的较小者。因此,这个函数通过计算min(d1,d2)返回两个几何体的合并。
glsl
float opUnion( float d1, float d2 )
{
return min(d1,d2);
}
交集 Intersection
集操作返回两个几何体共有部分中任意一点到边界的距离。在交集中,点到结果几何体的距离等于该点到两个几何体距离中的较大者。原因在于,只有当这个点同时在两个几何体的内部时,它才属于交集部分,这通过max(d1,d2)来确保。
glsl
float opIntersection( float d1, float d2 )
{
return max(d1,d2);
}
差集 Subtraction
当从一个几何体中减去另一个时,所有在第二个几何体内的点都变成了在结果几何体外的点,这通过取d1的负值来实现(在SDF中,负值表示在内部)。这个操作返回所有点中对于结果几何体来说,"最外面"点的距离,max(-d1,d2)确保了所有在减数几何体内部的点都被正确处理。
glsl
float opSubtraction( float d1, float d2 )
{
return max(-d1,d2);
}
异或 XOR
异或操作产生一个只包含两个几何体非共享部分的结果。换句话说,它包含属于第一个但不属于第二个几何体的部分,以及属于第二个但不属于第一个几何体的部分。这个函数通过首先查找两个几何体中较近的一个(min(d1,d2)),然后将其与两者中较远者的负值(-max(d1,d2))进行比较。
glsl
return max(min(d1,d2),-max(d1,d2));
丝滑逻辑运算 SmoothLogical
有以下两个函数图像, <math xmlns="http://www.w3.org/1998/Math/MathML"> a = x a=\sqrt{x} </math>a=x 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> b = ( 2 − x ) 2 b = (2-x)^2 </math>b=(2−x)2
用 <math xmlns="http://www.w3.org/1998/Math/MathML"> m i n ( a , b ) min(a, b) </math>min(a,b)的图像会产生两个尖角,这两个点是不连续的
IQ的设计函数 smooth_min(a, b, k)
, 输入两个函数a,b,通过k值可以控制两个函数smooth min到下图
scss
float opSmoothUnion( float d1, float d2, float k )
{
float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
return mix( d2, d1, h ) - k*h*(1.0-h);
}
float opSmoothSubtraction( float d1, float d2, float k )
{
float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
return mix( d2, -d1, h ) + k*h*(1.0-h);
}
float opSmoothIntersection( float d1, float d2, float k )
{
float h = clamp( 0.5 - 0.5*(d2-d1)/k, 0.0, 1.0 );
return mix( d2, d1, h ) + k*h*(1.0-h);
}
旋转移动 Rotation/Translation
坐标系中形状的变换可以由坐标系的逆变换完成
glsl
vec3 opTx( in vec3 p, in transform t, in sdf3d primitive )
{
return primitive( invert(t)*p );
}
缩放 Scale
缩放是通过压缩/扩张空间来做的,所以在最后的distance需要反向扩张/压缩回来。均匀缩放的计算非常简单,通用的非均匀缩放且获得正确的SDF是不可能的。因为维度的信息丢失了。这里直接贴IQ的代码
glsl
float opScale( in vec3 p, in float s, in sdf3d primitive )
{
return primitive(p/s)*s;
}
镜像Symmetry
镜像也是通过操作空间来达到的,对于单个axis镜像,只需要abs那个axis就行,如果对于某个平面做镜像,那就abs那个平面就可以了
glsl
float opSymX( in vec3 p, in sdf3d primitive )
{
p.x = abs(p.x);
return primitive(p);
}
float opSymXZ( in vec3 p, in sdf3d primitive )
{
p.xz = abs(p.xz);
return primitive(p);
}
表面位移 Displacement
改变物体表面点的位置,有时候通过三角函数,有时候沿着法线random。 displacement可以随意实验
glsl
float opDisplace( in sdf3d primitive, in vec3 p )
{
float d1 = primitive(p);
float d2 = displacement(p);
return d1+d2;
}
扭曲 Twist
扭曲是一种将几何体绕着某个轴线旋转的变形操作。通常来说,扭曲操作会将几何体沿着指定轴线分成多个层次,各层次依次旋转一定角度,以产生类似螺旋的效果. 例如下面的方法IQ就是绕着Y axis做了一些扭曲动作
glsl
float opTwist( in sdf3d primitive, in vec3 p )
{
const float k = 10.0; // or some other amount
float c = cos(k*p.y);
float s = sin(k*p.y);
mat2 m = mat2(c,-s,s,c);
vec3 q = vec3(m*p.xz,p.y);
return primitive(q);
}
弯曲 Bend
弯曲是一种将几何体沿着某个方向或轴线弯曲的变形操作。通过将物体的一部分固定,然后将另一部分沿某个指定方向弯曲,可以产生柔和的曲线效果. 例如下面的方法IQ就绕着 x axis做了弯曲
glsl
float opCheapBend( in sdf3d primitive, in vec3 p )
{
const float k = 10.0; // or some other amount
float c = cos(k*p.x);
float s = sin(k*p.x);
mat2 m = mat2(c,-s,s,c);
vec3 q = vec3(m*p.xy,p.z);
return primitive(q);
}
到了这里除了空间复制之外,基本所有的操作都走了一遍,下一期将试着用现在所学做一个3D的卡通形象