SPH
RayTracer.cpp
Go to the documentation of this file.
2 #include "gui/Factory.h"
3 #include "gui/objects/Bitmap.h"
4 #include "gui/objects/Camera.h"
9 #include "system/Profiler.h"
10 
12 
14  : IRaytracer(scheduler, settings) {
15  kernel = CubicSpline<3>();
16  fixed.dirToSun = getNormalized(settings.get<Vector>(GuiSettingsId::SURFACE_SUN_POSITION));
17  fixed.brdf = Factory::getBrdf(settings);
18  fixed.renderSpheres = settings.get<bool>(GuiSettingsId::RAYTRACE_SPHERES);
19  fixed.shadows = settings.get<bool>(GuiSettingsId::RAYTRACE_SHADOWS);
20 }
21 
22 RayMarcher::~RayMarcher() = default;
23 
24 constexpr Size BLEND_ALL_FLAG = 0x80;
25 
26 void RayMarcher::initialize(const Storage& storage,
27  const IColorizer& colorizer,
28  const ICamera& UNUSED(camera)) {
29  MEASURE_SCOPE("Building BVH");
30  cached.r = storage.getValue<Vector>(QuantityId::POSITION).clone();
31  const Size particleCnt = cached.r.size();
32 
33  if (storage.has(QuantityId::UVW)) {
34  cached.uvws = storage.getValue<Vector>(QuantityId::UVW).clone();
35  } else {
36  cached.uvws.clear();
37  }
38 
39  cached.flags.resize(particleCnt);
40  if (storage.has(QuantityId::FLAG) && storage.has(QuantityId::STRESS_REDUCING)) {
43  // avoid blending particles of different bodies, except if they are fully damaged
44  for (Size i = 0; i < particleCnt; ++i) {
45  cached.flags[i] = idxs[i];
46  if (reduce[i] == 0._f) {
47  cached.flags[i] |= BLEND_ALL_FLAG;
48  }
49  }
50  } else {
51  cached.flags.fill(0);
52  }
53 
54  cached.materialIDs.resize(particleCnt);
55  cached.materialIDs.fill(0);
56  const bool loadTextures = cached.textures.empty();
57  if (loadTextures) {
58  cached.textures.resize(storage.getMaterialCnt());
59  }
61  for (Size matId = 0; matId < storage.getMaterialCnt(); ++matId) {
62  MaterialView body = storage.getMaterial(matId);
63  for (Size i : body.sequence()) {
64  cached.materialIDs[i] = matId;
65  }
66 
67  std::string texturePath = body->getParam<std::string>(BodySettingsId::VISUALIZATION_TEXTURE);
68  if (loadTextures && !texturePath.empty()) {
69  if (textureMap.contains(texturePath)) {
70  cached.textures[matId] = textureMap[texturePath];
71  } else {
72  SharedPtr<Texture> texture =
73  makeShared<Texture>(Path(texturePath), TextureFiltering::BILINEAR);
74  textureMap.insert(texturePath, texture);
75  cached.textures[matId] = texture;
76  }
77  }
78  }
79 
80  cached.v.resize(particleCnt);
81  if (storage.has(QuantityId::MASS) && storage.has(QuantityId::DENSITY)) {
84  for (Size matId = 0; matId < storage.getMaterialCnt(); ++matId) {
85  MaterialView material = storage.getMaterial(matId);
86  const Float rho = material->getParam<Float>(BodySettingsId::DENSITY);
87  for (Size i : material.sequence()) {
88  cached.v[i] = m[i] / rho;
89  }
90  }
91  } else {
92  for (Size i = 0; i < particleCnt; ++i) {
93  cached.v[i] = sphereVolume(cached.r[i][H]);
94  }
95  }
96 
97  cached.doEmission = typeid(colorizer) == typeid(BeautyColorizer);
98  cached.colors.resize(particleCnt);
99  for (Size i = 0; i < particleCnt; ++i) {
100  cached.colors[i] = colorizer.evalColor(i);
101  if (cached.doEmission) {
102  cached.colors[i] = cached.colors[i] * colorizer.evalScalar(i).value();
103  }
104  }
105 
106  Array<BvhSphere> spheres;
107  spheres.reserve(particleCnt);
108  for (Size i = 0; i < particleCnt; ++i) {
109  BvhSphere& s = spheres.emplaceBack(cached.r[i], /*2.f * */ cached.r[i][H]);
110  s.userData = i;
111  }
112  bvh.build(std::move(spheres));
113 
115  finder->build(*scheduler, cached.r);
116 
117  for (ThreadData& data : threadData) {
118  MarchData march;
119  march.previousIdx = Size(-1);
120  data.data = std::move(march);
121  }
122 
123  shouldContinue = true;
124 }
125 
127  return !cached.r.empty();
128 }
129 
130 Rgba RayMarcher::shade(const RenderParams& params, const CameraRay& cameraRay, ThreadData& data) const {
131  const Vector dir = getNormalized(cameraRay.target - cameraRay.origin);
132  const Ray ray(cameraRay.origin, dir);
133 
134  MarchData& march(data.data);
135  if (Optional<Vector> hit = this->intersect(march, ray, params.surface.level, false)) {
136  return this->getSurfaceColor(march, params, march.previousIdx, hit.value(), ray.direction());
137  } else {
138  return this->getEnviroColor(cameraRay);
139  }
140 }
141 
142 ArrayView<const Size> RayMarcher::getNeighbourList(MarchData& data, const Size index) const {
143  // look for neighbours only if the intersected particle differs from the previous one
144  if (index != data.previousIdx) {
145  Array<NeighbourRecord> neighs;
146  finder->findAll(index, kernel.radius() * cached.r[index][H], neighs);
147  data.previousIdx = index;
148 
149  // find the actual list of neighbours
150  data.neighs.clear();
151  for (NeighbourRecord& n : neighs) {
152  const Size flag1 = cached.flags[index];
153  const Size flag2 = cached.flags[n.index];
154  if ((flag1 & BLEND_ALL_FLAG) || (flag2 & BLEND_ALL_FLAG) || (flag1 == flag2)) {
155  data.neighs.push(n.index);
156  }
157  }
158  }
159  return data.neighs;
160 }
161 
162 Optional<Vector> RayMarcher::intersect(MarchData& data,
163  const Ray& ray,
164  const Float surfaceLevel,
165  const bool occlusion) const {
166  data.intersections.clear();
167  bvh.getAllIntersections(ray, backInserter(data.intersections));
168  std::sort(data.intersections.begin(), data.intersections.end());
169 
170  for (const IntersectionInfo& intersect : data.intersections) {
171  IntersectContext sc;
172  sc.index = intersect.object->userData;
173  sc.ray = ray;
174  sc.t_min = intersect.t;
175  sc.surfaceLevel = surfaceLevel;
176  const Optional<Vector> hit = this->getSurfaceHit(data, sc, occlusion);
177  if (hit) {
178  return hit;
179  }
180  // rejected, process another intersection
181  }
182  return NOTHING;
183 }
184 
185 Optional<Vector> RayMarcher::getSurfaceHit(MarchData& data,
186  const IntersectContext& context,
187  bool occlusion) const {
188  if (fixed.renderSpheres) {
189  data.previousIdx = context.index;
190  return context.ray.origin() + context.ray.direction() * context.t_min;
191  }
192 
193  this->getNeighbourList(data, context.index);
194 
195  const Size i = context.index;
196  const Ray& ray = context.ray;
198  Vector v1 = ray.origin() + ray.direction() * context.t_min;
199  // the sphere hit should be always above the surface
200  // SPH_ASSERT(this->evalField(data.neighs, v1) < 0._f);
201  // look for the intersection up to hit + 4H; if we don't find it, we should reject the hit and look for
202  // the next intersection - the surface can be non-convex!!
203  const Float limit = 2._f * cached.r[i][H];
204  // initial step - cannot be too large otherwise the ray could 'tunnel through' on grazing angles
205  Float eps = 0.5_f * cached.r[i][H];
206  Vector v2 = v1 + eps * ray.direction();
207 
208  Float phi = 0._f;
209  Float travelled = eps;
210  while (travelled < limit && eps > 0.2_f * cached.r[i][H]) {
211  phi = this->evalField(data.neighs, v2) - context.surfaceLevel;
212  if (phi > 0._f) {
213  if (occlusion) {
214  return v2;
215  }
216  // we crossed the surface, move back
217  v2 = 0.5_f * (v1 + v2);
218  eps *= 0.5_f;
219  // since we crossed the surface, don't check for travelled distance anymore
220  travelled = -INFTY;
221  } else {
222  // we are still above the surface, move further
223  v1 = v2;
224  v2 += eps * ray.direction();
225  travelled += eps;
226  }
227  }
228 
229  if (travelled >= limit) {
230  // didn't find surface, reject the hit
231  return NOTHING;
232  } else {
233  return v2;
234  }
235 }
236 
237 Rgba RayMarcher::getSurfaceColor(MarchData& data,
238  const RenderParams& params,
239  const Size index,
240  const Vector& hit,
241  const Vector& dir) const {
242  Rgba diffuse = Rgba::white();
243  if (!cached.textures.empty() && !cached.uvws.empty()) {
244  Size textureIdx = cached.materialIDs[index];
245  SPH_ASSERT(textureIdx <= 10); // just sanity check, increase if necessary
246  if (textureIdx >= cached.textures.size()) {
247  textureIdx = 0;
248  }
249  if (cached.textures[textureIdx]) {
250  const Vector uvw = this->evalUvws(data.neighs, hit);
251  diffuse = cached.textures[textureIdx]->eval(uvw);
252  }
253  }
254 
255  // evaluate color before checking for occlusion as that invalidates the neighbour list
256  const Rgba colorizerValue =
257  fixed.renderSpheres ? cached.colors[index] : this->evalColor(data.neighs, hit);
258 
259  Rgba emission = Rgba::black();
260  if (cached.doEmission) {
261  emission = colorizerValue * params.surface.emission;
262  } else {
263  diffuse = diffuse * colorizerValue;
264  }
265 
266  // compute normal = gradient of the field
267  const Vector n = fixed.renderSpheres ? cached.r[index] - hit : this->evalGradient(data.neighs, hit);
268  SPH_ASSERT(n != Vector(0._f));
269  const Vector n_norm = getNormalized(n);
270  const Float cosPhi = dot(n_norm, fixed.dirToSun);
271  if (cosPhi <= 0._f) {
272  // not illuminated -> just ambient light + emission
273  return diffuse * params.surface.ambientLight + emission;
274  }
275 
276  // check for occlusion
277  if (fixed.shadows) {
278  Ray rayToSun(hit - 1.e-3_f * fixed.dirToSun, -fixed.dirToSun);
279  if (this->intersect(data, rayToSun, params.surface.level, true)) {
280  // casted shadow
281  return diffuse * params.surface.ambientLight + emission;
282  }
283  }
284 
285  // evaluate BRDF
286  const Float f = fixed.brdf->transport(n_norm, -dir, fixed.dirToSun);
287 
288  return diffuse * float(PI * f * cosPhi * params.surface.sunLight + params.surface.ambientLight) +
289  emission;
290 }
291 
292 Float RayMarcher::evalField(ArrayView<const Size> neighs, const Vector& pos1) const {
293  SPH_ASSERT(!neighs.empty());
294  Float value = 0._f;
295  for (Size index : neighs) {
296  const Vector& pos2 = cached.r[index];
298  const Float w = kernel.value(pos1 - pos2, pos2[H]);
299  value += cached.v[index] * w;
300  }
301  return value;
302 }
303 
304 Vector RayMarcher::evalGradient(ArrayView<const Size> neighs, const Vector& pos1) const {
305  Vector value(0._f);
306  for (Size index : neighs) {
307  const Vector& pos2 = cached.r[index];
308  const Vector grad = kernel.grad(pos1 - pos2, pos2[H]);
309  value += cached.v[index] * grad;
310  }
311  return value;
312 }
313 
314 Rgba RayMarcher::evalColor(ArrayView<const Size> neighs, const Vector& pos1) const {
315  SPH_ASSERT(!neighs.empty());
316  Rgba color = Rgba::black();
317  float weightSum = 0.f;
318  for (Size index : neighs) {
319  const Vector& pos2 = cached.r[index];
321  const float w = float(kernel.value(pos1 - pos2, pos2[H]) * cached.v[index]);
322  color += cached.colors[index] * w;
323  weightSum += w;
324  }
325  SPH_ASSERT(weightSum != 0._f);
326  return color / weightSum;
327 }
328 
329 constexpr Float SEAM_WIDTH = 0.1_f;
330 
331 Vector RayMarcher::evalUvws(ArrayView<const Size> neighs, const Vector& pos1) const {
332  SPH_ASSERT(!neighs.empty());
333  Vector uvws(0._f);
334  Float weightSum = 0._f;
335  int seamFlag = 0;
336  for (Size index : neighs) {
337  const Vector& pos2 = cached.r[index];
338  const Float weight = kernel.value(pos1 - pos2, pos2[H]) * cached.v[index];
339  uvws += cached.uvws[index] * weight;
340  weightSum += weight;
341  seamFlag |= cached.uvws[index][X] < SEAM_WIDTH ? 0x01 : 0;
342  seamFlag |= cached.uvws[index][X] > 1._f - SEAM_WIDTH ? 0x02 : 0;
343  }
344  if (seamFlag & 0x03) {
345  // we are near seam in u-coordinate, we cannot interpolate the UVWs directly
346  uvws = Vector(0._f);
347  weightSum = 0._f;
348  for (Size index : neighs) {
349  const Vector& pos2 = cached.r[index];
351  const Float weight = kernel.value(pos1 - pos2, pos2[H]) * cached.v[index];
352  Vector uvw = cached.uvws[index];
353  // if near the seam, subtract 1 to make the u-mapping continuous
354  uvw[X] -= (uvw[X] > 0.5_f) ? 1._f : 0._f;
355  uvws += uvw * weight;
356  weightSum += weight;
357  }
358  SPH_ASSERT(weightSum != 0._f);
359  uvws /= weightSum;
360  uvws[X] += (uvws[X] < 0._f) ? 1._f : 0._f;
361  return uvws;
362  } else {
363  SPH_ASSERT(weightSum != 0._f);
364  return uvws / weightSum;
365  }
366 }
367 
INLINE bool almostEqual(const AffineMatrix &m1, const AffineMatrix &m2, const Float eps=EPS)
Definition: AffineMatrix.h:278
#define SPH_ASSERT(x,...)
Definition: Assert.h:94
NAMESPACE_SPH_BEGIN
Definition: BarnesHut.cpp:13
Wrapper of wxBitmap, will be possibly replaced by custom implementation.
Defines projection transforming 3D particles onto 2D screen.
Object converting quantity values of particles into colors.
Key-value associative container implemented as a sorted array.
uint32_t Size
Integral type used to index arrays (by default).
Definition: Globals.h:16
double Float
Precision used withing the code. Use Float instead of float or double where precision is important.
Definition: Globals.h:13
INLINE Float weight(const Vector &r1, const Vector &r2)
constexpr INLINE Float sphereVolume(const Float radius)
Computes a volume of a sphere given its radius.
Definition: MathUtils.h:375
constexpr Float INFTY
Definition: MathUtils.h:38
constexpr Float PI
Mathematical constants.
Definition: MathUtils.h:361
#define UNUSED(x)
Definition: Object.h:37
#define NAMESPACE_SPH_END
Definition: Object.h:12
const NothingType NOTHING
Definition: Optional.h:16
Helper iterators allowing to save values to containers.
BackInserter< TContainer > backInserter(TContainer &c)
Tool to measure time spent in functions and profile the code.
#define MEASURE_SCOPE(name)
Definition: Profiler.h:70
@ FLAG
ID of original body, used to implement discontinuities between bodies in SPH.
@ POSITION
Positions (velocities, accelerations) of particles, always a vector quantity,.
@ DENSITY
Density, always a scalar quantity.
@ MASS
Paricles masses, always a scalar quantity.
@ STRESS_REDUCING
Total stress reduction factor due to damage and yielding. Is always scalar.
@ UVW
Texture mapping coordinates,.
constexpr Size BLEND_ALL_FLAG
Definition: RayTracer.cpp:24
constexpr Float SEAM_WIDTH
Definition: RayTracer.cpp:329
StaticArray< T0 &, sizeof...(TArgs)+1 > tie(T0 &t0, TArgs &... rest)
Creates a static array from a list of l-value references.
Definition: StaticArray.h:281
@ BILINEAR
Definition: Texture.h:12
INLINE Float getSqrLength(const Vector &v)
Definition: Vector.h:574
BasicVector< Float > Vector
Definition: Vector.h:539
INLINE float dot(const BasicVector< float > &v1, const BasicVector< float > &v2)
Make sure the vector is trivially constructible and destructible, needed for fast initialization of a...
Definition: Vector.h:548
INLINE Vector getNormalized(const Vector &v)
Definition: Vector.h:590
@ H
Definition: Vector.h:25
@ X
Definition: Vector.h:22
INLINE bool empty() const
Definition: ArrayView.h:105
void reserve(const TCounter newMaxSize)
Allocates enough memory to store the given number of elements.
Definition: Array.h:279
StorageType & emplaceBack(TArgs &&... args)
Constructs a new element at the end of the array in place, using the provided arguments.
Definition: Array.h:332
Trait for finding intersections with a sphere.
Definition: Bvh.h:122
void build(Array< TBvhObject > &&objects)
Contructs the BVH from given set of objects.
Definition: Bvh.inl.h:151
Size getAllIntersections(const Ray &ray, TOutIter iter) const
Returns all intersections of the ray.
Definition: Bvh.inl.h:129
Cubic spline (M4) kernel.
Definition: Kernel.h:150
Container of key-value pairs.
Definition: FlatMap.h:19
INLINE bool contains(const TKey &key) const
Returns true if the map contains element of given key.
Definition: FlatMap.h:140
INLINE TValue & insert(const TKey &key, const TValue &value)
Adds a new element into the map or sets new value of element with the same key.
Definition: FlatMap.h:65
INLINE TValue get(const GuiSettingsId id) const
Definition: Settings.h:245
void build(IScheduler &scheduler, ArrayView< const Vector > points)
Constructs the struct with an array of vectors.
virtual Size findAll(const Size index, const Float radius, Array< NeighbourRecord > &neighbours) const =0
Finds all neighbours within given radius from the point given by index.
Interface defining a camera or view, used by a renderer.
Definition: Camera.h:62
Interface for objects assigning colors to particles.
Definition: Colorizer.h:34
virtual Rgba evalColor(const Size idx) const =0
Returns the color of idx-th particle.
virtual Optional< float > evalScalar(const Size UNUSED(idx)) const
Returns the scalar representation of the colorized quantity for idx-th particle.
Definition: Colorizer.h:57
Base class for renderers based on raytracing.
Definition: IRenderer.h:196
Rgba getEnviroColor(const CameraRay &ray) const
Definition: IRenderer.cpp:149
ThreadLocal< ThreadData > threadData
Definition: IRenderer.h:210
SharedPtr< IScheduler > scheduler
Definition: IRenderer.h:198
Rgba color
Definition: IRenderer.h:225
std::atomic_bool shouldContinue
Definition: IRenderer.h:212
INLINE Float value(const Vector &r, const Float h) const noexcept
Definition: Kernel.h:26
INLINE Vector grad(const Vector &r, const Float h) const noexcept
Definition: Kernel.h:32
INLINE Float radius() const noexcept
Definition: Kernel.h:107
Non-owning wrapper of a material and particles with this material.
Definition: IMaterial.h:30
INLINE IndexSequence sequence()
Returns iterable index sequence.
Definition: IMaterial.h:75
Wrapper of type value of which may or may not be present.
Definition: Optional.h:23
INLINE Type & value()
Returns the reference to the stored value.
Definition: Optional.h:172
Object representing a path on a filesystem.
Definition: Path.h:17
Array< Vector > uvws
Mapping coordinates. May be empty.
Definition: RayTracer.h:74
virtual void initialize(const Storage &storage, const IColorizer &colorizer, const ICamera &camera) override
Prepares the objects for rendering and updates its data.
Definition: RayTracer.cpp:26
RayMarcher(SharedPtr< IScheduler > scheduler, const GuiSettings &settings)
Definition: RayTracer.cpp:13
virtual bool isInitialized() const override
Checks if the renderer has been initialized.
Definition: RayTracer.cpp:126
~RayMarcher() override
Definition: Bvh.h:10
const Vector & direction() const
Definition: Bvh.h:35
const Vector & origin() const
Definition: Bvh.h:31
Definition: Color.h:8
static Rgba black()
Definition: Color.h:190
static Rgba white()
Definition: Color.h:194
static const Settings & getDefaults()
\brief Returns a reference to object containing default values of all settings.
Container storing all quantities used within the simulations.
Definition: Storage.h:230
Size getMaterialCnt() const
Return the number of materials in the storage.
Definition: Storage.cpp:437
auto getValues(const QuantityId first, const QuantityId second, const TArgs... others)
Retrieves an array of quantities from the key.
Definition: Storage.h:359
MaterialView getMaterial(const Size matIdx) const
Returns an object containing a reference to given material.
Definition: Storage.cpp:366
bool has(const QuantityId key) const
Checks if the storage contains quantity with given key.
Definition: Storage.cpp:130
Array< TValue > & getValue(const QuantityId key)
Retrieves a quantity values from the storage, given its key and value type.
Definition: Storage.cpp:191
@ DENSITY
Density at zero pressure.
@ SURFACE_SUN_POSITION
Direction to the sun used for shading.
AutoPtr< IBrdf > getBrdf(const GuiSettings &settings)
Definition: Factory.cpp:100
AutoPtr< ISymmetricFinder > getFinder(const RunSettings &settings)
Definition: Factory.cpp:156
Size userData
Generic user data, can be used to store additional information to the primitives.
Definition: Bvh.h:49
Ray given by origin and target point.
Definition: Camera.h:56
Vector origin
Definition: Camera.h:57
Vector target
Definition: Camera.h:58
Holds intormation about intersection.
Definition: Bvh.h:53
Float t
Distance of the hit in units of ray.direction().
Definition: Bvh.h:55
const BvhPrimitive * object
Object hit by the ray, or nullptr if nothing has been hit.
Definition: Bvh.h:58
Holds information about a neighbour particles.
Parameters of the rendered image.
Definition: IRenderer.h:60
float sunLight
Intensity of the sunlight.
Definition: IRenderer.h:124
float level
Value of the iso-surface defining the rendered surface.
Definition: IRenderer.h:118
struct RenderParams::@35 surface
Parameters of rendered surface.
float emission
Emission multiplier.
Definition: IRenderer.h:127
float ambientLight
Intensity of the ambient light, illuminating every point unconditionally.
Definition: IRenderer.h:121