Possible bug in df::measured_factor due to incorrect texture coordinate wraparound?

After some investigation, i suspect that the bsdf modifier “df::measured_factor” introduced in MDL 2019.1 has a bug regarding how the input image is handled at the 0°/90° border.

I am using the MDL SDK 2020.0.1, with Optix 6.5.0

This is my simple material:

mdl 1.6;
import ::df::*;

export material measuredFactorTest()
= material(
  surface:material_surface(df::measured_factor(
       values: texture_2d("Gradient.png"),
       base: df::diffuse_reflection_bsdf (tint: color(1.0, 1.0, 1.0))
)));

the gradient image is a 1px-wide and 91-px high bitmap with a gradient from “black” at the top to “grey” at the bottom, with two control pixel bands (see (https://filebin.ca/5RvrEdgXy3UJ/Gradient.png) )

With a simple directional light source shining straight down, i now look straight down onto a flat surface using the above material, using a camera with a large viewing angle. I see a surface that nicely matches the gradient and shows the control rings

.

BUT: Right at the center, directly below the viewpoint, as the viewing angle approaches the normal, there is a dark spot where the brightest spot should be. The dark spot vanishes if i make the top pixel in the image the same color as the bottom pixel, and becomes an almost white spot if i change the top pixel to white. From what i understand, the top pixel should not have any influence here, the reflectance values at this spot should be controlled only by the bottom pixels of the input image.

Is this a bug in the MDL/Optix or am I maybe missing / misunderstanding the documentation?

Regards, Gregor

Thanks for the report, we will take a look!

Hi Gregor!

Is your texture runtime based on mdl_textures.cu from the OptiX 6.5.0 examples?
This example runtime contains a bug in WRAP_AND_CROP_OR_RETURN_BLACK which causes the wrong clamping.

Here is an updated version which should clamp correctly:

// Applies wrapping and cropping to the given coordinate.
// Note: This macro returns if wrap mode is clip and the coordinate is out of range.
#define WRAP_AND_CROP_OR_RETURN_BLACK(val, inv_dim, wrap_mode, crop_vals, store_res_func)    \
  do {                                                                                       \
    if ( (wrap_mode) == mi::neuraylib::TEX_WRAP_REPEAT &&                                    \
        (crop_vals)[0] == 0.0f && (crop_vals)[1] == 1.0f ) {                                 \
      /* Do nothing, use texture sampler default behavior */                                 \
    }                                                                                        \
    else                                                                                     \
    {                                                                                        \
      if ( (wrap_mode) == mi::neuraylib::TEX_WRAP_REPEAT )                                   \
        val = val - floorf(val);                                                             \
      else {                                                                                 \
        if ( (wrap_mode) == mi::neuraylib::TEX_WRAP_CLIP && (val < 0.0f || val >= 1.0f) ) {  \
          store_res_func(result, 0.0f);                                                      \
          return;                                                                            \
        }                                                                                    \
        else if ( (wrap_mode) == mi::neuraylib::TEX_WRAP_MIRRORED_REPEAT ) {                 \
          float floored_val = floorf(val);                                                   \
          if ( (int(floored_val) & 1) != 0 )                                                 \
            val = 1.0f - (val - floored_val);                                                \
          else                                                                               \
            val = val - floored_val;                                                         \
        }                                                                                    \
        float inv_hdim = 0.5f * (inv_dim);                                                   \
        val = fminf(fmaxf(val, inv_hdim), 1.f - inv_hdim);                                   \
      }                                                                                      \
      val = val * ((crop_vals)[1] - (crop_vals)[0]) + (crop_vals)[0];                        \
    }                                                                                        \
  } while ( 0 )

Note, that this function takes an additional parameter “inv_dim”, which should be the reciproc value of the corresponding dimension. You could use it like this:

uint3 size = rtTexSize(texture_sampler_ids[texture_idx - 1]);

float u = coord[0], v = coord[1], w = coord[2];
WRAP_AND_CROP_OR_RETURN_BLACK(u, 1.f / size.x, wrap_u, crop_u, store_result3);
WRAP_AND_CROP_OR_RETURN_BLACK(v, 1.f / size.y, wrap_v, crop_v, store_result3);
WRAP_AND_CROP_OR_RETURN_BLACK(w, 1.f / size.z, wrap_w, crop_w, store_result3);

Or precalculate those values and store them in another rtBuffer.

Hope that helps!
Moritz