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.
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).
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.
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.
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:
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()
orsqrt()
to calculate the distance to the center of the billboard.The book of shaders - Chapter 7
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.
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
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
now It's time to understanding modeling with distance functions
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 - 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
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);
}
if(value.x<0.){
value.x = 0.;
}
if(value.y<0.){
value.y = 0.;
}
if(value.z<0.){
value.z = 0.;
}
next we have absolution function.It used to remove additional parts.
if(value.x < -1.){
value.x = 1.;
}
if(value.y < -1.){
value.y = 1.;
}
if(value.z < -1.){
value.z = 1.;
}
Also you can make any shape by using 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.
float intersectSDF(float distA, float distB) {
return max(distA, distB);
}
float unionSDF(float distA, float distB) {
return min(distA, distB);
}
float differenceSDF(float distA, float distB) {
return max(distA, -distB);
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With