SDF Boolean Modeling Example

   3380   4   1
User Avatar
Member
94 posts
Joined: 7月 2019
Offline
People in the Modeler for Houdini [discord.gg] Discord group were interested in SDF boolean operations, so I decided to create a simple example of SDF usage and share some notes.

At the bottom of this post, I have attached two versions of the same example file:
VDB_SDF_Modeling.hiplc - The original file, but it contains nodes from the Modeler 2023.4 plugin.
VDB_SDF_Modeling_Stashed.hiplc - A copy of the first file in which I replaced all operations from the Modeler 2023.4 plugin with Stash nodes.
If you do not have the Modeler [alexeyvanzhula.gumroad.com] plugin, then download the second file and you can still get an idea of boolean operations using SDF volumes.

The file contains examples of using the following nodes and operations:
VDB from Polygons (for converting polygonal geometry to SDF volume).
VDB Smooth SDF (for smoothing the edges of geometry similar to the results from polygonal round bevel).
VDB Resample (for changing voxel density to increase object detail).
VDB Combine (for boolean operations: SDF Union, SDF Difference, SDF Intersection).
VDB Reshape SDF (for expanding or narrowing voxel shapes, similar to the Peak operation).
VDB Convert (for converting SDF to polygonal geometry).
Volume Deform (for deforming voxel shapes, by using Lattice from Volume node in conjunction with Bend node to deform point grid which represents voxel grid).
Name and Attribute Edit String (for building hierarchy of LOP primitives while working in SOP)

Notes about using SDF nodes:
  1. Increase voxel density only when necessary. Generally, I used a voxel size of 0.02 when converting geometry through VDB to Polygons and could move shapes interactivly. However, in some cases, I needed narrower or smaller bevels and increased density to 0.01 voxel per unit in exchange to performance.
  2. After all operations, you can also increase voxel density through VDB Resample to get a denser and more detailed polygonal mesh from Convert VDB.
  3. Use different smoothing modes to maintain performance. Instead of increasing the number of smoothing iterations in VDB Smooth SDF, try to change the type of operation: Mean Value for normal smoothing, Gaussian for strong smoothing, Mean Curvature Flow for weak smoothing or smoothing small details.
  4. If you need to place one object inside another, for example a button in a housing, use VDB Reshape SDF to expand the SDF before boolean subtraction.
  5. I didn't find a way to store color in SDF and pass it through boolean operations, so after converting to polygons by Convert VDB I used Bounding Volume in Group node to select the area that I want to color using SDF volume.
  6. Deform objects in voxels through Volume deform instead of deforming them in polygons. This is convenient because the bend deformer when used on polygons will not be able to bend the area where there is not enough geometry, but through Volume deform, you bend a point grid, so you can bend and deform even each pair of voxels.

Notes on modeling in SOPs within LOPs:
  1. To improve performance, prevent SOP from the constant transferring of data to LOPs. To do this, place Output node inside the SOP and connect the geometry to it only when you want to transfer it to the LOP, otherwise keep geometry disconnected.
  2. The same goes for working inside Component Geometry node: connect geometry to the default, proxy, and simproxy output nodes only when necessary, otherwise, the entire node tree will be recalculated with every change.
  3. Start building hierarchy of LOP primitives while still in SOPs. Hierarchy construction will occur from top to bottom, from leaf to root. At the very beginning (for leaves), add Name node that will create the corresponding attribute and enter the geometry name into it, for example, Mesh. Then add an Attribute Edit String node, specify that you want to edit Name attribute, and in Editor tab, set "*" (all existing names) in From parameter. To the To parameter add the name of the LOP primitive where you want to place the geometry, like "Primitive/*". Thus, the Name attribute will become "Primitive/Mesh". Further, continue to add Attribute Edit String nodes with the same parameters to assign more parents or to group primitives after merging. The last Attribute Edit String will have "/*" name set in To parameter and will close the path (for example, "/Gamepad/Triggers/Primitive1/Mesh").

I've used the video [youtu.be] as reference for the model.
Edited by FaitelTech - 2023年3月28日 13:12:11

Attachments:
thumbnail.png (1.2 MB)
Gamepad_Houdini.png (1.9 MB)
VDB_SDF_Modeling.hiplc (1.7 MB)
VDB_SDF_Modeling_Stashed.hiplc (1.8 MB)

User Avatar
Member
44 posts
Joined: 9月 2018
Online
Thank you for sharing! Amazing work as usual.
User Avatar
Member
671 posts
Joined: 9月 2013
Online
Great overview!

