| 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 |