Epsilon selection for bump generation from alpha?

jrandom

eye kan kode gud
My Simplex Noise node is nearly finished, but I'd like to add an output that generates a bump vector from multi-sampling so I can include effects from the Function input, along with any crazy things the user inputs in the alpha channels. (The regular, faster, bump vector output uses the direct-gradient calculation made by the simplex noise generator, but that can't incorporate the Function input and nor possibly the contrast setting.)

According to this article (section 5.6) I can do this using only three extra samples by getting the values from three surrounding points from the main spot coordinate by adding a small epsilon value.

What I can't figure out is what value to use. Is there some commonly-accepted constant I should use? Do I need to generate it somehow?
 
(if only the newtek node devs read this forum... they must have a good answer to this. :( )
 
Finally, the music for that darned commercial is done! Now I can get back to node coding...

I haven't run this yet, but this is what I'm going to be trying for epsilon generation and noise multi-sampling:

Code:
        // -------------------------------------------------------------------- Neighborhod_Epsilon()
        inline double Neighborhod_Epsilon( LWTypes::LWNodalAccess * nodal_access )
        {
            const double epsilon = (nodal_access->spotSize * 0.5) * nodal_access->cosine;
            
            return (epsilon > 0.0) ? epsilon : std::numeric_limits<double>::min();
        }

Since the spot size gets more and more inaccurate at glancing angles (the "spot" will actually be a long, skinny area), I'm attempting to pull in the epsilon offset so it's not overly-large. The more glancing the angle, the more the offset will be reigned back in.


After that, I just sample the three offset locations and I'm hoping that later today I'll have this all wired into the code that will calculate a gradient based on these three values + the original spot's noise value (calculated elsewhere in the code):

Code:
    // ---------------------------------------------------------------------------- Calculate_Simplex_Neighborhood()
    // Samples noise at three nearby coordinates from the original spot position
    
    Simplex_Noise::Simplex_Neighborhood_t
    
    Simplex_Noise::Calculate_Simplex_Neighborhood( LWTypes::LWDVector       spot_position,
                                                   Gradient_Flags_t         calculate_gradients,
                                                   LWTypes::LWNodalAccess * nodal_access )
    {
        // Determine offset of nearby coordinates
        const double epsilon = Neighborhod_Epsilon( nodal_access );
        
        // Generate the three neighborhood coordinates
        const LWTypes::LWDVector Neighbor_X( spot_position.X() + epsilon, spot_position.Y(),           spot_position.Z()           );
        const LWTypes::LWDVector Neighbor_Y( spot_position.X(),           spot_position.Y() + epsilon, spot_position.Z()           );
        const LWTypes::LWDVector Neighbor_Z( spot_position.X(),           spot_position.Y(),           spot_position.Z() + epsilon );
        
        // Sample noise at the neighborhood coordinates
        return Simplex_Neighborhood_t( epsilon,
                                       Calculate_Simplex( Neighbor_X, Gradient_Flags_t::No_Gradients, nodal_access ).Noise,
                                       Calculate_Simplex( Neighbor_Y, Gradient_Flags_t::No_Gradients, nodal_access ).Noise,
                                       Calculate_Simplex( Neighbor_Z, Gradient_Flags_t::No_Gradients, nodal_access ).Noise );
    }


Thanks to C++11's 'move' semantics, returning the Simplex_Neighborhood object by value shouldn't incur any extra value-copying overhead:

Code:
    // ============================================================================
    // Simplex Neighborhood
    //
    // Holds three sampled noise values -- used for bump generation when the
    //                                     directly-calculated gradient won't do.
    // ----------------------------------------------------------------------------
    template < typename value_t >
    
    class Simplex_Neighborhood
    {
    private:
        // -------------------------------------------------------------------- Values
        // coordinate epsilon, (x, y, z, w)
        const std::array< value_t, 5 > noise_EXYZW;
        
    public:
        // -------------------------------------------------------------------- Accessors
        double Epsilon() const { return noise_EXYZW[0]; }
        double Noise_X() const { return noise_EXYZW[1]; }
        double Noise_Y() const { return noise_EXYZW[2]; }
        double Noise_Z() const { return noise_EXYZW[3]; }
        double Noise_W() const { return noise_EXYZW[4]; }
        
        
    public:
        // -------------------------------------------------------------------- Constructor ( constant )
        Simplex_Neighborhood() : noise_EXYZW( {0.0, 0.0, 0.0, 0.0, 0.0} ) {}
        
        // -------------------------------------------------------------------- Constructor ( copy/move )
        Simplex_Neighborhood( const Simplex_Neighborhood &  source ) : noise_EXYZW(           source.noise_EXYZW  ) {}
        Simplex_Neighborhood(       Simplex_Neighborhood && source ) : noise_EXYZW( std::move(source.noise_EXYZW) ) {}
        
        // -------------------------------------------------------------------- Constructor ( from values )
        Simplex_Neighborhood( value_t epsilon,
                              value_t x,
                              value_t y = 0.0,
                              value_t z = 0.0,
                              value_t w = 0.0 )
        
            : noise_EXYZW( {epsilon, x, y, z, w} ) {}
    };
 
Last edited:
(This is, of course, assuming that the spot size get erroneously large at glancing angles. I do not yet know if this is actually the case.)

Edit: Some experimenting in Layout tells me I should ignore nodal_access->cosine for the time being and just use the spot size directly...
 
Last edited:
It works!

For my epsilon offset value, I'm just using the spot size's radius (nodal_access->spotSize * 0.5).

View attachment 114265

"Fast Bump" uses the simplex noise's directly-generated bump gradient. "Noise Bump" multi-samples the noise and generates the bump gradient from those, allowing Contrast and Function to influence the bump map in the way you'd intuitively expect:

View attachment 114266
 

Attachments

  • MultiSample_Bump.png
    MultiSample_Bump.png
    79.3 KB · Views: 543
  • MultiSample_BumpFunction.png
    MultiSample_BumpFunction.png
    53.4 KB · Views: 527
Time to multi-sample the alpha forground/background inputs!

I... will be shocked if this works on the first attempt.

Code:
    // ---------------------------------------------------------------------------- Get_Alpha_Neighborhood()
    LWTypes::LWDVector Simplex_Noise::Get_Alpha_Neighborhood ( LWTypes::LWNodalAccess * nodal_access )
    {
        // NOTE: Is only accessed by Alpha_Bump, so is not cached. Calculate directly.
        
        // Determine offset of nearby coordinates
        const double epsilon = Neighborhod_Epsilon( nodal_access );
        
        
        // Generate the three neighborhood coordinates
        const LWTypes::LWDVector object_spot( nodal_access->oPos );
        const LWTypes::LWDVector world_spot ( nodal_access->wPos );
        
        LWTypes::LWDVector object_neighbor_X( object_spot.X() + epsilon, object_spot.Y(),           object_spot.Z()           );
        LWTypes::LWDVector object_neighbor_Y( object_spot.X(),           object_spot.Y() + epsilon, object_spot.Z()           );
        LWTypes::LWDVector object_neighbor_Z( object_spot.X(),           object_spot.Y(),           object_spot.Z() + epsilon );
        
        LWTypes::LWDVector world_neighbor_X ( world_spot.X()  + epsilon, world_spot.Y(),            world_spot.Z()            );
        LWTypes::LWDVector world_neighbor_Y ( world_spot.X(),            world_spot.Y()  + epsilon, world_spot.Z()            );
        LWTypes::LWDVector world_neighbor_Z ( world_spot.X(),            world_spot.Y(),            world_spot.Z()  + epsilon );
        
        
        // Create nodal_access copies with offset coordinates
        auto Copy_Nodal_Access = [&]( LWTypes::LWDVector object_neighbor,
                                      LWTypes::LWDVector world_neighbor ) -> LWTypes::LWNodalAccess
        {
            LWTypes::LWNodalAccess nodal_access_copy = *nodal_access;
            
            // Assign offset coordintes and return
            memcpy( nodal_access_copy.oPos, (double*)object_neighbor, sizeof(double) * 3 );
            memcpy( nodal_access_copy.wPos, (double*)world_neighbor,  sizeof(double) * 3 );
            
            return nodal_access_copy;
        };
        
        LWTypes::LWNodalAccess nodal_access_X = Copy_Nodal_Access( object_neighbor_X, world_neighbor_X );
        LWTypes::LWNodalAccess nodal_access_Y = Copy_Nodal_Access( object_neighbor_Y, world_neighbor_Y );
        LWTypes::LWNodalAccess nodal_access_Z = Copy_Nodal_Access( object_neighbor_Z, world_neighbor_Z );
        
        
        // Sample alpha foreground and background at offset locations
        const double alpha_fg_neighbor_X = Input_Alpha_Foreground.Get( &nodal_access_X );
        const double alpha_fg_neighbor_Y = Input_Alpha_Foreground.Get( &nodal_access_Y );
        const double alpha_fg_neighbor_Z = Input_Alpha_Foreground.Get( &nodal_access_Z );
        
        const double alpha_bg_neighbor_X = Input_Alpha_Background.Get( &nodal_access_X );
        const double alpha_bg_neighbor_Y = Input_Alpha_Background.Get( &nodal_access_Y );
        const double alpha_bg_neighbor_Z = Input_Alpha_Background.Get( &nodal_access_Z );
        
        
        // Get noise neighborhood for mixing amounts
        const Simplex_Neighborhood_t noise_neighborhood( Get_Noise_Neighborhood(nodal_access) );

        
        // Mix alpha amounts and return
        return LWTypes::LWDVector ( LERP( noise_neighborhood.Noise_X(), alpha_bg_neighbor_X, alpha_fg_neighbor_X ),
                                    LERP( noise_neighborhood.Noise_Y(), alpha_bg_neighbor_Y, alpha_fg_neighbor_Y ),
                                    LERP( noise_neighborhood.Noise_Z(), alpha_bg_neighbor_Z, alpha_fg_neighbor_Z ) );
    }
 
Last edited:
And to wrap it all up:

It occurred to me that since I have separate fg/bg input bump vectors that get mixed into all of the bump outputs, I could do a side-by-side comparison of the difference between a regular bump output from a LW node, and the alpha-generated bump map from that same node.

The tinted areas are bump-from-alpha, and the grey areas are the bump map straight from the incoming node:

View attachment 114271

View attachment 114272
 

Attachments

  • Mixed_Bump.jpg
    Mixed_Bump.jpg
    522.5 KB · Views: 520
  • Mixed_Bump_Nodes.png
    Mixed_Bump_Nodes.png
    121.2 KB · Views: 502
For those of you following along with my mad ravings in this forum, altering the above multi-sampling routine to modify the spot coordinates in-place instead of copying the entire nodal_access structure results in a 1.04x increase in speed. Doesn't look like much on the surface, but translated to a 20-second-per-frame speed-up in my stress-test renders. This can make a huge difference when rendering animations.

Code:
    // ---------------------------------------------------------------------------- Get_Alpha_Neighborhood()
    LWTypes::LWDVector Simplex_Noise::Get_Alpha_Neighborhood ( LWTypes::LWNodalAccess * nodal_access )
    {
        // NOTE: Is only accessed by Alpha_Bump, so is not cached. Calculate directly.
        
        // Determine offset of nearby coordinates
        const double epsilon = Neighborhod_Epsilon( nodal_access );
        
        
        // Generate the three neighborhood coordinates
        const LWTypes::LWDVector object_spot( nodal_access->oPos );
        const LWTypes::LWDVector world_spot ( nodal_access->wPos );
        
        LWTypes::LWDVector object_neighbor_X( object_spot.X() + epsilon, object_spot.Y(),           object_spot.Z()           );
        LWTypes::LWDVector object_neighbor_Y( object_spot.X(),           object_spot.Y() + epsilon, object_spot.Z()           );
        LWTypes::LWDVector object_neighbor_Z( object_spot.X(),           object_spot.Y(),           object_spot.Z() + epsilon );
        
        LWTypes::LWDVector world_neighbor_X ( world_spot.X()  + epsilon, world_spot.Y(),            world_spot.Z()            );
        LWTypes::LWDVector world_neighbor_Y ( world_spot.X(),            world_spot.Y()  + epsilon, world_spot.Z()            );
        LWTypes::LWDVector world_neighbor_Z ( world_spot.X(),            world_spot.Y(),            world_spot.Z()  + epsilon );
        
        
        // Backup existing spot locations
        double oPos_backup[3];
        double wPos_backup[3];
        
        memcpy( oPos_backup, nodal_access->oPos, sizeof(double) * 3 );
        memcpy( wPos_backup, nodal_access->wPos, sizeof(double) * 3 );
        
        
        // Sample alpha foreground and background at offset locations
        auto Update_Coordinates = [&]( LWTypes::LWDVector object_neighbor,
                                       LWTypes::LWDVector world_neighbor )
        {
            memcpy( nodal_access->oPos, (double*)object_neighbor, sizeof(double) * 3 );
            memcpy( nodal_access->wPos, (double*)world_neighbor,  sizeof(double) * 3 );
        };
        
        auto Get_Alpha_from_Offset = [&]( const Input_Scalar_t     & Input,
                                                LWTypes::LWDVector   object_neighbor,
                                                LWTypes::LWDVector   world_neighbor ) -> double
        {
            Update_Coordinates( object_neighbor, world_neighbor );
            
            return Input.Get( nodal_access );
        };
        
        const double alpha_fg_neighbor_X = Get_Alpha_from_Offset( Input_Alpha_Foreground, object_neighbor_X, world_neighbor_X );
        const double alpha_fg_neighbor_Y = Get_Alpha_from_Offset( Input_Alpha_Foreground, object_neighbor_Y, world_neighbor_Y );
        const double alpha_fg_neighbor_Z = Get_Alpha_from_Offset( Input_Alpha_Foreground, object_neighbor_Z, world_neighbor_Z );
        
        const double alpha_bg_neighbor_X = Get_Alpha_from_Offset( Input_Alpha_Background, object_neighbor_X, world_neighbor_X );
        const double alpha_bg_neighbor_Y = Get_Alpha_from_Offset( Input_Alpha_Background, object_neighbor_Y, world_neighbor_Y );
        const double alpha_bg_neighbor_Z = Get_Alpha_from_Offset( Input_Alpha_Background, object_neighbor_Z, world_neighbor_Z );
        
        
        // Restore spot locations
        memcpy( nodal_access->oPos, oPos_backup, sizeof(double) * 3 );
        memcpy( nodal_access->wPos, wPos_backup, sizeof(double) * 3 );
        
        
        // Get noise neighborhood for mixing amounts
        const Simplex_Neighborhood_t noise_neighborhood( Get_Noise_Neighborhood(nodal_access) );

        
        // Mix alpha amounts and return
        return LWTypes::LWDVector ( LERP( noise_neighborhood.Noise_X(), alpha_bg_neighbor_X, alpha_fg_neighbor_X ),
                                    LERP( noise_neighborhood.Noise_Y(), alpha_bg_neighbor_Y, alpha_fg_neighbor_Y ),
                                    LERP( noise_neighborhood.Noise_Z(), alpha_bg_neighbor_Z, alpha_fg_neighbor_Z ) );
    }
 
Back
Top