You could also compress your node tree to a single volume wrangle containing all sorts of distance functions and a multiparm block:

https://procegen.konstantinmagnus.de/constructive-solid-geometry-tool [procegen.konstantinmagnus.de]

// SIGNED-DISTANCE FUNCTIONS
function float sd_sphere(vector xyz, pos; float r){
    float dist = length(xyz - pos) - r;
    return dist;
}

function float sd_box(vector xyz, pos, size){
    vector q = abs(xyz - pos) - size;
    float dist = length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
    return dist;
}

function float sd_capsule(vector xyz, pos, size){
    pos = xyz - pos;
    pos.y -= clamp(pos.y, 0.0, size[1]);
    return length(pos) - size[0];
}

function float sd(int type; vector xyz, pos, size){
    float dist = 0.0;
    if(type == 0)      dist = sd_sphere(xyz, pos, size[0]);
    else if(type == 1) dist = sd_box(xyz, pos, size);
    else               dist = sd_capsule(xyz, pos, size);
    return dist;
}

// BOOLEAN OPERATIONS
function float op_union(     float d1, d2) { return min(d1,  d2); }
function float op_subtract(  float d1, d2) { return max(-d1, d2); }
function float op_intersect( float d1, d2) { return max(d1,  d2); }

function float op_union_sm( float d1, d2, k ) {
    float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
    return lerp( d2, d1, h ) - k*h*(1.0-h); }

function float op_subtract_sm( float d1,  d2, k ) {
    float h = clamp( 0.5 - 0.5*(d2+d1)/k, 0.0, 1.0 );
    return lerp( d2, -d1, h ) + k*h*(1.0-h); }

function float op_intersect_sm( float d1, d2, k ) {
    float h = clamp( 0.5 - 0.5*(d2-d1)/k, 0.0, 1.0 );
    return lerp( d2, d1, h ) + k*h*(1.0-h); }

function float op(int comb; float d_0, d_1, r){
    float dist = 0.0;
    if(r < 1e-3){
        if      (comb == 0) dist = op_union     (d_0, d_1);
        else if (comb == 1) dist = op_subtract  (d_0, d_1);
        else                dist = op_intersect (d_0, d_1);
    }
    else{
        if      (comb == 0) dist = op_union_sm     (d_0, d_1, r);
        else if (comb == 1) dist = op_subtract_sm  (d_0, d_1, r);
        else                dist = op_intersect_sm (d_0, d_1, r);
    }
    return dist;
}

// SCENE ASSEMBLY
float dist_scene = 0.0;
for(int i = 0; i <= chi('objects'); i++){
    string num = itoa(i);
    int type_obj = chi('type_obj' + num);
    int type_op = chi('type_op' + num);
    
    vector rot = set(chf('rx' + num), chf('ry' + num), chf('rz' + num));
    vector xyz = v@P * maketransform(0, rot);
    vector size = set(chf('sx' + num), chf('sy' + num), chf('sz' + num));
    vector pos = set(chf('px' + num), chf('py' + num), chf('pz' + num));
    
    float r_sm = chf('sm' + num);
    
    float d = sd(type_obj, xyz, pos, size);
    if(i == 0) dist_scene = d;
    else       dist_scene = op(type_op, d, dist_scene, r_sm);
}

f@d = dist_scene;

Attachments:
csg_menu.png (51.1 KB)

https://procegen.konstantinmagnus.de/ [procegen.konstantinmagnus.de]
User Avatar
Member
94 posts
Joined: 7月 2019
Offline
Konstantin Magnus
Great overview!

You could also compress your node tree to a single volume wrangle containing all sorts of distance functions and a multiparm block:

https://procegen.konstantinmagnus.de/constructive-solid-geometry-tool [procegen.konstantinmagnus.de]

Great website, thanks for sharing!
User Avatar
Member
64 posts
Joined: 4月 2022
Offline
Houdini does not have the ability to work directly with SDF in real-time, as is the case with MagicaCSG for example. In MagicaCSG, an SDF-based approach is used, where operations are carried out in SDF space, and a polygonal mesh is not created until export is needed. This allows for real-time modeling, as MagicaCSG works with mathematical formulas, rather than thousands or millions of polygons.
Therefore, such modeling can be inconvenient due to performance issues. This is what I was trying to explain in the Modeler's chat.
alexeyvanzhula.gumroad.com
  • Quick Links