Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GLSL cube signed distance field implementation explanation?

I've been looking at and trying to understand the following bit of code

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));
}

I understand that length(d) handles the SDF case where the point is off to the 'corner' (ie. all components of d are positive) and that max(d.x, d.y, d.z) gives us the proper distance in all other cases. What I don't understand is how these two are combined here without the use of an if statement to check the signs of d's components.

When all of the d components are positive, the return expression can be reduced to length(d) because of the way min/max will evaluate - and when all of the d components are negative, we get max(d.x, d.y, d.z). But how am I supposed to understand the in-between cases? The ones where the components of d have mixed signs?

I've been trying to graph it out to no avail. I would really appreciate it if someone could explain this to me in geometrical/mathematical terms. Thanks.

like image 758
Fab Castel Avatar asked Feb 10 '16 21:02

Fab Castel


People also ask

How raymarching works?

The key idea of raymarching is rendering with a point that closes enough to the surface of the object, which is much faster and allows real-time photorealistic rendering with fairly complicated scenes. To make raymarching effective, a surface is often defined as a signed distance field (SDF).

What are signed distance fields used for?

Signed distance functions are applied, for example, in real-time rendering, for instance the method of SDF ray marching, and computer vision. A modified version of SDF was introduced as a loss function to minimise the error in interpenetration of pixels while rendering multiple objects.

How to rotate SDF?

To rotate, translate or scale an SDF model, apply the inverse transform to the input point within your distance function. Ex: This renders a sphere centered at (0, 3, 0). More prosaically, assemble your local-to-world transform as usual, but apply its inverse to the pt within your distance function.


2 Answers

If you like to know how It works It's better do the following steps:

1.first of all you should know definitions of shapes

2.It's always better to consider 2D shape of them, because three dimensions may be complex for you.

so let me to explain some shapes:

Circle

Circle

A circle is a simple closed shape. It is the set of all points in a plane that are at a given distance from a given point, the center.

You can use distance(), length() or sqrt() to calculate the distance to the center of the billboard.

The book of shaders - Chapter 7

Square

Square

In geometry, a square is a regular quadrilateral, which means that it has four equal sides and four equal angles (90-degree angles).


I describe 2D shapes In before section now let me to describe 3D definition.

Sphere

Sphere

A sphere is a perfectly round geometrical object in three-dimensional space that is the surface of a completely round ball.

Like a circle, which geometrically is an object in two-dimensional space, a sphere is defined mathematically as the set of points that are all at the same distance r from a given point, but in three-dimensional space. Refrence - Wikipedia

Cube

Cube

In geometry, a cube is a three-dimensional solid object bounded by six square faces, facets or sides, with three meeting at each vertex. Refrence : Wikipedia


Modeling with distance functions

now It's time to understanding modeling with distance functions

Sphere

As mentioned In last sections.In below code length() used to calculate the distance to the center of the billboard , and you can scale this shape by s parameter.

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

Box

// 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() used like previous example. next we have max(x,0) It called Positive and negative parts Positive_and_negative_parts

this is mean below code is equivalent:

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);
}

step 1

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

step1

step 2

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

step2

step 3

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

step3

step 4

next we have absolution function.It used to remove additional parts.

absolution

step4


Absolution Steps

step3

Absolution step 1

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

abs1

Absolution step 2

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

abs2

Absolution step 3

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

step4


Also you can make any shape by using Constructive solid geometry.

Constructive solid geometry

CSG is built on 3 primitive operations: intersection ( ), union ( ), and difference ( - ).

It turns out these operations are all concisely expressible when combining two surfaces expressed as SDFs.

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);
}
like image 129
Seyed Morteza Kamali Avatar answered Nov 08 '22 01:11

Seyed Morteza Kamali


I figured it out a while ago and wrote about this extensively in a blog post here: http://fabricecastel.github.io/blog/2016-02-11/main.html

Here's an excerpt (see the full post for a full explanation):

Consider the four points, A, B, C and D. Let's crudely reduce the distance function to try and get rid of the min/max functions in order to understand their effect (since that's what's puzzling about this function). The notation below is a little sloppy, I'm using square brackets to denote 2D vectors.

// 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]

Ok, so far nothing special. When A is inside the square, we essentially get our first distance function based on planes/lines and when B is in the area where our first distance function is inaccurate, it gets zeroed out and we get the second distance function (the length). The trick lies in the other two cases C and D. Let's work them out.

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]

If you look back to the graph above, you'll note C' and D'. Those points have coordinates [0,1] and [1,0], respectively. This method uses the fact that both distance fields intersect on the axes - that D and D' lie at the same distance from the square.

If we zero out all negative component of a vector and take its length we will get the proper distance between the point and the square (for points outside of the square only). This is what max(d,0.0) does; a component-wise max operation. So long as the vector has at least one positive component, min(max(d.x,d.y),0.0) will resolve to 0 leaving us with only the second part of the equation. In the event that the point is inside the square, we want to return the first part of the equation (since it represents our first distance function). If all components of the vector are negative it's easy to see our condition will be met.

This understanding should tranlsate back into 3D seamlessly once you wrap your head around it. You may or may not have to draw a few graphs by hand to really "get" it - I know I did and would encourage you to do so if you're dissatisfied with my explanation.

Working this implementation into our own code, we get this:

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));
}

And there you have it.

like image 41
Fab Castel Avatar answered Nov 08 '22 02:11

Fab Castel