Shader 3d RayMarching6 3D SDF造型

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的卡通形象

相关推荐
小彭努力中2 天前
20. gui调试3-下拉菜单、单选框
前端·3d·webgl
还是大剑师兰特2 天前
webGL 综合教程100+【目录】
webgl·webgl教程·webgl 示例
xiangshangdemayi5 天前
WebGL系列教程八(GLSL着色器基础语法)
webgl·基础·shader·着色器·语法·glsl
wjs04065 天前
WebGL入门:将3D世界带入网页的魔法
javascript·3d·webgl·前端开发
xiangshangdemayi5 天前
WebGL系列教程六(纹理映射与立方体贴图)
webgl·贴图·uv·立方体·纹理坐标·纹理映射
refineiks8 天前
three.js使用3DTilesRendererJS加载3d tiles数据
前端·3d·图形渲染·webgl
Ian102510 天前
Three.js new THREE.TextureLoader()纹理贴图使用png图片显示为黑色
前端·javascript·webgl·three.js·贴图·三维
常城12 天前
解决TMP_InputField 在WebGL(抖音)上不能唤起虚拟键盘,不能使用手机内置输入法的问题
webgl
DSLMing14 天前
微信小程序webgl 显示图片
微信小程序·小程序·webgl
GISer_Jing14 天前
Cesium加载高速公路样式线图层和利用CSS撰写高速公路样式
前端·css·webgl