Loading...
Searching...
No Matches
sampler.h
Go to the documentation of this file.
1#ifndef _BBM_NDF_SAMPLER_H_
2#define _BBM_NDF_SAMPLER_H_
3
4#include "bbm/ndf.h"
5#include "util/cdf.h"
6
7/************************************************************************/
8/*! \file sampler.h
9
10 \brief Replace an NDF's sample and pdf method by a data-driven version.
11 Monitors the NDF and update the internal state if required.
12
13 Assumes an isotropic NDF!
14
15************************************************************************/
16
17namespace bbm {
18 namespace ndf {
19
20 /*******************************************************************/
21 /*! \brief Replace an NDFs sample and pdf method with a data-driven
22 numerical approximation.
23
24 \tparam NDF = NDF for which to replace sample and pdf.
25
26 \tparam samplesTheta = number of samples to take along the theta angle
27 of the halfway vector (detault = 90).
28
29 \tparam samplesPhi = number of phi angles to *average* theta samples
30 over (default = 1).
31
32 \tparam NAME = name of the NDF (default is NDF::name + '_sampler').
33 ********************************************************************/
34 template<typename NDF, size_t samplesTheta=90, size_t samplesPhi=1, string_literal NAME=NDF::name + string_literal("_sampler")> requires concepts::ndf<NDF>
35 class sampler : public NDF
36 {
38 public:
40 static constexpr string_literal name = NAME;
41
42 //! \brief forward copy eval and attenuation
43 using NDF::NDF;
44 using NDF::eval;
45 using NDF::G1;
46
47 /********************************************************************/
48 /*! \brief Sample the NDF
49
50 \param view = view direction (ignored)
51 \param xi = 2D uniform random variables in [0..1] range
52 \param Mask = enable/disbale lanes
53 \returns A sampled microfacet normal.
54
55 To avoid discontunuities, we sample according to a linear interpolation
56 of the discrete PDF. Linear interpolation is achieved by distributing
57 the samples, given a discrete bin, according to a linear-tent
58 distribution centered at the mid-point of the bin, and extending to the
59 middle of the neighboring bins. At the edges (0 and Pi/2), we reflect
60 samples that exceed the hemispherical bounds.
61
62 ********************************************************************/
63 Vec3d sample(const Vec3d& /*view*/, const Vec2d& xi, Mask mask=true) const
64 {
65 // check if xi is within bounds
66 mask &= (xi[0] >= 0) && (xi[0] <= 1) && (xi[1] >= 0) && (xi[1] <= 1);
67
68 // quick bail out
69 if(bbm::none(mask)) return 0.0;
70
71 // reinit sampler?
72 initialize();
73
74 // sample cdf
75 auto [index, residual] = bbm::pick<"index", "residual">( _cdf.sample(xi[0], bbm::cast<index_mask_t<Value>>(mask)) );
76
77 // Linear interpolation with neighboring bins to avoid discontinuities in the pdf
78 auto xi_r = bbm::abs(residual - 0.5);
79 auto sample_offset = 1 - bbm::safe_sqrt(1 - 2*xi_r); // [0...1]
80
81 // sample halfway
82 Vec2d h;
83 spherical::theta(h) = bbm::pow( (index + 0.5 + bbm::sign(residual-0.5)*sample_offset) / samplesTheta, 2.0 ) * Constants::Pi(0.5);
84 spherical::phi(h) = Constants::Pi(2) * xi[1];
85
86 // reflect if below horizon (reflection at the zenith is automatic due
87 // to the the symmetry around the Z axis).
88 spherical::theta(h) = bbm::select(spherical::theta(h) > Constants::Pi(0.5), Constants::Pi() - spherical::theta(h), spherical::theta(h));
89
90 // Done.
91 return bbm::select(mask, spherical::convert(h), 0.0);
92 }
93
94 /********************************************************************/
95 /*! \brief PDF of sampling the NDF
96
97 \param view = view direction (ignored)
98 \param m = sampled microfacet normal
99 \param mask = enable/disable lanes [default = true]
100 \returns the PDF of sampling 'm' using the sample method.
101 *********************************************************************/
102 Value pdf(const Vec3d& /*view*/, const Vec3d& m, Mask mask=true) const
103 {
104 // m above surface?
105 mask &= (vec::z(m) > 0);
106
107 // quick bail out
108 if(bbm::none(mask)) return 0;
109
110 // reinit sampler?
111 initialize();
112
113 // compute cdf index
114 Value theta = spherical::theta(m);
115 Value theta_index = bbm::sqrt(theta / Constants::Pi(0.5)) * samplesTheta - 0.5;
116
117 // linear interpolate the cdf's pdf.
118 Value w = theta_index - bbm::floor(theta_index);
119 Size_t lidx = bbm::clamp( bbm::cast<Size_t>(bbm::floor(theta_index)), 0, samplesTheta-1); // handle reflection at horizon
120 Size_t uidx = bbm::clamp( bbm::cast<Size_t>(bbm::ceil(theta_index)), 0, samplesTheta-1); // handle reflection at zenith
121 Value pdf = _cdf.pdf( lidx, bbm::cast<index_mask_t<Value>>(mask) ) * (1-w) + _cdf.pdf( uidx, bbm::cast<index_mask_t<Value>>(mask)) * w;
122
123 // include Jacobian determinants
124 Value jac = (bbm::sqrt(theta) * Constants::Pi2(0.25) / samplesTheta) * bbm::abs(bbm::sin(theta)) * (Constants::Pi(2.0));
125
126 // Done.
127 return bbm::select(mask && (jac > Constants::Epsilon()), pdf / jac, 0);
128 }
129
130 private:
131 /******************************************************************/
132 /*! \brief initialize the data structures
133
134 Sample along the sqrt(theta) angle so that samples are more densely
135 placed near the specular peak. Because a NDF is a decreasing function,
136 we place the sample at the beginning of a sample 'bin'. We compensate
137 the non-linear sampling by scaling the sample with a compensation
138 factor. Because the weighting is increases, we sample at the end of
139 the 'bin' to avoid missing important samples (i.e., max(f*g) <
140 max(f)*max(g)).
141
142 ******************************************************************/
143 inline void initialize(void) const
144 {
145 // check if the CDF needs to be initialized or updated
146 if(_cdf.size() != 0 && _monitor == bbm::reflection::attributes(*this)) return;
147
148 // initialize _monitor
150
151 // sample the NDF
152 std::vector<Value> samples;
153
154 Vec2d h;
155 for(size_t thetaIdx=0; thetaIdx != samplesTheta; ++thetaIdx)
156 {
157 Value sample = 0;
158 spherical::theta(h) = bbm::pow(Scalar(thetaIdx) / Scalar(samplesTheta), 2.0) * Constants::Pi(0.5);
159
160 for(size_t phiIdx=0; phiIdx != samplesPhi; ++phiIdx)
161 {
162 spherical::phi(h) = Scalar(phiIdx) / Scalar(samplesPhi) * Constants::Pi(2);
163 sample += static_cast<const NDF>(*this).eval( spherical::convert(h) );
164 }
165
166 // average over phi samples
167 sample /= samplesPhi;
168
169 // Weight to compensate for non-linear sampling
170 Scalar theta1 = bbm::pow(Scalar(thetaIdx + 1) / Scalar(samplesTheta), 2.0) * Constants::Pi(0.5) ;
171 sample *= bbm::sin(theta1) * bbm::sqrt(theta1);
172
173 // store
174 samples.push_back(sample);
175 }
176
177 // Construct the CDF
178 _cdf = cdf(samples);
179
180 // Done.
181 }
182
183 //////////////////////
184 // Class Attributes
185 //////////////////////
186 mutable value_copy_tuple_t<anonymize_t<bbm::reflection::attributes_t<NDF>>> _monitor; //< copy of NDF parameters to check if _cdf needs to be reinit.
187 mutable cdf<std::vector<Value>> _cdf; //< sampled cdf of the NDF
188 };
189
190 } // end ndf namespace
191} // end bbm namespace
192
193#endif /* _BBM_NDF_SAMPLER_H_ */
All includes and helpers needed for declaring new ndfs.
Discrete Cummulative Distribution Function (cdf) constructed from a set of samples and accompanying s...
Replace an NDFs sample and pdf method with a data-driven numerical approximation.
Definition: sampler.h:36
value_copy_tuple_t< anonymize_t< bbm::reflection::attributes_t< NDF > > > _monitor
Definition: sampler.h:186
Value pdf(const Vec3d &, const Vec3d &m, Mask mask=true) const
PDF of sampling the NDF.
Definition: sampler.h:102
Vec3d sample(const Vec3d &, const Vec2d &xi, Mask mask=true) const
Sample the NDF.
Definition: sampler.h:63
cdf< std::vector< Value > > _cdf
Definition: sampler.h:187
void initialize(void) const
initialize the data structures
Definition: sampler.h:143
static constexpr string_literal name
Definition: sampler.h:40
constexpr decltype(auto) attributes(T &&t)
Definition: reflection.h:200
T & phi(vec2d< T > &v)
Definition: spherical.h:39
vec2d< T > convert(const vec3d< T > &v)
Definition: spherical.h:53
T & theta(vec2d< T > &v)
Definition: spherical.h:23
constexpr decltype(auto) z(bbm::vec3d< T > &v)
Definition: vec.h:26
Definition: aggregatebsdf.h:29
constexpr auto select(MASK &&mask, const A &a, const A &b)
Definition: backbone.h:255
decltype(value_copy_tuple(std::declval< std::decay_t< T > >())) value_copy_tuple_t
value-copy type of a tuple.
Definition: tuple.h:73
constexpr auto pick(T &&t)
Pick a subset/reshuffle a named container T and return as a named tuple.
Definition: named.h:305
CDF data structure.
Definition: cdf.h:29
size_t size(void) const
number of samples
Definition: cdf.h:65
auto sample(const value_type &xi, index_mask_t< index_type > mask=true) const
sample the CDF given a random variable xi
Definition: cdf.h:85
auto pdf(const index_type &idx, index_mask_t< index_type > mask=true) const
querry the PDF that an index will be sampled.
Definition: cdf.h:113
Definition: string_literal.h:16