<p>I realized I had made some faulty assumptions, and that the function is not calculating the delta/offset but rather the actual new intersection point.  But the actual problem was that <code>int_as_float</code> and <code>float_as_int</code> are meant to be reinterpret casts.  Here is the correct code in standard C++:</p>
<pre class="lang-cpp prettyprint-override"><code>// Adapted from 6.2.2.4 from: template &lt;IsFloat T&gt;
constexpr T origin() {
    if constexpr (std::same_as&lt;T, float&gt;) {
        return 1.f / 32.f;
    }
    else {
        return 1. / 64.;
    }
};

template &lt;IsFloat T&gt;
constexpr T float_scale() {
    if constexpr (std::same_as&lt;T, float&gt;) {
        return 1.f / 65536.f;
    }
    else {
        return 1. / 4294967296.;
    }
};

template &lt;IsFloat T&gt;
constexpr T int_scale() { 
    if constexpr (std::same_as&lt;T, float&gt;) {
        return 256.0f;
    }
    else {
        return 65536.;
    }
};

inline int32_t float_as_int(float val)
{
    return *reinterpret_cast&lt;int32_t*&gt;(&amp;val);
};

inline int64_t float_as_int(double val)
{
    return *reinterpret_cast&lt;int64_t*&gt;(&amp;val);
}

inline float int_as_float(int32_t val)
{
    return *reinterpret_cast&lt;float*&gt;(&amp;val);
};

inline double int_as_float(int64_t val)
{
    return *reinterpret_cast&lt;double*&gt;(&amp;val);
};

