Line |
Branch |
Exec |
Source |
1 |
|
|
// Copyright Contributors to the OpenVDB Project |
2 |
|
|
// SPDX-License-Identifier: MPL-2.0 |
3 |
|
|
|
4 |
|
|
/// @file RayTracer.h |
5 |
|
|
/// |
6 |
|
|
/// @author Ken Museth |
7 |
|
|
/// |
8 |
|
|
/// @brief Defines two simple but multithreaded renders, a level-set |
9 |
|
|
/// ray tracer and a volume render. To support these renders we also define |
10 |
|
|
/// perspective and orthographic cameras (both designed to mimic a Houdini camera), |
11 |
|
|
/// a Film class and some rather naive shaders. |
12 |
|
|
/// |
13 |
|
|
/// @note These classes are included mainly as reference implementations for |
14 |
|
|
/// ray-tracing of OpenVDB volumes. In other words they are not intended for |
15 |
|
|
/// production-quality rendering, but could be used for fast pre-visualization |
16 |
|
|
/// or as a starting point for a more serious render. |
17 |
|
|
|
18 |
|
|
#ifndef OPENVDB_TOOLS_RAYTRACER_HAS_BEEN_INCLUDED |
19 |
|
|
#define OPENVDB_TOOLS_RAYTRACER_HAS_BEEN_INCLUDED |
20 |
|
|
|
21 |
|
|
#include <openvdb/Types.h> |
22 |
|
|
#include <openvdb/math/BBox.h> |
23 |
|
|
#include <openvdb/math/Ray.h> |
24 |
|
|
#include <openvdb/math/Math.h> |
25 |
|
|
#include <openvdb/tools/RayIntersector.h> |
26 |
|
|
#include <openvdb/tools/Interpolation.h> |
27 |
|
|
#include <openvdb/openvdb.h> |
28 |
|
|
#include <deque> |
29 |
|
|
#include <iostream> |
30 |
|
|
#include <fstream> |
31 |
|
|
#include <limits> |
32 |
|
|
#include <memory> |
33 |
|
|
#include <string> |
34 |
|
|
#include <type_traits> |
35 |
|
|
#include <vector> |
36 |
|
|
|
37 |
|
|
namespace openvdb { |
38 |
|
|
OPENVDB_USE_VERSION_NAMESPACE |
39 |
|
|
namespace OPENVDB_VERSION_NAME { |
40 |
|
|
namespace tools { |
41 |
|
|
|
42 |
|
|
// Forward declarations |
43 |
|
|
class BaseCamera; |
44 |
|
|
class BaseShader; |
45 |
|
|
|
46 |
|
|
/// @brief Ray-trace a volume. |
47 |
|
|
template<typename GridT> |
48 |
|
|
void rayTrace(const GridT&, |
49 |
|
|
const BaseShader&, |
50 |
|
|
BaseCamera&, |
51 |
|
|
size_t pixelSamples = 1, |
52 |
|
|
unsigned int seed = 0, |
53 |
|
|
bool threaded = true); |
54 |
|
|
|
55 |
|
|
/// @brief Ray-trace a volume using a given ray intersector. |
56 |
|
|
template<typename GridT, typename IntersectorT> |
57 |
|
|
void rayTrace(const GridT&, |
58 |
|
|
const IntersectorT&, |
59 |
|
|
const BaseShader&, |
60 |
|
|
BaseCamera&, |
61 |
|
|
size_t pixelSamples = 1, |
62 |
|
|
unsigned int seed = 0, |
63 |
|
|
bool threaded = true); |
64 |
|
|
|
65 |
|
|
|
66 |
|
|
///////////////////////////////LEVEL SET RAY TRACER /////////////////////////////////////// |
67 |
|
|
|
68 |
|
|
/// @brief A (very) simple multithreaded ray tracer specifically for narrow-band level sets. |
69 |
|
|
/// @details Included primarily as a reference implementation. |
70 |
|
|
template<typename GridT, typename IntersectorT = tools::LevelSetRayIntersector<GridT> > |
71 |
|
|
class LevelSetRayTracer |
72 |
|
|
{ |
73 |
|
|
public: |
74 |
|
|
using GridType = GridT; |
75 |
|
|
using Vec3Type = typename IntersectorT::Vec3Type; |
76 |
|
|
using RayType = typename IntersectorT::RayType; |
77 |
|
|
|
78 |
|
|
/// @brief Constructor based on an instance of the grid to be rendered. |
79 |
|
|
LevelSetRayTracer(const GridT& grid, |
80 |
|
|
const BaseShader& shader, |
81 |
|
|
BaseCamera& camera, |
82 |
|
|
size_t pixelSamples = 1, |
83 |
|
|
unsigned int seed = 0); |
84 |
|
|
|
85 |
|
|
/// @brief Constructor based on an instance of the intersector |
86 |
|
|
/// performing the ray-intersections. |
87 |
|
|
LevelSetRayTracer(const IntersectorT& inter, |
88 |
|
|
const BaseShader& shader, |
89 |
|
|
BaseCamera& camera, |
90 |
|
|
size_t pixelSamples = 1, |
91 |
|
|
unsigned int seed = 0); |
92 |
|
|
|
93 |
|
|
/// @brief Copy constructor |
94 |
|
|
LevelSetRayTracer(const LevelSetRayTracer& other); |
95 |
|
|
|
96 |
|
|
/// @brief Destructor |
97 |
|
|
~LevelSetRayTracer(); |
98 |
|
|
|
99 |
|
|
/// @brief Set the level set grid to be ray-traced |
100 |
|
|
void setGrid(const GridT& grid); |
101 |
|
|
|
102 |
|
|
/// @brief Set the intersector that performs the actual |
103 |
|
|
/// intersection of the rays against the narrow-band level set. |
104 |
|
|
void setIntersector(const IntersectorT& inter); |
105 |
|
|
|
106 |
|
|
/// @brief Set the shader derived from the abstract BaseShader class. |
107 |
|
|
/// |
108 |
|
|
/// @note The shader is not assumed to be thread-safe so each |
109 |
|
|
/// thread will get its only deep copy. For instance it could |
110 |
|
|
/// contains a ValueAccessor into another grid with auxiliary |
111 |
|
|
/// shading information. Thus, make sure it is relatively |
112 |
|
|
/// light-weight and efficient to copy (which is the case for ValueAccesors). |
113 |
|
|
void setShader(const BaseShader& shader); |
114 |
|
|
|
115 |
|
|
/// @brief Set the camera derived from the abstract BaseCamera class. |
116 |
|
|
void setCamera(BaseCamera& camera); |
117 |
|
|
|
118 |
|
|
/// @brief Set the number of pixel samples and the seed for |
119 |
|
|
/// jittered sub-rays. A value larger than one implies |
120 |
|
|
/// anti-aliasing by jittered super-sampling. |
121 |
|
|
/// @throw ValueError if pixelSamples is equal to zero. |
122 |
|
|
void setPixelSamples(size_t pixelSamples, unsigned int seed = 0); |
123 |
|
|
|
124 |
|
|
/// @brief Perform the actual (potentially multithreaded) ray-tracing. |
125 |
|
|
void render(bool threaded = true) const; |
126 |
|
|
|
127 |
|
|
/// @brief Public method required by tbb::parallel_for. |
128 |
|
|
/// @warning Never call it directly. |
129 |
|
|
void operator()(const tbb::blocked_range<size_t>& range) const; |
130 |
|
|
|
131 |
|
|
private: |
132 |
|
|
const bool mIsMaster; |
133 |
|
|
double* mRand; |
134 |
|
|
IntersectorT mInter; |
135 |
|
|
std::unique_ptr<const BaseShader> mShader; |
136 |
|
|
BaseCamera* mCamera; |
137 |
|
|
size_t mSubPixels; |
138 |
|
|
};// LevelSetRayTracer |
139 |
|
|
|
140 |
|
|
|
141 |
|
|
///////////////////////////////VOLUME RENDER /////////////////////////////////////// |
142 |
|
|
|
143 |
|
|
/// @brief A (very) simple multithreaded volume render specifically for scalar density. |
144 |
|
|
/// @details Included primarily as a reference implementation. |
145 |
|
|
/// @note It will only compile if the IntersectorT is templated on a Grid with a |
146 |
|
|
/// floating-point voxel type. |
147 |
|
|
template <typename IntersectorT, typename SamplerT = tools::BoxSampler> |
148 |
|
|
class VolumeRender |
149 |
|
|
{ |
150 |
|
|
public: |
151 |
|
|
|
152 |
|
|
using GridType = typename IntersectorT::GridType; |
153 |
|
|
using RayType = typename IntersectorT::RayType; |
154 |
|
|
using ValueType = typename GridType::ValueType; |
155 |
|
|
using AccessorType = typename GridType::ConstAccessor; |
156 |
|
|
using SamplerType = tools::GridSampler<AccessorType, SamplerT>; |
157 |
|
|
static_assert(std::is_floating_point<ValueType>::value, |
158 |
|
|
"VolumeRender requires a floating-point-valued grid"); |
159 |
|
|
|
160 |
|
|
/// @brief Constructor taking an intersector and a base camera. |
161 |
|
|
VolumeRender(const IntersectorT& inter, BaseCamera& camera); |
162 |
|
|
|
163 |
|
|
/// @brief Copy constructor which creates a thread-safe clone |
164 |
|
|
VolumeRender(const VolumeRender& other); |
165 |
|
|
|
166 |
|
|
/// @brief Perform the actual (potentially multithreaded) volume rendering. |
167 |
|
|
void render(bool threaded=true) const; |
168 |
|
|
|
169 |
|
|
/// @brief Set the camera derived from the abstract BaseCamera class. |
170 |
|
✗ |
void setCamera(BaseCamera& camera) { mCamera = &camera; } |
171 |
|
|
|
172 |
|
|
/// @brief Set the intersector that performs the actual |
173 |
|
|
/// intersection of the rays against the volume. |
174 |
|
|
void setIntersector(const IntersectorT& inter); |
175 |
|
|
|
176 |
|
|
/// @brief Set the vector components of a directional light source |
177 |
|
|
/// @throw ArithmeticError if input is a null vector. |
178 |
|
✗ |
void setLightDir(Real x, Real y, Real z) { mLightDir = Vec3R(x,y,z).unit(); } |
179 |
|
|
|
180 |
|
|
/// @brief Set the color of the directional light source. |
181 |
|
✗ |
void setLightColor(Real r, Real g, Real b) { mLightColor = Vec3R(r,g,b); } |
182 |
|
|
|
183 |
|
|
/// @brief Set the integration step-size in voxel units for the primay ray. |
184 |
|
✗ |
void setPrimaryStep(Real primaryStep) { mPrimaryStep = primaryStep; } |
185 |
|
|
|
186 |
|
|
/// @brief Set the integration step-size in voxel units for the primay ray. |
187 |
|
✗ |
void setShadowStep(Real shadowStep) { mShadowStep = shadowStep; } |
188 |
|
|
|
189 |
|
|
/// @brief Set Scattering coefficients. |
190 |
|
✗ |
void setScattering(Real x, Real y, Real z) { mScattering = Vec3R(x,y,z); } |
191 |
|
|
|
192 |
|
|
/// @brief Set absorption coefficients. |
193 |
|
✗ |
void setAbsorption(Real x, Real y, Real z) { mAbsorption = Vec3R(x,y,z); } |
194 |
|
|
|
195 |
|
|
/// @brief Set parameter that imitates multi-scattering. A value |
196 |
|
|
/// of zero implies no multi-scattering. |
197 |
|
✗ |
void setLightGain(Real gain) { mLightGain = gain; } |
198 |
|
|
|
199 |
|
|
/// @brief Set the cut-off value for density and transmittance. |
200 |
|
✗ |
void setCutOff(Real cutOff) { mCutOff = cutOff; } |
201 |
|
|
|
202 |
|
|
/// @brief Print parameters, statistics, memory usage and other information. |
203 |
|
|
/// @param os a stream to which to write textual information |
204 |
|
|
/// @param verboseLevel 1: print parameters only; 2: include grid |
205 |
|
|
/// statistics; 3: include memory usage |
206 |
|
|
void print(std::ostream& os = std::cout, int verboseLevel = 1); |
207 |
|
|
|
208 |
|
|
/// @brief Public method required by tbb::parallel_for. |
209 |
|
|
/// @warning Never call it directly. |
210 |
|
|
void operator()(const tbb::blocked_range<size_t>& range) const; |
211 |
|
|
|
212 |
|
|
private: |
213 |
|
|
|
214 |
|
|
AccessorType mAccessor; |
215 |
|
|
BaseCamera* mCamera; |
216 |
|
|
std::unique_ptr<IntersectorT> mPrimary, mShadow; |
217 |
|
|
Real mPrimaryStep, mShadowStep, mCutOff, mLightGain; |
218 |
|
|
Vec3R mLightDir, mLightColor, mAbsorption, mScattering; |
219 |
|
|
};//VolumeRender |
220 |
|
|
|
221 |
|
|
//////////////////////////////////////// FILM //////////////////////////////////////// |
222 |
|
|
|
223 |
|
|
/// @brief A simple class that allows for concurrent writes to pixels in an image, |
224 |
|
|
/// background initialization of the image, and PPM file output. |
225 |
|
|
class Film |
226 |
|
|
{ |
227 |
|
|
public: |
228 |
|
|
/// @brief Floating-point RGBA components in the range [0, 1]. |
229 |
|
|
/// @details This is our preferred representation for color processing. |
230 |
|
|
struct RGBA |
231 |
|
|
{ |
232 |
|
|
using ValueT = float; |
233 |
|
|
|
234 |
|
|
RGBA() : r(0), g(0), b(0), a(1) {} |
235 |
|
|
explicit RGBA(ValueT intensity) : r(intensity), g(intensity), b(intensity), a(1) {} |
236 |
|
✗ |
RGBA(ValueT _r, ValueT _g, ValueT _b, ValueT _a = static_cast<ValueT>(1.0)): |
237 |
|
✗ |
r(_r), g(_g), b(_b), a(_a) |
238 |
|
|
{} |
239 |
|
|
RGBA(double _r, double _g, double _b, double _a = 1.0) |
240 |
|
|
: r(static_cast<ValueT>(_r)) |
241 |
|
|
, g(static_cast<ValueT>(_g)) |
242 |
|
|
, b(static_cast<ValueT>(_b)) |
243 |
|
|
, a(static_cast<ValueT>(_a)) |
244 |
|
|
{} |
245 |
|
|
|
246 |
|
✗ |
RGBA operator* (ValueT scale) const { return RGBA(r*scale, g*scale, b*scale);} |
247 |
|
|
RGBA operator+ (const RGBA& rhs) const { return RGBA(r+rhs.r, g+rhs.g, b+rhs.b);} |
248 |
|
|
RGBA operator* (const RGBA& rhs) const { return RGBA(r*rhs.r, g*rhs.g, b*rhs.b);} |
249 |
|
✗ |
RGBA& operator+=(const RGBA& rhs) { r+=rhs.r; g+=rhs.g; b+=rhs.b; a+=rhs.a; return *this;} |
250 |
|
|
|
251 |
|
|
void over(const RGBA& rhs) |
252 |
|
|
{ |
253 |
|
|
const float s = rhs.a*(1.0f-a); |
254 |
|
|
r = a*r+s*rhs.r; |
255 |
|
|
g = a*g+s*rhs.g; |
256 |
|
|
b = a*b+s*rhs.b; |
257 |
|
|
a = a + s; |
258 |
|
|
} |
259 |
|
|
|
260 |
|
|
ValueT r, g, b, a; |
261 |
|
|
}; |
262 |
|
|
|
263 |
|
|
|
264 |
|
|
Film(size_t width, size_t height) |
265 |
|
|
: mWidth(width), mHeight(height), mSize(width*height), mPixels(new RGBA[mSize]) |
266 |
|
|
{ |
267 |
|
|
} |
268 |
|
|
Film(size_t width, size_t height, const RGBA& bg) |
269 |
|
|
: mWidth(width), mHeight(height), mSize(width*height), mPixels(new RGBA[mSize]) |
270 |
|
|
{ |
271 |
|
|
this->fill(bg); |
272 |
|
|
} |
273 |
|
|
|
274 |
|
|
const RGBA& pixel(size_t w, size_t h) const |
275 |
|
|
{ |
276 |
|
|
assert(w < mWidth); |
277 |
|
|
assert(h < mHeight); |
278 |
|
|
return mPixels[w + h*mWidth]; |
279 |
|
|
} |
280 |
|
|
|
281 |
|
✗ |
RGBA& pixel(size_t w, size_t h) |
282 |
|
|
{ |
283 |
|
✗ |
assert(w < mWidth); |
284 |
|
✗ |
assert(h < mHeight); |
285 |
|
✗ |
return mPixels[w + h*mWidth]; |
286 |
|
|
} |
287 |
|
|
|
288 |
|
|
void fill(const RGBA& rgb=RGBA(0)) { for (size_t i=0; i<mSize; ++i) mPixels[i] = rgb; } |
289 |
|
|
void checkerboard(const RGBA& c1=RGBA(0.3f), const RGBA& c2=RGBA(0.6f), size_t size=32) |
290 |
|
|
{ |
291 |
|
|
RGBA *p = mPixels.get(); |
292 |
|
|
for (size_t j = 0; j < mHeight; ++j) { |
293 |
|
|
for (size_t i = 0; i < mWidth; ++i, ++p) { |
294 |
|
|
*p = ((i & size) ^ (j & size)) ? c1 : c2; |
295 |
|
|
} |
296 |
|
|
} |
297 |
|
|
} |
298 |
|
|
|
299 |
|
|
template <typename Type = unsigned char> |
300 |
|
|
std::unique_ptr<Type[]> convertToBitBuffer(const bool alpha = true) const |
301 |
|
|
{ |
302 |
|
|
const size_t totalSize = mSize * (alpha ? 4 : 3); |
303 |
|
|
std::unique_ptr<Type[]> buffer(new Type[totalSize]); |
304 |
|
|
Type *q = buffer.get(); |
305 |
|
|
const RGBA* p = this->pixels(); |
306 |
|
|
size_t n = mSize; |
307 |
|
|
while (n--) { |
308 |
|
|
*q++ = static_cast<Type>(255.0f*(*p).r); |
309 |
|
|
*q++ = static_cast<Type>(255.0f*(*p).g); |
310 |
|
|
*q++ = static_cast<Type>(255.0f*(*p).b); |
311 |
|
|
if(alpha) |
312 |
|
|
*q++ = static_cast<Type>(255.0f*(*p).a); |
313 |
|
|
++p; |
314 |
|
|
} |
315 |
|
|
return buffer; |
316 |
|
|
} |
317 |
|
|
|
318 |
|
|
void savePPM(const std::string& fileName) |
319 |
|
|
{ |
320 |
|
|
std::string name(fileName); |
321 |
|
|
if (name.find_last_of(".") == std::string::npos) name.append(".ppm"); |
322 |
|
|
|
323 |
|
|
std::ofstream os(name.c_str(), std::ios_base::binary); |
324 |
|
|
if (!os.is_open()) { |
325 |
|
|
std::cerr << "Error opening PPM file \"" << name << "\"" << std::endl; |
326 |
|
|
return; |
327 |
|
|
} |
328 |
|
|
|
329 |
|
|
auto buf = this->convertToBitBuffer<unsigned char>(/*alpha=*/false); |
330 |
|
|
unsigned char* tmp = buf.get(); |
331 |
|
|
|
332 |
|
|
os << "P6\n" << mWidth << " " << mHeight << "\n255\n"; |
333 |
|
|
os.write(reinterpret_cast<const char*>(&(*tmp)), 3 * mSize * sizeof(unsigned char)); |
334 |
|
|
} |
335 |
|
|
|
336 |
|
✗ |
size_t width() const { return mWidth; } |
337 |
|
✗ |
size_t height() const { return mHeight; } |
338 |
|
|
size_t numPixels() const { return mSize; } |
339 |
|
|
const RGBA* pixels() const { return mPixels.get(); } |
340 |
|
|
|
341 |
|
|
private: |
342 |
|
|
size_t mWidth, mHeight, mSize; |
343 |
|
|
std::unique_ptr<RGBA[]> mPixels; |
344 |
|
|
};// Film |
345 |
|
|
|
346 |
|
|
|
347 |
|
|
//////////////////////////////////////// CAMERAS //////////////////////////////////////// |
348 |
|
|
|
349 |
|
|
/// Abstract base class for the perspective and orthographic cameras |
350 |
|
|
class BaseCamera |
351 |
|
|
{ |
352 |
|
|
public: |
353 |
|
|
BaseCamera(Film& film, const Vec3R& rotation, const Vec3R& translation, |
354 |
|
|
double frameWidth, double nearPlane, double farPlane) |
355 |
|
|
: mFilm(&film) |
356 |
|
|
, mScaleWidth(frameWidth) |
357 |
|
|
, mScaleHeight(frameWidth * double(film.height()) / double(film.width())) |
358 |
|
|
{ |
359 |
|
|
assert(nearPlane > 0 && farPlane > nearPlane); |
360 |
|
|
mScreenToWorld.accumPostRotation(math::X_AXIS, rotation[0] * M_PI / 180.0); |
361 |
|
|
mScreenToWorld.accumPostRotation(math::Y_AXIS, rotation[1] * M_PI / 180.0); |
362 |
|
|
mScreenToWorld.accumPostRotation(math::Z_AXIS, rotation[2] * M_PI / 180.0); |
363 |
|
|
mScreenToWorld.accumPostTranslation(translation); |
364 |
|
|
this->initRay(nearPlane, farPlane); |
365 |
|
|
} |
366 |
|
|
|
367 |
|
|
virtual ~BaseCamera() {} |
368 |
|
|
|
369 |
|
✗ |
Film::RGBA& pixel(size_t i, size_t j) { return mFilm->pixel(i, j); } |
370 |
|
|
|
371 |
|
✗ |
size_t width() const { return mFilm->width(); } |
372 |
|
✗ |
size_t height() const { return mFilm->height(); } |
373 |
|
|
|
374 |
|
|
/// Rotate the camera so its negative z-axis points at xyz and its |
375 |
|
|
/// y axis is in the plane of the xyz and up vectors. In other |
376 |
|
|
/// words the camera will look at xyz and use up as the |
377 |
|
|
/// horizontal direction. |
378 |
|
|
void lookAt(const Vec3R& xyz, const Vec3R& up = Vec3R(0.0, 1.0, 0.0)) |
379 |
|
|
{ |
380 |
|
|
const Vec3R orig = mScreenToWorld.applyMap(Vec3R(0.0)); |
381 |
|
|
const Vec3R dir = orig - xyz; |
382 |
|
|
try { |
383 |
|
|
Mat4d xform = math::aim<Mat4d>(dir, up); |
384 |
|
|
xform.postTranslate(orig); |
385 |
|
|
mScreenToWorld = math::AffineMap(xform); |
386 |
|
|
this->initRay(mRay.t0(), mRay.t1()); |
387 |
|
|
} catch (...) {} |
388 |
|
|
} |
389 |
|
|
|
390 |
|
|
Vec3R rasterToScreen(double i, double j, double z) const |
391 |
|
|
{ |
392 |
|
|
return Vec3R( (2 * i / double(mFilm->width()) - 1) * mScaleWidth, |
393 |
|
|
(1 - 2 * j / double(mFilm->height())) * mScaleHeight, z ); |
394 |
|
|
} |
395 |
|
|
|
396 |
|
|
/// @brief Return a Ray in world space given the pixel indices and |
397 |
|
|
/// optional offsets in the range [0, 1]. An offset of 0.5 corresponds |
398 |
|
|
/// to the center of the pixel. |
399 |
|
|
virtual math::Ray<double> getRay( |
400 |
|
|
size_t i, size_t j, double iOffset = 0.5, double jOffset = 0.5) const = 0; |
401 |
|
|
|
402 |
|
|
protected: |
403 |
|
|
void initRay(double t0, double t1) |
404 |
|
|
{ |
405 |
|
|
mRay.setTimes(t0, t1); |
406 |
|
|
mRay.setEye(mScreenToWorld.applyMap(Vec3R(0.0))); |
407 |
|
|
mRay.setDir(mScreenToWorld.applyJacobian(Vec3R(0.0, 0.0, -1.0))); |
408 |
|
|
} |
409 |
|
|
|
410 |
|
|
Film* mFilm; |
411 |
|
|
double mScaleWidth, mScaleHeight; |
412 |
|
|
math::Ray<double> mRay; |
413 |
|
|
math::AffineMap mScreenToWorld; |
414 |
|
|
};// BaseCamera |
415 |
|
|
|
416 |
|
|
|
417 |
|
|
class PerspectiveCamera: public BaseCamera |
418 |
|
|
{ |
419 |
|
|
public: |
420 |
|
|
/// @brief Constructor |
421 |
|
|
/// @param film film (i.e. image) defining the pixel resolution |
422 |
|
|
/// @param rotation rotation in degrees of the camera in world space |
423 |
|
|
/// (applied in x, y, z order) |
424 |
|
|
/// @param translation translation of the camera in world-space units, |
425 |
|
|
/// applied after rotation |
426 |
|
|
/// @param focalLength focal length of the camera in mm |
427 |
|
|
/// (the default of 50mm corresponds to Houdini's default camera) |
428 |
|
|
/// @param aperture width in mm of the frame, i.e., the visible field |
429 |
|
|
/// (the default 41.2136 mm corresponds to Houdini's default camera) |
430 |
|
|
/// @param nearPlane depth of the near clipping plane in world-space units |
431 |
|
|
/// @param farPlane depth of the far clipping plane in world-space units |
432 |
|
|
/// |
433 |
|
|
/// @details If no rotation or translation is provided, the camera is placed |
434 |
|
|
/// at (0,0,0) in world space and points in the direction of the negative z axis. |
435 |
|
|
PerspectiveCamera(Film& film, |
436 |
|
|
const Vec3R& rotation = Vec3R(0.0), |
437 |
|
|
const Vec3R& translation = Vec3R(0.0), |
438 |
|
|
double focalLength = 50.0, |
439 |
|
|
double aperture = 41.2136, |
440 |
|
|
double nearPlane = 1e-3, |
441 |
|
|
double farPlane = std::numeric_limits<double>::max()) |
442 |
|
|
: BaseCamera(film, rotation, translation, 0.5*aperture/focalLength, nearPlane, farPlane) |
443 |
|
|
{ |
444 |
|
|
} |
445 |
|
|
|
446 |
|
|
~PerspectiveCamera() override = default; |
447 |
|
|
|
448 |
|
|
/// @brief Return a Ray in world space given the pixel indices and |
449 |
|
|
/// optional offsets in the range [0,1]. An offset of 0.5 corresponds |
450 |
|
|
/// to the center of the pixel. |
451 |
|
|
math::Ray<double> getRay( |
452 |
|
|
size_t i, size_t j, double iOffset = 0.5, double jOffset = 0.5) const override |
453 |
|
|
{ |
454 |
|
|
math::Ray<double> ray(mRay); |
455 |
|
|
Vec3R dir = BaseCamera::rasterToScreen(Real(i) + iOffset, Real(j) + jOffset, -1.0); |
456 |
|
|
dir = BaseCamera::mScreenToWorld.applyJacobian(dir); |
457 |
|
|
dir.normalize(); |
458 |
|
|
ray.scaleTimes(1.0/dir.dot(ray.dir())); |
459 |
|
|
ray.setDir(dir); |
460 |
|
|
return ray; |
461 |
|
|
} |
462 |
|
|
|
463 |
|
|
/// @brief Return the horizontal field of view in degrees given a |
464 |
|
|
/// focal lenth in mm and the specified aperture in mm. |
465 |
|
|
static double focalLengthToFieldOfView(double length, double aperture) |
466 |
|
|
{ |
467 |
|
|
return 360.0 / M_PI * atan(aperture/(2.0*length)); |
468 |
|
|
} |
469 |
|
|
/// @brief Return the focal length in mm given a horizontal field of |
470 |
|
|
/// view in degrees and the specified aperture in mm. |
471 |
|
|
static double fieldOfViewToFocalLength(double fov, double aperture) |
472 |
|
|
{ |
473 |
|
|
return aperture/(2.0*(tan(fov * M_PI / 360.0))); |
474 |
|
|
} |
475 |
|
|
};// PerspectiveCamera |
476 |
|
|
|
477 |
|
|
|
478 |
|
|
class OrthographicCamera: public BaseCamera |
479 |
|
|
{ |
480 |
|
|
public: |
481 |
|
|
/// @brief Constructor |
482 |
|
|
/// @param film film (i.e. image) defining the pixel resolution |
483 |
|
|
/// @param rotation rotation in degrees of the camera in world space |
484 |
|
|
/// (applied in x, y, z order) |
485 |
|
|
/// @param translation translation of the camera in world-space units, |
486 |
|
|
/// applied after rotation |
487 |
|
|
/// @param frameWidth width in of the frame in world-space units |
488 |
|
|
/// @param nearPlane depth of the near clipping plane in world-space units |
489 |
|
|
/// @param farPlane depth of the far clipping plane in world-space units |
490 |
|
|
/// |
491 |
|
|
/// @details If no rotation or translation is provided, the camera is placed |
492 |
|
|
/// at (0,0,0) in world space and points in the direction of the negative z axis. |
493 |
|
|
OrthographicCamera(Film& film, |
494 |
|
|
const Vec3R& rotation = Vec3R(0.0), |
495 |
|
|
const Vec3R& translation = Vec3R(0.0), |
496 |
|
|
double frameWidth = 1.0, |
497 |
|
|
double nearPlane = 1e-3, |
498 |
|
|
double farPlane = std::numeric_limits<double>::max()) |
499 |
|
|
: BaseCamera(film, rotation, translation, 0.5*frameWidth, nearPlane, farPlane) |
500 |
|
|
{ |
501 |
|
|
} |
502 |
|
|
~OrthographicCamera() override = default; |
503 |
|
|
|
504 |
|
|
math::Ray<double> getRay( |
505 |
|
|
size_t i, size_t j, double iOffset = 0.5, double jOffset = 0.5) const override |
506 |
|
|
{ |
507 |
|
|
math::Ray<double> ray(mRay); |
508 |
|
|
Vec3R eye = BaseCamera::rasterToScreen(Real(i) + iOffset, Real(j) + jOffset, 0.0); |
509 |
|
|
ray.setEye(BaseCamera::mScreenToWorld.applyMap(eye)); |
510 |
|
|
return ray; |
511 |
|
|
} |
512 |
|
|
};// OrthographicCamera |
513 |
|
|
|
514 |
|
|
|
515 |
|
|
//////////////////////////////////////// SHADERS //////////////////////////////////////// |
516 |
|
|
|
517 |
|
|
|
518 |
|
|
/// Abstract base class for the shaders |
519 |
|
|
class BaseShader |
520 |
|
|
{ |
521 |
|
|
public: |
522 |
|
|
using RayT = math::Ray<Real>; |
523 |
|
|
BaseShader() {} |
524 |
|
|
BaseShader(const BaseShader&) = default; |
525 |
|
|
virtual ~BaseShader() = default; |
526 |
|
|
/// @brief Defines the interface of the virtual function that returns a RGB color. |
527 |
|
|
/// @param xyz World position of the intersection point. |
528 |
|
|
/// @param nml Normal in world space at the intersection point. |
529 |
|
|
/// @param dir Direction of the ray in world space. |
530 |
|
|
virtual Film::RGBA operator()(const Vec3R& xyz, const Vec3R& nml, const Vec3R& dir) const = 0; |
531 |
|
|
virtual BaseShader* copy() const = 0; |
532 |
|
|
}; |
533 |
|
|
|
534 |
|
|
|
535 |
|
|
/// @brief Shader that produces a simple matte. |
536 |
|
|
/// |
537 |
|
|
/// @details The color can either be constant (if GridT = |
538 |
|
|
/// Film::RGBA which is the default) or defined in a separate Vec3 |
539 |
|
|
/// color grid. Use SamplerType to define the order of interpolation |
540 |
|
|
/// (default is zero order, i.e. closes-point). |
541 |
|
|
template<typename GridT = Film::RGBA, |
542 |
|
|
typename SamplerType = tools::PointSampler> |
543 |
|
|
class MatteShader: public BaseShader |
544 |
|
|
{ |
545 |
|
|
public: |
546 |
|
|
MatteShader(const GridT& grid) : mAcc(grid.getAccessor()), mXform(&grid.transform()) {} |
547 |
|
|
MatteShader(const MatteShader&) = default; |
548 |
|
|
~MatteShader() override = default; |
549 |
|
|
Film::RGBA operator()(const Vec3R& xyz, const Vec3R&, const Vec3R&) const override |
550 |
|
|
{ |
551 |
|
|
typename GridT::ValueType v = zeroVal<typename GridT::ValueType>(); |
552 |
|
|
SamplerType::sample(mAcc, mXform->worldToIndex(xyz), v); |
553 |
|
|
return Film::RGBA(v[0], v[1], v[2]); |
554 |
|
|
} |
555 |
|
|
BaseShader* copy() const override { return new MatteShader<GridT, SamplerType>(*this); } |
556 |
|
|
|
557 |
|
|
private: |
558 |
|
|
typename GridT::ConstAccessor mAcc; |
559 |
|
|
const math::Transform* mXform; |
560 |
|
|
}; |
561 |
|
|
|
562 |
|
|
// Template specialization using a constant color of the material. |
563 |
|
|
template<typename SamplerType> |
564 |
|
|
class MatteShader<Film::RGBA, SamplerType>: public BaseShader |
565 |
|
|
{ |
566 |
|
|
public: |
567 |
|
|
MatteShader(const Film::RGBA& c = Film::RGBA(1.0f)): mRGBA(c) {} |
568 |
|
|
MatteShader(const MatteShader&) = default; |
569 |
|
|
~MatteShader() override = default; |
570 |
|
|
Film::RGBA operator()(const Vec3R&, const Vec3R&, const Vec3R&) const override |
571 |
|
|
{ |
572 |
|
|
return mRGBA; |
573 |
|
|
} |
574 |
|
|
BaseShader* copy() const override { return new MatteShader<Film::RGBA, SamplerType>(*this); } |
575 |
|
|
|
576 |
|
|
private: |
577 |
|
|
const Film::RGBA mRGBA; |
578 |
|
|
}; |
579 |
|
|
|
580 |
|
|
|
581 |
|
|
/// @brief Color shader that treats the surface normal (x, y, z) as an |
582 |
|
|
/// RGB color. |
583 |
|
|
/// |
584 |
|
|
/// @details The color can either be constant (if GridT = |
585 |
|
|
/// Film::RGBA which is the default) or defined in a separate Vec3 |
586 |
|
|
/// color grid. Use SamplerType to define the order of interpolation |
587 |
|
|
/// (default is zero order, i.e. closes-point). |
588 |
|
|
template<typename GridT = Film::RGBA, |
589 |
|
|
typename SamplerType = tools::PointSampler> |
590 |
|
|
class NormalShader: public BaseShader |
591 |
|
|
{ |
592 |
|
|
public: |
593 |
|
|
NormalShader(const GridT& grid) : mAcc(grid.getAccessor()), mXform(&grid.transform()) {} |
594 |
|
|
NormalShader(const NormalShader&) = default; |
595 |
|
|
~NormalShader() override = default; |
596 |
|
|
Film::RGBA operator()(const Vec3R& xyz, const Vec3R& normal, const Vec3R&) const override |
597 |
|
|
{ |
598 |
|
|
typename GridT::ValueType v = zeroVal<typename GridT::ValueType>(); |
599 |
|
|
SamplerType::sample(mAcc, mXform->worldToIndex(xyz), v); |
600 |
|
|
return Film::RGBA(v[0]*(normal[0]+1.0), v[1]*(normal[1]+1.0), v[2]*(normal[2]+1.0)); |
601 |
|
|
} |
602 |
|
|
BaseShader* copy() const override { return new NormalShader<GridT, SamplerType>(*this); } |
603 |
|
|
|
604 |
|
|
private: |
605 |
|
|
typename GridT::ConstAccessor mAcc; |
606 |
|
|
const math::Transform* mXform; |
607 |
|
|
}; |
608 |
|
|
|
609 |
|
|
// Template specialization using a constant color of the material. |
610 |
|
|
template<typename SamplerType> |
611 |
|
|
class NormalShader<Film::RGBA, SamplerType>: public BaseShader |
612 |
|
|
{ |
613 |
|
|
public: |
614 |
|
|
NormalShader(const Film::RGBA& c = Film::RGBA(1.0f)) : mRGBA(c*0.5f) {} |
615 |
|
|
NormalShader(const NormalShader&) = default; |
616 |
|
|
~NormalShader() override = default; |
617 |
|
|
Film::RGBA operator()(const Vec3R&, const Vec3R& normal, const Vec3R&) const override |
618 |
|
|
{ |
619 |
|
|
return mRGBA * Film::RGBA(normal[0] + 1.0, normal[1] + 1.0, normal[2] + 1.0); |
620 |
|
|
} |
621 |
|
|
BaseShader* copy() const override { return new NormalShader<Film::RGBA, SamplerType>(*this); } |
622 |
|
|
|
623 |
|
|
private: |
624 |
|
|
const Film::RGBA mRGBA; |
625 |
|
|
}; |
626 |
|
|
|
627 |
|
|
|
628 |
|
|
/// @brief Color shader that treats position (x, y, z) as an RGB color in a |
629 |
|
|
/// cube defined from an axis-aligned bounding box in world space. |
630 |
|
|
/// |
631 |
|
|
/// @details The color can either be constant (if GridT = |
632 |
|
|
/// Film::RGBA which is the default) or defined in a separate Vec3 |
633 |
|
|
/// color grid. Use SamplerType to define the order of interpolation |
634 |
|
|
/// (default is zero order, i.e. closes-point). |
635 |
|
|
template<typename GridT = Film::RGBA, |
636 |
|
|
typename SamplerType = tools::PointSampler> |
637 |
|
|
class PositionShader: public BaseShader |
638 |
|
|
{ |
639 |
|
|
public: |
640 |
|
|
PositionShader(const math::BBox<Vec3R>& bbox, const GridT& grid) |
641 |
|
|
: mMin(bbox.min()) |
642 |
|
|
, mInvDim(1.0/bbox.extents()) |
643 |
|
|
, mAcc(grid.getAccessor()) |
644 |
|
|
, mXform(&grid.transform()) |
645 |
|
|
{ |
646 |
|
|
} |
647 |
|
|
PositionShader(const PositionShader&) = default; |
648 |
|
|
~PositionShader() override = default; |
649 |
|
|
Film::RGBA operator()(const Vec3R& xyz, const Vec3R&, const Vec3R&) const override |
650 |
|
|
{ |
651 |
|
|
typename GridT::ValueType v = zeroVal<typename GridT::ValueType>(); |
652 |
|
|
SamplerType::sample(mAcc, mXform->worldToIndex(xyz), v); |
653 |
|
|
const Vec3R rgb = (xyz - mMin) * mInvDim; |
654 |
|
|
return Film::RGBA(v[0],v[1],v[2]) * Film::RGBA(rgb[0], rgb[1], rgb[2]); |
655 |
|
|
} |
656 |
|
|
BaseShader* copy() const override { return new PositionShader<GridT, SamplerType>(*this); } |
657 |
|
|
|
658 |
|
|
private: |
659 |
|
|
const Vec3R mMin, mInvDim; |
660 |
|
|
typename GridT::ConstAccessor mAcc; |
661 |
|
|
const math::Transform* mXform; |
662 |
|
|
}; |
663 |
|
|
|
664 |
|
|
// Template specialization using a constant color of the material. |
665 |
|
|
template<typename SamplerType> |
666 |
|
|
class PositionShader<Film::RGBA, SamplerType>: public BaseShader |
667 |
|
|
{ |
668 |
|
|
public: |
669 |
|
|
PositionShader(const math::BBox<Vec3R>& bbox, const Film::RGBA& c = Film::RGBA(1.0f)) |
670 |
|
|
: mMin(bbox.min()), mInvDim(1.0/bbox.extents()), mRGBA(c) {} |
671 |
|
|
PositionShader(const PositionShader&) = default; |
672 |
|
|
~PositionShader() override = default; |
673 |
|
|
Film::RGBA operator()(const Vec3R& xyz, const Vec3R&, const Vec3R&) const override |
674 |
|
|
{ |
675 |
|
|
const Vec3R rgb = (xyz - mMin)*mInvDim; |
676 |
|
|
return mRGBA*Film::RGBA(rgb[0], rgb[1], rgb[2]); |
677 |
|
|
} |
678 |
|
|
BaseShader* copy() const override { return new PositionShader<Film::RGBA, SamplerType>(*this); } |
679 |
|
|
|
680 |
|
|
private: |
681 |
|
|
const Vec3R mMin, mInvDim; |
682 |
|
|
const Film::RGBA mRGBA; |
683 |
|
|
}; |
684 |
|
|
|
685 |
|
|
|
686 |
|
|
/// @brief Simple diffuse Lambertian surface shader. |
687 |
|
|
/// |
688 |
|
|
/// @details The diffuse color can either be constant (if GridT = |
689 |
|
|
/// Film::RGBA which is the default) or defined in a separate Vec3 |
690 |
|
|
/// color grid. Lambertian implies that the (radiant) intensity is |
691 |
|
|
/// directly proportional to the cosine of the angle between the |
692 |
|
|
/// surface normal and the direction of the light source. Use |
693 |
|
|
/// SamplerType to define the order of interpolation (default is |
694 |
|
|
/// zero order, i.e. closes-point). |
695 |
|
|
template<typename GridT = Film::RGBA, |
696 |
|
|
typename SamplerType = tools::PointSampler> |
697 |
|
|
class DiffuseShader: public BaseShader |
698 |
|
|
{ |
699 |
|
|
public: |
700 |
|
|
DiffuseShader(const GridT& grid): mAcc(grid.getAccessor()), mXform(&grid.transform()) {} |
701 |
|
|
DiffuseShader(const DiffuseShader&) = default; |
702 |
|
|
~DiffuseShader() override = default; |
703 |
|
|
Film::RGBA operator()(const Vec3R& xyz, const Vec3R& normal, const Vec3R& rayDir) const override |
704 |
|
|
{ |
705 |
|
|
typename GridT::ValueType v = zeroVal<typename GridT::ValueType>(); |
706 |
|
|
SamplerType::sample(mAcc, mXform->worldToIndex(xyz), v); |
707 |
|
|
// We take the abs of the dot product corresponding to having |
708 |
|
|
// light sources at +/- rayDir, i.e., two-sided shading. |
709 |
|
|
return Film::RGBA(v[0],v[1],v[2]) |
710 |
|
|
* static_cast<Film::RGBA::ValueT>(math::Abs(normal.dot(rayDir))); |
711 |
|
|
} |
712 |
|
|
BaseShader* copy() const override { return new DiffuseShader<GridT, SamplerType>(*this); } |
713 |
|
|
|
714 |
|
|
private: |
715 |
|
|
typename GridT::ConstAccessor mAcc; |
716 |
|
|
const math::Transform* mXform; |
717 |
|
|
}; |
718 |
|
|
|
719 |
|
|
// Template specialization using a constant color of the material. |
720 |
|
|
template <typename SamplerType> |
721 |
|
|
class DiffuseShader<Film::RGBA, SamplerType>: public BaseShader |
722 |
|
|
{ |
723 |
|
|
public: |
724 |
|
|
DiffuseShader(const Film::RGBA& d = Film::RGBA(1.0f)): mRGBA(d) {} |
725 |
|
|
DiffuseShader(const DiffuseShader&) = default; |
726 |
|
|
~DiffuseShader() override = default; |
727 |
|
|
Film::RGBA operator()(const Vec3R&, const Vec3R& normal, const Vec3R& rayDir) const override |
728 |
|
|
{ |
729 |
|
|
// We assume a single directional light source at the camera, |
730 |
|
|
// so the cosine of the angle between the surface normal and the |
731 |
|
|
// direction of the light source becomes the dot product of the |
732 |
|
|
// surface normal and inverse direction of the ray. We also ignore |
733 |
|
|
// negative dot products, corresponding to strict one-sided shading. |
734 |
|
|
//return mRGBA * math::Max(0.0, normal.dot(-rayDir)); |
735 |
|
|
|
736 |
|
|
// We take the abs of the dot product corresponding to having |
737 |
|
|
// light sources at +/- rayDir, i.e., two-sided shading. |
738 |
|
|
return mRGBA * static_cast<Film::RGBA::ValueT>(math::Abs(normal.dot(rayDir))); |
739 |
|
|
} |
740 |
|
|
BaseShader* copy() const override { return new DiffuseShader<Film::RGBA, SamplerType>(*this); } |
741 |
|
|
|
742 |
|
|
private: |
743 |
|
|
const Film::RGBA mRGBA; |
744 |
|
|
}; |
745 |
|
|
|
746 |
|
|
|
747 |
|
|
//////////////////////////////////////// RAYTRACER //////////////////////////////////////// |
748 |
|
|
|
749 |
|
|
template<typename GridT> |
750 |
|
✗ |
void rayTrace(const GridT& grid, |
751 |
|
|
const BaseShader& shader, |
752 |
|
|
BaseCamera& camera, |
753 |
|
|
size_t pixelSamples, |
754 |
|
|
unsigned int seed, |
755 |
|
|
bool threaded) |
756 |
|
|
{ |
757 |
|
|
LevelSetRayTracer<GridT, tools::LevelSetRayIntersector<GridT> > |
758 |
|
✗ |
tracer(grid, shader, camera, pixelSamples, seed); |
759 |
|
✗ |
tracer.render(threaded); |
760 |
|
|
} |
761 |
|
|
|
762 |
|
|
|
763 |
|
|
template<typename GridT, typename IntersectorT> |
764 |
|
✗ |
void rayTrace(const GridT&, |
765 |
|
|
const IntersectorT& inter, |
766 |
|
|
const BaseShader& shader, |
767 |
|
|
BaseCamera& camera, |
768 |
|
|
size_t pixelSamples, |
769 |
|
|
unsigned int seed, |
770 |
|
|
bool threaded) |
771 |
|
|
{ |
772 |
|
✗ |
LevelSetRayTracer<GridT, IntersectorT> tracer(inter, shader, camera, pixelSamples, seed); |
773 |
|
✗ |
tracer.render(threaded); |
774 |
|
|
} |
775 |
|
|
|
776 |
|
|
|
777 |
|
|
//////////////////////////////////////// LevelSetRayTracer //////////////////////////////////////// |
778 |
|
|
|
779 |
|
|
|
780 |
|
|
template<typename GridT, typename IntersectorT> |
781 |
|
✗ |
inline LevelSetRayTracer<GridT, IntersectorT>:: |
782 |
|
|
LevelSetRayTracer(const GridT& grid, |
783 |
|
|
const BaseShader& shader, |
784 |
|
|
BaseCamera& camera, |
785 |
|
|
size_t pixelSamples, |
786 |
|
|
unsigned int seed) |
787 |
|
|
: mIsMaster(true), |
788 |
|
|
mRand(nullptr), |
789 |
|
|
mInter(grid), |
790 |
|
✗ |
mShader(shader.copy()), |
791 |
|
✗ |
mCamera(&camera) |
792 |
|
|
{ |
793 |
|
✗ |
this->setPixelSamples(pixelSamples, seed); |
794 |
|
|
} |
795 |
|
|
|
796 |
|
|
template<typename GridT, typename IntersectorT> |
797 |
|
✗ |
inline LevelSetRayTracer<GridT, IntersectorT>:: |
798 |
|
|
LevelSetRayTracer(const IntersectorT& inter, |
799 |
|
|
const BaseShader& shader, |
800 |
|
|
BaseCamera& camera, |
801 |
|
|
size_t pixelSamples, |
802 |
|
|
unsigned int seed) |
803 |
|
|
: mIsMaster(true), |
804 |
|
|
mRand(nullptr), |
805 |
|
|
mInter(inter), |
806 |
|
✗ |
mShader(shader.copy()), |
807 |
|
✗ |
mCamera(&camera) |
808 |
|
|
{ |
809 |
|
✗ |
this->setPixelSamples(pixelSamples, seed); |
810 |
|
|
} |
811 |
|
|
|
812 |
|
|
template<typename GridT, typename IntersectorT> |
813 |
|
✗ |
inline LevelSetRayTracer<GridT, IntersectorT>:: |
814 |
|
|
LevelSetRayTracer(const LevelSetRayTracer& other) : |
815 |
|
|
mIsMaster(false), |
816 |
|
✗ |
mRand(other.mRand), |
817 |
|
|
mInter(other.mInter), |
818 |
|
✗ |
mShader(other.mShader->copy()), |
819 |
|
✗ |
mCamera(other.mCamera), |
820 |
|
✗ |
mSubPixels(other.mSubPixels) |
821 |
|
|
{ |
822 |
|
|
} |
823 |
|
|
|
824 |
|
|
template<typename GridT, typename IntersectorT> |
825 |
|
✗ |
inline LevelSetRayTracer<GridT, IntersectorT>:: |
826 |
|
|
~LevelSetRayTracer() |
827 |
|
|
{ |
828 |
|
✗ |
if (mIsMaster) delete [] mRand; |
829 |
|
|
} |
830 |
|
|
|
831 |
|
|
template<typename GridT, typename IntersectorT> |
832 |
|
|
inline void LevelSetRayTracer<GridT, IntersectorT>:: |
833 |
|
|
setGrid(const GridT& grid) |
834 |
|
|
{ |
835 |
|
|
assert(mIsMaster); |
836 |
|
|
mInter = IntersectorT(grid); |
837 |
|
|
} |
838 |
|
|
|
839 |
|
|
template<typename GridT, typename IntersectorT> |
840 |
|
|
inline void LevelSetRayTracer<GridT, IntersectorT>:: |
841 |
|
|
setIntersector(const IntersectorT& inter) |
842 |
|
|
{ |
843 |
|
|
assert(mIsMaster); |
844 |
|
|
mInter = inter; |
845 |
|
|
} |
846 |
|
|
|
847 |
|
|
template<typename GridT, typename IntersectorT> |
848 |
|
|
inline void LevelSetRayTracer<GridT, IntersectorT>:: |
849 |
|
|
setShader(const BaseShader& shader) |
850 |
|
|
{ |
851 |
|
|
assert(mIsMaster); |
852 |
|
|
mShader.reset(shader.copy()); |
853 |
|
|
} |
854 |
|
|
|
855 |
|
|
template<typename GridT, typename IntersectorT> |
856 |
|
|
inline void LevelSetRayTracer<GridT, IntersectorT>:: |
857 |
|
|
setCamera(BaseCamera& camera) |
858 |
|
|
{ |
859 |
|
|
assert(mIsMaster); |
860 |
|
|
mCamera = &camera; |
861 |
|
|
} |
862 |
|
|
|
863 |
|
|
template<typename GridT, typename IntersectorT> |
864 |
|
✗ |
inline void LevelSetRayTracer<GridT, IntersectorT>:: |
865 |
|
|
setPixelSamples(size_t pixelSamples, unsigned int seed) |
866 |
|
|
{ |
867 |
|
✗ |
assert(mIsMaster); |
868 |
|
✗ |
if (pixelSamples == 0) { |
869 |
|
✗ |
OPENVDB_THROW(ValueError, "pixelSamples must be larger than zero!"); |
870 |
|
|
} |
871 |
|
✗ |
mSubPixels = pixelSamples - 1; |
872 |
|
✗ |
delete [] mRand; |
873 |
|
✗ |
if (mSubPixels > 0) { |
874 |
|
✗ |
mRand = new double[16]; |
875 |
|
|
math::Rand01<double> rand(seed);//offsets for anti-aliaing by jittered super-sampling |
876 |
|
✗ |
for (size_t i=0; i<16; ++i) mRand[i] = rand(); |
877 |
|
|
} else { |
878 |
|
✗ |
mRand = nullptr; |
879 |
|
|
} |
880 |
|
|
} |
881 |
|
|
|
882 |
|
|
template<typename GridT, typename IntersectorT> |
883 |
|
✗ |
inline void LevelSetRayTracer<GridT, IntersectorT>:: |
884 |
|
|
render(bool threaded) const |
885 |
|
|
{ |
886 |
|
✗ |
tbb::blocked_range<size_t> range(0, mCamera->height()); |
887 |
|
✗ |
threaded ? tbb::parallel_for(range, *this) : (*this)(range); |
888 |
|
|
} |
889 |
|
|
|
890 |
|
|
template<typename GridT, typename IntersectorT> |
891 |
|
✗ |
inline void LevelSetRayTracer<GridT, IntersectorT>:: |
892 |
|
|
operator()(const tbb::blocked_range<size_t>& range) const |
893 |
|
|
{ |
894 |
|
|
const BaseShader& shader = *mShader; |
895 |
|
|
Vec3Type xyz, nml; |
896 |
|
✗ |
const float frac = 1.0f / (1.0f + float(mSubPixels)); |
897 |
|
✗ |
for (size_t j=range.begin(), n=0, je = range.end(); j<je; ++j) { |
898 |
|
✗ |
for (size_t i=0, ie = mCamera->width(); i<ie; ++i) { |
899 |
|
✗ |
Film::RGBA& bg = mCamera->pixel(i,j); |
900 |
|
✗ |
RayType ray = mCamera->getRay(i, j);//primary ray |
901 |
|
✗ |
Film::RGBA c = mInter.intersectsWS(ray, xyz, nml) ? shader(xyz, nml, ray.dir()) : bg; |
902 |
|
✗ |
for (size_t k=0; k<mSubPixels; ++k, n +=2 ) { |
903 |
|
✗ |
ray = mCamera->getRay(i, j, mRand[n & 15], mRand[(n+1) & 15]); |
904 |
|
✗ |
c += mInter.intersectsWS(ray, xyz, nml) ? shader(xyz, nml, ray.dir()) : bg; |
905 |
|
|
}//loop over sub-pixels |
906 |
|
✗ |
bg = c*frac; |
907 |
|
|
}//loop over image height |
908 |
|
|
}//loop over image width |
909 |
|
|
} |
910 |
|
|
|
911 |
|
|
//////////////////////////////////////// VolumeRender //////////////////////////////////////// |
912 |
|
|
|
913 |
|
|
template<typename IntersectorT, typename SampleT> |
914 |
|
✗ |
inline VolumeRender<IntersectorT, SampleT>:: |
915 |
|
|
VolumeRender(const IntersectorT& inter, BaseCamera& camera) |
916 |
|
|
: mAccessor(inter.grid().getConstAccessor()) |
917 |
|
|
, mCamera(&camera) |
918 |
|
✗ |
, mPrimary(new IntersectorT(inter)) |
919 |
|
✗ |
, mShadow(new IntersectorT(inter)) |
920 |
|
|
, mPrimaryStep(1.0) |
921 |
|
|
, mShadowStep(3.0) |
922 |
|
|
, mCutOff(0.005) |
923 |
|
|
, mLightGain(0.2) |
924 |
|
✗ |
, mLightDir(Vec3R(0.3, 0.3, 0).unit()) |
925 |
|
|
, mLightColor(0.7, 0.7, 0.7) |
926 |
|
|
, mAbsorption(0.1) |
927 |
|
✗ |
, mScattering(1.5) |
928 |
|
|
{ |
929 |
|
|
} |
930 |
|
|
|
931 |
|
|
template<typename IntersectorT, typename SampleT> |
932 |
|
✗ |
inline VolumeRender<IntersectorT, SampleT>:: |
933 |
|
|
VolumeRender(const VolumeRender& other) |
934 |
|
|
: mAccessor(other.mAccessor) |
935 |
|
✗ |
, mCamera(other.mCamera) |
936 |
|
✗ |
, mPrimary(new IntersectorT(*(other.mPrimary))) |
937 |
|
✗ |
, mShadow(new IntersectorT(*(other.mShadow))) |
938 |
|
✗ |
, mPrimaryStep(other.mPrimaryStep) |
939 |
|
✗ |
, mShadowStep(other.mShadowStep) |
940 |
|
✗ |
, mCutOff(other.mCutOff) |
941 |
|
✗ |
, mLightGain(other.mLightGain) |
942 |
|
|
, mLightDir(other.mLightDir) |
943 |
|
|
, mLightColor(other.mLightColor) |
944 |
|
|
, mAbsorption(other.mAbsorption) |
945 |
|
✗ |
, mScattering(other.mScattering) |
946 |
|
|
{ |
947 |
|
|
} |
948 |
|
|
|
949 |
|
|
template<typename IntersectorT, typename SampleT> |
950 |
|
✗ |
inline void VolumeRender<IntersectorT, SampleT>:: |
951 |
|
|
print(std::ostream& os, int verboseLevel) |
952 |
|
|
{ |
953 |
|
✗ |
if (verboseLevel>0) { |
954 |
|
✗ |
os << "\nPrimary step: " << mPrimaryStep |
955 |
|
✗ |
<< "\nShadow step: " << mShadowStep |
956 |
|
✗ |
<< "\nCutoff: " << mCutOff |
957 |
|
✗ |
<< "\nLightGain: " << mLightGain |
958 |
|
✗ |
<< "\nLightDir: " << mLightDir |
959 |
|
✗ |
<< "\nLightColor: " << mLightColor |
960 |
|
✗ |
<< "\nAbsorption: " << mAbsorption |
961 |
|
✗ |
<< "\nScattering: " << mScattering << std::endl; |
962 |
|
|
} |
963 |
|
✗ |
mPrimary->print(os, verboseLevel); |
964 |
|
|
} |
965 |
|
|
|
966 |
|
|
template<typename IntersectorT, typename SampleT> |
967 |
|
✗ |
inline void VolumeRender<IntersectorT, SampleT>:: |
968 |
|
|
setIntersector(const IntersectorT& inter) |
969 |
|
|
{ |
970 |
|
✗ |
mPrimary.reset(new IntersectorT(inter)); |
971 |
|
✗ |
mShadow.reset( new IntersectorT(inter)); |
972 |
|
|
} |
973 |
|
|
|
974 |
|
|
template<typename IntersectorT, typename SampleT> |
975 |
|
✗ |
inline void VolumeRender<IntersectorT, SampleT>:: |
976 |
|
|
render(bool threaded) const |
977 |
|
|
{ |
978 |
|
✗ |
tbb::blocked_range<size_t> range(0, mCamera->height()); |
979 |
|
✗ |
threaded ? tbb::parallel_for(range, *this) : (*this)(range); |
980 |
|
|
} |
981 |
|
|
|
982 |
|
|
template<typename IntersectorT, typename SampleT> |
983 |
|
✗ |
inline void VolumeRender<IntersectorT, SampleT>:: |
984 |
|
|
operator()(const tbb::blocked_range<size_t>& range) const |
985 |
|
|
{ |
986 |
|
✗ |
SamplerType sampler(mAccessor, mShadow->grid().transform());//light-weight wrapper |
987 |
|
|
|
988 |
|
|
// Any variable prefixed with p (or s) means it's associated with a primary (or shadow) ray |
989 |
|
|
const Vec3R extinction = -mScattering-mAbsorption, One(1.0); |
990 |
|
|
const Vec3R albedo = mLightColor*mScattering/(mScattering+mAbsorption);//single scattering |
991 |
|
✗ |
const Real sGain = mLightGain;//in-scattering along shadow ray |
992 |
|
✗ |
const Real pStep = mPrimaryStep;//Integration step along primary ray in voxel units |
993 |
|
✗ |
const Real sStep = mShadowStep;//Integration step along shadow ray in voxel units |
994 |
|
✗ |
const Real cutoff = mCutOff;//Cutoff for density and transmittance |
995 |
|
|
|
996 |
|
|
// For the sake of completeness we show how to use two different |
997 |
|
|
// methods (hits/march) in VolumeRayIntersector that produce |
998 |
|
|
// segments along the ray that intersects active values. Comment out |
999 |
|
|
// the line below to use VolumeRayIntersector::march instead of |
1000 |
|
|
// VolumeRayIntersector::hits. |
1001 |
|
|
#define USE_HITS |
1002 |
|
|
#ifdef USE_HITS |
1003 |
|
|
std::vector<typename RayType::TimeSpan> pTS, sTS; |
1004 |
|
|
//std::deque<typename RayType::TimeSpan> pTS, sTS; |
1005 |
|
|
#endif |
1006 |
|
|
|
1007 |
|
✗ |
RayType sRay(Vec3R(0), mLightDir);//Shadow ray |
1008 |
|
✗ |
for (size_t j=range.begin(), je = range.end(); j<je; ++j) { |
1009 |
|
✗ |
for (size_t i=0, ie = mCamera->width(); i<ie; ++i) { |
1010 |
|
✗ |
Film::RGBA& bg = mCamera->pixel(i, j); |
1011 |
|
✗ |
bg.a = bg.r = bg.g = bg.b = 0; |
1012 |
|
✗ |
RayType pRay = mCamera->getRay(i, j);// Primary ray |
1013 |
|
✗ |
if( !mPrimary->setWorldRay(pRay)) continue; |
1014 |
|
|
Vec3R pTrans(1.0), pLumi(0.0); |
1015 |
|
|
#ifndef USE_HITS |
1016 |
|
|
Real pT0, pT1; |
1017 |
|
|
while (mPrimary->march(pT0, pT1)) { |
1018 |
|
|
for (Real pT = pStep*ceil(pT0/pStep); pT <= pT1; pT += pStep) { |
1019 |
|
|
#else |
1020 |
|
|
mPrimary->hits(pTS); |
1021 |
|
✗ |
for (size_t k=0; k<pTS.size(); ++k) { |
1022 |
|
✗ |
Real pT = pStep*ceil(pTS[k].t0/pStep), pT1=pTS[k].t1; |
1023 |
|
✗ |
for (; pT <= pT1; pT += pStep) { |
1024 |
|
|
#endif |
1025 |
|
✗ |
Vec3R pPos = mPrimary->getWorldPos(pT); |
1026 |
|
✗ |
const Real density = sampler.wsSample(pPos); |
1027 |
|
✗ |
if (density < cutoff) continue; |
1028 |
|
✗ |
const Vec3R dT = math::Exp(extinction * density * pStep); |
1029 |
|
|
Vec3R sTrans(1.0); |
1030 |
|
|
sRay.setEye(pPos); |
1031 |
|
✗ |
if( !mShadow->setWorldRay(sRay)) continue; |
1032 |
|
|
#ifndef USE_HITS |
1033 |
|
|
Real sT0, sT1; |
1034 |
|
|
while (mShadow->march(sT0, sT1)) { |
1035 |
|
|
for (Real sT = sStep*ceil(sT0/sStep); sT <= sT1; sT+= sStep) { |
1036 |
|
|
#else |
1037 |
|
|
mShadow->hits(sTS); |
1038 |
|
✗ |
for (size_t l=0; l<sTS.size(); ++l) { |
1039 |
|
✗ |
Real sT = sStep*ceil(sTS[l].t0/sStep), sT1=sTS[l].t1; |
1040 |
|
✗ |
for (; sT <= sT1; sT+= sStep) { |
1041 |
|
|
#endif |
1042 |
|
✗ |
const Real d = sampler.wsSample(mShadow->getWorldPos(sT)); |
1043 |
|
✗ |
if (d < cutoff) continue; |
1044 |
|
✗ |
sTrans *= math::Exp(extinction * d * sStep/(1.0+sT*sGain)); |
1045 |
|
✗ |
if (sTrans.lengthSqr()<cutoff) goto Luminance;//Terminate sRay |
1046 |
|
|
}//Integration over shadow segment |
1047 |
|
|
}// Shadow ray march |
1048 |
|
✗ |
Luminance: |
1049 |
|
✗ |
pLumi += albedo * sTrans * pTrans * (One-dT); |
1050 |
|
|
pTrans *= dT; |
1051 |
|
✗ |
if (pTrans.lengthSqr()<cutoff) goto Pixel; // Terminate Ray |
1052 |
|
|
}//Integration over primary segment |
1053 |
|
|
}// Primary ray march |
1054 |
|
✗ |
Pixel: |
1055 |
|
✗ |
bg.r = static_cast<Film::RGBA::ValueT>(pLumi[0]); |
1056 |
|
✗ |
bg.g = static_cast<Film::RGBA::ValueT>(pLumi[1]); |
1057 |
|
✗ |
bg.b = static_cast<Film::RGBA::ValueT>(pLumi[2]); |
1058 |
|
✗ |
bg.a = static_cast<Film::RGBA::ValueT>(1.0f - pTrans.sum()/3.0f); |
1059 |
|
|
}//Horizontal pixel scan |
1060 |
|
|
}//Vertical pixel scan |
1061 |
|
|
} |
1062 |
|
|
|
1063 |
|
|
|
1064 |
|
|
//////////////////////////////////////// |
1065 |
|
|
|
1066 |
|
|
|
1067 |
|
|
// Explicit Template Instantiation |
1068 |
|
|
|
1069 |
|
|
#ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION |
1070 |
|
|
|
1071 |
|
|
#ifdef OPENVDB_INSTANTIATE_RAYTRACER |
1072 |
|
|
#include <openvdb/util/ExplicitInstantiation.h> |
1073 |
|
|
#endif |
1074 |
|
|
|
1075 |
|
|
#define _FUNCTION(TreeT) \ |
1076 |
|
|
void rayTrace(const Grid<TreeT>&, const BaseShader&, BaseCamera&, size_t, unsigned int, bool) |
1077 |
|
|
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) |
1078 |
|
|
#undef _FUNCTION |
1079 |
|
|
|
1080 |
|
|
#define _FUNCTION(TreeT) \ |
1081 |
|
|
void rayTrace(const Grid<TreeT>&, const tools::LevelSetRayIntersector<Grid<TreeT>>&, const BaseShader&, BaseCamera&, size_t, unsigned int, bool) |
1082 |
|
|
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) |
1083 |
|
|
#undef _FUNCTION |
1084 |
|
|
|
1085 |
|
|
OPENVDB_INSTANTIATE_CLASS VolumeRender<tools::VolumeRayIntersector<FloatGrid>, tools::BoxSampler>; |
1086 |
|
|
OPENVDB_INSTANTIATE_CLASS VolumeRender<tools::VolumeRayIntersector<DoubleGrid>, tools::BoxSampler>; |
1087 |
|
|
|
1088 |
|
|
#endif // OPENVDB_USE_EXPLICIT_INSTANTIATION |
1089 |
|
|
|
1090 |
|
|
|
1091 |
|
|
} // namespace tools |
1092 |
|
|
} // namespace OPENVDB_VERSION_NAME |
1093 |
|
|
} // namespace openvdb |
1094 |
|
|
|
1095 |
|
|
#endif // OPENVDB_TOOLS_RAYTRACER_HAS_BEEN_INCLUDED |
1096 |
|
|
|