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