Photon Engine 2.0.0-beta
A physically based renderer.
Loading...
Searching...
No Matches
spectral_samples.ipp
Go to the documentation of this file.
1#pragma once
2
11
12#include <array>
13#include <vector>
14
15namespace ph::math
16{
17
18template<typename T, CSpectralSampleProps SampleProps>
19inline constexpr T wavelength_interval_of() noexcept
20{
21 return static_cast<T>(SampleProps::MAX_WAVELENGTH_NM - SampleProps::MIN_WAVELENGTH_NM) /
22 static_cast<T>(SampleProps::NUM_SAMPLES);
23}
24
25template<typename T, CSpectralSampleProps SampleProps>
26inline constexpr auto wavelength_range_of(const std::size_t sampleIndex) noexcept
27-> std::pair<T, T>
28{
29 constexpr auto INTERVAL_NM = wavelength_interval_of<T, SampleProps>();
30
31 return
32 {
33 static_cast<T>(SampleProps::MIN_WAVELENGTH_NM) + static_cast<T>(sampleIndex + 0) * INTERVAL_NM,
34 static_cast<T>(SampleProps::MIN_WAVELENGTH_NM) + static_cast<T>(sampleIndex + 1) * INTERVAL_NM
35 };
36}
37
38template<typename T, CSpectralSampleProps SampleProps>
40{
41 const T sum = TArithmeticArray<T, SampleProps::NUM_SAMPLES>(srcSamples).sum();
42 return sum > 0 ? sum : 0;
43}
44
45template<typename T, CSpectralSampleProps SampleProps>
47{
49
50 const T energy = estimate_samples_energy<T, SampleProps>(srcSamples);
51 if(energy > 0)
52 {
53 samples.divLocal(energy);
54 }
55
56 return samples.toArray();
57}
58
59template<typename T, CSpectralSampleProps SampleProps>
61{
63 samples.fill(constant);
64 return samples;
65}
66
67template<typename T, typename U, CSpectralSampleProps SampleProps>
69 const U* const wavelengthsNM,
70 const U* const values,
71 const std::size_t numPoints,
72 const ESpectralResample algorithm)
73{
74 PH_ASSERT(wavelengthsNM);
75 PH_ASSERT(values);
76 PH_ASSERT(algorithm != ESpectralResample::Unspecified);
77
79 sampled.fill(0);
80
82 {
83 // Construct a curve from specified points
84 // TODO: add option for clamp to edge or set as zero, etc. for out of bound samples
85
87 for(std::size_t i = 0; i < numPoints; i++)
88 {
89 const U wavelengthNm = wavelengthsNM[i];
90 const U value = values[i];
91
92 curve.addPoint({wavelengthNm, value});
93 }
94 curve.update();
95
96 // Sample curve values by averaging each wavelength interval
97 // (note that <numPoints> does not necessarily equal to <SampleProps::NUM_SAMPLES>)
98
99 TAnalyticalIntegrator1D<U> areaCalculator;
100 for(std::size_t i = 0; i < SampleProps::NUM_SAMPLES; ++i)
101 {
102 const auto& range = wavelength_range_of<U, SampleProps>(i);
103
104 areaCalculator.setIntegrationDomain(range.first, range.second);
105
106 const U area = areaCalculator.integrate(curve);
107 const U avgValue = area / (range.second - range.first);
108 sampled[i] = static_cast<T>(avgValue);
109 }
110 }
111 else
112 {
113 PH_ASSERT_UNREACHABLE_SECTION();
114 }
115
116 return sampled;
117}
118
119template<typename T, CSpectralSampleProps SampleProps>
127
128template<typename T, CSpectralSampleProps SampleProps>
138
139template<typename T, CSpectralSampleProps SampleProps>
141{
142 const auto samples = resample_black_body_spectral_radiance<T, SampleProps>(temperatureK);
143
144 // Normalize the spectral radiance distribution (slightly cheaper to compute than radiance)
145 // for the SPD. Does not matter which unit to take as we want the distribution only.
147}
148
149template<typename T, CSpectralSampleProps SampleProps>
151{
152 const auto spectralRadianceSamples = resample_black_body_spectral_radiance<T, SampleProps>(
153 temperatureK);
154
155 // A more proper & accurate way to obtain radiance from spectral radiance is to directly
156 // integrate the spectral radiance curve within each wavelength interval. We cheap out
157 // here by directly multiplying each spectral radiance sample with its corresponding
158 // wavelength interval. This should have similar numerical precision as integrating the
159 // curve using trapezoidal rule.
160
161 constexpr auto wavelengthIntervalInMeter =
162 wavelength_interval_of<double, SampleProps>() * 1e-9;// convert nm to m;
163
164 TArithmeticArray<T, SampleProps::NUM_SAMPLES> radianceSamples(spectralRadianceSamples);
165 radianceSamples.mulLocal(static_cast<T>(wavelengthIntervalInMeter));
166 return radianceSamples.toArray();
167}
168
169template<typename T, CSpectralSampleProps SampleProps>
171{
172 using ComputeT = double;
173
174 std::vector<ComputeT> spectralRadianceLambdas;
175 const std::vector<ComputeT> spectralRadianceValues = black_body_spectral_radiance_curve<ComputeT>(
176 temperatureK,
177 SampleProps::MIN_WAVELENGTH_NM,
178 SampleProps::MAX_WAVELENGTH_NM,
179 SampleProps::NUM_SAMPLES,
180 &spectralRadianceLambdas);
181
183 spectralRadianceLambdas.data(),
184 spectralRadianceValues.data(),
185 spectralRadianceValues.size());
186
187 return samples;
188}
189
190namespace detail
191{
192
193template<typename T, CSpectralSampleProps SampleProps>
195{
197
198 std::array<ArrayType, 3> weights;
201
203 {
204 // Sample XYZ color matching functions first, then later normalize it so
205 // that dotting them with sampled E spectrum is equivalent to computing
206 // (X, Y, Z) tristimulus values and will yield (1, 1, 1).
207
208 // Sampling XYZ CMF
209
210 using XYZCMFValueType = spectral_data::ArrayD65::value_type;
211 constexpr auto NMU_XYZ_CMF_POINTS = std::tuple_size_v<spectral_data::ArrayXYZCMF>;
212
216 NMU_XYZ_CMF_POINTS);
217
221 NMU_XYZ_CMF_POINTS);
222
226 NMU_XYZ_CMF_POINTS);
227
228 weights[0].set(sampledCmfValuesX);
229 weights[1].set(sampledCmfValuesY);
230 weights[2].set(sampledCmfValuesZ);
231
232 // Normalizing
233
234 constexpr T wavelengthIntervalNM = wavelength_interval_of<T, SampleProps>();
235
236 // Integration of CMF-Y by Riemann Sum
237 const T integratedCmfY = (weights[1] * wavelengthIntervalNM).sum();
238
239 const auto uniformUnitSamples = ArrayType(1);
240 const auto illuminantD65Samples = ArrayType(resample_illuminant_D65<T, SampleProps>());
241 const auto illuminantESamples = ArrayType(resample_illuminant_E<T, SampleProps>());
242 const auto CIEXYZD65WhitePoint = CIEXYZ_of<T>(EReferenceWhite::D65);
243 const auto CIEXYZEWhitePoint = CIEXYZ_of<T>(EReferenceWhite::E);
244
245 for(std::size_t ci = 0; ci < 3; ++ci)
246 {
247 // Multiplier of Riemann Sum and denominator
248 weights[ci] = (weights[ci] * wavelengthIntervalNM) / integratedCmfY;
249
250 // Normalize weights[ci] such that <uniformUnitSamples> will be weighted to 1
251 // (sum of weights[ci] should be ~= 1 already, this is equivalent to explicitly make them sum to 1)
252 weights[ci] /= weights[ci].dot(uniformUnitSamples);
253
254 // Now, weights[ci] is usable, but may need further refinements depending on usage
255
256 // Normalization multiplier based on a D65 illuminant
257 // (this multiplier will ensure a normalized D65 SPD get the corresponding standard white point)
258 illuminantD65Normalizer[ci] = CIEXYZD65WhitePoint[ci] / weights[ci].dot(illuminantD65Samples);
259
260 // Normalization multiplier based on a E illuminant
261 // (this multiplier will ensure a normalized E SPD get the corresponding standard white point)
262 illuminantENormalizer[ci] = CIEXYZEWhitePoint[ci] / weights[ci].dot(illuminantESamples);
263 }
264 }
265};
266
267}// end detail
268
269template<typename T, CSpectralSampleProps SampleProps, EReferenceWhite NORMALIZER>
271{
273
274 const TVectorN<T, SampleProps::NUM_SAMPLES> copiedSrcSamples(srcSamples);
275
276 TArithmeticArray<T, 3> CIEXYZColor;
277 for(std::size_t ci = 0; ci < 3; ++ci)
278 {
279 CIEXYZColor[ci] = copiedSrcSamples.dot(kernel.weights[ci]);
280 }
281
282 switch(usage)
283 {
284 case EColorUsage::EMR:
285 if constexpr(NORMALIZER == EReferenceWhite::D65)
286 {
287 // Note that this multiplier will ensure a normalized D65 SPD get the corresponding standard
288 // white point defined in CIE-XYZ. The multiplier does not meant only for D65-based illuminants.
289 // Just that most illuminants are defined with respect to D65, so it is reasonable to "calibrate"
290 // the kernel using D65 in most cases.
291 //
292 // However, after the normalization, illuminant E no longer produce (1, 1, 1) in CIE-XYZ but around
293 // (0.825197, 0.825142, 0.825735), which is close to constant. This can be bad for round-trip
294 // operations. As this does not change the chromaticity of the color, it can be easily fixed by
295 // adjusting the brighness value afterwards.
296 //
297 CIEXYZColor *= kernel.illuminantD65Normalizer;
298 }
299 else
300 {
301 static_assert(NORMALIZER == EReferenceWhite::E,
302 "Currently normalizer supports only D65 and E.");
303
304 // After the normalization, D65 SPD will produce values in the range of 1.1 ~ 1.4. Whether the
305 // color will be distorted is yet to be tested.
306 //
307 CIEXYZColor *= kernel.illuminantENormalizer;
308 }
309 break;
310
311 case EColorUsage::ECF:
312 // The largest possible <srcSamples> in this case is a constant spectrum of value 1--the resulting
313 // CIE-XYZ color should always be in [0, 1].
314 CIEXYZColor.clampLocal(0, 1);
315 break;
316
317 case EColorUsage::Raw:
318 // Do nothing
319 break;
320
321 default:
322 throw ColorError(
323 "A color usage must be specified when converting spectral color samples.");
324 break;
325 }
326
327 return CIEXYZColor.toArray();
328}
329
330}// end namespace ph::math
Error on the color-related functionalities.
Definition math_exceptions.h:24
Definition TAnalyticalIntegrator1D.h:10
T integrate(const TPiecewiseLinear1D< T > &func) const
Definition TAnalyticalIntegrator1D.ipp:23
void setIntegrationDomain(T x0, T x1)
Definition TAnalyticalIntegrator1D.ipp:66
T sum() const
Definition TArithmeticArrayBase.ipp:336
std::array< T, N > toArray() const
Definition TArithmeticArrayBase.ipp:855
Derived & divLocal(const Derived &rhs)
Definition TArithmeticArrayBase.ipp:148
Derived & mulLocal(const Derived &rhs)
Definition TArithmeticArrayBase.ipp:112
Derived & clampLocal(T lowerBound, T upperBound)
Definition TArithmeticArrayBase.ipp:271
Definition TArithmeticArray.h:13
Definition TPiecewiseLinear1D.h:26
void addPoint(const TVector2< T > &point)
Definition TPiecewiseLinear1D.h:96
void update()
Definition TPiecewiseLinear1D.h:89
T dot(const Derived &rhs) const
Definition TVectorNBase.ipp:14
Definition TVectorN.h:12
const ArrayD65 & CIE_D65_wavelengths_nm()
Definition spectral_data.cpp:771
const ArrayXYZCMF & XYZ_CMF_CIE_1931_2_degree_X()
Definition spectral_data.cpp:756
const ArrayXYZCMF & XYZ_CMF_CIE_1931_2_degree_Z()
Definition spectral_data.cpp:766
const ArrayXYZCMF & XYZ_CMF_CIE_1931_2_degree_wavelengths_nm()
Definition spectral_data.cpp:751
const ArrayXYZCMF & XYZ_CMF_CIE_1931_2_degree_Y()
Definition spectral_data.cpp:761
const ArrayD65 & CIE_D65_values()
Definition spectral_data.cpp:776
Math functions and utilities.
Definition TransformInfo.h:10
constexpr TSpectralSampleValues< T, SampleProps > constant_spectral_samples(T constant)
Definition spectral_samples.ipp:60
TRawColorValues< T, SampleProps::NUM_SAMPLES > TSpectralSampleValues
Definition color_basics.h:54
std::vector< T > black_body_spectral_radiance_curve(const T temperatureK, const T lambdaMinNM, const T lambdaMaxNM, const std::size_t numCurvePoints, std::vector< T > *const out_lambdaValues=nullptr)
Get a curve for Black-body radiation. Note that this function is not returning radiance but spectral ...
Definition black_body.h:58
TSpectralSampleValues< T, SampleProps > resample_illuminant_E()
SPD of standard illuminants. Any light source which statistically has the same relative SPD as a stan...
Definition spectral_samples.ipp:120
TSpectralSampleValues< T, SampleProps > normalize_samples_energy(const TSpectralSampleValues< T, SampleProps > &srcSamples)
Normalize spectral samples as if they carry energy. Normalized spectral samples, together,...
Definition spectral_samples.ipp:46
TTristimulusValues< T > CIEXYZ_of(const EReferenceWhite refWhite)
Definition color_basics.h:243
TTristimulusValues< T > spectral_samples_to_CIE_XYZ(const TSpectralSampleValues< T, SampleProps > &srcSamples, EColorUsage usage)
Converting spectral samples to CIE-XYZ using standard CMFs.
Definition spectral_samples.ipp:270
ESpectralResample
Definition color_enums.h:161
EColorUsage
Definition color_enums.h:140
TSpectralSampleValues< T, SampleProps > resample_spectral_samples(const U *wavelengthsNM, const U *values, std::size_t numPoints, ESpectralResample algorithm=ESpectralResample::Default)
Definition spectral_samples.ipp:68
TRawColorValues< T, 3 > TTristimulusValues
Definition color_basics.h:48
TSpectralSampleValues< T, SampleProps > resample_black_body_radiance(T temperatureK)
SPD of black-body radiation in radiance. If a normalized energy distribution is desired,...
Definition spectral_samples.ipp:150
T estimate_samples_energy(const TSpectralSampleValues< T, SampleProps > &srcSamples)
Definition spectral_samples.ipp:39
TSpectralSampleValues< T, SampleProps > resample_black_body_spectral_radiance(T temperatureK)
SPD of black-body radiation in spectral radiance. Note that this function is not returning radiance b...
Definition spectral_samples.ipp:170
TSpectralSampleValues< T, SampleProps > resample_illuminant_D65()
SPD of standard illuminants D65, with total energy = 1.
Definition spectral_samples.ipp:129
constexpr std::pair< T, T > wavelength_range_of(std::size_t sampleIndex) noexcept
Definition spectral_samples.ipp:26
constexpr T wavelength_interval_of() noexcept
Definition spectral_samples.ipp:19
TSpectralSampleValues< T, SampleProps > resample_black_body(T temperatureK)
SPD of black-body radiation, with total energy = 1.
Definition spectral_samples.ipp:140
Definition spectral_samples.ipp:195
TArithmeticArray< T, 3 > illuminantENormalizer
Definition spectral_samples.ipp:200
std::array< ArrayType, 3 > weights
Definition spectral_samples.ipp:198
TCIEXYZCmfKernel()
Definition spectral_samples.ipp:202
TArithmeticArray< T, 3 > illuminantD65Normalizer
Definition spectral_samples.ipp:199
TVectorN< T, SampleProps::NUM_SAMPLES > ArrayType
Definition spectral_samples.ipp:196