template &lt;IsFloat TFloat&gt;
inline void offsetIntersection(vec3&lt;TFloat&gt;&amp; intersection, const vec3&lt;float&gt;&amp; N)
{
    if constexpr (std::same_as&lt;TFloat, float&gt;) {
        vec3&lt;int32_t&gt; of_i{ int_scale&lt;TFloat&gt;() * N.x, int_scale&lt;TFloat&gt;() * N.y, int_scale&lt;TFloat&gt;() * N.z };
    
        vec3&lt;TFloat&gt; p_i{
            int_as_float(float_as_int(intersection.x) + ((intersection.x &lt; 0) ? -of_i.x : of_i.x)),
            int_as_float(float_as_int(intersection.y) + ((intersection.y &lt; 0) ? -of_i.y : of_i.y)),
            int_as_float(float_as_int(intersection.z) + ((intersection.z &lt; 0) ? -of_i.z : of_i.z))
        };
    
        intersection.x = (std::fabsf(intersection.x) &lt; origin&lt;TFloat&gt;() ? intersection.x + float_scale&lt;TFloat&gt;() * N.x : p_i.x);
        intersection.y = (std::fabsf(intersection.y) &lt; origin&lt;TFloat&gt;() ? intersection.y + float_scale&lt;TFloat&gt;() * N.y : p_i.y);
        intersection.z = (std::fabsf(intersection.z) &lt; origin&lt;TFloat&gt;() ? intersection.z + float_scale&lt;TFloat&gt;() * N.z : p_i.z);
    }
    else {
        vec3&lt;int64_t&gt; of_i{ int_scale&lt;TFloat&gt;() * N.x, int_scale&lt;TFloat&gt;() * N.y, int_scale&lt;TFloat&gt;() * N.z };
    
        vec3&lt;TFloat&gt; p_i{
            int_as_float(float_as_int(intersection.x) + ((intersection.x &lt; 0) ? -of_i.x : of_i.x)),
            int_as_float(float_as_int(intersection.y) + ((intersection.y &lt; 0) ? -of_i.y : of_i.y)),
            int_as_float(float_as_int(intersection.z) + ((intersection.z &lt; 0) ? -of_i.z : of_i.z))
        };
    
        intersection.x = (std::fabsf(intersection.x) &lt; origin&lt;TFloat&gt;() ? intersection.x + float_scale&lt;TFloat&gt;() * N.x : p_i.x);
        intersection.y = (std::fabsf(intersection.y) &lt; origin&lt;TFloat&gt;() ? intersection.y + float_scale&lt;TFloat&gt;() * N.y : p_i.y);
        intersection.z = (std::fabsf(intersection.z) &lt; origin&lt;TFloat&gt;() ? intersection.z + float_scale&lt;TFloat&gt;() * N.z : p_i.z);
    }
};
</code></pre>
<p>Edit: Updated code to work with <code>double</code> precision as well.  The first attempt did not work correctly with the <code>reinterpret_cast</code>.  I believe the issue came from not using fixed-width integer types, as switching to fixed-width resolved the problem.</p>
<p><em>NOTE:</em> <code>IsFloat</code> is a <code>concept</code> I have defined elsewhere, that is simply defined as:</p>
<pre class="lang-cpp prettyprint-override"><code>template &lt;typename T&gt;
concept IsFloat = (std::floating_point&lt;T&gt;);
</code></pre>
<hr />
<p>Edit 2:  Another thing to be careful about (as I've just discovered) is if you are attempting to render using single-precision geometry but double precision poses (using the scene graph to transform from world space to a local mesh space, appropriately casting the ray precision as needed alongside the necessary transformations).  You'll want to modify the ray by the precision of the mesh, rather than the precision of the scene.  I accomplish this by simply casting my intersection point from the scene precision to the mesh precision, and then running this offset algorithm on that, before casting back to scene precision.  If you have a single precision mesh, but a double precision scene, and so attempt to offset a double-precision intersection point, it will not work correctly.</p>
<p>That is probably a very niche problem, but I figured I'd mention it all the same.</p>
<hr />
<p>Edit 3:  Visual Studio is perfectly fine with what I've written above, but GCC complains about my use of <code>reinterpret_cast</code>, stating:</p>
<pre><code>dereferencing type-punned pointer will break strict-aliasing rules
</code></pre>
<p>A workaround that appears to work as expected is to simply use <code>memcpy</code>, so that the conversion functions become:</p>
<pre class="lang-cpp prettyprint-override"><code>inline int32_t float_as_int(float val)
{
    int32_t out;
    std::memcpy(&amp;out, &amp;val, sizeof(float));
    return out;
};

inline int64_t float_as_int(double val)
{
    int64_t out;
    std::memcpy(&amp;out, &amp;val, sizeof(double));
    return out;
}

inline float int_as_float(int32_t val)
{
    float out;
    std::memcpy(&amp;out, &amp;val, sizeof(int32_t));
    return out;
};

inline double int_as_float(int64_t val)
{
    double out;
    std::memcpy(&amp;out, &amp;val, sizeof(int64_t));
    return out;
};
</code></pre>

CC BY-SA 4.0
2024-09-19T18:09:31.463Z
2024-09-28T14:27:45.063Z
2024-09-28T14:27:45.063Z
Chris Gnam
<p>I've been attempting to implement an adaptive ray-offset to prevent self intersection when computing bounce rays.  <a href="https://link.springer.com/content/pdf/10.1007/978-1-4842-4427-2_6.pdf" rel="nofollow noreferrer">This article</a> outlines such an algorithm (specifically in section 6.2.2.4 on page 8).</p>
<p>Their algorithm is as follows:</p>
<pre class="lang-cpp prettyprint-override"><code>constexpr float origin()      { return 1.0f / 32.0f; }
constexpr float float_scale() { return 1.0f / 65536.0f; }
constexpr float int_scale()   { return 256.0f; }

// Normal points outward for rays exiting the surface, else is flipped.
float3 offset_ray(const float3 p, const float3 n)
{
    int3 of_i(int_scale() * n.x, int_scale() * n.y, int_scale() * n.z);

    float3 p_i(
        int_as_float(float_as_int(p.x)+((p.x &lt; 0) ? -of_i.x : of_i.x)),
        int_as_float(float_as_int(p.y)+((p.y &lt; 0) ? -of_i.y : of_i.y)),
        int_as_float(float_as_int(p.z)+((p.z &lt; 0) ? -of_i.z : of_i.z)));

    return float3(fabsf(p.x) &lt; origin() ? p.x+ float_scale()*n.x : p_i.x,
                  fabsf(p.y) &lt; origin() ? p.y+ float_scale()*n.y : p_i.y,
                  fabsf(p.z) &lt; origin() ? p.z+ float_scale()*n.z : p_i.z);
}
</code></pre>
<p>I have attempted to augment the algorithm to work for both single and double precision situations (as I have a CPU ray-tracer that is templated to support both single and double precision).   What I have attempted is as follows:</p>
<pre class="lang-cpp prettyprint-override"><code>template &lt;IsFloat T&gt;
constexpr T origin() {
    if constexpr (std::same_as&lt;T, float&gt;) {
        return 1.f / 32.f;
    }
    else {
        return 1. / 64.;
    }
};

template &lt;IsFloat T&gt;
constexpr T float_scale() {
    if constexpr (std::same_as&lt;T, float&gt;) {
        return 1.f / 65536.f;
    }
    else {
        return 1. / 4294967296.;
    }
};

template &lt;IsFloat T&gt;
constexpr T int_scale() { 
    if constexpr (std::same_as&lt;T, float&gt;) {
        return 256.0f;
    }
    else {
        return 65536.;
    }
};

template &lt;IsFloat T&gt;
int float_as_int(T val)
{
    return static_cast&lt;int&gt;(int_scale&lt;T&gt;() * val);
    //return static_cast&lt;int&gt;(val);
};

template &lt;IsFloat T&gt;
T int_as_float(int val)
{
    return float_scale&lt;T&gt;() * static_cast&lt;T&gt;(val);
    //return static_cast&lt;T&gt;(val);
};

template &lt;IsFloat TFloat&gt;
inline void offsetIntersection(vec3&lt;TFloat&gt;&amp; intersection, const vec3&lt;float&gt;&amp; N)
{
    vec3&lt;int&gt; of_i{ int_scale&lt;TFloat&gt;() * N.x, int_scale&lt;TFloat&gt;() * N.y, int_scale&lt;TFloat&gt;() * N.z };

    vec3&lt;TFloat&gt; p_i{
        int_as_float&lt;TFloat&gt;(float_as_int&lt;TFloat&gt;(intersection.x) + ((intersection.x &lt; 0) ? -of_i.x : of_i.x)),
        int_as_float&lt;TFloat&gt;(float_as_int&lt;TFloat&gt;(intersection.y) + ((intersection.y &lt; 0) ? -of_i.y : of_i.y)),
        int_as_float&lt;TFloat&gt;(float_as_int&lt;TFloat&gt;(intersection.z) + ((intersection.z &lt; 0) ? -of_i.z : of_i.z))
    };
    
    vec3&lt;TFloat&gt; delta{ 0,0,0 };
    if (std::fabsf(intersection.x) &lt; origin&lt;TFloat&gt;()) {
        delta.x = intersection.x + float_scale&lt;TFloat&gt;() * N.x;
    }
    else {
        delta.x = p_i.x * N.x;
    }

    if (std::fabsf(intersection.y) &lt; origin&lt;TFloat&gt;()) {
        delta.y = intersection.y + float_scale&lt;TFloat&gt;() * N.y;
    }
    else {
        delta.y = p_i.y * N.y;
    }

    if (std::fabsf(intersection.z) &lt; origin&lt;TFloat&gt;()) {
        delta.z = intersection.z + float_scale&lt;TFloat&gt;() * N.z;
    }
    else {
        delta.z = p_i.z * N.z;
    }

    intersection += delta;
};
</code></pre>
<p>(NOTE: I made a slight change to update the intersection point rather than just compute the offset, as I believe the original article was doing, hence the <code>+=</code> in the last line)</p>
<p>This does not work, and I believe the source of the problem is <code>int_as_float</code> and <code>float_as_int</code>, which I am treating as a simple <code>static_cast</code> however I believe this is incorrect.  I have also attempted to convert using both a static_cast and the <code>float_scale()</code>/<code>int_scale()</code>.  This works slightly better, but still not great.</p>
<p>What are these functions supposed to be?  The article does not provide a description for them, hence why I thought it was intended to be a simple casting.</p>

<raytracing><pathtracing><c++>
Question on Adaptive offsetting along geometric normal
Chris Gnam
# Question on Adaptive offsetting along geometric normal

(Asked by: username_0 on 2024-09-19)

I've been attempting to implement an adaptive ray-offset to prevent self intersection when computing bounce rays. [This article](https://link.springer.com/content/pdf/10.1007/978-1-4842-4427-2_6.pdf) outlines such an algorithm (specifically in section 6.2.2.4 on page 8).

Their algorithm is as follows:

```cpp
constexpr float origin()      { return 1.0f / 32.0f; }
constexpr float float_scale() { return 1.0f / 65536.0f; }
constexpr float int_scale()   { return 256.0f; }

// Normal points outward for rays exiting the surface, else is flipped.
float3 offset_ray(const float3 p, const float3 n)
{
    int3 of_i(int_scale() * n.x, int_scale() * n.y, int_scale() * n.z);

    float3 p_i(
        int_as_float(float_as_int(p.x)+((p.x < 0) ? -of_i.x : of_i.x)),
        int_as_float(float_as_int(p.y)+((p.y < 0) ? -of_i.y : of_i.y)),
        int_as_float(float_as_int(p.z)+((p.z < 0) ? -of_i.z : of_i.z)));

    return float3(fabsf(p.x) < origin() ? p.x+ float_scale()*n.x : p_i.x,
                  fabsf(p.y) < origin() ? p.y+ float_scale()*n.y : p_i.y,
                  fabsf(p.z) < origin() ? p.z+ float_scale()*n.z : p_i.z);
}

```

I have attempted to augment the algorithm to work for both single and double precision situations (as I have a CPU ray-tracer that is templated to support both single and double precision). What I have attempted is as follows:

```cpp
template <IsFloat T>
constexpr T origin() {
    if constexpr (std::same_as<T, float>) {
        return 1.f / 32.f;
    }
    else {
        return 1. / 64.;
    }
};

template <IsFloat T>
constexpr T float_scale() {
    if constexpr (std::same_as<T, float>) {
        return 1.f / 65536.f;
    }
    else {
        return 1. / 4294967296.;
    }
};

template <IsFloat T>
constexpr T int_scale() { 
    if constexpr (std::same_as<T, float>) {
        return 256.0f;
    }
    else {
        return 65536.;
    }
};

template <IsFloat T>
int float_as_int(T val)
{
    return static_cast<int>(int_scale<T>() * val);
    //return static_cast<int>(val);
};

template <IsFloat T>
T int_as_float(int val)
{
    return float_scale<T>() * static_cast<T>(val);
    //return static_cast<T>(val);
};

template <IsFloat TFloat>
inline void offsetIntersection(vec3<TFloat>& intersection, const vec3<float>& N)
{
    vec3<int> of_i{ int_scale<TFloat>() * N.x, int_scale<TFloat>() * N.y, int_scale<TFloat>() * N.z };

    vec3<TFloat> p_i{
        int_as_float<TFloat>(float_as_int<TFloat>(intersection.x) + ((intersection.x < 0) ? -of_i.x : of_i.x)),
        int_as_float<TFloat>(float_as_int<TFloat>(intersection.y) + ((intersection.y < 0) ? -of_i.y : of_i.y)),
        int_as_float<TFloat>(float_as_int<TFloat>(intersection.z) + ((intersection.z < 0) ? -of_i.z : of_i.z))
    };
    
    vec3<TFloat> delta{ 0,0,0 };
    if (std::fabsf(intersection.x) < origin<TFloat>()) {
        delta.x = intersection.x + float_scale<TFloat>() * N.x;
    }
    else {
        delta.x = p_i.x * N.x;
    }

    if (std::fabsf(intersection.y) < origin<TFloat>()) {
        delta.y = intersection.y + float_scale<TFloat>() * N.y;
    }
    else {
        delta.y = p_i.y * N.y;
    }

    if (std::fabsf(intersection.z) < origin<TFloat>()) {
        delta.z = intersection.z + float_scale<TFloat>() * N.z;
    }
    else {
        delta.z = p_i.z * N.z;
    }

    intersection += delta;
};

```

(NOTE: I made a slight change to update the intersection point rather than just compute the offset, as I believe the original article was doing, hence the `+=` in the last line)

This does not work, and I believe the source of the problem is `int_as_float` and `float_as_int`, which I am treating as a simple `static_cast` however I believe this is incorrect. I have also attempted to convert using both a static\_cast and the `float_scale()`/`int_scale()`. This works slightly better, but still not great.

What are these functions supposed to be? The article does not provide a description for them, hence why I thought it was intended to be a simple casting.

**Tags:** raytracing, pathtracing, c++


---

## 1 answers

### Answer by username_0 on 2024-09-19. Score: 1 | Accepted answer

I realized I had made some faulty assumptions, and that the function is not calculating the delta/offset but rather the actual new intersection point. But the actual problem was that `int_as_float` and `float_as_int` are meant to be reinterpret casts. Here is the correct code in standard C++:

```cpp
// Adapted from 6.2.2.4 from: template <IsFloat T>
constexpr T origin() {
    if constexpr (std::same_as<T, float>) {
        return 1.f / 32.f;
    }
    else {
        return 1. / 64.;
    }
};

template <IsFloat T>
constexpr T float_scale() {
    if constexpr (std::same_as<T, float>) {
        return 1.f / 65536.f;
    }
    else {
        return 1. / 4294967296.;
    }
};

template <IsFloat T>
constexpr T int_scale() { 
    if constexpr (std::same_as<T, float>) {
        return 256.0f;
    }
    else {
        return 65536.;
    }
};

inline int32_t float_as_int(float val)
{
    return *reinterpret_cast<int32_t*>(&val);
};

inline int64_t float_as_int(double val)
{
    return *reinterpret_cast<int64_t*>(&val);
}

inline float int_as_float(int32_t val)
{
    return *reinterpret_cast<float*>(&val);
};

inline double int_as_float(int64_t val)
{
    return *reinterpret_cast<double*>(&val);
};

template <IsFloat TFloat>
inline void offsetIntersection(vec3<TFloat>& intersection, const vec3<float>& N)
{
    if constexpr (std::same_as<TFloat, float>) {
        vec3<int32_t> of_i{ int_scale<TFloat>() * N.x, int_scale<TFloat>() * N.y, int_scale<TFloat>() * N.z };
    
        vec3<TFloat> p_i{
            int_as_float(float_as_int(intersection.x) + ((intersection.x < 0) ? -of_i.x : of_i.x)),
            int_as_float(float_as_int(intersection.y) + ((intersection.y < 0) ? -of_i.y : of_i.y)),
            int_as_float(float_as_int(intersection.z) + ((intersection.z < 0) ? -of_i.z : of_i.z))
        };
    
        intersection.x = (std::fabsf(intersection.x) < origin<TFloat>() ? intersection.x + float_scale<TFloat>() * N.x : p_i.x);
        intersection.y = (std::fabsf(intersection.y) < origin<TFloat>() ? intersection.y + float_scale<TFloat>() * N.y : p_i.y);
        intersection.z = (std::fabsf(intersection.z) < origin<TFloat>() ? intersection.z + float_scale<TFloat>() * N.z : p_i.z);
    }
    else {
        vec3<int64_t> of_i{ int_scale<TFloat>() * N.x, int_scale<TFloat>() * N.y, int_scale<TFloat>() * N.z };
    
        vec3<TFloat> p_i{
            int_as_float(float_as_int(intersection.x) + ((intersection.x < 0) ? -of_i.x : of_i.x)),
            int_as_float(float_as_int(intersection.y) + ((intersection.y < 0) ? -of_i.y : of_i.y)),
            int_as_float(float_as_int(intersection.z) + ((intersection.z < 0) ? -of_i.z : of_i.z))
        };
    
        intersection.x = (std::fabsf(intersection.x) < origin<TFloat>() ? intersection.x + float_scale<TFloat>() * N.x : p_i.x);
        intersection.y = (std::fabsf(intersection.y) < origin<TFloat>() ? intersection.y + float_scale<TFloat>() * N.y : p_i.y);
        intersection.z = (std::fabsf(intersection.z) < origin<TFloat>() ? intersection.z + float_scale<TFloat>() * N.z : p_i.z);
    }
};

```

Edit: Updated code to work with `double` precision as well. The first attempt did not work correctly with the `reinterpret_cast`. I believe the issue came from not using fixed-width integer types, as switching to fixed-width resolved the problem.

*NOTE:* `IsFloat` is a `concept` I have defined elsewhere, that is simply defined as:

```cpp
template <typename T>
concept IsFloat = (std::floating_point<T>);

```

---

Edit 2: Another thing to be careful about (as I've just discovered) is if you are attempting to render using single-precision geometry but double precision poses (using the scene graph to transform from world space to a local mesh space, appropriately casting the ray precision as needed alongside the necessary transformations). You'll want to modify the ray by the precision of the mesh, rather than the precision of the scene. I accomplish this by simply casting my intersection point from the scene precision to the mesh precision, and then running this offset algorithm on that, before casting back to scene precision. If you have a single precision mesh, but a double precision scene, and so attempt to offset a double-precision intersection point, it will not work correctly.

That is probably a very niche problem, but I figured I'd mention it all the same.

---

Edit 3: Visual Studio is perfectly fine with what I've written above, but GCC complains about my use of `reinterpret_cast`, stating:

```
dereferencing type-punned pointer will break strict-aliasing rules

```

A workaround that appears to work as expected is to simply use `memcpy`, so that the conversion functions become:

```cpp
inline int32_t float_as_int(float val)
{
    int32_t out;
    std::memcpy(&out, &val, sizeof(float));
    return out;
};

inline int64_t float_as_int(double val)
{
    int64_t out;
    std::memcpy(&out, &val, sizeof(double));
    return out;
}

inline float int_as_float(int32_t val)
{
    float out;
    std::memcpy(&out, &val, sizeof(int32_t));
    return out;
};

inline double int_as_float(int64_t val)
{
    double out;
    std::memcpy(&out, &val, sizeof(int64_t));
    return out;
};

```


---

