Photon Engine 2.0.0-beta
A physically based renderer.
Loading...
Searching...
No Matches
SurfaceHitRefinery.h
Go to the documentation of this file.
1#pragma once
2
3#include "Core/SurfaceHit.h"
4#include "Core/Ray.h"
5#include "Math/TVector3.h"
6#include "Math/math.h"
8
9#include <Common/config.h>
10#include <Common/assertion.h>
11#include <Common/primitive_type.h>
12
13#include <cstddef>
14#include <cmath>
15#include <limits>
16#include <optional>
17#include <atomic>
18#include <utility>
19
20namespace ph { class EngineInitSettings; }
21
22namespace ph::lta
23{
24
31{
32public:
33 explicit SurfaceHitRefinery(const SurfaceHit& X);
34
40 Ray escape(const math::Vector3R& dir) const;
41
47 Ray escapeManually(const math::Vector3R& dir, real delta = selfIntersectDelta()) const;
48
53 Ray escapeEmpirically(const math::Vector3R& dir) const;
54
63 const math::Vector3R& dir,
64 std::size_t numIters = numIterations()) const;
65
71 std::optional<Ray> tryEscape(const SurfaceHit& X2) const;
72
78 std::optional<Ray> tryEscapeManually(const SurfaceHit& X2, real delta = selfIntersectDelta()) const;
79
84 std::optional<Ray> tryEscapeEmpirically(const SurfaceHit& X2) const;
85
91 std::optional<Ray> tryEscapeIteratively(
92 const SurfaceHit& X2,
93 std::size_t numIters = numIterations()) const;
94
95public:
98 static void init(const EngineInitSettings& settings);
99
102 static real selfIntersectDelta();
103
106 static std::size_t numIterations();
107
108private:
109 struct IterativeOffsetResult
110 {
111 math::Vector3R offset{0};
112 math::Vector3R unitDir{0};
113 real maxDistance{0};
114 };
115
116 static real fallbackOffsetDist(const SurfaceHit& X, real distanceFactor);
117 static real maxErrorOffsetDist(const SurfaceHit& X);
118 static real meanErrorOffsetDist(const SurfaceHit& X);
119 static math::Vector3R empiricalOffsetVec(const SurfaceHit& X, const math::Vector3R& dir);
120
121 static IterativeOffsetResult iterativeOffset(
122 const SurfaceHit& X,
123 const math::Vector3R& dir,
124 std::size_t numIters);
125
126 static IterativeOffsetResult iterativeMutualOffset(
127 const SurfaceHit& X,
128 const SurfaceHit& X2,
129 const math::Vector3R& dir,
130 std::size_t numIters);
131
132 static bool reintersect(const SurfaceHit& X, const Ray& ray, HitProbe& probe);
133
138 static bool canVerifyOffset(const SurfaceHit& X, const math::Vector3R& dir);
139
140 static bool verifyOffset(
141 const SurfaceHit& X,
142 const math::Vector3R& dir,
143 const math::Vector3R& rayOrigin,
144 const math::Vector3R& rayOffset,
145 real rayLength = std::numeric_limits<real>::max());
146
147 static ESurfaceRefineMode s_refineMode;
148 static real s_selfIntersectDelta;
149 static std::size_t s_numIterations;
150
151 const SurfaceHit& m_X;
152
153#if PH_ENABLE_HIT_EVENT_STATS
154public:
155 static void reportStats();
156
157private:
158 struct HitEventStats
159 {
160 std::atomic_uint64_t numEvents;
161 std::atomic_uint64_t numFailedEmpiricalEscapes;
162 std::atomic_uint64_t numFailedInitialEscapes;
163 std::atomic_uint64_t numFailedIterativeEscapes;
164 std::atomic_uint64_t numReintersecs;
165
166 void markEvent(const std::uint64_t num = 1)
167 {
168 numEvents.fetch_add(num, std::memory_order_relaxed);
169 }
170
171 void markFailedEmpiricalEscape()
172 {
173 numFailedEmpiricalEscapes.fetch_add(1, std::memory_order_relaxed);
174 }
175
176 void markFailedInitialEscape()
177 {
178 numFailedInitialEscapes.fetch_add(1, std::memory_order_relaxed);
179 }
180
181 void markFailedIterativeEscape()
182 {
183 numFailedIterativeEscapes.fetch_add(1, std::memory_order_relaxed);
184 }
185
186 void markReintersect()
187 {
188 numReintersecs.fetch_add(1, std::memory_order_relaxed);
189 }
190 };
191
192 static HitEventStats s_stats;
193#endif
194};
195
197 : m_X(X)
198{}
199
201{
202 PH_ASSERT_MSG(dir.isFinite() && !dir.isZero(), dir.toString());
203
204 switch(s_refineMode)
205 {
207 return escapeManually(dir);
208
210 return escapeEmpirically(dir);
211
213 return escapeIteratively(dir);
214 }
215
216 PH_ASSERT_UNREACHABLE_SECTION();
217 return Ray{};
218}
219
220inline Ray SurfaceHitRefinery::escapeManually(const math::Vector3R& dir, const real delta) const
221{
222#if PH_ENABLE_HIT_EVENT_STATS
223 s_stats.markEvent();
224#endif
225
226 return Ray(
227 m_X.getPos(),
228 dir.normalize(),
229 delta,
230 std::numeric_limits<real>::max(),
231 m_X.getTime());
232}
233
235{
236#if PH_ENABLE_HIT_EVENT_STATS
237 s_stats.markEvent();
238#endif
239
240 return Ray(
241 m_X.getPos() + empiricalOffsetVec(m_X, dir),
242 dir.normalize(),
243 m_X.getTime());
244}
245
247 const math::Vector3R& dir,
248 const std::size_t numIters) const
249{
250#if PH_ENABLE_HIT_EVENT_STATS
251 s_stats.markEvent();
252#endif
253
254 const IterativeOffsetResult result = iterativeOffset(m_X, dir, numIters);
255 PH_ASSERT_GT(result.maxDistance, 0.0_r);
256
257 return Ray(
258 m_X.getPos() + result.offset,
259 result.unitDir,
260 0,
261 result.maxDistance,
262 m_X.getTime());
263}
264
265inline std::optional<Ray> SurfaceHitRefinery::tryEscape(const SurfaceHit& X2) const
266{
267 switch(s_refineMode)
268 {
270 return tryEscapeManually(X2);
271
273 return tryEscapeEmpirically(X2);
274
276 return tryEscapeIteratively(X2);
277 }
278
279 PH_ASSERT_UNREACHABLE_SECTION();
280 return Ray{};
281}
282
283inline std::optional<Ray> SurfaceHitRefinery::tryEscapeManually(const SurfaceHit& X2, const real delta) const
284{
285#if PH_ENABLE_HIT_EVENT_STATS
286 s_stats.markEvent(2);
287#endif
288
289 const auto xToX2 = X2.getPos() - m_X.getPos();
290 const auto distance2 = xToX2.lengthSquared();
291
292 // Make sure the two points are distant enough to avoid self-intersection
293 // (at least 3 deltas, 2 for ray endpoints and 1 for ray body)
294 if(distance2 > math::squared(delta * 3))
295 {
296 const auto distance = std::sqrt(distance2);
297 const auto rcpDistance = 1.0_r / distance;
298 return Ray(
299 m_X.getPos(),
300 xToX2 * rcpDistance,
301 delta,
302 distance - delta,
303 m_X.getTime());// following X's time
304 }
305 else
306 {
307 return std::nullopt;
308 }
309}
310
311inline std::optional<Ray> SurfaceHitRefinery::tryEscapeEmpirically(const SurfaceHit& X2) const
312{
313#if PH_ENABLE_HIT_EVENT_STATS
314 s_stats.markEvent(2);
315#endif
316
317 const auto xToX2 = X2.getPos() - m_X.getPos();
318 const auto originX = m_X.getPos() + empiricalOffsetVec(m_X, xToX2);
319 const auto originX2 = X2.getPos() + empiricalOffsetVec(X2, -xToX2);
320 const auto distance = (originX2 - originX).length();
321 const auto rcpDistance = 1.0_r / distance;
322 if(rcpDistance != 0 && std::isfinite(rcpDistance))
323 {
324 return Ray(
325 originX,
326 (originX2 - originX) * rcpDistance,
327 0,
328 distance,
329 m_X.getTime());// following X's time
330 }
331 else
332 {
333 return std::nullopt;
334 }
335}
336
338 const SurfaceHit& X2,
339 const std::size_t numIters) const
340{
341#if PH_ENABLE_HIT_EVENT_STATS
342 s_stats.markEvent(2);
343#endif
344
345 const auto xToX2 = X2.getPos() - m_X.getPos();
346 const IterativeOffsetResult result = iterativeMutualOffset(m_X, X2, xToX2, numIters);
347 if(result.maxDistance > 0.0_r && std::isfinite(result.maxDistance))
348 {
349 return Ray(
350 m_X.getPos() + result.offset,
351 result.unitDir,
352 0,
353 result.maxDistance,
354 m_X.getTime());// following X's time
355 }
356 else
357 {
358 return std::nullopt;
359 }
360}
361
363{
364 return s_selfIntersectDelta;
365}
366
368{
369 return s_numIterations;
370}
371
372inline real SurfaceHitRefinery::fallbackOffsetDist(const SurfaceHit& X, const real distanceFactor)
373{
374 const auto fallbackDist = std::max(X.getPos().length() * distanceFactor, selfIntersectDelta());
375 return std::isfinite(fallbackDist) ? fallbackDist : selfIntersectDelta();
376}
377
378inline real SurfaceHitRefinery::maxErrorOffsetDist(const SurfaceHit& X)
379{
380 const auto [_, maxFactor] = X.getDetail().getDistanceErrorFactors();
381 const auto offsetDist = X.getPos().length() * maxFactor;
382 if(std::isfinite(offsetDist))
383 {
384 // Allow to be 0 (if the implementation is confident--with 0 error)
385 return offsetDist;
386 }
387 else
388 {
389 // A pessimistic mapping obtained from a fairly extreme `IntersectError` test
390 constexpr real distanceFactor = 1e-3_r;
391 return fallbackOffsetDist(X, distanceFactor);
392 }
393}
394
395inline real SurfaceHitRefinery::meanErrorOffsetDist(const SurfaceHit& X)
396{
397 const auto [meanFactor, _] = X.getDetail().getDistanceErrorFactors();
398 const auto offsetDist = X.getPos().length() * meanFactor;
399 if(std::isfinite(offsetDist))
400 {
401 // Allow to be 0 (if the implementation is confident--with 0 error)
402 return offsetDist;
403 }
404 else
405 {
406 // A pessimistic mapping obtained from a fairly extreme `IntersectError` test
407 constexpr real distanceFactor = 1e-6_r;
408 return fallbackOffsetDist(X, distanceFactor);
409 }
410}
411
412inline math::Vector3R SurfaceHitRefinery::empiricalOffsetVec(const SurfaceHit& X, const math::Vector3R& dir)
413{
414 PH_ASSERT_MSG(dir.isFinite() && !dir.isZero(), dir.toString());
415
416 const auto N = X.getGeometryNormal();
417 const auto dist = maxErrorOffsetDist(X);
418 const auto offsetVec = N.dot(dir) > 0.0_r ? N * dist : N * -dist;
419
420#if PH_ENABLE_HIT_EVENT_STATS
421 if(canVerifyOffset(X, dir) && !verifyOffset(X, dir, X.getPos(), offsetVec))
422 {
423 s_stats.markFailedEmpiricalEscape();
424 }
425#endif
426
427 return offsetVec;
428}
429
430inline bool SurfaceHitRefinery::reintersect(const SurfaceHit& X, const Ray& ray, HitProbe& probe)
431{
432#if PH_ENABLE_HIT_EVENT_STATS
433 s_stats.markReintersect();
434#endif
435
436 return X.reintersect(ray, probe);
437}
438
439inline bool SurfaceHitRefinery::canVerifyOffset(const SurfaceHit& X, const math::Vector3R& dir)
440{
441 const auto N = X.getGeometryNormal();
442 const auto topology = X.getDetail().getFaceTopology();
443
444 return topology.hasNo(EFaceTopology::General) &&
445 (topology.hasNo(EFaceTopology::Concave) || N.dot(dir) < 0.0_r) &&
446 (topology.hasNo(EFaceTopology::Convex) || N.dot(dir) > 0.0_r);
447}
448
449inline bool SurfaceHitRefinery::verifyOffset(
450 const SurfaceHit& X,
451 const math::Vector3R& dir,
452 const math::Vector3R& rayOrigin,
453 const math::Vector3R& rayOffset,
454 const real rayLength)
455{
456 PH_ASSERT(canVerifyOffset(X, dir));
457
458 HitProbe probe;
459 const Ray ray(rayOrigin + rayOffset, dir.normalize(), 0, rayLength, X.getTime());
460 return !X.reintersect(ray, probe);
461}
462
463}// end namespace ph::lta
Options for initializing core engine. These settings are loaded on engine startup and remains constan...
Definition EngineInitSettings.h:20
std::pair< real, real > getDistanceErrorFactors() const
Definition HitDetail.h:185
Lightweight ray intersection testing and reporting object. If an intersection is found,...
Definition HitProbe.h:27
Represents a ray in space.
Definition Ray.h:21
General information about a ray-surface intersection event.
Definition SurfaceHit.h:59
const HitDetail & getDetail() const
Definition SurfaceHit.h:159
math::Vector3R getPos() const
Definition SurfaceHit.h:186
const Time & getTime() const
Definition SurfaceHit.h:181
Algorithms for various hit point adjustments. For surface escaping routines, the generated ray is not...
Definition SurfaceHitRefinery.h:31
std::optional< Ray > tryEscapeEmpirically(const SurfaceHit &X2) const
Mutually escape from X and X2 by some adaptive offset.
Definition SurfaceHitRefinery.h:311
std::optional< Ray > tryEscape(const SurfaceHit &X2) const
Mutually escape from X and X2. The method to use for escaping the surfaces is determined by engine se...
Definition SurfaceHitRefinery.h:265
static std::size_t numIterations()
Number of iterations to perform when escaping in iterative mode.
Definition SurfaceHitRefinery.h:367
Ray escapeIteratively(const math::Vector3R &dir, std::size_t numIters=numIterations()) const
Escape this surface in a specific direction by iteratively re-intersect with the surface....
Definition SurfaceHitRefinery.h:246
Ray escapeManually(const math::Vector3R &dir, real delta=selfIntersectDelta()) const
Escape this surface in a specific direction by a small offset.
Definition SurfaceHitRefinery.h:220
Ray escape(const math::Vector3R &dir) const
Escape this surface in a specific direction. The method to use for escaping the surface is determined...
Definition SurfaceHitRefinery.h:200
SurfaceHitRefinery(const SurfaceHit &X)
Definition SurfaceHitRefinery.h:196
std::optional< Ray > tryEscapeManually(const SurfaceHit &X2, real delta=selfIntersectDelta()) const
Mutually escape from X and X2 by a small offset.
Definition SurfaceHitRefinery.h:283
std::optional< Ray > tryEscapeIteratively(const SurfaceHit &X2, std::size_t numIters=numIterations()) const
Mutually escape from X and X2 by iteratively re-intersect with the surfaces.
Definition SurfaceHitRefinery.h:337
Ray escapeEmpirically(const math::Vector3R &dir) const
Escape this surface in a specific direction by some adaptive offset.
Definition SurfaceHitRefinery.h:234
static real selfIntersectDelta()
A small value for resolving self-intersections.
Definition SurfaceHitRefinery.h:362
static void init(const EngineInitSettings &settings)
Initialize from engine settings.
Definition SurfaceHitRefinery.cpp:43
bool isZero() const
Definition TArithmeticArrayBase.ipp:549
T length() const
Definition TVectorNBase.ipp:38
Derived normalize() const
Normalize the vector. Notice that normalizing a integer typed vector will result in 0-vector most of ...
Definition TVectorNBase.ipp:50
std::string toString() const
Definition TArithmeticArrayBase.ipp:825
bool isFinite() const
Definition TArithmeticArrayBase.ipp:585
T lengthSquared() const
Definition TVectorNBase.ipp:44
Miscellaneous math utilities.
Light transport algorithms.
Definition enums.h:6
T squared(const T value)
Definition math.h:59
T length(const std::array< T, N > &vec)
Treating input values as a vector and calculates its length.
Definition math.ipp:50
TVector3< real > Vector3R
Definition math_fwd.h:52
The root for all renderer implementations.
Definition EEngineProject.h:6
ESurfaceRefineMode
Definition ESurfaceRefineMode.h:11