1 // scattersim.cpp
2 //
3 // Copyright (C) 2007, Chris Laurel <claurel@shatters.net>
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10
11 #include <iostream>
12 #include <fstream>
13 #include <string>
14 #include <cstdlib>
15 #include <cmath>
16 #include <algorithm>
17 #include <map>
18 #include <celutil/basictypes.h>
19 #include <celmath/vecmath.h>
20 #include <celmath/mathlib.h>
21 #include <celmath/ray.h>
22 #include <celmath/sphere.h>
23 #include <celmath/intersect.h>
24 #ifdef MACOSX
25 #include "../../../macosx/png.h"
26 #else
27 #include "png.h"
28 #endif // MACOSX
29
30 using namespace std;
31
32
33
34 // Extinction lookup table dimensions
35 const unsigned int ExtinctionLUTHeightSteps = 256;
36 const unsigned int ExtinctionLUTViewAngleSteps = 512;
37
38 // Scattering lookup table dimensions
39 const unsigned int ScatteringLUTHeightSteps = 64;
40 const unsigned int ScatteringLUTViewAngleSteps = 64;
41 const unsigned int ScatteringLUTLightAngleSteps = 64;
42
43 // Values settable via the command line
44 static unsigned int IntegrateScatterSteps = 20;
45 static unsigned int IntegrateDepthSteps = 20;
46 static unsigned int OutputImageWidth = 600;
47 static unsigned int OutputImageHeight = 450;
48 enum LUTUsageType
49 {
50 NoLUT,
51 UseExtinctionLUT,
52 UseScatteringLUT
53 };
54
55 static LUTUsageType LUTUsage = NoLUT;
56 static bool UseFisheyeCameras = false;
57 static double CameraExposure = 0.0;
58
59
60 typedef map<string, double> ParameterSet;
61
62
63 struct Color
64 {
ColorColor65 Color() : r(0.0f), g(0.0f), b(0.0f) {}
ColorColor66 Color(float _r, float _g, float _b) : r(_r), g(_g), b(_b) {}
67
exposureColor68 Color exposure(float e) const
69 {
70 #if 0
71 float brightness = max(r, max(g, b));
72 float f = 1.0f - (float) exp(-e * brightness);
73 return Color(r * f, g * f, b * f);
74 #endif
75 return Color((float) (1.0 - exp(-e * r)),
76 (float) (1.0 - exp(-e * g)),
77 (float) (1.0 - exp(-e * b)));
78 }
79
80 float r, g, b;
81 };
82
operator *(const Color & color,double d)83 Color operator*(const Color& color, double d)
84 {
85 return Color((float) (color.r * d),
86 (float) (color.g * d),
87 (float) (color.b * d));
88 }
89
operator +(const Color & a,const Color & b)90 Color operator+(const Color& a, const Color& b)
91 {
92 return Color(a.r + b.r, a.g + b.g, a.b + b.b);
93 }
94
operator *(const Color & a,const Color & b)95 Color operator*(const Color& a, const Color& b)
96 {
97 return Color(a.r * b.r, a.g * b.g, a.b * b.b);
98 }
99
operator *(const Color & a,const Vec3d & v)100 Color operator*(const Color& a, const Vec3d& v)
101 {
102 return Color((float) (a.r * v.x), (float) (a.g * v.y), (float) (a.b * v.z));
103 }
104
105
floatToByte(float f)106 static uint8 floatToByte(float f)
107 {
108 if (f <= 0.0f)
109 return 0;
110 else if (f >= 1.0f)
111 return 255;
112 else
113 return (uint8) (f * 255.99f);
114 }
115
116
117 class Camera
118 {
119 public:
120 enum CameraType
121 {
122 Planar,
123 Spherical,
124 };
125
Camera()126 Camera() :
127 fov(PI / 2.0),
128 front(1.0),
129 transform(Mat4d::identity()),
130 type(Planar)
131 {
132 }
133
getViewRay(double viewportX,double viewportY) const134 Ray3d getViewRay(double viewportX, double viewportY) const
135 {
136 Vec3d viewDir;
137
138 if (type == Planar)
139 {
140 double viewPlaneHeight = tan(fov / 2.0) * 2 * front;
141 viewDir.x = viewportX * viewPlaneHeight;
142 viewDir.y = viewportY * viewPlaneHeight;
143 viewDir.z = front;
144 viewDir.normalize();
145 }
146 else
147 {
148 double phi = -viewportY * fov / 2.0 + PI / 2.0;
149 double theta = viewportX * fov / 2.0 + PI / 2.0;
150 viewDir.x = sin(phi) * cos(theta);
151 viewDir.y = cos(phi);
152 viewDir.z = sin(phi) * sin(theta);
153 viewDir.normalize();
154 }
155
156 Ray3d viewRay(Point3d(0.0, 0.0, 0.0), viewDir);
157
158 return viewRay * transform;
159 }
160
161 double fov;
162 double front;
163 Mat4d transform;
164 CameraType type;
165 };
166
167
168 class Viewport
169 {
170 public:
Viewport(unsigned int _x,unsigned int _y,unsigned int _width,unsigned int _height)171 Viewport(unsigned int _x, unsigned int _y,
172 unsigned int _width, unsigned int _height) :
173 x(_x), y(_y), width(_width), height(_height)
174 {
175 }
176
177 unsigned int x;
178 unsigned int y;
179 unsigned int width;
180 unsigned int height;
181 };
182
183
184 class Light
185 {
186 public:
187 Vec3d direction;
188 Color color;
189 };
190
191
192 struct OpticalDepths
193 {
194 double rayleigh;
195 double mie;
196 double absorption;
197 };
198
sumOpticalDepths(OpticalDepths a,OpticalDepths b)199 OpticalDepths sumOpticalDepths(OpticalDepths a, OpticalDepths b)
200 {
201 OpticalDepths depths;
202 depths.rayleigh = a.rayleigh + b.rayleigh;
203 depths.mie = a.mie + b.mie;
204 depths.absorption = a.absorption + b.absorption;
205 return depths;
206 }
207
208
209 typedef double (*MiePhaseFunction)(double cosTheta, double asymmetry);
210 double phaseHenyeyGreenstein_CS(double, double);
211 double phaseHenyeyGreenstein(double, double);
212 double phaseSchlick(double, double);
213
214 class Atmosphere
215 {
216 public:
Atmosphere()217 Atmosphere() :
218 miePhaseFunction(phaseHenyeyGreenstein_CS)
219 {
220 }
221
calcShellHeight()222 double calcShellHeight()
223 {
224 double maxScaleHeight = max(rayleighScaleHeight, max(mieScaleHeight, absorbScaleHeight));
225 return -log(0.002) * maxScaleHeight;
226 }
227
miePhase(double cosAngle) const228 double miePhase(double cosAngle) const
229 {
230 return miePhaseFunction(cosAngle, mieAsymmetry);
231 }
232
233 double rayleighDensity(double h) const;
234 double mieDensity(double h) const;
235 double absorbDensity(double h) const;
236
237 Vec3d computeExtinction(const OpticalDepths&) const;
238
239 double rayleighScaleHeight;
240 double mieScaleHeight;
241 double absorbScaleHeight;
242
243 Vec3d rayleighCoeff;
244 Vec3d absorbCoeff;
245 double mieCoeff;
246
247 double mieAsymmetry;
248
249 MiePhaseFunction miePhaseFunction;
250 };
251
252
253 class LUT2
254 {
255 public:
256 LUT2(unsigned int w, unsigned int h);
257
258 Vec3d getValue(unsigned int x, unsigned int y) const;
259 void setValue(unsigned int x, unsigned int y, const Vec3d&);
260 Vec3d lookup(double x, double y) const;
getWidth() const261 unsigned int getWidth() const { return width; }
getHeight() const262 unsigned int getHeight() const { return height; }
263
264 private:
265 unsigned int width;
266 unsigned int height;
267 float* values;
268 };
269
270
271 class LUT3
272 {
273 public:
274 LUT3(unsigned int w, unsigned int h, unsigned int d);
275
276 Vec4d getValue(unsigned int x, unsigned int y, unsigned int z) const;
277 void setValue(unsigned int x, unsigned int y, unsigned int z, const Vec4d&);
278 Vec4d lookup(double x, double y, double z) const;
getWidth() const279 unsigned int getWidth() const { return width; }
getHeight() const280 unsigned int getHeight() const { return height; }
getDepth() const281 unsigned int getDepth() const { return depth; }
282
283 private:
284 unsigned int width;
285 unsigned int height;
286 unsigned int depth;
287 float* values;
288 };
289
290
291 class Scene
292 {
293 public:
Scene()294 Scene() {};
295
296 void setParameters(ParameterSet& params);
297
298 Color raytrace(const Ray3d& ray) const;
299 Color raytrace_LUT(const Ray3d& ray) const;
300
301 Color background;
302 Light light;
303 Sphered planet;
304 Color planetColor;
305 Color planetColor2;
306 Atmosphere atmosphere;
307
308 double atmosphereShellHeight;
309
310 double sunAngularDiameter;
311
312 LUT2* extinctionLUT;
313 LUT3* scatteringLUT;
314 };
315
316
317 class RGBImage
318 {
319 public:
RGBImage(int w,int h)320 RGBImage(int w, int h) :
321 width(w),
322 height(h),
323 pixels(NULL)
324 {
325 pixels = new uint8[width * height * 3];
326 }
327
~RGBImage()328 ~RGBImage()
329 {
330 delete[] pixels;
331 }
332
clearRect(const Color & color,unsigned int x,unsigned int y,unsigned int w,unsigned int h)333 void clearRect(const Color& color,
334 unsigned int x,
335 unsigned int y,
336 unsigned int w,
337 unsigned int h)
338 {
339
340 uint8 r = floatToByte(color.r);
341 uint8 g = floatToByte(color.g);
342 uint8 b = floatToByte(color.b);
343 for (unsigned int i = y; i < y + h; i++)
344 {
345 for (unsigned int j = x; j < x + w; j++)
346 {
347 pixels[(i * width + j) * 3 ] = r;
348 pixels[(i * width + j) * 3 + 1] = g;
349 pixels[(i * width + j) * 3 + 2] = b;
350 }
351 }
352 }
353
clear(const Color & color)354 void clear(const Color& color)
355 {
356 clearRect(color, 0, 0, width, height);
357 }
358
setPixel(unsigned int x,unsigned int y,const Color & color)359 void setPixel(unsigned int x, unsigned int y, const Color& color)
360 {
361 if (x >= 0 && x < width && y >= 0 && y < height)
362 {
363 unsigned int pix = (x + y * width) * 3;
364 pixels[pix + 0] = floatToByte(color.r);
365 pixels[pix + 1] = floatToByte(color.g);
366 pixels[pix + 2] = floatToByte(color.b);
367 }
368 }
369
370 unsigned int width;
371 unsigned int height;
372 unsigned char* pixels;
373 };
374
375
usage()376 void usage()
377 {
378 cerr << "Usage: scattersim [options] <config file>\n";
379 cerr << " --lut (or -l) : accelerate calculation by using a lookup table\n";
380 cerr << " --fisheye (or -f) : use wide angle cameras on surface\n";
381 cerr << " --exposure <value> (or -e) : set exposure for HDR\n";
382 cerr << " --width <value> (or -w) : set width of output image\n";
383 cerr << " --height <value> (or -h) : set height of output image\n";
384 cerr << " --image <filename> (or -i) : set filename of output image\n";
385 cerr << " (default is out.png)\n";
386 cerr << " --depthsteps <value> (or -d)\n";
387 cerr << " set the number of integration steps for depth\n";
388 cerr << " --scattersteps <value> (or -s)\n";
389 cerr << " set the number of integration steps for scattering\n";
390 }
391
392
PNGWriteData(png_structp png_ptr,png_bytep data,png_size_t length)393 static void PNGWriteData(png_structp png_ptr, png_bytep data, png_size_t length)
394 {
395 FILE* fp = (FILE*) png_get_io_ptr(png_ptr);
396 fwrite((void*) data, 1, length, fp);
397 }
398
399
WritePNG(const string & filename,const RGBImage & image)400 bool WritePNG(const string& filename, const RGBImage& image)
401
402 {
403 int rowStride = image.width * 3;
404
405 FILE* out;
406 out = fopen(filename.c_str(), "wb");
407 if (out == NULL)
408 {
409 cerr << "Can't open screen capture file " << filename << "\n";
410 return false;
411 }
412
413 png_bytep* row_pointers = new png_bytep[image.height];
414 for (unsigned int i = 0; i < image.height; i++)
415 row_pointers[i] = (png_bytep) &image.pixels[rowStride * (image.height - i - 1)];
416
417 png_structp png_ptr;
418 png_infop info_ptr;
419
420 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
421 NULL, NULL, NULL);
422
423 if (png_ptr == NULL)
424 {
425 cerr << "Screen capture: error allocating png_ptr\n";
426 fclose(out);
427 delete[] row_pointers;
428 return false;
429 }
430
431 info_ptr = png_create_info_struct(png_ptr);
432 if (info_ptr == NULL)
433 {
434 cerr << "Screen capture: error allocating info_ptr\n";
435 fclose(out);
436 delete[] row_pointers;
437 png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
438 return false;
439 }
440
441 if (setjmp(png_jmpbuf(png_ptr)))
442 {
443 cerr << "Error writing PNG file " << filename << endl;
444 fclose(out);
445 delete[] row_pointers;
446 png_destroy_write_struct(&png_ptr, &info_ptr);
447 return false;
448 }
449
450 // png_init_io(png_ptr, out);
451 png_set_write_fn(png_ptr, (void*) out, PNGWriteData, NULL);
452
453 png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
454 png_set_IHDR(png_ptr, info_ptr,
455 image.width, image.height,
456 8,
457 PNG_COLOR_TYPE_RGB,
458 PNG_INTERLACE_NONE,
459 PNG_COMPRESSION_TYPE_DEFAULT,
460 PNG_FILTER_TYPE_DEFAULT);
461
462 png_write_info(png_ptr, info_ptr);
463 png_write_image(png_ptr, row_pointers);
464 png_write_end(png_ptr, info_ptr);
465
466 // Clean up everything . . .
467 png_destroy_write_struct(&png_ptr, &info_ptr);
468 delete[] row_pointers;
469 fclose(out);
470
471 return true;
472 }
473
474
mapColor(double c)475 float mapColor(double c)
476 {
477 //return (float) ((5.0 + log(max(0.0, c))) * 1.0);
478 return (float) c;
479 }
480
481
DumpLUT(const LUT3 & lut,const string & filename)482 void DumpLUT(const LUT3& lut, const string& filename)
483 {
484 unsigned int xtiles = 8;
485 unsigned int ytiles = lut.getDepth() / xtiles;
486 unsigned int tileWidth = lut.getHeight();
487 unsigned int tileHeight = lut.getWidth();
488 double scale = 1.0 / 1.0;
489
490 RGBImage img(xtiles * tileWidth, xtiles * tileHeight);
491
492 for (unsigned int i = 0; i < ytiles; i++)
493 {
494 for (unsigned int j = 0; j < xtiles; j++)
495 {
496 unsigned int z = j + xtiles * i;
497 for (unsigned int k = 0; k < tileWidth; k++)
498 {
499 unsigned int y = k;
500 for (unsigned int l = 0; l < tileHeight; l++)
501 {
502 unsigned int x = l;
503 Vec4d v = lut.getValue(x, y, z);
504 Color c(mapColor(v.x * 0.000617f * 4.0 * PI),
505 mapColor(v.y * 0.00109f * 4.0 * PI),
506 mapColor(v.z * 0.00195f * 4.0 * PI));
507 img.setPixel(j * tileWidth + k, i * tileHeight + l, c);
508 }
509 }
510 }
511 }
512
513 for (unsigned int blah = 0; blah < img.width; blah++)
514 img.setPixel(blah, 0, Color(1.0f, 0.0f, 0.0f));
515
516 WritePNG(filename, img);
517 }
518
DumpLUT(const LUT2 & lut,const string & filename)519 void DumpLUT(const LUT2& lut, const string& filename)
520 {
521 RGBImage img(lut.getHeight(), lut.getWidth());
522
523 for (unsigned int i = 0; i < lut.getWidth(); i++)
524 {
525 for (unsigned int j = 0; j < lut.getHeight(); j++)
526 {
527 Vec3d v = lut.getValue(i, j);
528 Color c(mapColor(v.x), mapColor(v.y), mapColor(v.z));
529 img.setPixel(j, i, c);
530 }
531 }
532
533 for (unsigned int blah = 0; blah < img.width; blah++)
534 img.setPixel(blah, 0, Color(1.0f, 0.0f, 0.0f));
535
536 WritePNG(filename, img);
537 }
538
539
540
lerp(double t,T v0,T v1)541 template<class T> T lerp(double t, T v0, T v1)
542 {
543 return (1 - t) * v0 + t * v1;
544 }
545
bilerp(double t,double u,T v00,T v01,T v10,T v11)546 template<class T> T bilerp(double t, double u, T v00, T v01, T v10, T v11)
547 {
548 return lerp(u, lerp(t, v00, v01), lerp(t, v10, v11));
549 }
550
trilerp(double t,double u,double v,T v000,T v001,T v010,T v011,T v100,T v101,T v110,T v111)551 template<class T> T trilerp(double t, double u, double v,
552 T v000, T v001, T v010, T v011,
553 T v100, T v101, T v110, T v111)
554 {
555 return lerp(v,
556 bilerp(t, u, v000, v001, v010, v011),
557 bilerp(t, u, v100, v101, v110, v111));
558 }
559
560
LUT2(unsigned int _width,unsigned int _height)561 LUT2::LUT2(unsigned int _width, unsigned int _height) :
562 width(_width),
563 height(_height),
564 values(NULL)
565 {
566 values = new float[width * height * 3];
567 }
568
569
570 Vec3d
getValue(unsigned int x,unsigned int y) const571 LUT2::getValue(unsigned int x, unsigned int y) const
572 {
573 unsigned int n = 3 * (x + y * width);
574
575 return Vec3d(values[n], values[n + 1], values[n + 2]);
576 }
577
578
579 void
setValue(unsigned int x,unsigned int y,const Vec3d & v)580 LUT2::setValue(unsigned int x, unsigned int y, const Vec3d& v)
581 {
582 unsigned int n = 3 * (x + y * width);
583 values[n] = (float) v.x;
584 values[n + 1] = (float) v.y;
585 values[n + 2] = (float) v.z;
586 }
587
588
589 Vec3d
lookup(double x,double y) const590 LUT2::lookup(double x, double y) const
591 {
592 x = max(0.0, min(x, 0.999999));
593 y = max(0.0, min(y, 0.999999));
594 unsigned int fx = (unsigned int) (x * (width - 1));
595 unsigned int fy = (unsigned int) (y * (height - 1));
596 double t = x * (width - 1) - fx;
597 double u = y * (height - 1) - fy;
598
599 return bilerp(t, u,
600 getValue(fx, fy),
601 getValue(fx + 1, fy),
602 getValue(fx, fy + 1),
603 getValue(fx + 1, fy + 1));
604 }
605
606
LUT3(unsigned int _width,unsigned int _height,unsigned int _depth)607 LUT3::LUT3(unsigned int _width, unsigned int _height, unsigned int _depth) :
608 width(_width),
609 height(_height),
610 depth(_depth),
611 values(NULL)
612 {
613 values = new float[width * height * depth * 4];
614 }
615
616
617 Vec4d
getValue(unsigned int x,unsigned int y,unsigned int z) const618 LUT3::getValue(unsigned int x, unsigned int y, unsigned int z) const
619 {
620 unsigned int n = 4 * (x + (y + z * height) * width);
621
622 return Vec4d(values[n], values[n + 1], values[n + 2], values[n + 3]);
623 }
624
625
626 void
setValue(unsigned int x,unsigned int y,unsigned int z,const Vec4d & v)627 LUT3::setValue(unsigned int x, unsigned int y, unsigned int z, const Vec4d& v)
628 {
629 unsigned int n = 4 * (x + (y + z * height) * width);
630
631 values[n] = (float) v.x;
632 values[n + 1] = (float) v.y;
633 values[n + 2] = (float) v.z;
634 values[n + 3] = (float) v.w;
635 }
636
637
638 Vec4d
lookup(double x,double y,double z) const639 LUT3::lookup(double x, double y, double z) const
640 {
641 x = max(0.0, min(x, 0.999999));
642 y = max(0.0, min(y, 0.999999));
643 z = max(0.0, min(z, 0.999999));
644 unsigned int fx = (unsigned int) (x * (width - 1));
645 unsigned int fy = (unsigned int) (y * (height - 1));
646 unsigned int fz = (unsigned int) (z * (depth - 1));
647 double t = x * (width - 1) - fx;
648 double u = y * (height - 1) - fy;
649 double v = z * (depth - 1) - fz;
650
651 return trilerp(t, u, v,
652 getValue(fx, fy, fz),
653 getValue(fx + 1, fy, fz),
654 getValue(fx, fy + 1, fz),
655 getValue(fx + 1, fy + 1, fz),
656 getValue(fx, fy, fz + 1),
657 getValue(fx + 1, fy, fz + 1),
658 getValue(fx, fy + 1, fz + 1),
659 getValue(fx + 1, fy + 1, fz + 1));
660 }
661
662
raySphereIntersect(const Ray3<T> & ray,const Sphere<T> & sphere,T & dist0,T & dist1)663 template<class T> bool raySphereIntersect(const Ray3<T>& ray,
664 const Sphere<T>& sphere,
665 T& dist0,
666 T& dist1)
667 {
668 Vector3<T> diff = ray.origin - sphere.center;
669 T s = (T) 1.0 / square(sphere.radius);
670 T a = ray.direction * ray.direction * s;
671 T b = ray.direction * diff * s;
672 T c = diff * diff * s - (T) 1.0;
673 T disc = b * b - a * c;
674 if (disc < 0.0)
675 return false;
676
677 disc = (T) sqrt(disc);
678 T sol0 = (-b + disc) / a;
679 T sol1 = (-b - disc) / a;
680
681 if (sol0 <= 0.0 && sol1 <= 0.0)
682 {
683 return false;
684 }
685 else if (sol0 < sol1)
686 {
687 dist0 = max(0.0, sol0);
688 dist1 = sol1;
689 return true;
690 }
691 else if (sol1 < sol0)
692 {
693 dist0 = max(0.0, sol1);
694 dist1 = sol0;
695 return true;
696 }
697 else
698 {
699 return false;
700 }
701 }
702
703
raySphereIntersect2(const Ray3<T> & ray,const Sphere<T> & sphere,T & dist0,T & dist1)704 template<class T> bool raySphereIntersect2(const Ray3<T>& ray,
705 const Sphere<T>& sphere,
706 T& dist0,
707 T& dist1)
708 {
709 Vector3<T> diff = ray.origin - sphere.center;
710 T s = (T) 1.0 / square(sphere.radius);
711 T a = ray.direction * ray.direction * s;
712 T b = ray.direction * diff * s;
713 T c = diff * diff * s - (T) 1.0;
714 T disc = b * b - a * c;
715 if (disc < 0.0)
716 return false;
717
718 disc = (T) sqrt(disc);
719 T sol0 = (-b + disc) / a;
720 T sol1 = (-b - disc) / a;
721
722 if (sol0 < sol1)
723 {
724 dist0 = sol0;
725 dist1 = sol1;
726 return true;
727 }
728 else if (sol0 > sol1)
729 {
730 dist0 = sol1;
731 dist1 = sol0;
732 return true;
733 }
734 else
735 {
736 // One solution to quadratic indicates grazing intersection; treat
737 // as no intersection.
738 return false;
739 }
740 }
741
742
743
phaseRayleigh(double cosTheta)744 double phaseRayleigh(double cosTheta)
745 {
746 return 0.75 * (1.0 + cosTheta * cosTheta);
747 }
748
749
phaseHenyeyGreenstein(double cosTheta,double g)750 double phaseHenyeyGreenstein(double cosTheta, double g)
751 {
752 return (1.0 - g * g) / pow(1.0 + g * g - 2.0 * g * cosTheta, 1.5);
753 }
754
mu2g(double mu)755 double mu2g(double mu)
756 {
757 // mu = <cosTheta>
758 // This routine invertes the simple relation of the Cornette-Shanks
759 // improved Henyey-Greenstein(HG) phase function:
760 // mu = <cosTheta>= 3*g *(g^2 + 4)/(5*(2+g^2))
761
762 double mu2 = mu * mu;
763 double x = 0.5555555556 * mu + 0.17146776 * mu * mu2 + sqrt(max(2.3703704 - 1.3374486 * mu2 + 0.57155921 * mu2 * mu2, 0.0));
764 double y = pow(x, 0.33333333333);
765 return 0.55555555556 * mu - (1.33333333333 - 0.30864198 * mu2) / y + y;
766 }
767
768
phaseHenyeyGreenstein_CS(double cosTheta,double g)769 double phaseHenyeyGreenstein_CS(double cosTheta, double g)
770 {
771 // improved HG - phase function -> Rayleigh phase function for
772 // g -> 0, -> HG-phase function for g -> 1.
773 double g2 = g * g;
774 return 1.5 * (1.0 - g2) * (1.0 + cosTheta * cosTheta) /((2.0 + g2) * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5));
775 }
776
777
778 // Convert the asymmetry paramter for the Henyey-Greenstein function to
779 // the approximate equivalent for the Schlick phase function. From Blasi,
780 // Saec, and Schlick: 1993, "A rendering algorithm for discrete volume
781 // density objects"
schlick_g2k(double g)782 double schlick_g2k(double g)
783 {
784 return 1.55 * g - 0.55 * g * g * g;
785 }
786
787
788 // The Schlick phase function is a less computationally expensive than the
789 // Henyey-Greenstein function, but produces similar results. May be more
790 // appropriate for a GPU implementation.
phaseSchlick(double cosTheta,double k)791 double phaseSchlick(double cosTheta, double k)
792 {
793 return (1 - k * k) / square(1 - k * cosTheta);
794 }
795
796
797 /*
798 * Theory:
799 * Atmospheres are assumed to be composed of three different populations of
800 * particles: Rayleigh scattering, Mie scattering, and absorbing. The density
801 * of each population decreases exponentially with height above the planet
802 * surface to a degree determined by a scale height:
803 *
804 * density(height) = e^(-height/scaleHeight)
805 *
806 * Rayleigh scattering is wavelength dependent, with a fixed phase function.
807 *
808 * Mie scattering is wavelength independent, with a phase function determined
809 * by a single parameter g (the asymmetry parameter)
810 *
811 * Absorption is wavelength dependent
812 *
813 * The light source is assumed to be at an effectively infinite distance from
814 * the planet. This means that for any view ray, the light angle will be
815 * constant, and the phase function can thus be pulled out of the inscattering
816 * integral to simplify the calculation.
817 */
818
819
820 /*** Pure ray marching integration functions; no use of lookup tables ***/
821
822
integrateOpticalDepth(const Scene & scene,const Point3d & atmStart,const Point3d & atmEnd)823 OpticalDepths integrateOpticalDepth(const Scene& scene,
824 const Point3d& atmStart,
825 const Point3d& atmEnd)
826 {
827 unsigned int nSteps = IntegrateDepthSteps;
828
829 OpticalDepths depth;
830 depth.rayleigh = 0.0;
831 depth.mie = 0.0;
832 depth.absorption = 0.0;
833
834 Vec3d dir = atmEnd - atmStart;
835 double length = dir.length();
836 double stepDist = length / (double) nSteps;
837 if (length == 0.0)
838 return depth;
839
840 dir = dir * (1.0 / length);
841 Point3d samplePoint = atmStart + (0.5 * stepDist * dir);
842
843 for (unsigned int i = 0; i < nSteps; i++)
844 {
845 double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
846
847 // Optical depth due to two phenomena:
848 // Outscattering by Rayleigh and Mie scattering particles
849 // Absorption by absorbing particles
850 depth.rayleigh += scene.atmosphere.rayleighDensity(h) * stepDist;
851 depth.mie += scene.atmosphere.mieDensity(h) * stepDist;
852 depth.absorption += scene.atmosphere.absorbDensity(h) * stepDist;
853
854 samplePoint += stepDist * dir;
855 }
856
857 return depth;
858 }
859
860
861 double
rayleighDensity(double h) const862 Atmosphere::rayleighDensity(double h) const
863 {
864 return exp(min(1.0, -h / rayleighScaleHeight));
865 }
866
867
868 double
mieDensity(double h) const869 Atmosphere::mieDensity(double h) const
870 {
871 return exp(min(1.0, -h / mieScaleHeight));
872 }
873
874
875 double
absorbDensity(double h) const876 Atmosphere::absorbDensity(double h) const
877 {
878 return exp(min(1.0, -h / absorbScaleHeight));
879 }
880
881
882 Vec3d
computeExtinction(const OpticalDepths & depth) const883 Atmosphere::computeExtinction(const OpticalDepths& depth) const
884 {
885 Vec3d extinction;
886 extinction.x = exp(-depth.rayleigh * rayleighCoeff.x -
887 depth.mie * mieCoeff -
888 depth.absorption * absorbCoeff.x);
889 extinction.y = exp(-depth.rayleigh * rayleighCoeff.y -
890 depth.mie * mieCoeff -
891 depth.absorption * absorbCoeff.y);
892 extinction.z = exp(-depth.rayleigh * rayleighCoeff.z -
893 depth.mie * mieCoeff -
894 depth.absorption * absorbCoeff.z);
895
896 return extinction;
897 }
898
899
integrateInscattering(const Scene & scene,const Point3d & atmStart,const Point3d & atmEnd)900 Vec3d integrateInscattering(const Scene& scene,
901 const Point3d& atmStart,
902 const Point3d& atmEnd)
903 {
904 const unsigned int nSteps = IntegrateScatterSteps;
905
906 Vec3d dir = atmEnd - atmStart;
907 Point3d origin = Point3d(0.0, 0.0, 0.0) + (atmStart - scene.planet.center);
908 double stepDist = dir.length() / (double) nSteps;
909 dir.normalize();
910
911 // Start at the midpoint of the first interval
912 Point3d samplePoint = origin + 0.5 * stepDist * dir;
913
914 Vec3d rayleighScatter(0.0, 0.0, 0.0);
915 Vec3d mieScatter(0.0, 0.0, 0.0);
916
917 Vec3d lightDir = -scene.light.direction;
918 Sphered shell = Sphered(Point3d(0.0, 0.0, 0.0),
919 scene.planet.radius + scene.atmosphereShellHeight);
920
921 for (unsigned int i = 0; i < nSteps; i++)
922 {
923 Ray3d sunRay(samplePoint, lightDir);
924 double sunDist = 0.0;
925 testIntersection(sunRay, shell, sunDist);
926
927 // Compute the optical depth along path from sample point to the sun
928 OpticalDepths sunDepth = integrateOpticalDepth(scene, samplePoint, sunRay.point(sunDist));
929 // Compute the optical depth along the path from the sample point to the eye
930 OpticalDepths eyeDepth = integrateOpticalDepth(scene, samplePoint, atmStart);
931
932 // Sum the optical depths to get the depth on the complete path from sun
933 // to sample point to eye.
934 OpticalDepths totalDepth = sumOpticalDepths(sunDepth, eyeDepth);
935 totalDepth.rayleigh *= 4.0 * PI;
936 totalDepth.mie *= 4.0 * PI;
937
938 Vec3d extinction = scene.atmosphere.computeExtinction(totalDepth);
939
940 double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
941
942 // Add the inscattered light from Rayleigh and Mie scattering particles
943 rayleighScatter += scene.atmosphere.rayleighDensity(h) * stepDist * extinction;
944 mieScatter += scene.atmosphere.mieDensity(h) * stepDist * extinction;
945
946 samplePoint += stepDist * dir;
947 }
948
949 double cosSunAngle = lightDir * dir;
950
951 double miePhase = scene.atmosphere.miePhase(cosSunAngle);
952 const Vec3d& rayleigh = scene.atmosphere.rayleighCoeff;
953 return phaseRayleigh(cosSunAngle) * Vec3d(rayleighScatter.x * rayleigh.x,
954 rayleighScatter.y * rayleigh.y,
955 rayleighScatter.z * rayleigh.z) +
956 miePhase * mieScatter * scene.atmosphere.mieCoeff;
957 }
958
959
integrateInscatteringFactors(const Scene & scene,const Point3d & atmStart,const Point3d & atmEnd,const Vec3d & lightDir)960 Vec4d integrateInscatteringFactors(const Scene& scene,
961 const Point3d& atmStart,
962 const Point3d& atmEnd,
963 const Vec3d& lightDir)
964 {
965 const unsigned int nSteps = IntegrateScatterSteps;
966
967 Vec3d dir = atmEnd - atmStart;
968 Point3d origin = Point3d(0.0, 0.0, 0.0) + (atmStart - scene.planet.center);
969 double stepDist = dir.length() / (double) nSteps;
970 dir.normalize();
971
972 // Start at the midpoint of the first interval
973 Point3d samplePoint = origin + 0.5 * stepDist * dir;
974
975 Vec3d rayleighScatter(0.0, 0.0, 0.0);
976 Vec3d mieScatter(0.0, 0.0, 0.0);
977
978 Sphered shell = Sphered(Point3d(0.0, 0.0, 0.0),
979 scene.planet.radius + scene.atmosphereShellHeight);
980
981 for (unsigned int i = 0; i < nSteps; i++)
982 {
983 Ray3d sunRay(samplePoint, lightDir);
984 double sunDist = 0.0;
985 testIntersection(sunRay, shell, sunDist);
986
987 // Compute the optical depth along path from sample point to the sun
988 OpticalDepths sunDepth = integrateOpticalDepth(scene, samplePoint, sunRay.point(sunDist));
989 // Compute the optical depth along the path from the sample point to the eye
990 OpticalDepths eyeDepth = integrateOpticalDepth(scene, samplePoint, atmStart);
991
992 // Sum the optical depths to get the depth on the complete path from sun
993 // to sample point to eye.
994 OpticalDepths totalDepth = sumOpticalDepths(sunDepth, eyeDepth);
995 totalDepth.rayleigh *= 4.0 * PI;
996 totalDepth.mie *= 4.0 * PI;
997
998 Vec3d extinction = scene.atmosphere.computeExtinction(totalDepth);
999
1000 double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
1001
1002 // Add the inscattered light from Rayleigh and Mie scattering particles
1003 rayleighScatter += scene.atmosphere.rayleighDensity(h) * stepDist * extinction;
1004 mieScatter += scene.atmosphere.mieDensity(h) * stepDist * extinction;
1005
1006 samplePoint += stepDist * dir;
1007 }
1008
1009 return Vec4d(rayleighScatter.x,
1010 rayleighScatter.y,
1011 rayleighScatter.z,
1012 0.0);
1013 }
1014
1015
1016
1017 /**** Lookup table acceleration of scattering ****/
1018
1019
1020 // Pack a signed value in [-1, 1] into [0, 1]
packSNorm(double sn)1021 double packSNorm(double sn)
1022 {
1023 return (sn + 1.0) * 0.5;
1024 }
1025
1026
1027 // Expand an unsigned value in [0, 1] into [-1, 1]
unpackSNorm(double un)1028 double unpackSNorm(double un)
1029 {
1030 return un * 2.0 - 1.0;
1031 }
1032
1033
1034 LUT2*
buildExtinctionLUT(const Scene & scene)1035 buildExtinctionLUT(const Scene& scene)
1036 {
1037 LUT2* lut = new LUT2(ExtinctionLUTHeightSteps,
1038 ExtinctionLUTViewAngleSteps);
1039
1040 Sphered planet = Sphered(scene.planet.radius);
1041 Sphered shell = Sphered(scene.planet.radius + scene.atmosphereShellHeight);
1042
1043 for (unsigned int i = 0; i < ExtinctionLUTHeightSteps; i++)
1044 {
1045 double h = (double) i / (double) (ExtinctionLUTHeightSteps - 1) *
1046 scene.atmosphereShellHeight * 0.9999;
1047 Point3d atmStart = Point3d(0.0, 0.0, 0.0) +
1048 Vec3d(1.0, 0.0, 0.0) * (h + scene.planet.radius);
1049
1050 for (unsigned int j = 0; j < ExtinctionLUTViewAngleSteps; j++)
1051 {
1052 double cosAngle = (double) j / (ExtinctionLUTViewAngleSteps - 1) * 2.0 - 1.0;
1053 double sinAngle = sqrt(1.0 - min(1.0, cosAngle * cosAngle));
1054 Vec3d viewDir(cosAngle, sinAngle, 0.0);
1055
1056 Ray3d ray(atmStart, viewDir);
1057 double dist = 0.0;
1058
1059 if (!testIntersection(ray, shell, dist))
1060 dist = 0.0;
1061
1062 OpticalDepths depth = integrateOpticalDepth(scene, atmStart,
1063 ray.point(dist));
1064 depth.rayleigh *= 4.0 * PI;
1065 depth.mie *= 4.0 * PI;
1066 Vec3d ext = scene.atmosphere.computeExtinction(depth);
1067 ext.x = max(ext.x, 1.0e-18);
1068 ext.y = max(ext.y, 1.0e-18);
1069 ext.z = max(ext.z, 1.0e-18);
1070
1071 lut->setValue(i, j, ext);
1072 }
1073 }
1074
1075 return lut;
1076 }
1077
1078
1079 Vec3d
lookupExtinction(const Scene & scene,const Point3d & atmStart,const Point3d & atmEnd)1080 lookupExtinction(const Scene& scene,
1081 const Point3d& atmStart,
1082 const Point3d& atmEnd)
1083 {
1084 Vec3d viewDir = atmEnd - atmStart;
1085 Vec3d toCenter = atmStart - Point3d(0.0, 0.0, 0.0);
1086 viewDir.normalize();
1087 toCenter.normalize();
1088 double h = (atmStart.distanceFromOrigin() - scene.planet.radius) /
1089 scene.atmosphereShellHeight;
1090 double cosViewAngle = viewDir * toCenter;
1091
1092 return scene.extinctionLUT->lookup(h, packSNorm(cosViewAngle));
1093 }
1094
1095
1096
1097 LUT2*
buildOpticalDepthLUT(const Scene & scene)1098 buildOpticalDepthLUT(const Scene& scene)
1099 {
1100 LUT2* lut = new LUT2(ExtinctionLUTHeightSteps,
1101 ExtinctionLUTViewAngleSteps);
1102
1103 Sphered planet = Sphered(scene.planet.radius);
1104 Sphered shell = Sphered(scene.planet.radius + scene.atmosphereShellHeight);
1105
1106 for (unsigned int i = 0; i < ExtinctionLUTHeightSteps; i++)
1107 {
1108 double h = (double) i / (double) (ExtinctionLUTHeightSteps - 1) *
1109 scene.atmosphereShellHeight;
1110 Point3d atmStart = Point3d(0.0, 0.0, 0.0) +
1111 Vec3d(1.0, 0.0, 0.0) * (h + scene.planet.radius);
1112
1113 for (unsigned int j = 0; j < ExtinctionLUTViewAngleSteps; j++)
1114 {
1115 double cosAngle = (double) j / (ExtinctionLUTViewAngleSteps - 1) * 2.0 - 1.0;
1116 double sinAngle = sqrt(1.0 - min(1.0, cosAngle * cosAngle));
1117 Vec3d dir(cosAngle, sinAngle, 0.0);
1118
1119 Ray3d ray(atmStart, dir);
1120 double dist = 0.0;
1121
1122 if (!testIntersection(ray, shell, dist))
1123 dist = 0.0;
1124
1125 OpticalDepths depth = integrateOpticalDepth(scene, atmStart,
1126 ray.point(dist));
1127 depth.rayleigh *= 4.0 * PI;
1128 depth.mie *= 4.0 * PI;
1129
1130 lut->setValue(i, j, Vec3d(depth.rayleigh, depth.mie, depth.absorption));
1131 }
1132 }
1133
1134 return lut;
1135 }
1136
1137
1138 OpticalDepths
lookupOpticalDepth(const Scene & scene,const Point3d & atmStart,const Point3d & atmEnd)1139 lookupOpticalDepth(const Scene& scene,
1140 const Point3d& atmStart,
1141 const Point3d& atmEnd)
1142 {
1143 Vec3d dir = atmEnd - atmStart;
1144 Vec3d toCenter = atmStart - Point3d(0.0, 0.0, 0.0);
1145 dir.normalize();
1146 toCenter.normalize();
1147 double h = (atmStart.distanceFromOrigin() - scene.planet.radius) /
1148 scene.atmosphereShellHeight;
1149 double cosViewAngle = dir * toCenter;
1150
1151 Vec3d v = scene.extinctionLUT->lookup(h, (cosViewAngle + 1.0) * 0.5);
1152 OpticalDepths depth;
1153 depth.rayleigh = v.x;
1154 depth.mie = v.y;
1155 depth.absorption = v.z;
1156
1157 return depth;
1158 }
1159
1160
integrateInscattering_LUT(const Scene & scene,const Point3d & atmStart,const Point3d & atmEnd,const Point3d & eyePt,bool hitPlanet)1161 Vec3d integrateInscattering_LUT(const Scene& scene,
1162 const Point3d& atmStart,
1163 const Point3d& atmEnd,
1164 const Point3d& eyePt,
1165 bool hitPlanet)
1166 {
1167 const unsigned int nSteps = IntegrateScatterSteps;
1168
1169 double shellHeight = scene.planet.radius + scene.atmosphereShellHeight;
1170 Sphered shell = Sphered(shellHeight);
1171 bool eyeInsideAtmosphere = eyePt.distanceFromOrigin() < shellHeight;
1172
1173 Vec3d lightDir = -scene.light.direction;
1174
1175 Point3d origin = eyeInsideAtmosphere ? eyePt : atmStart;
1176 Vec3d viewDir = atmEnd - origin;
1177 double stepDist = viewDir.length() / (double) nSteps;
1178 viewDir.normalize();
1179
1180 // Start at the midpoint of the first interval
1181 Point3d samplePoint = origin + 0.5 * stepDist * viewDir;
1182
1183 Vec3d rayleighScatter(0.0, 0.0, 0.0);
1184 Vec3d mieScatter(0.0, 0.0, 0.0);
1185
1186 for (unsigned int i = 0; i < nSteps; i++)
1187 {
1188 Ray3d sunRay(samplePoint, lightDir);
1189 double sunDist = 0.0;
1190 testIntersection(sunRay, shell, sunDist);
1191
1192 Vec3d sunExt = lookupExtinction(scene, samplePoint, sunRay.point(sunDist));
1193 Vec3d eyeExt;
1194 if (!eyeInsideAtmosphere)
1195 {
1196 eyeExt = lookupExtinction(scene, samplePoint, atmStart);
1197 }
1198 else
1199 {
1200 // Eye is inside the atmosphere, so we need to subtract
1201 // extinction from the part of the light path not traveled.
1202 // Do this carefully! We want to avoid doing arithmetic with
1203 // intervals that pass through the planet, since they tend to
1204 // have values extremely close to zero.
1205 Vec3d subExt;
1206 if (hitPlanet)
1207 {
1208 eyeExt = lookupExtinction(scene, samplePoint, atmStart);
1209 subExt = lookupExtinction(scene, eyePt, atmStart);
1210 }
1211 else
1212 {
1213 eyeExt = lookupExtinction(scene, eyePt, atmEnd);
1214 subExt = lookupExtinction(scene, samplePoint, atmEnd);
1215 }
1216
1217 // Subtract the extinction from the untraversed portion of the
1218 // light path.
1219 eyeExt = Vec3d(eyeExt.x / subExt.x,
1220 eyeExt.y / subExt.y,
1221 eyeExt.z / subExt.z);
1222 }
1223
1224 // Compute the extinction along the entire light path from sun to sample point
1225 // to eye.
1226 Vec3d extinction(sunExt.x * eyeExt.x, sunExt.y * eyeExt.y, sunExt.z * eyeExt.z);
1227
1228 double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
1229
1230 // Add the inscattered light from Rayleigh and Mie scattering particles
1231 rayleighScatter += scene.atmosphere.rayleighDensity(h) * stepDist * extinction;
1232 mieScatter += scene.atmosphere.mieDensity(h) * stepDist * extinction;
1233
1234 samplePoint += stepDist * viewDir;
1235 }
1236
1237 double cosSunAngle = lightDir * viewDir;
1238
1239 double miePhase = scene.atmosphere.miePhase(cosSunAngle);
1240 const Vec3d& rayleigh = scene.atmosphere.rayleighCoeff;
1241 return phaseRayleigh(cosSunAngle) * Vec3d(rayleighScatter.x * rayleigh.x,
1242 rayleighScatter.y * rayleigh.y,
1243 rayleighScatter.z * rayleigh.z) +
1244 miePhase * mieScatter * scene.atmosphere.mieCoeff;
1245 }
1246
1247
1248 // Used for building LUT; start point is assumed to be within
1249 // atmosphere.
integrateInscatteringFactors_LUT(const Scene & scene,const Point3d & atmStart,const Point3d & atmEnd,const Vec3d & lightDir,bool planetHit)1250 Vec4d integrateInscatteringFactors_LUT(const Scene& scene,
1251 const Point3d& atmStart,
1252 const Point3d& atmEnd,
1253 const Vec3d& lightDir,
1254 bool planetHit)
1255 {
1256 const unsigned int nSteps = IntegrateScatterSteps;
1257
1258 double shellHeight = scene.planet.radius + scene.atmosphereShellHeight;
1259 Sphered shell = Sphered(shellHeight);
1260
1261 Point3d origin = atmStart;
1262 Vec3d viewDir = atmEnd - origin;
1263 double stepDist = viewDir.length() / (double) nSteps;
1264 viewDir.normalize();
1265
1266 // Start at the midpoint of the first interval
1267 Point3d samplePoint = origin + 0.5 * stepDist * viewDir;
1268
1269 Vec3d rayleighScatter(0.0, 0.0, 0.0);
1270 Vec3d mieScatter(0.0, 0.0, 0.0);
1271
1272 for (unsigned int i = 0; i < nSteps; i++)
1273 {
1274 Ray3d sunRay(samplePoint, lightDir);
1275 double sunDist = 0.0;
1276 testIntersection(sunRay, shell, sunDist);
1277
1278 Vec3d sunExt = lookupExtinction(scene, samplePoint, sunRay.point(sunDist));
1279 Vec3d eyeExt;
1280 Vec3d subExt;
1281 if (planetHit)
1282 {
1283 eyeExt = lookupExtinction(scene, samplePoint, atmEnd);
1284 subExt = lookupExtinction(scene, atmEnd, atmStart);
1285 }
1286 else
1287 {
1288 eyeExt = lookupExtinction(scene, atmStart, atmEnd);
1289 subExt = lookupExtinction(scene, samplePoint, atmEnd);
1290 }
1291
1292 // Subtract the extinction from the untraversed portion of the
1293 // light path.
1294 eyeExt = Vec3d(eyeExt.x / subExt.x,
1295 eyeExt.y / subExt.y,
1296 eyeExt.z / subExt.z);
1297
1298 //Vec3d eyeExt = lookupExtinction(scene, samplePoint, atmStart);
1299
1300
1301 // Compute the extinction along the entire light path from sun to sample point
1302 // to eye.
1303 Vec3d extinction(sunExt.x * eyeExt.x, sunExt.y * eyeExt.y, sunExt.z * eyeExt.z);
1304
1305 double h = samplePoint.distanceFromOrigin() - scene.planet.radius;
1306
1307 // Add the inscattered light from Rayleigh and Mie scattering particles
1308 rayleighScatter += scene.atmosphere.rayleighDensity(h) * stepDist * extinction;
1309 mieScatter += scene.atmosphere.mieDensity(h) * stepDist * extinction;
1310
1311 samplePoint += stepDist * viewDir;
1312 }
1313
1314 return Vec4d(rayleighScatter.x,
1315 rayleighScatter.y,
1316 rayleighScatter.z,
1317 0.0);
1318 }
1319
1320
1321 LUT3*
buildScatteringLUT(const Scene & scene)1322 buildScatteringLUT(const Scene& scene)
1323 {
1324 LUT3* lut = new LUT3(ScatteringLUTHeightSteps,
1325 ScatteringLUTViewAngleSteps,
1326 ScatteringLUTLightAngleSteps);
1327
1328 Sphered shell = Sphered(scene.planet.radius + scene.atmosphereShellHeight);
1329
1330 for (unsigned int i = 0; i < ScatteringLUTHeightSteps; i++)
1331 {
1332 double h = (double) i / (double) (ScatteringLUTHeightSteps - 1) *
1333 scene.atmosphereShellHeight * 0.9999;
1334 Point3d atmStart = Point3d(0.0, 0.0, 0.0) +
1335 Vec3d(1.0, 0.0, 0.0) * (h + scene.planet.radius);
1336
1337 for (unsigned int j = 0; j < ScatteringLUTViewAngleSteps; j++)
1338 {
1339 double cosAngle = unpackSNorm((double) j / (ScatteringLUTViewAngleSteps - 1));
1340 double sinAngle = sqrt(1.0 - min(1.0, cosAngle * cosAngle));
1341 Vec3d viewDir(cosAngle, sinAngle, 0.0);
1342
1343 Ray3d viewRay(atmStart, viewDir);
1344 double dist = 0.0;
1345 if (!testIntersection(viewRay, shell, dist))
1346 dist = 0.0;
1347
1348 Point3d atmEnd = viewRay.point(dist);
1349
1350 for (unsigned int k = 0; k < ScatteringLUTLightAngleSteps; k++)
1351 {
1352 double cosLightAngle = unpackSNorm((double) k / (ScatteringLUTLightAngleSteps - 1));
1353 double sinLightAngle = sqrt(1.0 - min(1.0, cosLightAngle * cosLightAngle));
1354 Vec3d lightDir(cosLightAngle, sinLightAngle, 0.0);
1355
1356 #if 0
1357 Vec4d inscatter = integrateInscatteringFactors_LUT(scene,
1358 atmStart,
1359 atmEnd,
1360 lightDir,
1361 true);
1362 #else
1363 Vec4d inscatter = integrateInscatteringFactors(scene,
1364 atmStart,
1365 atmEnd,
1366 lightDir);
1367 #endif
1368 lut->setValue(i, j, k, inscatter);
1369 }
1370 }
1371 }
1372
1373 return lut;
1374 }
1375
1376
1377 Vec3d
lookupScattering(const Scene & scene,const Point3d & atmStart,const Point3d & atmEnd,const Vec3d & lightDir)1378 lookupScattering(const Scene& scene,
1379 const Point3d& atmStart,
1380 const Point3d& atmEnd,
1381 const Vec3d& lightDir)
1382 {
1383 Vec3d viewDir = atmEnd - atmStart;
1384 Vec3d toCenter = atmStart - Point3d(0.0, 0.0, 0.0);
1385 viewDir.normalize();
1386 toCenter.normalize();
1387 double h = (atmStart.distanceFromOrigin() - scene.planet.radius) /
1388 scene.atmosphereShellHeight;
1389 double cosViewAngle = viewDir * toCenter;
1390 double cosLightAngle = lightDir * toCenter;
1391
1392 Vec4d v = scene.scatteringLUT->lookup(h,
1393 packSNorm(cosViewAngle),
1394 packSNorm(cosLightAngle));
1395
1396 return Vec3d(v.x, v.y, v.z);
1397 }
1398
1399
getPlanetColor(const Scene & scene,const Point3d & p)1400 Color getPlanetColor(const Scene& scene, const Point3d& p)
1401 {
1402 Vec3d n = p - Point3d(0.0, 0.0, 0.0);
1403 n.normalize();
1404
1405 // Give the planet a checkerboard texture
1406 double phi = atan2(n.z, n.x);
1407 double theta = asin(n.y);
1408 int tx = (int) (8 + 8 * phi / PI);
1409 int ty = (int) (8 + 8 * theta / PI);
1410
1411 return ((tx ^ ty) & 0x1) ? scene.planetColor : scene.planetColor2;
1412 }
1413
1414
raytrace(const Ray3d & ray) const1415 Color Scene::raytrace(const Ray3d& ray) const
1416 {
1417 double dist = 0.0;
1418 double atmEnter = 0.0;
1419 double atmExit = 0.0;
1420
1421 double shellRadius = planet.radius + atmosphereShellHeight;
1422
1423 Color color = background;
1424 if (ray.direction * -light.direction > cos(sunAngularDiameter / 2.0))
1425 color = light.color;
1426
1427 if (raySphereIntersect(ray,
1428 Sphered(planet.center, shellRadius),
1429 atmEnter,
1430 atmExit))
1431 {
1432 Color baseColor = color;
1433 Point3d atmStart = ray.origin + atmEnter * ray.direction;
1434 Point3d atmEnd = ray.origin + atmExit * ray.direction;
1435
1436 if (testIntersection(ray, planet, dist))
1437 {
1438 Point3d intersectPoint = ray.point(dist);
1439 Vec3d normal = intersectPoint - planet.center;
1440 normal.normalize();
1441 Vec3d lightDir = -light.direction;
1442 double diffuse = max(0.0, normal * lightDir);
1443
1444 Point3d surfacePt = Point3d(0.0, 0.0, 0.0) + (intersectPoint - planet.center);
1445 Color planetColor = getPlanetColor(*this, surfacePt);
1446
1447 Sphered shell = Sphered(shellRadius);
1448
1449 // Compute ray from surface point to edge of the atmosphere in the direction
1450 // of the sun.
1451 Ray3d sunRay(surfacePt, lightDir);
1452 double sunDist = 0.0;
1453 testIntersection(sunRay, shell, sunDist);
1454
1455 // Compute color of sunlight filtered by the atmosphere; consider extinction
1456 // along both the sun-to-surface and surface-to-eye paths.
1457 OpticalDepths sunDepth = integrateOpticalDepth(*this, surfacePt, sunRay.point(sunDist));
1458 OpticalDepths eyeDepth = integrateOpticalDepth(*this, atmStart, surfacePt);
1459 OpticalDepths totalDepth = sumOpticalDepths(sunDepth, eyeDepth);
1460 totalDepth.rayleigh *= 4.0 * PI;
1461 totalDepth.mie *= 4.0 * PI;
1462 Vec3d extinction = atmosphere.computeExtinction(totalDepth);
1463
1464 // Reflected color of planet surface is:
1465 // surface color * sun color * atmospheric extinction
1466 baseColor = (planetColor * extinction) * light.color * diffuse;
1467
1468 atmEnd = ray.origin + dist * ray.direction;
1469 }
1470
1471 Vec3d inscatter = integrateInscattering(*this, atmStart, atmEnd) * 4.0 * PI;
1472
1473 return Color((float) inscatter.x, (float) inscatter.y, (float) inscatter.z) +
1474 baseColor;
1475 }
1476 else
1477 {
1478 return color;
1479 }
1480 }
1481
1482
1483 Color
raytrace_LUT(const Ray3d & ray) const1484 Scene::raytrace_LUT(const Ray3d& ray) const
1485 {
1486 double atmEnter = 0.0;
1487 double atmExit = 0.0;
1488
1489 double shellRadius = planet.radius + atmosphereShellHeight;
1490 Sphered shell(shellRadius);
1491 Point3d eyePt = Point3d(0.0, 0.0, 0.0) + (ray.origin - planet.center);
1492
1493 Color color = background;
1494 if (ray.direction * -light.direction > cos(sunAngularDiameter / 2.0))
1495 color = light.color;
1496
1497 // Transform ray to model space
1498 Ray3d mray(eyePt, ray.direction);
1499
1500 bool hit = raySphereIntersect2(mray, shell, atmEnter, atmExit);
1501 if (hit && atmExit > 0.0)
1502 {
1503 Color baseColor = color;
1504
1505 bool eyeInsideAtmosphere = atmEnter < 0.0;
1506 Point3d atmStart = mray.origin + atmEnter * mray.direction;
1507 Point3d atmEnd = mray.origin + atmExit * mray.direction;
1508
1509 double planetEnter = 0.0;
1510 double planetExit = 0.0;
1511 hit = raySphereIntersect2(mray,
1512 Sphered(planet.radius),
1513 planetEnter,
1514 planetExit);
1515 if (hit && planetEnter > 0.0)
1516 {
1517 Point3d surfacePt = mray.point(planetEnter);
1518
1519 // Lambert lighting
1520 Vec3d normal = surfacePt - Point3d(0, 0, 0);
1521 normal.normalize();
1522 Vec3d lightDir = -light.direction;
1523 double diffuse = max(0.0, normal * lightDir);
1524
1525 Color planetColor = getPlanetColor(*this, surfacePt);
1526
1527 // Compute ray from surface point to edge of the atmosphere in the direction
1528 // of the sun.
1529 Ray3d sunRay(surfacePt, lightDir);
1530 double sunDist = 0.0;
1531 testIntersection(sunRay, shell, sunDist);
1532
1533 // Compute color of sunlight filtered by the atmosphere; consider extinction
1534 // along both the sun-to-surface and surface-to-eye paths.
1535 Vec3d sunExt = lookupExtinction(*this, surfacePt, sunRay.point(sunDist));
1536 Vec3d eyeExt = lookupExtinction(*this, surfacePt, atmStart);
1537 if (eyeInsideAtmosphere)
1538 {
1539 Vec3d oppExt = lookupExtinction(*this, eyePt, atmStart);
1540 eyeExt = Vec3d(eyeExt.x / oppExt.x,
1541 eyeExt.y / oppExt.y,
1542 eyeExt.z / oppExt.z);
1543 }
1544
1545 Vec3d extinction(sunExt.x * eyeExt.x,
1546 sunExt.y * eyeExt.y,
1547 sunExt.z * eyeExt.z);
1548
1549 // Reflected color of planet surface is:
1550 // surface color * sun color * atmospheric extinction
1551 baseColor = (planetColor * extinction) * light.color * diffuse;
1552
1553 atmEnd = mray.point(planetEnter);
1554 }
1555
1556 Vec3d inscatter;
1557
1558 if (LUTUsage == UseExtinctionLUT)
1559 {
1560 bool hitPlanet = hit && planetEnter > 0.0;
1561 inscatter = integrateInscattering_LUT(*this, atmStart, atmEnd, eyePt, hitPlanet) * 4.0 * PI;
1562 //if (!hit)
1563 //inscatter = Vec3d(0.0, 0.0, 0.0);
1564 }
1565 else if (LUTUsage == UseScatteringLUT)
1566 {
1567 Vec3d rayleighScatter;
1568 if (eyeInsideAtmosphere)
1569 {
1570 #if 1
1571 if (!hit || planetEnter < 0.0)
1572 {
1573 rayleighScatter =
1574 lookupScattering(*this, eyePt, atmEnd, -light.direction);
1575 }
1576 else
1577 {
1578 atmEnd = atmStart;
1579 atmStart = mray.point(planetEnter);
1580 //cout << atmEnter << ", " << planetEnter << ", " << atmExit << "\n";
1581 rayleighScatter =
1582 lookupScattering(*this, atmStart, atmEnd, -light.direction) -
1583 lookupScattering(*this, eyePt, atmEnd, -light.direction);
1584
1585 //cout << rayleighScatter.y << endl;
1586 //cout << (atmStart - atmEnd).length() - (eyePt - atmEnd).length()
1587 //<< ", " << rayleighScatter.y
1588 //<< endl;
1589 //rayleighScatter = Vec3d(0.0, 0.0, 0.0);
1590 }
1591 #else
1592 //rayleighScatter = lookupScattering(*this, atmEnd, atmStart, -light.direction);
1593 #endif
1594 }
1595 else
1596 {
1597 rayleighScatter = lookupScattering(*this, atmEnd, atmStart, -light.direction);
1598 }
1599
1600 const Vec3d& rayleigh = atmosphere.rayleighCoeff;
1601 double cosSunAngle = mray.direction * -light.direction;
1602 inscatter = phaseRayleigh(cosSunAngle) *
1603 Vec3d(rayleighScatter.x * rayleigh.x,
1604 rayleighScatter.y * rayleigh.y,
1605 rayleighScatter.z * rayleigh.z);
1606 inscatter = inscatter * 4.0 * PI;
1607 }
1608
1609 return Color((float) inscatter.x, (float) inscatter.y, (float) inscatter.z) +
1610 baseColor;
1611 }
1612 else
1613 {
1614 return color;
1615 }
1616 }
1617
1618
render(const Scene & scene,const Camera & camera,const Viewport & viewport,RGBImage & image)1619 void render(const Scene& scene,
1620 const Camera& camera,
1621 const Viewport& viewport,
1622 RGBImage& image)
1623 {
1624 double aspectRatio = (double) image.width / (double) image.height;
1625
1626 if (viewport.x >= image.width || viewport.y >= image.height)
1627 return;
1628 unsigned int right = min(image.width, viewport.x + viewport.width);
1629 unsigned int bottom = min(image.height, viewport.y + viewport.height);
1630
1631 cout << "Rendering " << viewport.width << "x" << viewport.height << " view" << endl;
1632 for (unsigned int i = viewport.y; i < bottom; i++)
1633 {
1634 unsigned int row = i - viewport.y;
1635 if (row % 50 == 49)
1636 cout << row + 1 << endl;
1637 else if (row % 10 == 0)
1638 cout << ".";
1639
1640 for (unsigned int j = viewport.x; j < right; j++)
1641 {
1642 double viewportX = ((double) (j - viewport.x) / (double) (viewport.width - 1) - 0.5) * aspectRatio;
1643 double viewportY ((double) (i - viewport.y) / (double) (viewport.height - 1) - 0.5);
1644
1645 Ray3d viewRay = camera.getViewRay(viewportX, viewportY);
1646
1647 Color color;
1648 if (LUTUsage != NoLUT)
1649 color = scene.raytrace_LUT(viewRay);
1650 else
1651 color = scene.raytrace(viewRay);
1652
1653 if (CameraExposure != 0.0)
1654 color = color.exposure((float) CameraExposure);
1655
1656 image.setPixel(j, i, color);
1657 }
1658 }
1659 cout << endl << "Complete" << endl;
1660 }
1661
1662
computeRayleighCoeffs(Vec3d wavelengths)1663 Vec3d computeRayleighCoeffs(Vec3d wavelengths)
1664 {
1665 return Vec3d(pow(wavelengths.x, -4.0),
1666 pow(wavelengths.y, -4.0),
1667 pow(wavelengths.z, -4.0));
1668 }
1669
1670
setParameters(ParameterSet & params)1671 void Scene::setParameters(ParameterSet& params)
1672 {
1673 atmosphere.rayleighScaleHeight = params["RayleighScaleHeight"];
1674 atmosphere.rayleighCoeff.x = params["RayleighRed"];
1675 atmosphere.rayleighCoeff.y = params["RayleighGreen"];
1676 atmosphere.rayleighCoeff.z = params["RayleighBlue"];
1677
1678 atmosphere.mieScaleHeight = params["MieScaleHeight"];
1679 atmosphere.mieCoeff = params["Mie"];
1680
1681 double phaseFunc = params["MiePhaseFunction"];
1682 if (phaseFunc == 0)
1683 {
1684 double mu = params["MieAsymmetry"];
1685 atmosphere.mieAsymmetry = mu2g(mu);
1686 atmosphere.miePhaseFunction = phaseHenyeyGreenstein_CS;
1687 }
1688 else if (phaseFunc == 1)
1689 {
1690 atmosphere.mieAsymmetry = params["MieAsymmetry"];
1691 atmosphere.miePhaseFunction = phaseHenyeyGreenstein;
1692 }
1693 else if (phaseFunc == 2)
1694 {
1695 double k = params["MieAsymmetry"];
1696 atmosphere.mieAsymmetry = schlick_g2k(k);
1697 atmosphere.miePhaseFunction = phaseSchlick;
1698 }
1699
1700 atmosphere.absorbScaleHeight = params["AbsorbScaleHeight"];
1701 atmosphere.absorbCoeff.x = params["AbsorbRed"];
1702 atmosphere.absorbCoeff.y = params["AbsorbGreen"];
1703 atmosphere.absorbCoeff.z = params["AbsorbBlue"];
1704
1705 atmosphereShellHeight = atmosphere.calcShellHeight();
1706
1707 sunAngularDiameter = degToRad(params["SunAngularDiameter"]);
1708
1709 planet.radius = params["Radius"];
1710 planet.center = Point3d(0.0, 0.0, 0.0);
1711
1712 planetColor.r = (float) params["SurfaceRed"];
1713 planetColor.g = (float) params["SurfaceGreen"];
1714 planetColor.b = (float) params["SurfaceBlue"];
1715
1716 planetColor2 = planetColor + Color(0.15f, 0.15f, 0.15f);
1717 }
1718
1719
setSceneDefaults(ParameterSet & params)1720 void setSceneDefaults(ParameterSet& params)
1721 {
1722 params["RayleighScaleHeight"] = 79.94;
1723 params["RayleighRed"] = 0.0;
1724 params["RayleighGreen"] = 0.0;
1725 params["RayleighBlue"] = 0.0;
1726
1727 params["MieScaleHeight"] = 1.2;
1728 params["Mie"] = 0.0;
1729 params["MieAsymmetry"] = 0.0;
1730
1731 params["AbsorbScaleHeight"] = 7.994;
1732 params["AbsorbRed"] = 0.0;
1733 params["AbsorbGreen"] = 0.0;
1734 params["AbsorbBlue"] = 0.0;
1735
1736 params["Radius"] = 6378.0;
1737
1738 params["SurfaceRed"] = 0.2;
1739 params["SurfaceGreen"] = 0.3;
1740 params["SurfaceBlue"] = 1.0;
1741
1742 params["SunAngularDiameter"] = 0.5; // degrees
1743
1744 params["MiePhaseFunction"] = 0;
1745 }
1746
1747
LoadParameterSet(ParameterSet & params,const string & filename)1748 bool LoadParameterSet(ParameterSet& params, const string& filename)
1749 {
1750 ifstream in(filename.c_str());
1751
1752 if (!in.good())
1753 {
1754 cerr << "Error opening config file " << filename << endl;
1755 return false;
1756 }
1757
1758 while (in.good())
1759 {
1760 string name;
1761
1762 in >> name;
1763
1764 if (name == "MiePhaseFunction")
1765 {
1766 string strValue;
1767 in >> strValue;
1768 if (strValue == "HenyeyGreenstein_CS")
1769 {
1770 params[name] = 0;
1771 }
1772 else if (strValue == "HenyeyGreenstein")
1773 {
1774 params[name] = 1;
1775 }
1776 else if (strValue == "Schlick")
1777 {
1778 params[name] = 2;
1779 }
1780 }
1781 else
1782 {
1783 double numValue;
1784 in >> numValue;
1785 if (in.good())
1786 {
1787 params[name] = numValue;
1788 }
1789 }
1790 }
1791
1792 if (in.bad())
1793 {
1794 cerr << "Error in scene config file " << filename << endl;
1795 return false;
1796 }
1797 else
1798 {
1799 return true;
1800 }
1801 }
1802
1803
1804
1805 template<class T> static Matrix4<T>
lookAt(Point3<T> from,Point3<T> to,Vector3<T> up)1806 lookAt(Point3<T> from, Point3<T> to, Vector3<T> up)
1807 {
1808 Vector3<T> n = to - from;
1809 n.normalize();
1810 Vector3<T> v = n ^ up;
1811 v.normalize();
1812 Vector3<T> u = v ^ n;
1813
1814 Matrix4<T> rot(Vector4<T>(v.x, v.y, v.z, 0.0),
1815 Vector4<T>(u.x, u.y, u.z, 0.0),
1816 -Vector4<T>(n.x, n.y, n.z, 0.0),
1817 Vector4<T>(0.0, 0.0, 0.0, 1.0));
1818
1819 return Matrix4<T>::translation(from) * rot;
1820 }
1821
1822
lookAt(Point3d cameraPos,Point3d targetPos,Vec3d up,double fov)1823 Camera lookAt(Point3d cameraPos,
1824 Point3d targetPos,
1825 Vec3d up,
1826 double fov)
1827 {
1828 Camera camera;
1829 camera.fov = degToRad(fov);
1830 camera.front = 1.0;
1831 camera.transform = lookAt(cameraPos, targetPos, up);
1832
1833 return camera;
1834 }
1835
1836
1837
1838 string configFilename;
1839 string outputImageName("out.png");
1840
parseCommandLine(int argc,char * argv[])1841 bool parseCommandLine(int argc, char* argv[])
1842 {
1843 int i = 1;
1844 int fileCount = 0;
1845
1846 while (i < argc)
1847 {
1848 if (argv[i][0] == '-')
1849 {
1850 if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--lut"))
1851 {
1852 LUTUsage = UseExtinctionLUT;
1853 }
1854 else if (!strcmp(argv[i], "-L") || !strcmp(argv[i], "--LUT"))
1855 {
1856 LUTUsage = UseScatteringLUT;
1857 }
1858 else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--fisheye"))
1859 {
1860 UseFisheyeCameras = true;
1861 }
1862 else if (!strcmp(argv[i], "-e") || !strcmp(argv[i], "--exposure"))
1863 {
1864 if (i == argc - 1)
1865 {
1866 return false;
1867 }
1868 else
1869 {
1870 if (sscanf(argv[i + 1], " %lf", &CameraExposure) != 1)
1871 return false;
1872 i++;
1873 }
1874 }
1875 else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--scattersteps"))
1876 {
1877 if (i == argc - 1)
1878 {
1879 return false;
1880 }
1881 else
1882 {
1883 if (sscanf(argv[i + 1], " %u", &IntegrateScatterSteps) != 1)
1884 return false;
1885 i++;
1886 }
1887 }
1888 else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--depthsteps"))
1889 {
1890 if (i == argc - 1)
1891 {
1892 return false;
1893 }
1894 else
1895 {
1896 if (sscanf(argv[i + 1], " %u", &IntegrateDepthSteps) != 1)
1897 return false;
1898 i++;
1899 }
1900 }
1901 else if (!strcmp(argv[i], "-w") || !strcmp(argv[i], "--width"))
1902 {
1903 if (i == argc - 1)
1904 {
1905 return false;
1906 }
1907 else
1908 {
1909 if (sscanf(argv[i + 1], " %u", &OutputImageWidth) != 1)
1910 return false;
1911 i++;
1912 }
1913 }
1914 else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--height"))
1915 {
1916 if (i == argc - 1)
1917 {
1918 return false;
1919 }
1920 else
1921 {
1922 if (sscanf(argv[i + 1], " %u", &OutputImageHeight) != 1)
1923 return false;
1924 i++;
1925 }
1926 }
1927 else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--image"))
1928 {
1929 if (i == argc - 1)
1930 {
1931 return false;
1932 }
1933 else
1934 {
1935 outputImageName = string(argv[i + 1]);
1936 i++;
1937 }
1938 }
1939 else
1940 {
1941 return false;
1942 }
1943 i++;
1944 }
1945 else
1946 {
1947 if (fileCount == 0)
1948 {
1949 // input filename first
1950 configFilename = string(argv[i]);
1951 fileCount++;
1952 }
1953 else
1954 {
1955 // more than one filenames on the command line is an error
1956 return false;
1957 }
1958 i++;
1959 }
1960 }
1961
1962 return true;
1963 }
1964
1965
main(int argc,char * argv[])1966 int main(int argc, char* argv[])
1967 {
1968 bool commandLineOK = parseCommandLine(argc, argv);
1969 if (!commandLineOK || configFilename.empty())
1970 {
1971 usage();
1972 exit(1);
1973 }
1974
1975 ParameterSet sceneParams;
1976 setSceneDefaults(sceneParams);
1977 if (!LoadParameterSet(sceneParams, configFilename))
1978 {
1979 exit(1);
1980 }
1981
1982 Scene scene;
1983 scene.light.color = Color(1, 1, 1);
1984 scene.light.direction = Vec3d(0.0, 0.0, 1.0);
1985 scene.light.direction.normalize();
1986
1987 #if 0
1988 // N0 = 2.668e25 // m^-3
1989 scene.planet = Sphered(Point3d(0.0, 0.0, 0.0), planetRadius);
1990
1991 scene.atmosphere.rayleighScaleHeight = 79.94;
1992 scene.atmosphere.mieScaleHeight = 1.2;
1993 scene.atmosphere.rayleighCoeff =
1994 computeRayleighCoeffs(Vec3d(0.600, 0.520, 0.450)) * 0.000008;
1995 scene.atmosphereShellHeight = scene.atmosphere.calcShellHeight();
1996
1997 scene.atmosphere.rayleighCoeff =
1998 computeRayleighCoeffs(Vec3d(0.600, 0.520, 0.450)) * 0.000008;
1999 #endif
2000
2001 scene.setParameters(sceneParams);
2002
2003 cout << "atmosphere height: " << scene.atmosphereShellHeight << "\n";
2004 cout << "attenuation coeffs: " <<
2005 scene.atmosphere.rayleighCoeff.x * 4 * PI << ", " <<
2006 scene.atmosphere.rayleighCoeff.y * 4 * PI << ", " <<
2007 scene.atmosphere.rayleighCoeff.z * 4 * PI << endl;
2008
2009
2010 if (LUTUsage != NoLUT)
2011 {
2012 cout << "Building extinction LUT...\n";
2013 scene.extinctionLUT = buildExtinctionLUT(scene);
2014 cout << "Complete!\n";
2015 DumpLUT(*scene.extinctionLUT, "extlut.png");
2016 }
2017
2018 if (LUTUsage == UseScatteringLUT)
2019 {
2020 cout << "Building scattering LUT...\n";
2021 scene.scatteringLUT = buildScatteringLUT(scene);
2022 cout << "Complete!\n";
2023 DumpLUT(*scene.scatteringLUT, "lut.png");
2024 }
2025
2026 double planetRadius = scene.planet.radius;
2027 double cameraFarDist = planetRadius * 3;
2028 double cameraCloseDist = planetRadius * 1.2;
2029
2030 Camera cameraLowPhase;
2031 cameraLowPhase.fov = degToRad(45.0);
2032 cameraLowPhase.front = 1.0;
2033 cameraLowPhase.transform =
2034 Mat4d::translation(Point3d(0.0, 0.0, -cameraFarDist)) *
2035 Mat4d::yrotation(degToRad(20.0));
2036
2037 Camera cameraHighPhase;
2038 cameraHighPhase.fov = degToRad(45.0);
2039 cameraHighPhase.front = 1.0;
2040 cameraHighPhase.transform =
2041 Mat4d::translation(Point3d(0.0, 0.0, -cameraFarDist)) *
2042 Mat4d::yrotation(degToRad(160.0));
2043
2044 Camera cameraClose;
2045 cameraClose.fov = degToRad(45.0);
2046 cameraClose.front = 1.0;
2047 cameraClose.transform =
2048 Mat4d::xrotation(degToRad(55.0)) *
2049 Mat4d::translation(Point3d(0.0, 0.0, -cameraCloseDist)) *
2050 Mat4d::yrotation(degToRad(50.0));
2051
2052 Camera cameraSurface;
2053 cameraSurface.fov = degToRad(45.0);
2054 cameraSurface.front = 1.0;
2055 cameraSurface.transform =
2056 Mat4d::xrotation(degToRad(85.0)) *
2057 Mat4d::translation(Point3d(0.0, 0.0, -planetRadius * 1.0002)) *
2058 Mat4d::yrotation(degToRad(20.0));
2059
2060 double aspectRatio = (double) OutputImageWidth / (double) OutputImageHeight;
2061 // Make the horizontal FOV of the fisheye cameras 180 degrees
2062 double fisheyeFOV = degToRad(max(180.0, 180.0 / aspectRatio));
2063
2064 Camera cameraFisheyeMidday;
2065 cameraFisheyeMidday.fov = fisheyeFOV;
2066 cameraFisheyeMidday.type = Camera::Spherical;
2067 cameraFisheyeMidday.transform =
2068 Mat4d::xrotation(degToRad(85.0)) *
2069 Mat4d::translation(Point3d(0.0, 0.0, -planetRadius * 1.0002)) *
2070 Mat4d::yrotation(degToRad(20.0));
2071
2072 Camera cameraFisheyeSunset;
2073 cameraFisheyeSunset.fov = degToRad(180.0);
2074 cameraFisheyeSunset.type = Camera::Spherical;
2075 cameraFisheyeSunset.transform =
2076 Mat4d::zrotation(degToRad(-5.0)) *
2077 Mat4d::yrotation(degToRad(90.0)) *
2078 Mat4d::xrotation(degToRad(85.0)) *
2079 Mat4d::translation(Point3d(0.0, 0.0, -planetRadius * 1.0002)) *
2080 Mat4d::yrotation(degToRad(80.0));
2081
2082
2083 RGBImage image(OutputImageWidth, OutputImageHeight);
2084
2085 Viewport topleft (0, 0, image.width / 2, image.height / 2);
2086 Viewport topright(image.width / 2, 0, image.width / 2, image.height / 2);
2087 Viewport botleft (0, image.height / 2, image.width / 2, image.height / 2);
2088 Viewport botright(image.width / 2, image.height / 2, image.width / 2, image.height / 2);
2089 Viewport tophalf (0, 0, image.width, image.height / 2);
2090 Viewport bothalf (0, image.height / 2, image.width, image.height / 2);
2091
2092 image.clear(Color(0.1f, 0.1f, 1.0f));
2093
2094 if (UseFisheyeCameras)
2095 {
2096 render(scene, cameraFisheyeMidday, tophalf, image);
2097 render(scene, cameraFisheyeSunset, bothalf, image);
2098 }
2099 else
2100 {
2101 render(scene, cameraLowPhase, topleft, image);
2102 render(scene, cameraHighPhase, topright, image);
2103 render(scene, cameraClose, botleft, image);
2104 render(scene, cameraSurface, botright, image);
2105 }
2106
2107 WritePNG(outputImageName, image);
2108
2109 exit(0);
2110
2111 }
2112