GLSL立方体有符号距离场实现解释。

10

我一直在查看并尝试理解下面的代码:

float sdBox( vec3 p, vec3 b )
{
  vec3 d = abs(p) - b;
  return min(max(d.x,max(d.y,d.z)),0.0) +
         length(max(d,0.0));
}

我明白 length(d) 处理的是点偏离“角落”的情况(即:所有d的分量都为正),而max(d.x, d.y, d.z) 则在其他情况下给出正确的距离。但我不明白在不使用 if 语句检查 d 分量符号的情况下,这两者是如何结合起来的。

当所有的d分量都为正时,返回表达式可以简化为 length(d),因为 min/max 的计算方式 - 而当所有的d分量都为负时,我们得到了 max(d.x, d.y, d.z)。但是,我该如何理解中间的情况?那些d的分量具有混合符号的情况?

我一直在尝试绘制图形,但无法成功。如果有人能以几何/数学术语向我解释这个问题,我将不胜感激。谢谢。

2个回答

14

如果你想知道它是如何工作的,最好按照以下步骤进行:

1.首先您应该了解形状的定义。

2.最好考虑它们的2D形状,因为三个维度可能会让您感到复杂。

所以让我来解释一些形状:

圆形

Circle

圆形是一个简单的闭合形状。它是平面上所有距离给定点(中心点)给定距离的点的集合。

您可以使用distance()length()sqrt()计算到广告牌中心的距离。

《着色器之书》- 第7章

正方形

Square

在几何学中,正方形是一个正方形四边相等且四角相等(90度角)的正四面体。


我在之前的部分中描述了2D形状,现在让我描述3D定义。

球体

Sphere

球体是三维空间中一个完美圆形的几何对象,是完全圆形球体的表面。

像圆一样,在几何上是二维空间中的一个对象,球体在数学上被定义为所有距离给定点r相同的点的集合,但是在三维空间中。参考- 维基百科

立方体

Cube

在几何学中,一个立方体是一个由六个正方形面、平面或边界限定的三维固体物体,每个顶点处有三个相遇。

参考:维基百科


距离函数建模

现在是理解距离函数建模的时候了。

球体

如上节所述,在下面的代码中使用length()计算到广告牌中心的距离,您可以通过参数s进行形状缩放。

//Sphere - signed - exact
/// <param name="p">Position.</param>
/// <param name="s">Scale.</param>
float sdSphere( vec3 p, float s )
{
  return length(p)-s;
}

盒子

// Box - unsigned - exact
/// <param name="p">Position.</param>
/// <param name="b">Bound(Scale).</param> 
float udBox( vec3 p, vec3 b )
{
  return length(max(abs(p)-b,0.0));
}

length() 的用法和上一个例子一样。 接下来我们有 max(x,0),它被称为正负分解Positive_and_negative_parts

这意味着下面的代码是等价的:

float udBox( vec3 p, vec3 b )
{

   vec3 value = abs(p)-b;


   if(value.x<0.){
   value.x = 0.;  
   }

  if(value.y<0.){
  value.y = 0.;  
  }

  if(value.z<0.){
  value.z = 0.;  
  }  

  return length(value);
}

步骤 1

   if(value.x<0.){
   value.x = 0.;  
   }

步骤1

步骤2

  if(value.y<0.){
  value.y = 0.;  
  }

step2

第三步

  if(value.z<0.){
  value.z = 0.;  
  }  

step3

第四步

接下来我们有一个去除额外部分的absolution函数。

absolution

step4


前往Absolution

step3

Absolution 第一步

if(value.x < -1.){
   value.x = 1.; 
}

abs1

赎罪步骤二

if(value.y < -1.){
value.y = 1.; 
}

abs2

赎罪步骤3

if(value.z < -1.){
value.z = 1.; 
}

step4


您还可以使用建设性实体几何来制作任何形状。

Constructive solid geometry

CSG基于三个基本操作:交集(),并集()和差集(-)。

事实证明,当组合两个表述为SDF的表面时,这些操作都可以简洁地表示。

intersectSDF

float intersectSDF(float distA, float distB) {
    return max(distA, distB);
}

unionSDF

float unionSDF(float distA, float distB) {
    return min(distA, distB);
}

differenceSDF

float differenceSDF(float distA, float distB) {
    return max(distA, -distB);
}

非常详细的解释。你第一和第二张截图中使用的是什么软件? - Daniel A. Thompson
@DanielA.Thompson ShaderForge 是Unity和ShaderToy的可视化脚本着色器工具。 - Seyed Morteza Kamali
虽然我很感谢你花时间写这个答案,但是我的问题是关于有符号距离场的,而你的解释是针对无符号版本的,没有解释如何计算负距离。 - Fab Castel
@FabCastel 我会更新我的答案,同时我在这里问了同样的问题(https://computergraphics.stackexchange.com/questions/7485/explain-of-the-implementation-of-some-shapes-by-using-sdf),希望对你有用。 - Seyed Morteza Kamali

5
我在一段时间前找到了答案,并在这篇博客文章中详细介绍了它:http://fabricecastel.github.io/blog/2016-02-11/main.html 以下是摘录(请查看完整文章以获取全面解释):

考虑四个点A、B、C和D。为了理解最小/最大函数的影响(因为这是令人困惑的部分),我们粗略地简化距离函数,以尝试摆脱这些函数。下面的符号有点随意,我使用方括号来表示二维向量。
// 2D version of the function
d(p) = min(max(p.x, p.y), 0)
       + length(max(p, 0))

---

d(A) = min(max(-1, -1), 0)
       + length(max([-1, -1], 0))

d(A) = -1 + length[0, 0]

---

d(B) = min(max(1, 1), 0)
       + length(max([1, 1], 0))

d(B) = 0 + length[1, 1]

好的,到目前为止还没有什么特别的。当A在正方形内部时,我们基本上得到了基于平面/线的第一个距离函数,当B处于我们第一个距离函数不准确的区域时,它被清零,我们得到第二个距离函数(长度)。诀窍在于另外两种情况C和D。让我们来解决它们。

d(C) = min(max(-1, 1), 0)
      + length(max([-1, 1], 0))

d(C) = 0 + length[0, 1]

---

d(D) = min(max(1, -1), 0)
       + length(max([-1, 1], 0))

d(D) = 0 + length[1, 0]

如果您回顾上面的图表,您会注意到C'和D'。这些点的坐标分别为[0,1]和[1,0]。该方法利用了距离场在坐标轴上的交点 - 即D和D'距正方形的距离相同。
如果我们将向量的所有负分量清零并取其长度,我们将得到点与正方形之间的正确距离(仅适用于正方形外的点)。这就是max(d,0.0)所做的事情;一个逐分量的最大操作。只要向量至少有一个正分量,min(max(d.x,d.y),0.0) 将解决为0,使我们只剩下方程的第二部分。如果点在正方形内部,我们希望返回方程的第一部分(因为它表示我们的第一个距离函数)。如果向量的所有分量都是负数,很容易看出我们的条件将被满足。
一旦您理解了这个概念,就可以毫不费力地将其转换回3D。如果您对我的解释不满意,可能需要手动绘制几个图表才能真正“理解”它 - 我知道我需要这样做,并鼓励您这样做。
将此实现工作到我们自己的代码中,我们得到以下结果:
float distanceToNearestSurface(vec3 p){
  float s = 1.0;
  vec3 d = abs(p) - vec3(s);
  return min(max(d.x, max(d.y,d.z)), 0.0)
      + length(max(d,0.0));
}

就是这样。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接