2022-04-07 18:46:57 +02:00

508 lines
22 KiB
C++

//-*****************************************************************************
// Copyright (c) 2012, Pixar. All rights reserved. *
// *
// This license governs use of the accompanying software. If you *
// use the software, you accept this license. If you do not accept *
// the license, do not use the software. *
// *
// 1. Definitions *
// The terms "reproduce," "reproduction," "derivative works," and *
// "distribution" have the same meaning here as under U.S. *
// copyright law. A "contribution" is the original software, or *
// any additions or changes to the software. *
// A "contributor" is any person or entity that distributes its *
// contribution under this license. *
// "Licensed patents" are a contributor's patent claims that read *
// directly on its contribution. *
// *
// 2. Grant of Rights *
// (A) Copyright Grant- Subject to the terms of this license, *
// including the license conditions and limitations in section 3, *
// each contributor grants you a non-exclusive, worldwide, *
// royalty-free copyright license to reproduce its contribution, *
// prepare derivative works of its contribution, and distribute *
// its contribution or any derivative works that you create. *
// (B) Patent Grant- Subject to the terms of this license, *
// including the license conditions and limitations in section 3, *
// each contributor grants you a non-exclusive, worldwide, *
// royalty-free license under its licensed patents to make, have *
// made, use, sell, offer for sale, import, and/or otherwise *
// dispose of its contribution in the software or derivative works *
// of the contribution in the software. *
// *
// 3. Conditions and Limitations *
// (A) No Trademark License- This license does not grant you *
// rights to use any contributor's name, logo, or trademarks. *
// (B) If you bring a patent claim against any contributor over *
// patents that you claim are infringed by the software, your *
// patent license from such contributor to the software ends *
// automatically. *
// (C) If you distribute any portion of the software, you must *
// retain all copyright, patent, trademark, and attribution *
// notices that are present in the software. *
// (D) If you distribute any portion of the software in source *
// code form, you may do so only under this license by including a *
// complete copy of this license with your distribution. If you *
// distribute any portion of the software in compiled or object *
// code form, you may only do so under a license that complies *
// with this license. *
// (E) The software is licensed "as-is." You bear the risk of *
// using it. The contributors give no express warranties, *
// guarantees or conditions. You may have additional consumer *
// rights under your local laws which this license cannot change. *
// To the extent permitted under your local laws, the contributors *
// exclude the implied warranties of merchantability, fitness for *
// a particular purpose and non-infringement. *
//-*****************************************************************************
//-*****************************************************************************
// Written by Pixar Animation Studios, 2011-2012.
//-*****************************************************************************
#ifndef _PxDeepUtils_h_
#define _PxDeepUtils_h_
#include <ImfNamespace.h>
#include <ImfPixelType.h>
#include <ImfPartType.h>
#include <ImathFun.h>
#include <ImathVec.h>
#include <ImathBox.h>
#include <ImathMatrix.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <iostream>
#include <exception>
#include <stdexcept>
#include <string>
#include <vector>
#include <algorithm>
#include <utility>
#include <dtex.h>
#include <half.h>
#include <math.h>
#include <float.h>
#include <cmath>
#include <sys/types.h>
using std::isfinite;
namespace PxDeep {
//-*****************************************************************************
// The large block of comments below explains our working terminology and
// the justification of our limits & magic numbers. In a few places, the
// use of centimeters as a spatial unit does affect the absolute position of
// various minima and maxima, but in normal usage those should be well outside
// working ranges.
//-*****************************************************************************
//-*****************************************************************************
// DENSITY
//-*****************************************************************************
// "Density" refers to the the optical density which, when integrated
// through a line, produces an alpha. The relationship between alpha, density,
// and line segment of a given length "dz" is as follows:
//
// alpha = 1.0 - exp( -dz * density )
//
// We use a minimum non-zero density in some places in our code, which
// represents the density of dry air at atmospheric pressure. Though
// different wavelengths of light are attenuated differently, the average
// attenuation is 10^-5 per meter. To make it very minimal,
// we'll work with 1/10th that density (tiny tiny). Since our facility
// works in centimeters, this works out to (using & rearranging the
// equation above)
//
// 10^-6 = 1.0 - exp( -100.0 * MIN_NON_ZERO_DENSITY )
// exp( -100.0 * MIN_NON_ZERO_DENSITY ) = 1.0 - 10^-6
// -100.0 * MIN_NON_ZERO_DENSITY = log( 1.0 - 10^-6 )
// MIN_NON_ZERO_DENSITY = log( 1.0 - 10^-6 ) / -100.0
// MIN_NON_ZERO_DENSITY = 1.0000050000290891e-08
//
// We use double precision for density and dz calculations.
//
//-*****************************************************************************
// VISIBILITY (or 'VIZ', or 'TRANSMISSION')
//-*****************************************************************************
// Throughout the code below, we transform "alpha" into its inverse, which is
// transmissivity, or visibility, or for short, 'viz'. The relationship between
// alpha and viz is simple:
//
// alpha = 1.0 - viz, or viz = 1.0 - alpha.
//
// Similarly, the relationship between viz and density & dz is simple:
//
// viz = exp( -dz * density )
// log( viz ) = -dz * density
//
// Viz is easier to work with than alpha, because to accumulate a total
// visibility of many adjacent samples, the relationship is just, for the
// set of sample viz's: {viz0, viz1, viz2, ..., vizN-1}
//
// totalViz = viz0 * viz1 * viz2 * ... * vizN-1
//
// It's interesting to note that for any given set of spans, their accumulated
// visibility is the same regardless of what order they're accumulated in,
// since A*B == B*A.
//
// When using viz, we use double precision because the operation
// 1.0f - ( 1.0f - a ) loses precision, and we want as little of that as
// possible!
//
//-*****************************************************************************
// DEPTH RANGES
//-*****************************************************************************
// Because we need to be able to arithmetically manipulate depths, we place
// a range on the valid depth values. Positive Infinity is a valid depth value
// to be stored in a DTEX file, but in order to make everything else work, we
// set the maximum depth to near (but not at) FLT_MAX, 10^30. Similarly, we
// set the minimum depth to just slightly greater than zero, 10^-4. This
// could potentially clip effects being deep composited with very small
// distances and units of meters.
//
//-*****************************************************************************
// DEEP OPACITY
//-*****************************************************************************
// "Deep Opacity" refers to a depth function in which the sample at each point
// represents the total accumulated opacity at that depth. This represents
// the way that deep shadows would have been produced by renderman with the
// Display Driver Line: "deepshad" "deepopacity", except that the files actually
// store the inverse (1.0-opacity) at each point. It is important to note
// that for any given Dtex deepopacity sample, the value represents the
// accumulation of visibility on the NEAR side of the sample - up to and
// including the sample's depth, but no further in depth. Deep Opacity
// functions are monotonically decreasing in depth, and are always
// between 0 and 1.
//
// A complication arises when the 0'th continuous deep opacity sample has a
// non-zero deep opacity, because we don't have enough information to infer
// where the continuous span that ends at the 0th sample begins in depth. We
// solve the problem by interrogating the entire deep pixel for the maximum
// density of all its spans (see above), and then solving for what dz
// would produce the given accumulated alpha for that max density. The
// near point of the initial span is then 'dz' units in front of the 0th
// sample depth.
//
// We sometimes use 'deepViz' to in the code below to refer to 1.0 - deepOpacity
//
//-*****************************************************************************
// DEEP ALPHA
//-*****************************************************************************
// "Deep Alpha" refers to a depth function in which the sample at each point
// represents the non-accumulated alpha of that single sample. When interpreting
// the depth function as continuous instead of discrete, Deep Alpha represents
// the alpha of the FAR side of the sample - from the depth of the sample
// up to, but not including, the depth of the next sample.
//
// A complication arises when the last continuous deep alpha sample has a
// non-zero deep alpha, because we don't have enough information to infer
// where the continuous span that begins at the last sample ends in depth. We
// solve this problem analagously to how we solve the DeepOpacity problem.
// We get the maximum density along the entire deep pixel and extrapolate to
// determine an end depth.
//
//-*****************************************************************************
// DEEP RGBA
//-*****************************************************************************
// Deep RGBA is exactly the same as Deep Alpha, for both discrete and
// continuous cases, with the additional R,G, and B channels carried along.
// The RGB can be read as premultiplied by alpha, or not. The output deep
// pixel expects RGB to be premultiplied by alpha.
// The use of premultiplied alpha makes it possible to entangle emitted and
// reflected light - basically "glows", when premultiplied R,G,B are non-zero
// but alpha is zero. However, in order for us to collapse coindicent samples,
// we need to temporarily store RGB unpremultiplied. We simply don't affect
// the samples that have zero alpha, and don't remultiply samples that have
// zero alpha. There's no way for uncombined samples that had non-zero alpha
// to produce a combined sample with zero alpha, so any sample that has
// zero alpha at the end of all the combining was entirely composed of zero
// alpha samples to begin with. SO, if the alpha is zero, we don't
// multiply by it!
//-*****************************************************************************
//-*****************************************************************************
//-*****************************************************************************
//-*****************************************************************************
// UTILITY CONSTANTS AND FUNCTIONS
//-*****************************************************************************
//-*****************************************************************************
//-*****************************************************************************
//-*****************************************************************************
// Explained above in the "Density" section of the comments.
// We set this value to one tenth the attenuation of light in dry air
// at atmospheric pressure.
// 10^-6 = 1.0 - exp( -100.0 * MIN_NON_ZERO_DENSITY )
// exp( -100.0 * MIN_NON_ZERO_DENSITY ) = 1.0 - 10^-6
// -100.0 * MIN_NON_ZERO_DENSITY = log( 1.0 - 10^-6 )
// MIN_NON_ZERO_DENSITY = log( 1.0 - 10^-6 ) / -100.0
// MIN_NON_ZERO_DENSITY = 1.0000050000290891e-08
// static const double MIN_NON_ZERO_DENSITY = log( 1.0 - 1.0e-6 ) / -100.0;
#define PXDU_MIN_NON_ZERO_DENSITY 1.0000050000290891e-08L
//-*****************************************************************************
// The change in depth which produces maximum alpha for maximum density.
// We want this to be small without risking subnormality.
// static const double DZ_OF_ALPHA_1 = 0.001;
// static const double DZ_OF_VIZ_0 = 0.001;
#define PXDU_DZ_OF_ALPHA_1 0.001
#define PXDU_DZ_OF_VIZ_0 0.001
//-*****************************************************************************
// We set the max density of alpha 1 to the density which would produce
// an alpha of 0.99999 in a distance of 0.001 centimeters (DZ_OF_ALPHA_1)
//
// 0.99999 = 1.0 - exp( -0.001 * MAX_DENSITY )
// exp( -0.001 * MAX_DENSITY ) = 1.0 - 0.99999
// -0.001 * MAX_DENSITY = log( 1.0 - 0.99999 )
// MAX_DENSITY = log( 1.0 - 0.99999 ) / -0.001
// MAX_DENSITY = 11512.925464974779
// static const double DENSITY_OF_ALPHA_1 = log( 1.0 - 0.99999 ) / -0.001;
// static const double DENSITY_OF_VIZ_0 = DENSITY_OF_ALPHA_1;
#define PXDU_DENSITY_OF_ALPHA_1 11512.92546497478
#define PXDU_DENSITY_OF_VIZ_0 11512.92546497478
//-*****************************************************************************
// Just in case we need it. These are the constants used above.
#define PXDU_MAX_NON_OPAQUE_ALPHA 0.99999
#define PXDU_MIN_NON_OPAQUE_VIZ 0.00001
#define PXDU_MIN_NON_TRANSPARENT_ALPHA 0.00001
#define PXDU_MAX_NON_TRANSPARENT_VIZ 0.99999
//-*****************************************************************************
// Explained above in the "Depth" section of the comments.
// static const double MIN_DEEP_DEPTH = 1.0e-4;
// static const double MAX_DEEP_DEPTH = 1.0e30;
#define PXDU_MIN_DEEP_DEPTH 1.0e-4
#define PXDU_MAX_DEEP_DEPTH 1.0e30
//-*****************************************************************************
// A maximum depth change (dz)
// static const double MAX_DZ = double( MAX_DEEP_DEPTH ) -
// double( MIN_DEEP_DEPTH );
#define PXDU_MAX_DZ 1.0e30
//-*****************************************************************************
// IEEE 754 floats can be incremented to the "next" positive float
// in this manner, for positive float inputs.
inline float IncrementPositiveFloat( float i_a, int32_t i_inc=1 )
{
typedef union
{
int32_t i;
float f;
} intfloat;
intfloat a;
a.f = i_a;
a.i += i_inc;
return a.f;
}
//-*****************************************************************************
// IEEE 754 floats can be decremented to the "previous" positive float
// in this manner, for positive float inputs.
inline float DecrementPositiveFloat( float i_a, int32_t i_inc=1 )
{
typedef union
{
int32_t i;
float f;
} intfloat;
intfloat a;
a.f = i_a;
a.i -= i_inc;
return a.f;
}
//-*****************************************************************************
// From: man isinf
// isinf(x) returns 1 if x is positive infinity, and -1 if x is nega-
// tive infinity.
template <typename T>
inline bool IsInfinity( T i_f )
{
return ( isinf( i_f ) == 1 );
}
//-*****************************************************************************
// A zero-nan functon, which actually zeros inf as well.
template <typename T>
inline T ZeroNAN( T i_f )
{
if ( !isfinite( i_f ) ) { return ( T )0; }
else { return i_f; }
}
//-*****************************************************************************
template <typename T>
inline T ClampDepth( T i_depth )
{
if ( IsInfinity( i_depth ) )
{
return PXDU_MAX_DEEP_DEPTH;
}
else
{
return Imath::clamp( i_depth,
( T )PXDU_MIN_DEEP_DEPTH,
( T )PXDU_MAX_DEEP_DEPTH );
}
}
//-*****************************************************************************
template <typename T>
inline T ClampDz( T i_dz )
{
return Imath::clamp( ZeroNAN( i_dz ), ( T )0, ( T )PXDU_MAX_DZ );
}
//-*****************************************************************************
template <typename T>
inline T ClampNonZeroDz( T i_dz )
{
return Imath::clamp( ZeroNAN( i_dz ),
( T )PXDU_DZ_OF_ALPHA_1,
( T )PXDU_MAX_DZ );
}
//-*****************************************************************************
template <typename T>
inline T ClampAlpha( T i_alpha )
{
return Imath::clamp( ZeroNAN( i_alpha ), ( T )0, ( T )1 );
}
//-*****************************************************************************
// "plausible" in this case means not completely transparent, nor
// completely opaque.
template <typename T>
inline T ClampPlausibleAlpha( T i_alpha )
{
return Imath::clamp( ZeroNAN( i_alpha ),
( T )PXDU_MIN_NON_TRANSPARENT_ALPHA,
( T )PXDU_MAX_NON_OPAQUE_ALPHA );
}
//-*****************************************************************************
template <typename T>
inline double ClampViz( T i_viz )
{
return Imath::clamp( ZeroNAN( i_viz ), ( T )0, ( T )1 );
}
//-*****************************************************************************
// "plausible" in this case means not completely transparent, nor
// completely opaque.
template <typename T>
inline T ClampPlausibleViz( T i_viz )
{
return Imath::clamp( ZeroNAN( i_viz ),
( T )PXDU_MIN_NON_OPAQUE_VIZ,
( T )PXDU_MAX_NON_TRANSPARENT_VIZ );
}
//-*****************************************************************************
// Plausible density is clamped between min non-zero density
// and density of alpha 1.
template <typename T>
inline T ClampPlausibleDensity( T i_density )
{
return Imath::clamp( ZeroNAN( i_density ),
( T )PXDU_MIN_NON_ZERO_DENSITY,
( T )PXDU_DENSITY_OF_ALPHA_1 );
}
//-*****************************************************************************
// Density/Viz/DZ calculations are always performed in double precision.
// We try to leave them alone as much as possible, but the logarithm can get
// weird for very very small numbers. The "isfinite" call basically rules
// out NaN and Infinity results, though it doesn't bother with subnormal
// numbers, since the error case we're worried about is log being too big.
// viz = exp( -dz * density )
// log( viz ) = -dz * density
// density = -log( viz ) / dz
double DensityFromVizDz( double i_viz, double i_dz );
//-*****************************************************************************
// We can often treat "density times dz" as a single quantity without
// separating it.
// viz = exp( -densityTimesDz )
// log( viz ) = -densityTimesDz
// densityTimesDz = -log( viz )
double DensityTimesDzFromViz( double i_viz );
//-*****************************************************************************
// Plausible density defined above.
inline double PlausibleDensityFromVizDz( double i_viz, double i_dz )
{
return ClampPlausibleDensity( DensityFromVizDz( i_viz, i_dz ) );
}
//-*****************************************************************************
// viz = exp( -dz * density )
// log( viz ) = -dz * density
// dz = -log( viz ) / density
// Note that this is basically the same as the computation above.
double DzFromVizDensity( double i_viz, double i_density );
//-*****************************************************************************
// viz = exp( -dz * density ) // valid for all finite numbers.
// negative densities or dz's will give greater than 1 viz's, which will
// get clamped!
inline double VizFromDensityDz( double i_density, double i_dz )
{
return ClampViz( exp( -ZeroNAN( i_density * i_dz ) ) );
}
//-*****************************************************************************
// same as above.
inline double VizFromDensityTimesDz( double i_densityTimesDz )
{
return ClampViz( exp( -ZeroNAN( i_densityTimesDz ) ) );
}
//-*****************************************************************************
//-*****************************************************************************
//-*****************************************************************************
// IMF SPECIFIC STUFF
//-*****************************************************************************
//-*****************************************************************************
//-*****************************************************************************
//-*****************************************************************************
template <typename T>
Imf::PixelType ImfPixelType();
template <>
inline Imf::PixelType ImfPixelType<half>() { return Imf::HALF; }
template <>
inline Imf::PixelType ImfPixelType<float>() { return Imf::FLOAT; }
template <>
inline Imf::PixelType ImfPixelType<uint>() { return Imf::UINT; }
//-*****************************************************************************
// Handy exception macro.
#define PXDU_THROW( TEXT ) \
do \
{ \
std::stringstream sstr; \
sstr << TEXT; \
std::runtime_error exc( sstr.str() ); \
throw( exc ); \
} \
while( 0 )
} // End namespace PxDeep
#endif