Page MenuHome

Cycles: Add LogLUV32 encoding of float environment textures
Needs ReviewPublic

Authored by Lukas Stockner (lukasstockner97) on Feb 22 2016, 10:43 PM.

Details

Summary

This patch adds the option to store environment textures in the LogLUV32 encoding,
which uses only a third of the memory that full float textures require.
Of course, it loses a bit of precision, but the difference should be below the visible
threshold in nearly every case. The supported luminance range is approximately 1e-19 to 1e19,
which is ~128EV and should be enough for any HDRI.

The encoding works by transforming the RGB colors into the YUV color space, where luminance
and color are separated. The base-2 logarithm of the luminance is stored as a 16bit fixed-point
value, and the U and V coordinates are stored in two 8bit fixed-point integers.

The main remaining problem is interpolation: Since the encoded colors are stored as uchar4s
in the regular texture slots, they are interpolated as uchars. Because of the encoding, this
works better than it might seem at first: U and V are separate fields in the uchar4, which
means that the color coordinates are decently interpolated. The interpolation of the luminance
works as well by combining the two interpolated 8bit fields. However, since the field actually
stores the logarithm of the luminance, the interpolation is performed in log-space as well,
leading to darker results.
Another current limitations is that the code assumes sRGB input data (that's hardcoded into
the conversion matrices). That has to be fixed at some point, but that's a general issue
with Cycles (see T46860).

Diff Detail

Repository
rB Blender
Branch
logluv32

Event Timeline

Lukas Stockner (lukasstockner97) retitled this revision from to Cycles: Add LogLUV32 encoding of float environment textures.

These darker results from interpolation, did you find any renders where that's actually visible? What is the effect on render time?

For the UI, I'd probably just add a boolean named "Compress" and not even mention a technical term like LogLUV to the user. Or if possible, have no option at all.

intern/cycles/util/util_color.h
244–247

Would be nice if we could just work on the bits here instead of floats, using bit shift instead of float multiplication, division and exp2. But probably converting from float to int is too slow, and I'm not sure there's a quick way to get the right bits from the float.

Nice work. Here's some feedback.

  • Did you measure performance impact?
  • Did you compare render results with with LogLUV and full-float?
  • While interpolation is indeed mathematically should happen in log space, do you have visible artifacts when doing it in linear one?

The only thing i don't really fan of is the increasing number of arguments to add_image, it's becoming difficult to follow. We should totally consider re-factoring into a bitfield here: IMAGE_USE_ALPHA | IMAGE_USE_SRGB | IMAGE_USE_LOGLUV and remove decoupled bool flags from there. This will also help packing SVM nodes i think and will let us to use same flags for both render/ and kernel/ i think. Also didn't go into details, but are't srgb and use_logluv are mutually exclusive? Meaning, maybe we should have storage_colorspace which will be either LINEAR, SRGB or LOGLUV instead? Not saying it's all huge stoppers, but would be nice to simplify logic here.

Usage feedback:

  • Do artists really care it's LogLUV or other trick to save memory? Having boolean flag called "Use Compression" will be more natural to use.
  • Why to limit to an environment textures only? This could also be applied to image textures perhaps.
  • If there's no measurable difference for the environment, can we just always use compression here?
intern/cycles/blender/blender_shader.cpp
754

Seems OSL is another limitation which is not listed in the original comment?

intern/cycles/kernel/svm/svm_image.h
493

Don't like the hardcoded nature here. IMO better would be to have an anonymous enum like

enum {
  NODE_ENVIRONMENT_FLAG_USE_SRGB = (1 << 0),
  NODE_ENVIRONMENT_FLAG_USE_LOGUV = (1 << 1),
}

but also read the other ideas.

intern/cycles/render/image.cpp
749

Capital and full-stop.

intern/cycles/util/util_color.h
242

logluv_to_float4

257

float4_to_logluv

intern/cycles/util/util_types.h
475

Fullstop.

source/blender/makesdna/DNA_node_types.h
754

Even no extra memory in DNA! :)

intern/cycles/util/util_color.h
242

Actually, shouldn't it be float_logluv_to_linear_float() and linear_float_to_uchar_logluv? Otherwise space of float and datatype of logluv is hidden.

244–247

We can also use fast math here. Hard to tell without measuring how much speedup we'll gain.

Regarding the quality/speed difference: Here are some tests (empty scene, cropped panorama camera, "River Rocks" HDRI by @Greg Zaal (gregzaal), 32 samples):

8k HDRI version, Floats:


8k HDRI version, LogLUV32:

512 HDRI version, Floats:

512 HDRI version, LogLUV32:

Of course, that is an extreme case: The high-frequency content of the image makes the interpolation differences obvious, and the empty scene makes the render time differences noticeable.
If the environment is just used for illumination, the difference isn't visible.

As for the other points:

  • I agree with reducing the option to a checkbox, it's unlikely that we'll add more encoding options.
  • LogLUV could be extended to regular Image textures, but I can't really think of a case where really large float image textures would be used.
  • Code comments are answered inline.
intern/cycles/blender/blender_shader.cpp
754

Hm, true, I forgot to mention that.
However, on second thought, it could most likely be implemented using pretty much the same code as for SVM.

intern/cycles/kernel/svm/svm_image.h
493

Yes, indeed - using enum flags is the clean approach, both in image.cpp and here.

intern/cycles/util/util_color.h
242

I agree with logluv_to_float4, but float_logluv_to_linear_float is probably a bit much - IMHO mentioning the encoding details in a comment before the function is clear enough.

244–247

I'd guess that on modern CPUs, converting the two fields to ints, bitshifting and or'ing them together and converting back to float is slower than just doing the calculations in float. Also, just doing float((int(r.z*255.f) << 8) | int(r.w*255.f))/65535 will cause problems with interpolation since rounding r.z back to 8 bits will cause further problems with interpolation (that's the interpolation-after-float-conversion we discussed in IRC a while back).
The term inside the exp2f could be folded into the Le calculation, though.

Reducing size of float textures for Image Texture is quite handy for the GPU rendering guys, so they've got more precision and dynamic range than with regular float textures and could save some GPU memory as well.

Another neat thing here, is because you store LogLUV in a char storage, you can avoid limit of 5 float textures on GPU.

intern/cycles/blender/blender_shader.cpp
754

Indeed. I was also thinking of some possible headers reshuffle so we can use exact same code in both OSL and SVM, without need to copy-paste it to .osl file. But that's just one of own ideas which might work here.

Think OSL we can add later once we're fully done with SVM.

intern/cycles/kernel/svm/svm_image.h
493

If you have time to do so -- excellent :)

Otherwise i can hep with such a refactor (would need to be done as a part of this patch, so we don't cause merge conflicts).

intern/cycles/util/util_color.h
242

Fine with me. Shorter name, short comment above the function will just do it.